rust-lang / rust-lang/cargo

Build script fingerprint of registry dependencies is not tracked in fingerprint.

Open
#6,733 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

A-build-scripts A-rebuild-detection C-bug S-needs-design
Dominant language
Rust
Stars
15.5k
Forks
3k
Avg merge
23h 30m
Merged PRs (30d)
51

Description

The fingerprint of a build script is not included for dependencies. #6720 added this tracking, but because it requires mtimes to work correctly, #6734 removed it for registry dependencies. This is because dependencies are often cached in Docker images, and Docker zeros out the nanoseconds portion of the mtime when the image is saved.

This only affects the scenario when two separate commands are run, such as cargo build then cargo test. The first command will pick up the changes, but the second will not.

There are a few ways that I can think of that this would occur: changing an environment variable tracked by rerun-if-env-changed, a system library changes that is tracked by rerun-if-changed, or adding a build override after already building.

I cannot think of an easy way to fix this. I'd rather not try to change the mtime resolution in the fingerprint. I think the ideal solution would be: instead of tracking the invoked.timestamp, include the same fingerprint information calculated by build_script_local_fingerprints when computing dependencies, and use a hybrid mtime/content hash for rerun-if-changed files.

Below are some tests that demonstrate this behavior. Both of these will fail on the last step if #6734 is merged.

#[test]
fn rerun_build_script_dep() {
    Package::new("bar", "0.1.0")
        .file(
            "build.rs",
            r#"
            fn main() {
                println!("cargo:rerun-if-env-changed=MY_TEST_VAR");
            }
        "#,
        )
        .file("src/lib.rs", "")
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
            [package]
            name = "foo"
            version = "0.1.0"
            [dependencies]
            bar = "0.1"
        "#,
        )
        .file("src/lib.rs", "")
        .build();

    p.cargo("build").env("MY_TEST_VAR", "1").run();
    p.cargo("test --lib").env("MY_TEST_VAR", "1").run();
    p.cargo("build -vv")
        .env("MY_TEST_VAR", "1")
        .with_stderr_contains("[FRESH] bar v0.1.0")
        .with_stderr_contains("[FRESH] foo v0.1.0 [..]")
        .run();
    p.cargo("build -vv")
        .env("MY_TEST_VAR", "2")
        .with_stderr_contains("[COMPILING] bar v0.1.0")
        .with_stderr_contains("[COMPILING] foo v0.1.0 [..]")
        .run();
    p.cargo("test -vv --lib")
        .env("MY_TEST_VAR", "2")
        .with_stderr_contains("[FRESH] bar v0.1.0")
        .with_stderr_contains("[COMPILING] foo v0.1.0 [..]")
        .run();
}

#[test]
fn changing_dep_override_invalidates() {
    let target = rustc_host();

    Package::new("dep1", "0.1.0")
        .file(
            "Cargo.toml",
            r#"
                [project]
                name = "dep1"
                version = "0.1.0"
                links = "asdf"
            "#,
        )
        .file("build.rs", "fn main() {}")
        .file("src/lib.rs", "pub fn f() -> bool { cfg!(trigger) }")
        .publish();

    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [project]
                name = "foo"
                version = "0.5.0"
                [dependencies]
                dep1 = "0.1"
            "#,
        )
        .file(
            "src/lib.rs",
            r#"
                extern crate dep1;
                #[test]
                fn t1() {
                    assert_eq!(dep1::f(), true, "trigger not set");
                }
            "#,
        )
        .build();

    p.cargo("build").run();
    p.cargo("test --lib -vv")
        .with_stderr_contains("[FRESH] dep1 v0.1.0")
        .with_stdout_contains("[..]trigger not set[..]")
        .with_status(101)
        .run();

    p.change_file(
        ".cargo/config",
        &format!(
            r#"
                [target.{}.asdf]
                rustc-cfg = ["trigger"]
            "#,
            target
        ),
    );

    p.cargo("build -vv")
        .with_stderr_contains("[COMPILING] dep1 v0.1.0")
        .with_stderr_contains("[COMPILING] foo [..]")
        .run();
    p.cargo("test --lib -vv")
        .with_stderr_contains("[FRESH] dep1")
        .with_stderr_contains("[COMPILING] foo [..]")
        .run();
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/cargo/core/compiler/fingerprint.rs, especially build_script_local_fingerprints, and reproduce the behavior with the rerun_build_script_dep and changing_dep_override_invalidates tests described in the issue. Trace how registry dependency fingerprints are computed across cargo build and cargo test. Done means both scenarios invalidate and rebuild the dependent package when the tracked environment or build override changes.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
build-system
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.