diff --git a/Makefile.toml b/Makefile.toml index ba99371d3..f9e56a14e 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -278,12 +278,36 @@ info "-- IMAGE = ${IMAGE_NAME}" info "-- TAG = $TAG" # info "-- TEST_BINARIES_DIR = ${TEST_BINARIES_DIR}" +# Run podman in foreground with proper signal handling # Set container name for cleanup. Suffix with $$ (script PID) so concurrent # `makers container-test` invocations on the same host don't collide on the # `zaino-testing` name. The base-script cleanup trap reads CONTAINER_NAME # from script scope and stops the right container on EXIT/INT/TERM. CONTAINER_NAME="zaino-testing-$$" +# Capture git info on the host and forward it into the container so the +# build script does not need to shell out to git from within the bind +# mount. In a worktree the bind-mounted .git is a file pointing to a +# host-only path, so an in-container `git rev-parse` returns empty and +# (worse) a missing-path `cargo:rerun-if-changed` invalidates every +# build. Reading these from env vars keeps the container build +# deterministic across runs. +GIT_COMMIT="$(git rev-parse HEAD 2>/dev/null || echo unknown)" +BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)" + +# Conditionally mount a local infras checkout if one exists at the +# expected sibling path. integration-tests/Cargo.toml may carry +# path-based deps like `../../../infras/dev/zcash_local_net` that only +# resolve when this directory is present inside the container at the +# matching location. If the host doesn't have an infras checkout the +# mount is skipped and any path-dep error surfaces as it would today. +EXTRA_MOUNTS=() +if [[ -d "$PWD/../../infras/dev" ]]; then + INFRAS_HOST_PATH="$(cd "$PWD/../../infras/dev" && pwd -P)" + info "-- INFRAS_MOUNT = ${INFRAS_HOST_PATH} -> /home/infras/dev" + EXTRA_MOUNTS+=(-v "${INFRAS_HOST_PATH}:/home/infras/dev") +fi + # Run podman in foreground with proper signal handling. # # `--pids-limit=-1` removes the default 2048-process cgroup cap. With @@ -301,10 +325,13 @@ podman run --rm \ -v zaino-container-target:/home/container_user/zaino/target:U \ -v zaino-cargo-git:/usr/local/cargo/git:U \ -v zaino-cargo-registry:/usr/local/cargo/registry:U \ + ${EXTRA_MOUNTS[@]+"${EXTRA_MOUNTS[@]}"} \ -e "TEST_BINARIES_DIR=${TEST_BINARIES_DIR}" \ -e "NEXTEST_EXPERIMENTAL_LIBTEST_JSON=1" \ -e "ZAINOLOG_FORMAT=${ZAINOLOG_FORMAT:-stream}" \ -e "RUST_LOG=${RUST_LOG:-}" \ + -e "GIT_COMMIT=${GIT_COMMIT}" \ + -e "BRANCH=${BRANCH}" \ -w /home/container_user/zaino \ -u container_user \ "${IMAGE_NAME}:$TAG" \ diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 267724b65..24efcc332 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -57,7 +57,6 @@ corez = "0.1" futures = "0.3.30" hex = "0.4" once_cell = "1.20" -portpicker = "0.1" serde_json = "1.0" tempfile = "3.2" tracing = "0.1" diff --git a/integration-tests/zaino-testutils/Cargo.toml b/integration-tests/zaino-testutils/Cargo.toml index d1ccfe547..ae85aec9d 100644 --- a/integration-tests/zaino-testutils/Cargo.toml +++ b/integration-tests/zaino-testutils/Cargo.toml @@ -51,7 +51,6 @@ zingolib_testutils.workspace = true # Miscellaneous http = { workspace = true } once_cell = { workspace = true } -portpicker = { workspace = true } tokio = { workspace = true } tonic.workspace = true tempfile = { workspace = true } diff --git a/integration-tests/zaino-testutils/src/lib.rs b/integration-tests/zaino-testutils/src/lib.rs index bd1cc0ae7..0624e7f0a 100644 --- a/integration-tests/zaino-testutils/src/lib.rs +++ b/integration-tests/zaino-testutils/src/lib.rs @@ -36,6 +36,7 @@ use zcash_local_net::{ error::LaunchError, validator::zcashd::{Zcashd, ZcashdConfig}, }; +use zcash_local_net::network::pick_unused_port; use zcash_local_net::{logs::LogsToStdoutAndStderr, process::Process}; use zebra_chain::parameters::NetworkKind; #[cfg(test)] @@ -58,7 +59,7 @@ fn binary_path(binary_name: &str) -> Option { } /// Create local URI from port. -pub fn make_uri(indexer_port: portpicker::Port) -> http::Uri { +pub fn make_uri(indexer_port: u16) -> http::Uri { format!("http://127.0.0.1:{indexer_port}") .try_into() .unwrap() @@ -453,10 +454,20 @@ where zaino_json_listen_address, zaino_json_server_cookie_dir, ) = if enable_zaino { - let zaino_grpc_listen_port = portpicker::pick_unused_port().expect("No ports free"); + // Was `portpicker::pick_unused_port()` — that helper samples + // randomly from `15000..25000` and bind-checks, a 10 000-port + // range that suffers measurable birthday-paradox collisions + // when many test subprocesses run in parallel (the in-the-wild + // `ConnectionRefused` flakes on `zebrad::get::*`, + // `zebra::lightwallet_indexer::*`, etc., all reproduce here). + // `zcash_local_net::network::pick_unused_port` lets the kernel + // assign an ephemeral instead — sequential allocation in the + // 32768+ range, TIME_WAIT-cooled, effectively zero-collision + // even across test subprocesses. + let zaino_grpc_listen_port = pick_unused_port(None); let zaino_grpc_listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), zaino_grpc_listen_port); - let zaino_json_listen_port = portpicker::pick_unused_port().expect("No ports free"); + let zaino_json_listen_port = pick_unused_port(None); let zaino_json_listen_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), zaino_json_listen_port); debug!( diff --git a/packages/zaino-state/build.rs b/packages/zaino-state/build.rs index 960be7f5c..bf11232cd 100644 --- a/packages/zaino-state/build.rs +++ b/packages/zaino-state/build.rs @@ -14,19 +14,62 @@ fn git(args: &[&str]) -> String { .to_string() } +/// `git rev-parse --git-path ` — resolves a name inside the gitdir +/// to its on-disk location, transparently handling worktrees (where the +/// gitdir lives under `
/.git/worktrees//`). Returns `None` +/// when git can't resolve the repository at all (e.g. a workspace bind- +/// mounted into a container with no access to the host gitdir). +fn git_path(name: &str) -> Option { + let out = Command::new("git") + .args(["rev-parse", "--git-path", name]) + .output() + .ok()?; + if !out.status.success() { + return None; + } + let path = String::from_utf8(out.stdout).ok()?.trim().to_string(); + (!path.is_empty()).then_some(path) +} + fn main() -> io::Result<()> { // Without these, cargo's default is "rerun if any file in the package // changes", which combined with wall-clock-derived rustc-env values // would invalidate this crate (and everything downstream) on every build. println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=../../.git/HEAD"); + // Resolve HEAD's actual path at build time. In a regular clone this is + // `.git/HEAD`; in a worktree `.git` is a file, not a directory, and + // HEAD lives at `
/.git/worktrees//HEAD`. Hardcoding + // `../../.git/HEAD` is fine for regular clones but resolves to a + // missing path under a worktree, which cargo treats as + // always-changed — forcing this crate (and everything downstream) to + // recompile on every build. `git rev-parse --git-path HEAD` returns + // the right path in both cases. If git can't resolve the repo at all + // (e.g. the workspace bind-mounted into a container can't reach the + // host gitdir), skip the directive — `cargo:rerun-if-env-changed=…` + // above already covers the in-container invalidation story. + if let Some(head_path) = git_path("HEAD") { + println!("cargo:rerun-if-changed={head_path}"); + } println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH"); + println!("cargo:rerun-if-env-changed=GIT_COMMIT"); + println!("cargo:rerun-if-env-changed=BRANCH"); + + // Prefer caller-supplied values: the makers container-test task computes + // these on the host and passes them via `-e`, so the in-container build + // does not depend on a working git inside the bind-mounted workspace + // (which is broken across worktree gitdir indirection). Fall back to + // shelling out for direct host invocations of `cargo build`. + let git_commit = env::var("GIT_COMMIT") + .ok() + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| git(&["rev-parse", "HEAD"])); + println!("cargo:rustc-env=GIT_COMMIT={git_commit}"); - println!("cargo:rustc-env=GIT_COMMIT={}", git(&["rev-parse", "HEAD"])); - println!( - "cargo:rustc-env=BRANCH={}", - git(&["rev-parse", "--abbrev-ref", "HEAD"]) - ); + let branch = env::var("BRANCH") + .ok() + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| git(&["rev-parse", "--abbrev-ref", "HEAD"])); + println!("cargo:rustc-env=BRANCH={branch}"); // BUILD_DATE: SOURCE_DATE_EPOCH if set // (https://reproducible-builds.org/docs/source-date-epoch/), otherwise