Skip to content

rerun-if-changed doesn't handle past timestamps #10791

@ehuss

Description

@ehuss

Problem

Using rerun-if-changed to a file that changes, but changes with old timestamps, Cargo won't detect that it needs to run. For example:

  1. Extract a tarball with file foo with timestamp 1.
  2. cargo build with rerun-if-changed of foo. This marks the build script as timestamp 5.
  3. Extract a different tarball with file foo with timestamp 2.
  4. Run cargo build again, it doesn't pick up the change from timestamp 1 to 2 because both values are less than 5.

I would expect any change to the file to cause it to re-run the build script.

The real-world use case is in the rust LLVM build system. It extracts LLVM from a downloaded tarball. The rustc_llvm build script has a rerun-if-changed=/path/to/llvm-config. When rustbuild determines a new tarball needs to be downloaded, the rustc_llvm build script doesn't get the memo and doesn't get rerun. This can cause it to link against the wrong file.

Steps

The following is a test using Cargo's testsuite:

#[cargo_test]
fn past_rerun_if_changed() {
    // Tests that a file that changes with an old timestamp triggers a build
    // script to rerun. This can happen when extracting files from a tarball
    // which preserves the timestamps.
    let p = project()
        .file("src/lib.rs", "")
        .file(
            "build.rs",
            r#"
                fn main() {
                    println!("cargo:rerun-if-changed=somefile");
                }
            "#,
        )
        .file("somefile", "")
        .build();

    // Simulate extracting some tarball.
    let now = std::time::SystemTime::now();
    let time_a = FileTime::from_system_time(now - Duration::new(60 * 10, 0));
    let time_b = FileTime::from_system_time(now - Duration::new(60 * 5, 0));
    let somefile = p.root().join("somefile");
    filetime::set_file_times(&somefile, time_a, time_a).unwrap();
    p.cargo("check").run();
    // Simulate extracting a different tarball, but whose timestamp is still
    // in the past.
    p.change_file("somefile", "123");
    filetime::set_file_times(&somefile, time_b, time_b).unwrap();
    // FIXME: This should trigger a rebuild, but it currently does not.
    p.cargo("check -v")
        .with_stderr(
            "\
[COMPILING] foo [..]
[RUNNING] `[ROOT]/foo/target/debug/build/foo-[..]/build-script-build`
[RUNNING] `rustc --crate-name foo [..]
[FINISHED] [..]
",
        )
        .run();
}

Possible Solution(s)

A few possible options:

  • Hash the timestamp and file size of all rerun-if-changed files, and include that in the final hash (instead of just comparing the timestamps). This could potentially trigger rebuilds more often, though offhand I can't think of how it could happen.
  • Hash the contents of the files (such as (Option to) Fingerprint by file contents instead of mtime #6529). This might be very expensive so I'm not sure if it would be a good idea to do this (possibly make this optional?).

Notes

No response

Version

cargo 1.63.0-nightly (03a849043 2022-06-19)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-build-scriptsArea: build.rs scriptsA-rebuild-detectionArea: rebuild detection and fingerprintingC-bugCategory: bugS-triageStatus: This issue is waiting on initial triage.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions