summaryrefslogtreecommitdiffstats
path: root/src/tools/cargo/tests/testsuite/freshness.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/cargo/tests/testsuite/freshness.rs')
-rw-r--r--src/tools/cargo/tests/testsuite/freshness.rs2816
1 files changed, 2816 insertions, 0 deletions
diff --git a/src/tools/cargo/tests/testsuite/freshness.rs b/src/tools/cargo/tests/testsuite/freshness.rs
new file mode 100644
index 000000000..86b186af8
--- /dev/null
+++ b/src/tools/cargo/tests/testsuite/freshness.rs
@@ -0,0 +1,2816 @@
+//! Tests for fingerprinting (rebuild detection).
+
+use filetime::FileTime;
+use std::fs::{self, OpenOptions};
+use std::io;
+use std::io::prelude::*;
+use std::net::TcpListener;
+use std::path::{Path, PathBuf};
+use std::process::Stdio;
+use std::thread;
+use std::time::SystemTime;
+
+use super::death;
+use cargo_test_support::paths::{self, CargoPathExt};
+use cargo_test_support::registry::Package;
+use cargo_test_support::{
+ basic_manifest, is_coarse_mtime, project, rustc_host, rustc_host_env, sleep_ms,
+};
+
+#[cargo_test]
+fn modifying_and_moving() {
+ let p = project()
+ .file("src/main.rs", "mod a; fn main() {}")
+ .file("src/a.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build").with_stdout("").run();
+ p.root().move_into_the_past();
+ p.root().join("target").move_into_the_past();
+
+ p.change_file("src/a.rs", "#[allow(unused)]fn main() {}");
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([CWD]): the file `src/a.rs` has changed ([..])
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ fs::rename(&p.root().join("src/a.rs"), &p.root().join("src/b.rs")).unwrap();
+ p.cargo("build")
+ .with_status(101)
+ .with_stderr_contains("[..]file not found[..]")
+ .run();
+}
+
+#[cargo_test]
+fn modify_only_some_files() {
+ let p = project()
+ .file("src/lib.rs", "mod a;")
+ .file("src/a.rs", "")
+ .file("src/main.rs", "mod b; fn main() {}")
+ .file("src/b.rs", "")
+ .file("tests/test.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.cargo("test").run();
+ sleep_ms(1000);
+
+ assert!(p.bin("foo").is_file());
+
+ let lib = p.root().join("src/lib.rs");
+ p.change_file("src/lib.rs", "invalid rust code");
+ p.change_file("src/b.rs", "#[allow(unused)]fn foo() {}");
+ lib.move_into_the_past();
+
+ // Make sure the binary is rebuilt, not the lib
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([CWD]): the file `src/b.rs` has changed ([..])
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ assert!(p.bin("foo").is_file());
+}
+
+#[cargo_test]
+fn rebuild_sub_package_then_while_package() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [dependencies.a]
+ path = "a"
+ [dependencies.b]
+ path = "b"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; extern crate b;")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ authors = []
+ version = "0.0.1"
+ [dependencies.b]
+ path = "../b"
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate b;")
+ .file("b/Cargo.toml", &basic_manifest("b", "0.0.1"))
+ .file("b/src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] b [..]
+[COMPILING] a [..]
+[COMPILING] foo [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ p.change_file("b/src/lib.rs", "pub fn b() {}");
+
+ p.cargo("build -pb -v")
+ .with_stderr(
+ "\
+[DIRTY] b v0.0.1 ([..]): the file `b/src/lib.rs` has changed ([..])
+[COMPILING] b [..]
+[RUNNING] `rustc --crate-name b [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ p.change_file(
+ "src/lib.rs",
+ "extern crate a; extern crate b; pub fn toplevel() {}",
+ );
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] b [..]
+[DIRTY] a [..]: the dependency b was rebuilt ([..])
+[COMPILING] a [..]
+[RUNNING] `rustc --crate-name a [..]
+[DIRTY] foo [..]: the dependency b was rebuilt ([..])
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]
+[FINISHED] dev [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_lib_features_caches_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build --features foo")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ /* Targets should be cached from the first build */
+
+ p.cargo("build")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+
+ p.cargo("build").with_stdout("").run();
+
+ p.cargo("build --features foo")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn changing_profiles_caches_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("test")
+ .with_stderr(
+ "\
+[..]Compiling foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target[..]debug[..]deps[..]foo-[..][EXE])
+[DOCTEST] foo
+",
+ )
+ .run();
+
+ /* Targets should be cached from the first build */
+
+ p.cargo("build")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+
+ p.cargo("test foo")
+ .with_stderr(
+ "\
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] [..] (target[..]debug[..]deps[..]foo-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_bin_paths_common_target_features_caches_targets() {
+ // Make sure dep_cache crate is built once per feature
+ let p = project()
+ .no_manifest()
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = "./target"
+ "#,
+ )
+ .file(
+ "dep_crate/Cargo.toml",
+ r#"
+ [package]
+ name = "dep_crate"
+ version = "0.0.1"
+ authors = []
+
+ [features]
+ ftest = []
+ "#,
+ )
+ .file(
+ "dep_crate/src/lib.rs",
+ r#"
+ #[cfg(feature = "ftest")]
+ pub fn yo() {
+ println!("ftest on")
+ }
+ #[cfg(not(feature = "ftest"))]
+ pub fn yo() {
+ println!("ftest off")
+ }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ dep_crate = {path = "../dep_crate", features = []}
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "a/src/main.rs",
+ r#"
+ extern crate dep_crate;
+ use dep_crate::yo;
+ fn main() {
+ yo();
+ }
+ "#,
+ )
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ dep_crate = {path = "../dep_crate", features = ["ftest"]}
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "b/src/main.rs",
+ r#"
+ extern crate dep_crate;
+ use dep_crate::yo;
+ fn main() {
+ yo();
+ }
+ "#,
+ )
+ .build();
+
+ /* Build and rebuild a/. Ensure dep_crate only builds once */
+ p.cargo("run")
+ .cwd("a")
+ .with_stdout("ftest off")
+ .with_stderr(
+ "\
+[..]Compiling dep_crate v0.0.1 ([..])
+[..]Compiling a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/a[EXE]`
+",
+ )
+ .run();
+ p.cargo("clean -p a").cwd("a").run();
+ p.cargo("run")
+ .cwd("a")
+ .with_stdout("ftest off")
+ .with_stderr(
+ "\
+[..]Compiling a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/a[EXE]`
+",
+ )
+ .run();
+
+ /* Build and rebuild b/. Ensure dep_crate only builds once */
+ p.cargo("run")
+ .cwd("b")
+ .with_stdout("ftest on")
+ .with_stderr(
+ "\
+[..]Compiling dep_crate v0.0.1 ([..])
+[..]Compiling b v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/b[EXE]`
+",
+ )
+ .run();
+ p.cargo("clean -p b").cwd("b").run();
+ p.cargo("run")
+ .cwd("b")
+ .with_stdout("ftest on")
+ .with_stderr(
+ "\
+[..]Compiling b v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/b[EXE]`
+",
+ )
+ .run();
+
+ /* Build a/ package again. If we cache different feature dep builds correctly,
+ * this should not cause a rebuild of dep_crate */
+ p.cargo("clean -p a").cwd("a").run();
+ p.cargo("run")
+ .cwd("a")
+ .with_stdout("ftest off")
+ .with_stderr(
+ "\
+[..]Compiling a v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/a[EXE]`
+",
+ )
+ .run();
+
+ /* Build b/ package again. If we cache different feature dep builds correctly,
+ * this should not cause a rebuild */
+ p.cargo("clean -p b").cwd("b").run();
+ p.cargo("run")
+ .cwd("b")
+ .with_stdout("ftest on")
+ .with_stderr(
+ "\
+[..]Compiling b v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `[..]target/debug/b[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_bin_features_caches_targets() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ authors = []
+ version = "0.0.1"
+
+ [features]
+ foo = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ let msg = if cfg!(feature = "foo") { "feature on" } else { "feature off" };
+ println!("{}", msg);
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.rename_run("foo", "off1").with_stdout("feature off").run();
+
+ p.cargo("build --features foo")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+ p.rename_run("foo", "on1").with_stdout("feature on").run();
+
+ /* Targets should be cached from the first build */
+
+ let mut e = p.cargo("build -v");
+
+ // MSVC does not include hash in binary filename, so it gets recompiled.
+ if cfg!(target_env = "msvc") {
+ e.with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the list of features changed
+[COMPILING] foo[..]
+[RUNNING] `rustc [..]
+[FINISHED] dev[..]",
+ );
+ } else {
+ e.with_stderr("[FRESH] foo v0.0.1 ([..])\n[FINISHED] dev[..]");
+ }
+ e.run();
+ p.rename_run("foo", "off2").with_stdout("feature off").run();
+
+ let mut e = p.cargo("build --features foo -v");
+ if cfg!(target_env = "msvc") {
+ e.with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the list of features changed
+[COMPILING] foo[..]
+[RUNNING] `rustc [..]
+[FINISHED] dev[..]",
+ );
+ } else {
+ e.with_stderr(
+ "\
+[FRESH] foo v0.0.1 ([..])
+[FINISHED] dev[..]",
+ );
+ }
+ e.run();
+ p.rename_run("foo", "on2").with_stdout("feature on").run();
+}
+
+#[cargo_test]
+fn rebuild_tests_if_lib_changes() {
+ let p = project()
+ .file("src/lib.rs", "pub fn foo() {}")
+ .file(
+ "tests/foo.rs",
+ r#"
+ extern crate foo;
+ #[test]
+ fn test() { foo::foo(); }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test").run();
+
+ sleep_ms(1000);
+ p.change_file("src/lib.rs", "");
+
+ p.cargo("build -v").run();
+ p.cargo("test -v")
+ .with_status(101)
+ .with_stderr_contains("[..]cannot find function `foo`[..]")
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_transitive_target_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ [dev-dependencies]
+ b = { path = "b" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("tests/foo.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+
+ [target.foo.dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file("c/Cargo.toml", &basic_manifest("c", "0.0.1"))
+ .file("c/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test --no-run")
+ .with_stderr(
+ "\
+[COMPILING] c v0.0.1 ([..])
+[COMPILING] b v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] test [unoptimized + debuginfo] target(s) in [..]
+[EXECUTABLE] unittests src/lib.rs (target/debug/deps/foo-[..][EXE])
+[EXECUTABLE] tests/foo.rs (target/debug/deps/foo-[..][EXE])
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rerun_if_changed_in_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.0.1"
+ authors = []
+ build = "build.rs"
+ "#,
+ )
+ .file(
+ "a/build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ "#,
+ )
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stdout("").run();
+}
+
+#[cargo_test]
+fn same_build_dir_cached_packages() {
+ let p = project()
+ .no_manifest()
+ .file(
+ "a1/Cargo.toml",
+ r#"
+ [package]
+ name = "a1"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ b = { path = "../b" }
+ "#,
+ )
+ .file("a1/src/lib.rs", "")
+ .file(
+ "a2/Cargo.toml",
+ r#"
+ [package]
+ name = "a2"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ b = { path = "../b" }
+ "#,
+ )
+ .file("a2/src/lib.rs", "")
+ .file(
+ "b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ c = { path = "../c" }
+ "#,
+ )
+ .file("b/src/lib.rs", "")
+ .file(
+ "c/Cargo.toml",
+ r#"
+ [package]
+ name = "c"
+ version = "0.0.1"
+ authors = []
+ [dependencies]
+ d = { path = "../d" }
+ "#,
+ )
+ .file("c/src/lib.rs", "")
+ .file("d/Cargo.toml", &basic_manifest("d", "0.0.1"))
+ .file("d/src/lib.rs", "")
+ .file(
+ ".cargo/config",
+ r#"
+ [build]
+ target-dir = "./target"
+ "#,
+ )
+ .build();
+
+ p.cargo("build")
+ .cwd("a1")
+ .with_stderr(&format!(
+ "\
+[COMPILING] d v0.0.1 ({dir}/d)
+[COMPILING] c v0.0.1 ({dir}/c)
+[COMPILING] b v0.0.1 ({dir}/b)
+[COMPILING] a1 v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ dir = p.url().to_file_path().unwrap().to_str().unwrap()
+ ))
+ .run();
+ p.cargo("build")
+ .cwd("a2")
+ .with_stderr(
+ "\
+[COMPILING] a2 v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_if_build_artifacts_move_backwards_in_time() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ p.root().move_into_the_past();
+
+ p.cargo("build")
+ .with_stdout("")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_if_build_artifacts_move_forward_in_time() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("a/Cargo.toml", &basic_manifest("a", "0.0.1"))
+ .file("a/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ p.root().move_into_the_future();
+
+ p.cargo("build")
+ .env("CARGO_LOG", "")
+ .with_stdout("")
+ .with_stderr(
+ "\
+[COMPILING] a v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_if_environment_changes() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ description = "old desc"
+ version = "0.0.1"
+ authors = []
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{}", env!("CARGO_PKG_DESCRIPTION"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("run")
+ .with_stdout("old desc")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([CWD])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+
+ p.change_file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ description = "new desc"
+ version = "0.0.1"
+ authors = []
+ "#,
+ );
+
+ p.cargo("run -v")
+ .with_stdout("new desc")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([CWD]): the metadata changed
+[COMPILING] foo v0.0.1 ([CWD])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+[RUNNING] `target/debug/foo[EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn no_rebuild_when_rename_dir() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [workspace]
+
+ [dependencies]
+ foo = { path = "foo" }
+ "#,
+ )
+ .file("src/_unused.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file("foo/Cargo.toml", &basic_manifest("foo", "0.0.1"))
+ .file("foo/src/lib.rs", "")
+ .file("foo/build.rs", "fn main() {}")
+ .build();
+
+ // make sure the most recently modified file is `src/lib.rs`, not
+ // `Cargo.toml`, to expose a historical bug where we forgot to strip the
+ // `Cargo.toml` path from looking for the package root.
+ cargo_test_support::sleep_ms(100);
+ fs::write(p.root().join("src/lib.rs"), "").unwrap();
+
+ p.cargo("build").run();
+ let mut new = p.root();
+ new.pop();
+ new.push("bar");
+ fs::rename(p.root(), &new).unwrap();
+
+ p.cargo("build")
+ .cwd(&new)
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+#[cargo_test]
+fn unused_optional_dep() {
+ Package::new("registry1", "0.1.0").publish();
+ Package::new("registry2", "0.1.0").publish();
+ Package::new("registry3", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "p"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ baz = { path = "baz" }
+ registry1 = "*"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+ authors = []
+
+ [dev-dependencies]
+ registry2 = "*"
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ registry3 = { version = "*", optional = true }
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn path_dev_dep_registry_updates() {
+ Package::new("registry1", "0.1.0").publish();
+ Package::new("registry2", "0.1.0").publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "p"
+ authors = []
+ version = "0.1.0"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ registry1 = "*"
+
+ [dev-dependencies]
+ baz = { path = "../baz"}
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [dependencies]
+ registry2 = "*"
+ "#,
+ )
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn change_panic_mode() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ['bar', 'baz']
+ [profile.dev]
+ panic = 'abort'
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+ authors = []
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ bar = { path = '../bar' }
+ "#,
+ )
+ .file("baz/src/lib.rs", "extern crate bar;")
+ .build();
+
+ p.cargo("build -p bar").run();
+ p.cargo("build -p baz").run();
+}
+
+#[cargo_test]
+fn dont_rebuild_based_on_plugins() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+
+ [workspace]
+ members = ['baz']
+
+ [dependencies]
+ proc-macro-thing = { path = 'proc-macro-thing' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file(
+ "proc-macro-thing/Cargo.toml",
+ r#"
+ [package]
+ name = "proc-macro-thing"
+ version = "0.1.1"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ qux = { path = '../qux' }
+ "#,
+ )
+ .file("proc-macro-thing/src/lib.rs", "")
+ .file(
+ "baz/Cargo.toml",
+ r#"
+ [package]
+ name = "baz"
+ version = "0.1.1"
+
+ [dependencies]
+ qux = { path = '../qux' }
+ "#,
+ )
+ .file("baz/src/main.rs", "fn main() {}")
+ .file("qux/Cargo.toml", &basic_manifest("qux", "0.1.1"))
+ .file("qux/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build -p baz").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]\n").run();
+ p.cargo("build -p bar")
+ .with_stderr("[FINISHED] [..]\n")
+ .run();
+}
+
+#[cargo_test]
+fn reuse_workspace_lib() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.1.1"
+
+ [workspace]
+
+ [dependencies]
+ baz = { path = 'baz' }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("baz/Cargo.toml", &basic_manifest("baz", "0.1.1"))
+ .file("baz/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("test -p baz -v --no-run")
+ .with_stderr(
+ "\
+[COMPILING] baz v0.1.1 ([..])
+[RUNNING] `rustc[..] --test [..]`
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/debug/deps/baz-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reuse_shared_build_dep() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ shared = {path = "shared"}
+
+ [workspace]
+ members = ["shared", "bar"]
+ "#,
+ )
+ .file("src/main.rs", "fn main() {}")
+ .file("shared/Cargo.toml", &basic_manifest("shared", "0.0.1"))
+ .file("shared/src/lib.rs", "")
+ .file(
+ "bar/Cargo.toml",
+ r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+
+ [build-dependencies]
+ shared = { path = "../shared" }
+ "#,
+ )
+ .file("bar/src/lib.rs", "")
+ .file("bar/build.rs", "fn main() {}")
+ .build();
+
+ p.cargo("build --workspace").run();
+ // This should not recompile!
+ p.cargo("build -p foo -v")
+ .with_stderr(
+ "\
+[FRESH] shared [..]
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_rustflags_is_cached() {
+ let p = project().file("src/lib.rs", "").build();
+
+ // This isn't ever cached, we always have to recompile
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ p.cargo("build -v")
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ p.cargo("build -v")
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the rustflags changed
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn update_dependency_mtime_does_not_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr(
+ "\
+[COMPILING] bar v0.0.1 ([..])
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ // This does not make new files, but it does update the mtime of the dependency.
+ p.cargo("build -p bar -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ // This should not recompile!
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .env("RUSTFLAGS", "-C linker=cc")
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+}
+
+fn fingerprint_cleaner(mut dir: PathBuf, timestamp: filetime::FileTime) {
+ // Cargo is experimenting with letting outside projects develop some
+ // limited forms of GC for target_dir. This is one of the forms.
+ // Specifically, Cargo is updating the mtime of a file in
+ // target/profile/.fingerprint each time it uses the fingerprint.
+ // So a cleaner can remove files associated with a fingerprint
+ // if all the files in the fingerprint's folder are older then a time stamp without
+ // effecting any builds that happened since that time stamp.
+ let mut cleaned = false;
+ dir.push(".fingerprint");
+ for fing in fs::read_dir(&dir).unwrap() {
+ let fing = fing.unwrap();
+
+ let outdated = |f: io::Result<fs::DirEntry>| {
+ filetime::FileTime::from_last_modification_time(&f.unwrap().metadata().unwrap())
+ <= timestamp
+ };
+ if fs::read_dir(fing.path()).unwrap().all(outdated) {
+ fs::remove_dir_all(fing.path()).unwrap();
+ println!("remove: {:?}", fing.path());
+ // a real cleaner would remove the big files in deps and build as well
+ // but fingerprint is sufficient for our tests
+ cleaned = true;
+ } else {
+ }
+ }
+ assert!(
+ cleaned,
+ "called fingerprint_cleaner, but there was nothing to remove"
+ );
+}
+
+#[cargo_test]
+fn fingerprint_cleaner_does_not_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+
+ [features]
+ a = []
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .run();
+ p.cargo("build -Z mtime-on-use --features a")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ let timestamp = filetime::FileTime::from_system_time(SystemTime::now());
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ // This does not make new files, but it does update the mtime.
+ p.cargo("build -Z mtime-on-use --features a")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ fingerprint_cleaner(p.target_debug_dir(), timestamp);
+ // This should not recompile!
+ p.cargo("build -Z mtime-on-use --features a")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr("[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]")
+ .run();
+ // But this should be cleaned and so need a rebuild
+ p.cargo("build -Z mtime-on-use")
+ .masquerade_as_nightly_cargo(&["mtime-on-use"])
+ .with_stderr(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reuse_panic_build_dep_test() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [build-dependencies]
+ bar = { path = "bar" }
+
+ [dev-dependencies]
+ bar = { path = "bar" }
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("build.rs", "fn main() {}")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .build();
+
+ // Check that `bar` is not built twice. It is only needed once (without `panic`).
+ p.cargo("test --lib --no-run -v")
+ .with_stderr(
+ "\
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name build_script_build [..]
+[RUNNING] [..]build-script-build`
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]--test[..]
+[FINISHED] [..]
+[EXECUTABLE] `[..]/target/debug/deps/foo-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn reuse_panic_pm() {
+ // foo(panic) -> bar(panic)
+ // somepm(nopanic) -> bar(nopanic)
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ bar = { path = "bar" }
+ somepm = { path = "somepm" }
+
+ [profile.dev]
+ panic = "abort"
+ "#,
+ )
+ .file("src/lib.rs", "extern crate bar;")
+ .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1"))
+ .file("bar/src/lib.rs", "")
+ .file(
+ "somepm/Cargo.toml",
+ r#"
+ [package]
+ name = "somepm"
+ version = "0.0.1"
+
+ [lib]
+ proc-macro = true
+
+ [dependencies]
+ bar = { path = "../bar" }
+ "#,
+ )
+ .file("somepm/src/lib.rs", "extern crate bar;")
+ .build();
+
+ // bar is built once without panic (for proc-macro) and once with (for the
+ // normal dependency).
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[COMPILING] bar [..]
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link[..]
+[RUNNING] `rustc --crate-name bar bar/src/lib.rs [..]--crate-type lib --emit=[..]link -C panic=abort[..]-C debuginfo=2 [..]
+[COMPILING] somepm [..]
+[RUNNING] `rustc --crate-name somepm [..]
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo src/lib.rs [..]-C panic=abort[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn bust_patched_dep() {
+ Package::new("registry1", "0.1.0").publish();
+ Package::new("registry2", "0.1.0")
+ .dep("registry1", "0.1.0")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.0.1"
+
+ [dependencies]
+ registry2 = "0.1.0"
+
+ [patch.crates-io]
+ registry1 = { path = "reg1new" }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .file("reg1new/Cargo.toml", &basic_manifest("registry1", "0.1.0"))
+ .file("reg1new/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ p.change_file("reg1new/src/lib.rs", "");
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[DIRTY] registry1 v0.1.0 ([..]): the file `reg1new/src/lib.rs` has changed ([..])
+[COMPILING] registry1 v0.1.0 ([..])
+[RUNNING] `rustc [..]
+[DIRTY] registry2 v0.1.0: the dependency registry1 was rebuilt
+[COMPILING] registry2 v0.1.0
+[RUNNING] `rustc [..]
+[DIRTY] foo v0.0.1 ([..]): the dependency registry2 was rebuilt
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] registry1 v0.1.0 ([..])
+[FRESH] registry2 v0.1.0
+[FRESH] foo v0.0.1 ([..])
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn rebuild_on_mid_build_file_modification() {
+ let server = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = server.local_addr().unwrap();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [workspace]
+ members = ["root", "proc_macro_dep"]
+ "#,
+ )
+ .file(
+ "root/Cargo.toml",
+ r#"
+ [package]
+ name = "root"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ proc_macro_dep = { path = "../proc_macro_dep" }
+ "#,
+ )
+ .file(
+ "root/src/lib.rs",
+ r#"
+ #[macro_use]
+ extern crate proc_macro_dep;
+
+ #[derive(Noop)]
+ pub struct X;
+ "#,
+ )
+ .file(
+ "proc_macro_dep/Cargo.toml",
+ r#"
+ [package]
+ name = "proc_macro_dep"
+ version = "0.1.0"
+ authors = []
+
+ [lib]
+ proc-macro = true
+ "#,
+ )
+ .file(
+ "proc_macro_dep/src/lib.rs",
+ &format!(
+ r#"
+ extern crate proc_macro;
+
+ use std::io::Read;
+ use std::net::TcpStream;
+ use proc_macro::TokenStream;
+
+ #[proc_macro_derive(Noop)]
+ pub fn noop(_input: TokenStream) -> TokenStream {{
+ let mut stream = TcpStream::connect("{}").unwrap();
+ let mut v = Vec::new();
+ stream.read_to_end(&mut v).unwrap();
+ "".parse().unwrap()
+ }}
+ "#,
+ addr
+ ),
+ )
+ .build();
+ let root = p.root();
+
+ let t = thread::spawn(move || {
+ let socket = server.accept().unwrap().0;
+ sleep_ms(1000);
+ let mut file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(root.join("root/src/lib.rs"))
+ .unwrap();
+ writeln!(file, "// modified").expect("Failed to append to root sources");
+ drop(file);
+ drop(socket);
+ drop(server.accept().unwrap());
+ });
+
+ p.cargo("build")
+ .with_stderr(
+ "\
+[COMPILING] proc_macro_dep v0.1.0 ([..]/proc_macro_dep)
+[COMPILING] root v0.1.0 ([..]/root)
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ p.cargo("build -v")
+ .with_stderr(
+ "\
+[FRESH] proc_macro_dep v0.1.0 ([..]/proc_macro_dep)
+[DIRTY] root v0.1.0 ([..]/root): the file `root/src/lib.rs` has changed ([..])
+[COMPILING] root v0.1.0 ([..]/root)
+[RUNNING] `rustc [..]
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+",
+ )
+ .run();
+
+ t.join().ok().unwrap();
+}
+
+#[cargo_test]
+fn dirty_both_lib_and_test() {
+ // This tests that all artifacts that depend on the results of a build
+ // script will get rebuilt when the build script reruns, even for separate
+ // commands. It does the following:
+ //
+ // 1. Project "foo" has a build script which will compile a small
+ // staticlib to link against. Normally this would use the `cc` crate,
+ // but here we just use rustc to avoid the `cc` dependency.
+ // 2. Build the library.
+ // 3. Build the unit test. The staticlib intentionally has a bad value.
+ // 4. Rewrite the staticlib with the correct value.
+ // 5. Build the library again.
+ // 6. Build the unit test. This should recompile.
+
+ let slib = |n| {
+ format!(
+ r#"
+ #[no_mangle]
+ pub extern "C" fn doit() -> i32 {{
+ return {};
+ }}
+ "#,
+ n
+ )
+ };
+
+ let p = project()
+ .file(
+ "src/lib.rs",
+ r#"
+ extern "C" {
+ fn doit() -> i32;
+ }
+
+ #[test]
+ fn t1() {
+ assert_eq!(unsafe { doit() }, 1, "doit assert failure");
+ }
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ use std::env;
+ use std::path::PathBuf;
+ use std::process::Command;
+
+ fn main() {
+ let rustc = env::var_os("RUSTC").unwrap();
+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+ assert!(
+ Command::new(rustc)
+ .args(&[
+ "--crate-type=staticlib",
+ "--out-dir",
+ out_dir.to_str().unwrap(),
+ "slib.rs"
+ ])
+ .status()
+ .unwrap()
+ .success(),
+ "slib build failed"
+ );
+ println!("cargo:rustc-link-lib=slib");
+ println!("cargo:rustc-link-search={}", out_dir.display());
+ }
+ "#,
+ )
+ .file("slib.rs", &slib(2))
+ .build();
+
+ p.cargo("build").run();
+
+ // 2 != 1
+ p.cargo("test --lib")
+ .with_status(101)
+ .with_stdout_contains("[..]doit assert failure[..]")
+ .run();
+
+ if is_coarse_mtime() {
+ // #5918
+ sleep_ms(1000);
+ }
+ // Fix the mistake.
+ p.change_file("slib.rs", &slib(1));
+
+ p.cargo("build").run();
+ // This should recompile with the new static lib, and the test should pass.
+ p.cargo("test --lib").run();
+}
+
+#[cargo_test]
+fn script_fails_stay_dirty() {
+ // Check if a script is aborted (such as hitting Ctrl-C) that it will re-run.
+ // Steps:
+ // 1. Build to establish fingerprints.
+ // 2. Make a change that triggers the build script to re-run. Abort the
+ // script while it is running.
+ // 3. Run the build again and make sure it re-runs the script.
+ let p = project()
+ .file(
+ "build.rs",
+ r#"
+ mod helper;
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ helper::doit();
+ }
+ "#,
+ )
+ .file("helper.rs", "pub fn doit() {}")
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ if is_coarse_mtime() {
+ sleep_ms(1000);
+ }
+ p.change_file("helper.rs", r#"pub fn doit() {panic!("Crash!");}"#);
+ p.cargo("build")
+ .with_stderr_contains("[..]Crash![..]")
+ .with_status(101)
+ .run();
+ // There was a bug where this second call would be "fresh".
+ p.cargo("build")
+ .with_stderr_contains("[..]Crash![..]")
+ .with_status(101)
+ .run();
+}
+
+#[cargo_test]
+fn simulated_docker_deps_stay_cached() {
+ // Test what happens in docker where the nanoseconds are zeroed out.
+ Package::new("regdep", "1.0.0").publish();
+ Package::new("regdep_old_style", "1.0.0")
+ .file("build.rs", "fn main() {}")
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("regdep_env", "1.0.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=SOMEVAR");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+ Package::new("regdep_rerun", "1.0.0")
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .publish();
+
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ pathdep = { path = "pathdep" }
+ regdep = "1.0"
+ regdep_old_style = "1.0"
+ regdep_env = "1.0"
+ regdep_rerun = "1.0"
+ "#,
+ )
+ .file(
+ "src/lib.rs",
+ "
+ extern crate pathdep;
+ extern crate regdep;
+ extern crate regdep_old_style;
+ extern crate regdep_env;
+ extern crate regdep_rerun;
+ ",
+ )
+ .file("build.rs", "fn main() {}")
+ .file("pathdep/Cargo.toml", &basic_manifest("pathdep", "1.0.0"))
+ .file("pathdep/src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ let already_zero = {
+ // This happens on HFS with 1-second timestamp resolution,
+ // or other filesystems where it just so happens to write exactly on a
+ // 1-second boundary.
+ let metadata = fs::metadata(p.root().join("src/lib.rs")).unwrap();
+ let mtime = FileTime::from_last_modification_time(&metadata);
+ mtime.nanoseconds() == 0
+ };
+
+ // Recursively remove `nanoseconds` from every path.
+ fn zeropath(path: &Path) {
+ for entry in walkdir::WalkDir::new(path)
+ .into_iter()
+ .filter_map(|e| e.ok())
+ {
+ let metadata = fs::metadata(entry.path()).unwrap();
+ let mtime = metadata.modified().unwrap();
+ let mtime_duration = mtime.duration_since(SystemTime::UNIX_EPOCH).unwrap();
+ let trunc_mtime = FileTime::from_unix_time(mtime_duration.as_secs() as i64, 0);
+ let atime = metadata.accessed().unwrap();
+ let atime_duration = atime.duration_since(SystemTime::UNIX_EPOCH).unwrap();
+ let trunc_atime = FileTime::from_unix_time(atime_duration.as_secs() as i64, 0);
+ if let Err(e) = filetime::set_file_times(entry.path(), trunc_atime, trunc_mtime) {
+ // Windows doesn't allow changing filetimes on some things
+ // (directories, other random things I'm not sure why). Just
+ // ignore them.
+ if e.kind() == std::io::ErrorKind::PermissionDenied {
+ println!("PermissionDenied filetime on {:?}", entry.path());
+ } else {
+ panic!("FileTime error on {:?}: {:?}", entry.path(), e);
+ }
+ }
+ }
+ }
+ zeropath(&p.root());
+ zeropath(&paths::home());
+
+ if already_zero {
+ println!("already zero");
+ // If it was already truncated, then everything stays fresh.
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] pathdep [..]
+[FRESH] regdep [..]
+[FRESH] regdep_env [..]
+[FRESH] regdep_old_style [..]
+[FRESH] regdep_rerun [..]
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ } else {
+ println!("not already zero");
+ // It is not ideal that `foo` gets recompiled, but that is the current
+ // behavior. Currently mtimes are ignored for registry deps.
+ //
+ // Note that this behavior is due to the fact that `foo` has a build
+ // script in "old" mode where it doesn't print `rerun-if-*`. In this
+ // mode we use `Precalculated` to fingerprint a path dependency, where
+ // `Precalculated` is an opaque string which has the most recent mtime
+ // in it. It differs between builds because one has nsec=0 and the other
+ // likely has a nonzero nsec. Hence, the rebuild.
+ p.cargo("build -v")
+ .with_stderr_unordered(
+ "\
+[FRESH] pathdep [..]
+[FRESH] regdep [..]
+[FRESH] regdep_env [..]
+[FRESH] regdep_old_style [..]
+[FRESH] regdep_rerun [..]
+[DIRTY] foo [..]: the precalculated components changed
+[COMPILING] foo [..]
+[RUNNING] [..]/foo-[..]/build-script-build[..]
+[RUNNING] `rustc --crate-name foo[..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ }
+}
+
+#[cargo_test]
+fn metadata_change_invalidates() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+
+ for attr in &[
+ "authors = [\"foo\"]",
+ "description = \"desc\"",
+ "homepage = \"https://example.com\"",
+ "repository =\"https://example.com\"",
+ ] {
+ let mut file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .open(p.root().join("Cargo.toml"))
+ .unwrap();
+ writeln!(file, "{}", attr).unwrap();
+ p.cargo("build")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ }
+ p.cargo("build -v")
+ .with_stderr_contains("[FRESH] foo[..]")
+ .run();
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rlib").count(), 1);
+}
+
+#[cargo_test]
+fn edition_change_invalidates() {
+ const MANIFEST: &str = r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#;
+ let p = project()
+ .file("Cargo.toml", MANIFEST)
+ .file("src/lib.rs", "")
+ .build();
+ p.cargo("build").run();
+ p.change_file("Cargo.toml", &format!("{}edition = \"2018\"", MANIFEST));
+ p.cargo("build")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ p.change_file(
+ "Cargo.toml",
+ &format!(
+ r#"{}edition = "2018"
+ [lib]
+ edition = "2015"
+ "#,
+ MANIFEST
+ ),
+ );
+ p.cargo("build")
+ .with_stderr_contains("[COMPILING] foo [..]")
+ .run();
+ p.cargo("build -v")
+ .with_stderr_contains("[FRESH] foo[..]")
+ .run();
+ assert_eq!(p.glob("target/debug/deps/libfoo-*.rlib").count(), 1);
+}
+
+#[cargo_test]
+fn rename_with_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = { path = 'a' }
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; pub fn foo() { a::foo(); }")
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ b = { path = 'b' }
+ "#,
+ )
+ .file("a/src/lib.rs", "extern crate b; pub fn foo() { b::foo() }")
+ .file(
+ "a/b/Cargo.toml",
+ r#"
+ [package]
+ name = "b"
+ version = "0.5.0"
+ authors = []
+ "#,
+ )
+ .file("a/b/src/lib.rs", "pub fn foo() { }");
+ let p = p.build();
+
+ p.cargo("build").run();
+
+ // Now rename the root directory and rerun `cargo run`. Not only should we
+ // not build anything but we also shouldn't crash.
+ let mut new = p.root();
+ new.pop();
+ new.push("foo2");
+
+ fs::rename(p.root(), &new).unwrap();
+
+ p.cargo("build")
+ .cwd(&new)
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn move_target_directory_with_path_deps() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.5.0"
+ authors = []
+
+ [dependencies]
+ a = { path = "a" }
+ "#,
+ )
+ .file(
+ "a/Cargo.toml",
+ r#"
+ [package]
+ name = "a"
+ version = "0.5.0"
+ authors = []
+ "#,
+ )
+ .file("src/lib.rs", "extern crate a; pub use a::print_msg;")
+ .file(
+ "a/build.rs",
+ r###"
+ use std::env;
+ use std::fs;
+ use std::path::Path;
+
+ fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+ let out_dir = env::var("OUT_DIR").unwrap();
+ let dest_path = Path::new(&out_dir).join("hello.rs");
+ fs::write(&dest_path, r#"
+ pub fn message() -> &'static str {
+ "Hello, World!"
+ }
+ "#).unwrap();
+ }
+ "###,
+ )
+ .file(
+ "a/src/lib.rs",
+ r#"
+ include!(concat!(env!("OUT_DIR"), "/hello.rs"));
+ pub fn print_msg() { message(); }
+ "#,
+ );
+ let p = p.build();
+
+ let mut parent = p.root();
+ parent.pop();
+
+ p.cargo("build").run();
+
+ let new_target = p.root().join("target2");
+ fs::rename(p.root().join("target"), &new_target).unwrap();
+
+ p.cargo("build")
+ .env("CARGO_TARGET_DIR", &new_target)
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn rerun_if_changes() {
+ let p = project()
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rerun-if-env-changed=FOO");
+ if std::env::var("FOO").is_ok() {
+ println!("cargo:rerun-if-env-changed=BAR");
+ }
+ }
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+
+ p.cargo("build -v")
+ .env("FOO", "1")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the env variable FOO changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "1")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+
+ p.cargo("build -v")
+ .env("FOO", "1")
+ .env("BAR", "1")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the env variable BAR changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "1")
+ .env("BAR", "1")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+
+ p.cargo("build -v")
+ .env("BAR", "2")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the env variable FOO changed
+[COMPILING] foo [..]
+[RUNNING] `[..]build-script-build`
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ p.cargo("build")
+ .env("BAR", "2")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn channel_shares_filenames() {
+ // Test that different "nightly" releases use the same output filename.
+
+ // Create separate rustc binaries to emulate running different toolchains.
+ let nightly1 = format!(
+ "\
+rustc 1.44.0-nightly (38114ff16 2020-03-21)
+binary: rustc
+commit-hash: 38114ff16e7856f98b2b4be7ab4cd29b38bed59a
+commit-date: 2020-03-21
+host: {}
+release: 1.44.0-nightly
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let nightly2 = format!(
+ "\
+rustc 1.44.0-nightly (a5b09d354 2020-03-31)
+binary: rustc
+commit-hash: a5b09d35473615e7142f5570f5c5fad0caf68bd2
+commit-date: 2020-03-31
+host: {}
+release: 1.44.0-nightly
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let beta1 = format!(
+ "\
+rustc 1.43.0-beta.3 (4c587bbda 2020-03-25)
+binary: rustc
+commit-hash: 4c587bbda04ab55aaf56feab11dfdfe387a85d7a
+commit-date: 2020-03-25
+host: {}
+release: 1.43.0-beta.3
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let beta2 = format!(
+ "\
+rustc 1.42.0-beta.5 (4e1c5f0e9 2020-02-28)
+binary: rustc
+commit-hash: 4e1c5f0e9769a588b91c977e3d81e140209ef3a2
+commit-date: 2020-02-28
+host: {}
+release: 1.42.0-beta.5
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let stable1 = format!(
+ "\
+rustc 1.42.0 (b8cedc004 2020-03-09)
+binary: rustc
+commit-hash: b8cedc00407a4c56a3bda1ed605c6fc166655447
+commit-date: 2020-03-09
+host: {}
+release: 1.42.0
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let stable2 = format!(
+ "\
+rustc 1.41.1 (f3e1a954d 2020-02-24)
+binary: rustc
+commit-hash: f3e1a954d2ead4e2fc197c7da7d71e6c61bad196
+commit-date: 2020-02-24
+host: {}
+release: 1.41.1
+LLVM version: 9.0
+",
+ rustc_host()
+ );
+
+ let compiler = project()
+ .at("compiler")
+ .file("Cargo.toml", &basic_manifest("compiler", "0.1.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ if std::env::args_os().any(|a| a == "-vV") {
+ print!("{}", env!("FUNKY_VERSION_TEST"));
+ return;
+ }
+ let mut cmd = std::process::Command::new("rustc");
+ cmd.args(std::env::args_os().skip(1));
+ assert!(cmd.status().unwrap().success());
+ }
+ "#,
+ )
+ .build();
+
+ let makeit = |version, vv| {
+ // Force a rebuild.
+ compiler.target_debug_dir().join("deps").rm_rf();
+ compiler.cargo("build").env("FUNKY_VERSION_TEST", vv).run();
+ fs::rename(compiler.bin("compiler"), compiler.bin(version)).unwrap();
+ };
+ makeit("nightly1", nightly1);
+ makeit("nightly2", nightly2);
+ makeit("beta1", beta1);
+ makeit("beta2", beta2);
+ makeit("stable1", stable1);
+ makeit("stable2", stable2);
+
+ // Run `cargo check` with different rustc versions to observe its behavior.
+ let p = project().file("src/lib.rs", "").build();
+
+ // Runs `cargo check` and returns the rmeta filename created.
+ // Checks that the freshness matches the given value.
+ let check = |version, fresh| -> String {
+ let output = p
+ .cargo("check --message-format=json")
+ .env("RUSTC", compiler.bin(version))
+ .exec_with_output()
+ .unwrap();
+ // Collect the filenames generated.
+ let mut artifacts: Vec<_> = std::str::from_utf8(&output.stdout)
+ .unwrap()
+ .lines()
+ .filter_map(|line| {
+ let value: serde_json::Value = serde_json::from_str(line).unwrap();
+ if value["reason"].as_str().unwrap() == "compiler-artifact" {
+ assert_eq!(value["fresh"].as_bool().unwrap(), fresh);
+ let filenames = value["filenames"].as_array().unwrap();
+ assert_eq!(filenames.len(), 1);
+ Some(filenames[0].to_string())
+ } else {
+ None
+ }
+ })
+ .collect();
+ // Should only generate one rmeta file.
+ assert_eq!(artifacts.len(), 1);
+ artifacts.pop().unwrap()
+ };
+
+ let nightly1_name = check("nightly1", false);
+ assert_eq!(check("nightly1", true), nightly1_name);
+ assert_eq!(check("nightly2", false), nightly1_name); // same as before
+ assert_eq!(check("nightly2", true), nightly1_name);
+ // Should rebuild going back to nightly1.
+ assert_eq!(check("nightly1", false), nightly1_name);
+
+ let beta1_name = check("beta1", false);
+ assert_ne!(beta1_name, nightly1_name);
+ assert_eq!(check("beta1", true), beta1_name);
+ assert_eq!(check("beta2", false), beta1_name); // same as before
+ assert_eq!(check("beta2", true), beta1_name);
+ // Should rebuild going back to beta1.
+ assert_eq!(check("beta1", false), beta1_name);
+
+ let stable1_name = check("stable1", false);
+ assert_ne!(stable1_name, nightly1_name);
+ assert_ne!(stable1_name, beta1_name);
+ let stable2_name = check("stable2", false);
+ assert_ne!(stable1_name, stable2_name);
+ // Check everything is fresh.
+ assert_eq!(check("stable1", true), stable1_name);
+ assert_eq!(check("stable2", true), stable2_name);
+ assert_eq!(check("beta1", true), beta1_name);
+ assert_eq!(check("nightly1", true), nightly1_name);
+}
+
+#[cargo_test]
+fn linking_interrupted() {
+ // Interrupt during the linking phase shouldn't leave test executable as "fresh".
+
+ // This is used to detect when linking starts, then to pause the linker so
+ // that the test can kill cargo.
+ let link_listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let link_addr = link_listener.local_addr().unwrap();
+
+ // This is used to detect when rustc exits.
+ let rustc_listener = TcpListener::bind("127.0.0.1:0").unwrap();
+ let rustc_addr = rustc_listener.local_addr().unwrap();
+
+ // Create a linker that we can interrupt.
+ let linker = project()
+ .at("linker")
+ .file("Cargo.toml", &basic_manifest("linker", "1.0.0"))
+ .file(
+ "src/main.rs",
+ &r#"
+ fn main() {
+ // Figure out the output filename.
+ let output = match std::env::args().find(|a| a.starts_with("/OUT:")) {
+ Some(s) => s[5..].to_string(),
+ None => {
+ let mut args = std::env::args();
+ loop {
+ if args.next().unwrap() == "-o" {
+ break;
+ }
+ }
+ args.next().unwrap()
+ }
+ };
+ std::fs::remove_file(&output).unwrap();
+ std::fs::write(&output, "").unwrap();
+ // Tell the test that we are ready to be interrupted.
+ let mut socket = std::net::TcpStream::connect("__ADDR__").unwrap();
+ // Wait for the test to kill us.
+ std::thread::sleep(std::time::Duration::new(60, 0));
+ }
+ "#
+ .replace("__ADDR__", &link_addr.to_string()),
+ )
+ .build();
+ linker.cargo("build").run();
+
+ // Create a wrapper around rustc that will tell us when rustc is finished.
+ let rustc = project()
+ .at("rustc-waiter")
+ .file("Cargo.toml", &basic_manifest("rustc-waiter", "1.0.0"))
+ .file(
+ "src/main.rs",
+ &r#"
+ fn main() {
+ let mut conn = None;
+ // Check for a normal build (not -vV or --print).
+ if std::env::args().any(|arg| arg == "t1") {
+ // Tell the test that rustc has started.
+ conn = Some(std::net::TcpStream::connect("__ADDR__").unwrap());
+ }
+ let status = std::process::Command::new("rustc")
+ .args(std::env::args().skip(1))
+ .status()
+ .expect("rustc to run");
+ std::process::exit(status.code().unwrap_or(1));
+ }
+ "#
+ .replace("__ADDR__", &rustc_addr.to_string()),
+ )
+ .build();
+ rustc.cargo("build").run();
+
+ // Build it once so that the fingerprint gets saved to disk.
+ let p = project()
+ .file("src/lib.rs", "")
+ .file("tests/t1.rs", "")
+ .build();
+ p.cargo("test --test t1 --no-run").run();
+
+ // Make a change, start a build, then interrupt it.
+ p.change_file("src/lib.rs", "// modified");
+ let linker_env = format!("CARGO_TARGET_{}_LINKER", rustc_host_env());
+ // NOTE: This assumes that the paths to the linker or rustc are not in the
+ // fingerprint. But maybe they should be?
+ let mut cmd = p
+ .cargo("test --test t1 --no-run")
+ .env(&linker_env, linker.bin("linker"))
+ .env("RUSTC", rustc.bin("rustc-waiter"))
+ .build_command();
+ let mut child = cmd
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .env("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE", "1")
+ .spawn()
+ .unwrap();
+ // Wait for rustc to start.
+ let mut rustc_conn = rustc_listener.accept().unwrap().0;
+ // Wait for linking to start.
+ drop(link_listener.accept().unwrap());
+
+ // Interrupt the child.
+ death::ctrl_c(&mut child);
+ assert!(!child.wait().unwrap().success());
+ // Wait for rustc to exit. If we don't wait, then the command below could
+ // start while rustc is still being torn down.
+ let mut buf = [0];
+ drop(rustc_conn.read_exact(&mut buf));
+
+ // Build again, shouldn't be fresh.
+ p.cargo("test --test t1 -v")
+ .with_stderr(
+ "\
+[DIRTY] foo v0.0.1 ([..]): the config settings changed
+[COMPILING] foo [..]
+[RUNNING] `rustc --crate-name foo [..]
+[RUNNING] `rustc --crate-name t1 [..]
+[FINISHED] [..]
+[RUNNING] `[..]target/debug/deps/t1-[..][EXE]`
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+#[cfg_attr(
+ not(all(target_arch = "x86_64", target_os = "windows", target_env = "msvc")),
+ ignore
+)]
+fn lld_is_fresh() {
+ // Check for bug when using lld linker that it remains fresh with dylib.
+ let p = project()
+ .file(
+ ".cargo/config",
+ r#"
+ [target.x86_64-pc-windows-msvc]
+ linker = "rust-lld"
+ rustflags = ["-C", "link-arg=-fuse-ld=lld"]
+ "#,
+ )
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [lib]
+ crate-type = ["dylib"]
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build -v")
+ .with_stderr("[FRESH] foo [..]\n[FINISHED] [..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_in_code_causes_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{:?}", option_env!("FOO"));
+ println!("{:?}", option_env!("FOO\nBAR"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").env_remove("FOO").run();
+ p.cargo("build")
+ .env_remove("FOO")
+ .with_stderr("[FINISHED] [..]")
+ .run();
+ p.cargo("build -v")
+ .env("FOO", "bar")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the environment variable FOO changed
+[COMPILING][..]
+[RUNNING] `rustc [..]
+[FINISHED][..]",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "bar")
+ .with_stderr("[FINISHED][..]")
+ .run();
+ p.cargo("build -v")
+ .env("FOO", "baz")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the environment variable FOO changed
+[COMPILING][..]
+[RUNNING] `rustc [..]
+[FINISHED][..]",
+ )
+ .run();
+ p.cargo("build")
+ .env("FOO", "baz")
+ .with_stderr("[FINISHED][..]")
+ .run();
+ p.cargo("build -v")
+ .env_remove("FOO")
+ .with_stderr(
+ "\
+[DIRTY] foo [..]: the environment variable FOO changed
+[COMPILING][..]
+[RUNNING] `rustc [..]
+[FINISHED][..]",
+ )
+ .run();
+ p.cargo("build")
+ .env_remove("FOO")
+ .with_stderr("[FINISHED][..]")
+ .run();
+
+ let interesting = " #!$\nabc\r\\\t\u{8}\r\n";
+ p.cargo("build").env("FOO", interesting).run();
+ p.cargo("build")
+ .env("FOO", interesting)
+ .with_stderr("[FINISHED][..]")
+ .run();
+
+ p.cargo("build").env("FOO\nBAR", interesting).run();
+ p.cargo("build")
+ .env("FOO\nBAR", interesting)
+ .with_stderr("[FINISHED][..]")
+ .run();
+}
+
+#[cargo_test]
+fn env_build_script_no_rebuild() {
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ "#,
+ )
+ .file(
+ "build.rs",
+ r#"
+ fn main() {
+ println!("cargo:rustc-env=FOO=bar");
+ }
+ "#,
+ )
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{:?}", env!("FOO"));
+ }
+ "#,
+ )
+ .build();
+
+ p.cargo("build").run();
+ p.cargo("build").with_stderr("[FINISHED] [..]").run();
+}
+
+#[cargo_test]
+fn cargo_env_changes() {
+ // Checks that changes to the env var CARGO in the dep-info file triggers
+ // a rebuild.
+ let p = project()
+ .file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
+ .file(
+ "src/main.rs",
+ r#"
+ fn main() {
+ println!("{:?}", env!("CARGO"));
+ }
+ "#,
+ )
+ .build();
+
+ let cargo_exe = cargo_test_support::cargo_exe();
+ let other_cargo_path = p.root().join(cargo_exe.file_name().unwrap());
+ std::fs::hard_link(&cargo_exe, &other_cargo_path).unwrap();
+ let other_cargo = || {
+ let mut pb = cargo_test_support::process(&other_cargo_path);
+ pb.cwd(p.root());
+ cargo_test_support::execs().with_process_builder(pb)
+ };
+
+ p.cargo("check").run();
+ other_cargo()
+ .arg("check")
+ .arg("-v")
+ .with_stderr(
+ "\
+[DIRTY] foo v1.0.0 ([..]): the environment variable CARGO changed
+[CHECKING] foo [..]
+[RUNNING] `rustc [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+
+ // And just to confirm that without using env! it doesn't rebuild.
+ p.change_file("src/main.rs", "fn main() {}");
+ p.cargo("check")
+ .with_stderr(
+ "\
+[CHECKING] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+ other_cargo()
+ .arg("check")
+ .arg("-v")
+ .with_stderr(
+ "\
+[FRESH] foo [..]
+[FINISHED] [..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn changing_linker() {
+ // Changing linker should rebuild.
+ let p = project().file("src/main.rs", "fn main() {}").build();
+ p.cargo("build").run();
+ let linker_env = format!("CARGO_TARGET_{}_LINKER", rustc_host_env());
+ p.cargo("build --verbose")
+ .env(&linker_env, "nonexistent-linker")
+ .with_status(101)
+ .with_stderr_contains(
+ "\
+[COMPILING] foo v0.0.1 ([..])
+[RUNNING] `rustc [..] -C linker=nonexistent-linker [..]`
+[ERROR] [..]linker[..]
+",
+ )
+ .run();
+}
+
+#[cargo_test]
+fn verify_source_before_recompile() {
+ Package::new("bar", "0.1.0")
+ .file("src/lib.rs", "")
+ .publish();
+ let p = project()
+ .file(
+ "Cargo.toml",
+ r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+
+ [dependencies]
+ bar = "0.1.0"
+ "#,
+ )
+ .file("src/lib.rs", "")
+ .build();
+
+ p.cargo("vendor --respect-source-config").run();
+ p.change_file(
+ ".cargo/config.toml",
+ r#"
+ [source.crates-io]
+ replace-with = 'vendor'
+
+ [source.vendor]
+ directory = 'vendor'
+ "#,
+ );
+ // Sanity check: vendoring works correctly.
+ p.cargo("check --verbose")
+ .with_stderr_contains("[RUNNING] `rustc --crate-name bar [CWD]/vendor/bar/src/lib.rs[..]")
+ .run();
+ // Now modify vendored crate.
+ p.change_file(
+ "vendor/bar/src/lib.rs",
+ r#"compile_error!("You shall not pass!");"#,
+ );
+ // Should ignore modifed sources without any recompile.
+ p.cargo("check --verbose")
+ .with_stderr(
+ "\
+[FRESH] bar v0.1.0
+[FRESH] foo v0.1.0 ([CWD])
+[FINISHED] dev [..]
+",
+ )
+ .run();
+
+ // Add a `RUSTFLAGS` to trigger a recompile.
+ //
+ // Cargo should refuse to build because of checksum verfication failure.
+ // Cargo shouldn't recompile dependency `bar`.
+ p.cargo("check --verbose")
+ .env("RUSTFLAGS", "-W warnings")
+ .with_status(101)
+ .with_stderr(
+ "\
+error: the listed checksum of `[CWD]/vendor/bar/src/lib.rs` has changed:
+expected: [..]
+actual: [..]
+
+directory sources are not [..]
+",
+ )
+ .run();
+}