//! Tests for build.rs scripts. use cargo_test_support::compare::assert_match_exact; use cargo_test_support::install::cargo_home; use cargo_test_support::paths::CargoPathExt; use cargo_test_support::registry::Package; use cargo_test_support::tools; use cargo_test_support::{ basic_manifest, cargo_exe, cross_compile, is_coarse_mtime, project, project_in, }; use cargo_test_support::{rustc_host, sleep_ms, slow_cpu_multiplier, symlink_supported}; use cargo_util::paths::{self, remove_dir_all}; use std::env; use std::fs; use std::io; use std::thread; #[cargo_test] fn custom_build_script_failed() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" "#, ) .file("src/main.rs", "fn main() {}") .file("build.rs", "fn main() { std::process::exit(101); }") .build(); p.cargo("build -v") .with_status(101) .with_stderr( "\ [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]` [RUNNING] `[..]/build-script-build` [ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])` Caused by: process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)", ) .run(); } #[cargo_test] fn custom_build_script_failed_backtraces_message() { // In this situation (no dependency sharing), debuginfo is turned off in // `dev.build-override`. However, if an error occurs running e.g. a build // script, and backtraces are opted into: a message explaining how to // improve backtraces is also displayed. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" "#, ) .file("src/main.rs", "fn main() {}") .file("build.rs", "fn main() { std::process::exit(101); }") .build(); p.cargo("build -v") .env("RUST_BACKTRACE", "1") .with_status(101) .with_stderr( "\ [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]` [RUNNING] `[..]/build-script-build` [ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])` note: To improve backtraces for build dependencies, set the \ CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable [..] Caused by: process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)", ) .run(); p.cargo("check -v") .env("RUST_BACKTRACE", "1") .with_status(101) .with_stderr( "\ [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `[..]/build-script-build` [ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])` note: To improve backtraces for build dependencies, set the \ CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable [..] Caused by: process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)", ) .run(); } #[cargo_test] fn custom_build_script_failed_backtraces_message_with_debuginfo() { // This is the same test as `custom_build_script_failed_backtraces_message` above, this time // ensuring that the message dedicated to improving backtraces by requesting debuginfo is not // shown when debuginfo is already turned on. let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" "#, ) .file("src/main.rs", "fn main() {}") .file("build.rs", "fn main() { std::process::exit(101); }") .build(); p.cargo("build -v") .env("RUST_BACKTRACE", "1") .env("CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG", "true") .with_status(101) .with_stderr( "\ [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]` [RUNNING] `[..]/build-script-build` [ERROR] failed to run custom build command for `foo v0.5.0 ([CWD])` Caused by: process didn't exit successfully: `[..]/build-script-build` (exit [..]: 101)", ) .run(); } #[cargo_test] fn custom_build_env_vars() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] [features] bar_feat = ["bar/foo"] [dependencies.bar] path = "bar" "#, ) .file("src/main.rs", "fn main() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" [features] foo = [] "#, ) .file("bar/src/lib.rs", "pub fn hello() {}"); let cargo = cargo_exe().canonicalize().unwrap(); let cargo = cargo.to_str().unwrap(); let rustc = paths::resolve_executable("rustc".as_ref()) .unwrap() .canonicalize() .unwrap(); let rustc = rustc.to_str().unwrap(); let file_content = format!( r##" use std::env; use std::path::Path; fn main() {{ let _target = env::var("TARGET").unwrap(); let _ncpus = env::var("NUM_JOBS").unwrap(); let _dir = env::var("CARGO_MANIFEST_DIR").unwrap(); let opt = env::var("OPT_LEVEL").unwrap(); assert_eq!(opt, "0"); let opt = env::var("PROFILE").unwrap(); assert_eq!(opt, "debug"); let debug = env::var("DEBUG").unwrap(); assert_eq!(debug, "true"); let out = env::var("OUT_DIR").unwrap(); assert!(out.starts_with(r"{0}")); assert!(Path::new(&out).is_dir()); let _host = env::var("HOST").unwrap(); let _feat = env::var("CARGO_FEATURE_FOO").unwrap(); let cargo = env::var("CARGO").unwrap(); if env::var_os("CHECK_CARGO_IS_RUSTC").is_some() {{ assert_eq!(cargo, r#"{rustc}"#); }} else {{ assert_eq!(cargo, r#"{cargo}"#); }} let rustc = env::var("RUSTC").unwrap(); assert_eq!(rustc, "rustc"); let rustdoc = env::var("RUSTDOC").unwrap(); assert_eq!(rustdoc, "rustdoc"); assert!(env::var("RUSTC_WRAPPER").is_err()); assert!(env::var("RUSTC_WORKSPACE_WRAPPER").is_err()); assert!(env::var("RUSTC_LINKER").is_err()); assert!(env::var("RUSTFLAGS").is_err()); let rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); assert_eq!(rustflags, ""); }} "##, p.root() .join("target") .join("debug") .join("build") .display(), ); let p = p.file("bar/build.rs", &file_content).build(); p.cargo("build --features bar_feat").run(); p.cargo("build --features bar_feat") // we use rustc since $CARGO is only used if it points to a path that exists .env("CHECK_CARGO_IS_RUSTC", "1") .env(cargo::CARGO_ENV, rustc) .run(); } #[cargo_test] fn custom_build_env_var_rustflags() { let rustflags = "--cfg=special"; let rustflags_alt = "--cfg=notspecial"; let p = project() .file( ".cargo/config.toml", &format!( r#" [build] rustflags = ["{}"] "#, rustflags ), ) .file( "build.rs", &format!( r#" use std::env; fn main() {{ // Static assertion that exactly one of the cfg paths is always taken. assert!(env::var("RUSTFLAGS").is_err()); let x; #[cfg(special)] {{ assert_eq!(env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(), "{}"); x = String::new(); }} #[cfg(notspecial)] {{ assert_eq!(env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(), "{}"); x = String::new(); }} let _ = x; }} "#, rustflags, rustflags_alt, ), ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); // RUSTFLAGS overrides build.rustflags, so --cfg=special shouldn't be passed p.cargo("check").env("RUSTFLAGS", rustflags_alt).run(); } #[cargo_test] fn custom_build_env_var_encoded_rustflags() { // NOTE: We use "-Clink-arg=-B nope" here rather than, say, "-A missing_docs", since for the // latter it won't matter if the whitespace accidentally gets split, as rustc will do the right // thing either way. let p = project() .file( ".cargo/config.toml", r#" [build] rustflags = ["-Clink-arg=-B nope", "--cfg=foo"] "#, ) .file( "build.rs", r#" use std::env; fn main() {{ assert_eq!(env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(), "-Clink-arg=-B nope\x1f--cfg=foo"); }} "#, ) .file("src/lib.rs", "") .build(); p.cargo("check").run(); } #[cargo_test] fn custom_build_env_var_rustc_wrapper() { let wrapper = tools::echo_wrapper(); let p = project() .file( "build.rs", r#" use std::env; fn main() {{ assert_eq!( env::var("RUSTC_WRAPPER").unwrap(), env::var("CARGO_RUSTC_WRAPPER_CHECK").unwrap() ); }} "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .env("CARGO_BUILD_RUSTC_WRAPPER", &wrapper) .env("CARGO_RUSTC_WRAPPER_CHECK", &wrapper) .run(); } #[cargo_test] fn custom_build_env_var_rustc_workspace_wrapper() { let wrapper = tools::echo_wrapper(); // Workspace wrapper should be set for any crate we're operating directly on. let p = project() .file( "build.rs", r#" use std::env; fn main() {{ assert_eq!( env::var("RUSTC_WORKSPACE_WRAPPER").unwrap(), env::var("CARGO_RUSTC_WORKSPACE_WRAPPER_CHECK").unwrap() ); }} "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .env("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", &wrapper) .env("CARGO_RUSTC_WORKSPACE_WRAPPER_CHECK", &wrapper) .run(); // But should not be set for a crate from the registry, as then it's not in a workspace. Package::new("bar", "0.1.0") .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" links = "a" "#, ) .file( "build.rs", r#" use std::env; fn main() {{ assert!(env::var("RUSTC_WORKSPACE_WRAPPER").is_err()); }} "#, ) .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("check") .env("CARGO_BUILD_RUSTC_WORKSPACE_WRAPPER", &wrapper) .run(); } #[cargo_test] fn custom_build_env_var_rustc_linker() { if cross_compile::disabled() { return; } let target = cross_compile::alternate(); let p = project() .file( ".cargo/config.toml", &format!( r#" [target.{}] linker = "/path/to/linker" "#, target ), ) .file( "build.rs", r#" use std::env; fn main() { assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); } "#, ) .file("src/lib.rs", "") .build(); // no crate type set => linker never called => build succeeds if and // only if build.rs succeeds, despite linker binary not existing. p.cargo("build --target").arg(&target).run(); } // Only run this test on linux, since it's difficult to construct // a case suitable for all platforms. // See:https://github.com/rust-lang/cargo/pull/12535#discussion_r1306618264 #[cargo_test] #[cfg(target_os = "linux")] fn custom_build_env_var_rustc_linker_with_target_cfg() { if cross_compile::disabled() { return; } let target = cross_compile::alternate(); let p = project() .file( ".cargo/config.toml", r#" [target.'cfg(target_pointer_width = "32")'] linker = "/path/to/linker" "#, ) .file( "build.rs", r#" use std::env; fn main() { assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); } "#, ) .file("src/lib.rs", "") .build(); // no crate type set => linker never called => build succeeds if and // only if build.rs succeeds, despite linker binary not existing. p.cargo("build --target").arg(&target).run(); } #[cargo_test] fn custom_build_env_var_rustc_linker_bad_host_target() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" [target.{}] linker = "/path/to/linker" "#, target ), ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); // build.rs should fail since host == target when no target is set p.cargo("build --verbose") .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/linker [..]` [ERROR] linker `[..]/path/to/linker` not found " ) .run(); } #[cargo_test] fn custom_build_env_var_rustc_linker_host_target() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" target-applies-to-host = false [target.{}] linker = "/path/to/linker" "#, target ), ) .file( "build.rs", r#" use std::env; fn main() { assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); } "#, ) .file("src/lib.rs", "") .build(); // no crate type set => linker never called => build succeeds if and // only if build.rs succeeds, despite linker binary not existing. p.cargo("build -Z target-applies-to-host --target") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .run(); } #[cargo_test] fn custom_build_env_var_rustc_linker_host_target_env() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" [target.{}] linker = "/path/to/linker" "#, target ), ) .file( "build.rs", r#" use std::env; fn main() { assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/linker")); } "#, ) .file("src/lib.rs", "") .build(); // no crate type set => linker never called => build succeeds if and // only if build.rs succeeds, despite linker binary not existing. p.cargo("build -Z target-applies-to-host --target") .env("CARGO_TARGET_APPLIES_TO_HOST", "false") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host"]) .run(); } #[cargo_test] fn custom_build_invalid_host_config_feature_flag() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" [target.{}] linker = "/path/to/linker" "#, target ), ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); // build.rs should fail due to -Zhost-config being set without -Ztarget-applies-to-host p.cargo("build -Z host-config --target") .arg(&target) .masquerade_as_nightly_cargo(&["host-config"]) .with_status(101) .with_stderr_contains( "\ error: the -Zhost-config flag requires the -Ztarget-applies-to-host flag to be set ", ) .run(); } #[cargo_test] fn custom_build_linker_host_target_with_bad_host_config() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" [host] linker = "/path/to/host/linker" [target.{}] linker = "/path/to/target/linker" "#, target ), ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); // build.rs should fail due to bad host linker being set p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]` [ERROR] linker `[..]/path/to/host/linker` not found " ) .run(); } #[cargo_test] fn custom_build_linker_bad_host() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" [host] linker = "/path/to/host/linker" [target.{}] linker = "/path/to/target/linker" "#, target ), ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); // build.rs should fail due to bad host linker being set p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]` [ERROR] linker `[..]/path/to/host/linker` not found " ) .run(); } #[cargo_test] fn custom_build_linker_bad_host_with_arch() { let target = rustc_host(); let p = project() .file( ".cargo/config.toml", &format!( r#" [host] linker = "/path/to/host/linker" [host.{}] linker = "/path/to/host/arch/linker" [target.{}] linker = "/path/to/target/linker" "#, target, target ), ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); // build.rs should fail due to bad host linker being set p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/arch/linker [..]` [ERROR] linker `[..]/path/to/host/arch/linker` not found " ) .run(); } #[cargo_test] fn custom_build_env_var_rustc_linker_cross_arch_host() { let target = rustc_host(); let cross_target = cross_compile::alternate(); let p = project() .file( ".cargo/config.toml", &format!( r#" [host.{}] linker = "/path/to/host/arch/linker" [target.{}] linker = "/path/to/target/linker" "#, cross_target, target ), ) .file( "build.rs", r#" use std::env; fn main() { assert!(env::var("RUSTC_LINKER").unwrap().ends_with("/path/to/target/linker")); } "#, ) .file("src/lib.rs", "") .build(); // build.rs should be built fine since cross target != host target. // assertion should succeed since it's still passed the target linker p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .run(); } #[cargo_test] fn custom_build_linker_bad_cross_arch_host() { let target = rustc_host(); let cross_target = cross_compile::alternate(); let p = project() .file( ".cargo/config.toml", &format!( r#" [host] linker = "/path/to/host/linker" [host.{}] linker = "/path/to/host/arch/linker" [target.{}] linker = "/path/to/target/linker" "#, cross_target, target ), ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); // build.rs should fail due to bad host linker being set p.cargo("build -Z target-applies-to-host -Z host-config --verbose --target") .arg(&target) .masquerade_as_nightly_cargo(&["target-applies-to-host", "host-config"]) .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin [..]-C linker=[..]/path/to/host/linker [..]` [ERROR] linker `[..]/path/to/host/linker` not found " ) .run(); } #[cargo_test] fn custom_build_script_wrong_rustc_flags() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" "#, ) .file("src/main.rs", "fn main() {}") .file( "build.rs", r#"fn main() { println!("cargo::rustc-flags=-aaa -bbb"); }"#, ) .build(); p.cargo("build") .with_status(101) .with_stderr_contains( "[ERROR] Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ([CWD])`: \ `-aaa -bbb`", ) .run(); } #[cargo_test] fn custom_build_script_rustc_flags() { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" authors = ["wycats@example.com"] [dependencies.foo] path = "foo" "#, ) .file("src/main.rs", "fn main() {}") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" "#, ) .file("foo/src/lib.rs", "") .file( "foo/build.rs", r#" fn main() { println!("cargo::rustc-flags=-l nonexistinglib -L /dummy/path1 -L /dummy/path2"); } "#, ) .build(); p.cargo("build --verbose") .with_stderr( "\ [COMPILING] foo [..] [RUNNING] `rustc --crate-name build_script_build foo/build.rs [..] [RUNNING] `[..]build-script-build` [RUNNING] `rustc --crate-name foo foo/src/lib.rs [..]\ -L dependency=[CWD]/target/debug/deps \ -L /dummy/path1 -L /dummy/path2 -l nonexistinglib` [COMPILING] bar [..] [RUNNING] `rustc --crate-name bar src/main.rs [..]\ -L dependency=[CWD]/target/debug/deps \ --extern foo=[..]libfoo-[..] \ -L /dummy/path1 -L /dummy/path2` [FINISHED] dev [..] ", ) .run(); } #[cargo_test] fn custom_build_script_rustc_flags_no_space() { let p = project() .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" authors = ["wycats@example.com"] [dependencies.foo] path = "foo" "#, ) .file("src/main.rs", "fn main() {}") .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = ["wycats@example.com"] build = "build.rs" "#, ) .file("foo/src/lib.rs", "") .file( "foo/build.rs", r#" fn main() { println!("cargo::rustc-flags=-lnonexistinglib -L/dummy/path1 -L/dummy/path2"); } "#, ) .build(); p.cargo("build --verbose") .with_stderr( "\ [COMPILING] foo [..] [RUNNING] `rustc --crate-name build_script_build foo/build.rs [..] [RUNNING] `[..]build-script-build` [RUNNING] `rustc --crate-name foo foo/src/lib.rs [..]\ -L dependency=[CWD]/target/debug/deps \ -L /dummy/path1 -L /dummy/path2 -l nonexistinglib` [COMPILING] bar [..] [RUNNING] `rustc --crate-name bar src/main.rs [..]\ -L dependency=[CWD]/target/debug/deps \ --extern foo=[..]libfoo-[..] \ -L /dummy/path1 -L /dummy/path2` [FINISHED] dev [..] ", ) .run(); } #[cargo_test] fn links_no_build_cmd() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "a" "#, ) .file("src/lib.rs", "") .build(); p.cargo("build") .with_status(101) .with_stderr( "\ [ERROR] failed to parse manifest at `[..]/foo/Cargo.toml` Caused by: package `foo v0.5.0 ([CWD])` specifies that it links to `a` but does \ not have a custom build script ", ) .run(); } #[cargo_test] fn links_duplicates() { // this tests that the links_duplicates are caught at resolver time let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "a" build = "build.rs" [dependencies.a-sys] path = "a-sys" "#, ) .file("src/lib.rs", "") .file("build.rs", "") .file( "a-sys/Cargo.toml", r#" [package] name = "a-sys" version = "0.5.0" authors = [] links = "a" build = "build.rs" "#, ) .file("a-sys/src/lib.rs", "") .file("a-sys/build.rs", "") .build(); p.cargo("build").with_status(101) .with_stderr("\ error: failed to select a version for `a-sys`. ... required by package `foo v0.5.0 ([..])` versions that meet the requirements `*` are: 0.5.0 the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([..])` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = \"a\"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. failed to select a version for `a-sys` which could resolve this conflict ").run(); } #[cargo_test] fn links_duplicates_old_registry() { // Test old links validator. See `validate_links`. Package::new("bar", "0.1.0") .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" links = "a" "#, ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" links = "a" [dependencies] bar = "0.1" "#, ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .build(); p.cargo("build") .with_status(101) .with_stderr( "\ [UPDATING] `[..]` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 ([..]) [ERROR] multiple packages link to native library `a`, \ but a native library can be linked only once package `bar v0.1.0` ... which satisfies dependency `bar = \"^0.1\"` (locked to 0.1.0) of package `foo v0.1.0 ([..]foo)` links to native library `a` package `foo v0.1.0 ([..]foo)` also links to native library `a` ", ) .run(); } #[cargo_test] fn links_duplicates_deep_dependency() { // this tests that the links_duplicates are caught at resolver time let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "a" build = "build.rs" [dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file("build.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" [dependencies.a-sys] path = "a-sys" "#, ) .file("a/src/lib.rs", "") .file("a/build.rs", "") .file( "a/a-sys/Cargo.toml", r#" [package] name = "a-sys" version = "0.5.0" authors = [] links = "a" build = "build.rs" "#, ) .file("a/a-sys/src/lib.rs", "") .file("a/a-sys/build.rs", "") .build(); p.cargo("build").with_status(101) .with_stderr("\ error: failed to select a version for `a-sys`. ... required by package `a v0.5.0 ([..])` ... which satisfies path dependency `a` of package `foo v0.5.0 ([..])` versions that meet the requirements `*` are: 0.5.0 the package `a-sys` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([..])` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = \"a\"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. failed to select a version for `a-sys` which could resolve this conflict ").run(); } #[cargo_test] fn overrides_and_links() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { assert_eq!(env::var("DEP_FOO_FOO").ok().expect("FOO missing"), "bar"); assert_eq!(env::var("DEP_FOO_BAR").ok().expect("BAR missing"), "baz"); } "#, ) .file( ".cargo/config.toml", &format!( r#" [target.{}.foo] rustc-flags = "-L foo -L bar" foo = "bar" bar = "baz" "#, target ), ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("a/src/lib.rs", "") .file("a/build.rs", "not valid rust code") .build(); p.cargo("build -v") .with_stderr( "\ [..] [..] [..] [..] [..] [RUNNING] `rustc --crate-name foo [..] -L foo -L bar` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn unused_overrides() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .file( ".cargo/config.toml", &format!( r#" [target.{}.foo] rustc-flags = "-L foo -L bar" foo = "bar" bar = "baz" "#, target ), ) .build(); p.cargo("build -v").run(); } #[cargo_test] fn links_passes_env_vars() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { assert_eq!(env::var("DEP_FOO_FOO").unwrap(), "bar"); assert_eq!(env::var("DEP_FOO_BAR").unwrap(), "baz"); } "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", r#" use std::env; fn main() { let lib = env::var("CARGO_MANIFEST_LINKS").unwrap(); assert_eq!(lib, "foo"); println!("cargo::metadata=foo=bar"); println!("cargo::metadata=bar=baz"); } "#, ) .build(); p.cargo("build -v").run(); } #[cargo_test] fn only_rerun_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .build(); p.cargo("build -v").run(); p.root().move_into_the_past(); p.change_file("some-new-file", ""); p.root().move_into_the_past(); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo v0.5.0 ([CWD]): the precalculated components changed [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn rebuild_continues_to_pass_env_vars() { let a = project() .at("a") .file( "Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::time::Duration; fn main() { println!("cargo::metadata=foo=bar"); println!("cargo::metadata=bar=baz"); std::thread::sleep(Duration::from_millis(500)); } "#, ) .build(); a.root().move_into_the_past(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [dependencies.a] path = '{}' "#, a.root().display() ), ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { assert_eq!(env::var("DEP_FOO_FOO").unwrap(), "bar"); assert_eq!(env::var("DEP_FOO_BAR").unwrap(), "baz"); } "#, ) .build(); p.cargo("build -v").run(); p.root().move_into_the_past(); p.change_file("some-new-file", ""); p.root().move_into_the_past(); p.cargo("build -v").run(); } #[cargo_test] fn testing_and_such() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .build(); println!("build"); p.cargo("build -v").run(); p.root().move_into_the_past(); p.change_file("src/lib.rs", ""); p.root().move_into_the_past(); println!("test"); p.cargo("test -vj1") .with_stderr( "\ [DIRTY] foo v0.5.0 ([CWD]): the precalculated components changed [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..]` [RUNNING] `rustc --crate-name foo [..]` [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]/foo-[..][EXE]` [DOCTEST] foo [RUNNING] `rustdoc [..]--test [..]`", ) .with_stdout_contains_n("running 0 tests", 2) .run(); println!("doc"); p.cargo("doc -v") .with_stderr( "\ [DOCUMENTING] foo v0.5.0 ([CWD]) [RUNNING] `rustdoc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [GENERATED] [CWD]/target/doc/foo/index.html ", ) .run(); p.change_file("src/main.rs", "fn main() {}"); println!("run"); p.cargo("run") .with_stderr( "\ [COMPILING] foo v0.5.0 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [RUNNING] `target/debug/foo[EXE]` ", ) .run(); } #[cargo_test] fn propagation_of_l_flags() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "bar" build = "build.rs" [dependencies.b] path = "../b" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", r#"fn main() { println!("cargo::rustc-flags=-L bar"); }"#, ) .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("b/src/lib.rs", "") .file("b/build.rs", "bad file") .file( ".cargo/config.toml", &format!( r#" [target.{}.foo] rustc-flags = "-L foo" "#, target ), ) .build(); p.cargo("build -v -j1") .with_stderr_contains( "\ [RUNNING] `rustc --crate-name a [..] -L bar[..]-L foo[..]` [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc --crate-name foo [..] -L bar -L foo` ", ) .run(); } #[cargo_test] fn propagation_of_l_flags_new() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "bar" build = "build.rs" [dependencies.b] path = "../b" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", r#" fn main() { println!("cargo::rustc-link-search=bar"); } "#, ) .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("b/src/lib.rs", "") .file("b/build.rs", "bad file") .file( ".cargo/config.toml", &format!( r#" [target.{}.foo] rustc-link-search = ["foo"] "#, target ), ) .build(); p.cargo("build -v -j1") .with_stderr_contains( "\ [RUNNING] `rustc --crate-name a [..] -L bar[..]-L foo[..]` [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc --crate-name foo [..] -L bar -L foo` ", ) .run(); } #[cargo_test] fn build_deps_simple() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [build-dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "build.rs", " #[allow(unused_extern_crates)] extern crate a; fn main() {} ", ) .file("a/Cargo.toml", &basic_manifest("a", "0.5.0")) .file("a/src/lib.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] a v0.5.0 ([CWD]/a) [RUNNING] `rustc --crate-name a [..]` [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc [..] build.rs [..] --extern a=[..]` [RUNNING] `[..]/foo-[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn build_deps_not_for_normal() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [build-dependencies.aaaaa] path = "a" "#, ) .file( "src/lib.rs", "#[allow(unused_extern_crates)] extern crate aaaaa;", ) .file( "build.rs", " #[allow(unused_extern_crates)] extern crate aaaaa; fn main() {} ", ) .file("a/Cargo.toml", &basic_manifest("aaaaa", "0.5.0")) .file("a/src/lib.rs", "") .build(); p.cargo("build -v --target") .arg(&target) .with_status(101) .with_stderr_contains("[..]can't find crate for `aaaaa`[..]") .with_stderr_contains( "\ [ERROR] could not compile `foo` (lib) due to 1 previous error Caused by: process didn't exit successfully: [..] ", ) .run(); } #[cargo_test] fn build_cmd_with_a_build_cmd() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [build-dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "build.rs", " #[allow(unused_extern_crates)] extern crate a; fn main() {} ", ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" [build-dependencies.b] path = "../b" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", "#[allow(unused_extern_crates)] extern crate b; fn main() {}", ) .file("b/Cargo.toml", &basic_manifest("b", "0.5.0")) .file("b/src/lib.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] b v0.5.0 ([CWD]/b) [RUNNING] `rustc --crate-name b [..]` [COMPILING] a v0.5.0 ([CWD]/a) [RUNNING] `rustc [..] a/build.rs [..] --extern b=[..]` [RUNNING] `[..]/a-[..]/build-script-build` [RUNNING] `rustc --crate-name a [..]lib.rs [..]--crate-type lib \ --emit=[..]link[..] \ -C metadata=[..] \ --out-dir [..]target/debug/deps \ -L [..]target/debug/deps` [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc --crate-name build_script_build build.rs [..]--crate-type bin \ --emit=[..]link[..]\ -C metadata=[..] --out-dir [..] \ -L [..]target/debug/deps \ --extern a=[..]liba[..].rlib` [RUNNING] `[..]/foo-[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..]lib.rs [..]--crate-type lib \ --emit=[..]link[..]-C debuginfo=2 [..]\ -C metadata=[..] \ --out-dir [..] \ -L [..]target/debug/deps` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn out_dir_is_preserved() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; use std::fs::File; use std::path::Path; fn main() { let out = env::var("OUT_DIR").unwrap(); File::create(Path::new(&out).join("foo")).unwrap(); } "#, ) .build(); // Make the file p.cargo("build -v").run(); // Change to asserting that it's there p.change_file( "build.rs", r#" use std::env; use std::fs::File; use std::path::Path; fn main() { let out = env::var("OUT_DIR").unwrap(); File::open(&Path::new(&out).join("foo")).unwrap(); } "#, ); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo [..]: the file `build.rs` has changed ([..]) [COMPILING] foo [..] [RUNNING] `rustc --crate-name build_script_build [..] [RUNNING] `[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..] [FINISHED] [..] ", ) .run(); // Run a fresh build where file should be preserved p.cargo("build -v") .with_stderr( "\ [FRESH] foo [..] [FINISHED] [..] ", ) .run(); // One last time to make sure it's still there. p.change_file("foo", ""); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo [..]: the precalculated components changed [COMPILING] foo [..] [RUNNING] `[..]build-script-build` [RUNNING] `rustc --crate-name foo [..] [FINISHED] [..] ", ) .run(); } #[cargo_test] fn output_separate_lines() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rustc-flags=-L foo"); println!("cargo::rustc-flags=-l static=foo"); } "#, ) .build(); p.cargo("build -v") .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc [..] build.rs [..]` [RUNNING] `[..]/foo-[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..] -L foo -l static=foo` [ERROR] could not find native static library [..] ", ) .run(); } #[cargo_test] fn output_separate_lines_new() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rustc-link-search=foo"); println!("cargo::rustc-link-lib=static=foo"); println!("cargo::rustc-link-lib=bar"); println!("cargo::rustc-link-search=bar"); } "#, ) .build(); // The order of the arguments passed to rustc is important. p.cargo("build -v") .with_status(101) .with_stderr_contains( "\ [COMPILING] foo v0.5.0 ([CWD]) [RUNNING] `rustc [..] build.rs [..]` [RUNNING] `[..]/foo-[..]/build-script-build` [RUNNING] `rustc --crate-name foo [..] -L foo -L bar -l static=foo -l bar` [ERROR] could not find native static library [..] ", ) .run(); } #[cargo_test] fn code_generation() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file( "src/main.rs", r#" include!(concat!(env!("OUT_DIR"), "/hello.rs")); fn main() { println!("{}", message()); } "#, ) .file( "build.rs", r#" use std::env; use std::fs; use std::path::PathBuf; fn main() { let dst = PathBuf::from(env::var("OUT_DIR").unwrap()); fs::write(dst.join("hello.rs"), " pub fn message() -> &'static str { \"Hello, World!\" } ") .unwrap(); } "#, ) .build(); p.cargo("run") .with_stderr( "\ [COMPILING] foo v0.5.0 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [RUNNING] `target/debug/foo[EXE]`", ) .with_stdout("Hello, World!") .run(); p.cargo("test").run(); } #[cargo_test] fn release_with_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() {} "#, ) .build(); p.cargo("build -v --release").run(); } #[cargo_test] fn build_script_only() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.0" authors = [] build = "build.rs" "#, ) .file("build.rs", r#"fn main() {}"#) .build(); p.cargo("build -v") .with_status(101) .with_stderr( "\ [ERROR] failed to parse manifest at `[..]` Caused by: no targets specified in the manifest either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present", ) .run(); } #[cargo_test] fn shared_dep_with_a_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [dependencies.a] path = "a" [build-dependencies.b] path = "b" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("a/build.rs", "fn main() {}") .file("a/src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] [dependencies.a] path = "../a" "#, ) .file("b/src/lib.rs", "") .build(); p.cargo("build -v").run(); } #[cargo_test] fn transitive_dep_host() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [build-dependencies.b] path = "b" "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("a/build.rs", "fn main() {}") .file("a/src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] [lib] name = "b" plugin = true [dependencies.a] path = "../a" "#, ) .file("b/src/lib.rs", "") .build(); p.cargo("build").run(); } #[cargo_test] fn test_a_lib_with_a_build_command() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file( "src/lib.rs", r#" include!(concat!(env!("OUT_DIR"), "/foo.rs")); /// ``` /// foo::bar(); /// ``` pub fn bar() { assert_eq!(foo(), 1); } "#, ) .file( "build.rs", r#" use std::env; use std::fs; use std::path::PathBuf; fn main() { let out = PathBuf::from(env::var("OUT_DIR").unwrap()); fs::write(out.join("foo.rs"), "fn foo() -> i32 { 1 }").unwrap(); } "#, ) .build(); p.cargo("test").run(); } #[cargo_test] fn test_dev_dep_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dev-dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("a/build.rs", "fn main() {}") .file("a/src/lib.rs", "") .build(); p.cargo("test").run(); } #[cargo_test] fn build_script_with_dynamic_native_dependency() { let build = project() .at("builder") .file( "Cargo.toml", r#" [package] name = "builder" version = "0.0.1" authors = [] [lib] name = "builder" crate-type = ["dylib"] "#, ) .file("src/lib.rs", "#[no_mangle] pub extern fn foo() {}") .build(); let foo = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" [build-dependencies.bar] path = "bar" "#, ) .file("build.rs", "extern crate bar; fn main() { bar::bar() }") .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "bar/build.rs", r#" use std::env; use std::fs; use std::path::PathBuf; fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let root = PathBuf::from(env::var("BUILDER_ROOT").unwrap()); let file = format!("{}builder{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX); let src = root.join(&file); let dst = out_dir.join(&file); fs::copy(src, dst).unwrap(); if cfg!(target_env = "msvc") { fs::copy(root.join("builder.dll.lib"), out_dir.join("builder.dll.lib")).unwrap(); } println!("cargo::rustc-link-search=native={}", out_dir.display()); } "#, ) .file( "bar/src/lib.rs", r#" pub fn bar() { #[cfg_attr(not(target_env = "msvc"), link(name = "builder"))] #[cfg_attr(target_env = "msvc", link(name = "builder.dll"))] extern { fn foo(); } unsafe { foo() } } "#, ) .build(); build .cargo("build -v") .env("CARGO_LOG", "cargo::ops::cargo_rustc") .run(); let root = build.root().join("target").join("debug"); foo.cargo("build -v") .env("BUILDER_ROOT", root) .env("CARGO_LOG", "cargo::ops::cargo_rustc") .run(); } #[cargo_test] fn profile_and_opt_level_set_correctly() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { assert_eq!(env::var("OPT_LEVEL").unwrap(), "3"); assert_eq!(env::var("PROFILE").unwrap(), "release"); assert_eq!(env::var("DEBUG").unwrap(), "false"); } "#, ) .build(); p.cargo("bench").run(); } #[cargo_test] fn profile_debug_0() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" [profile.dev] debug = 0 "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { assert_eq!(env::var("OPT_LEVEL").unwrap(), "0"); assert_eq!(env::var("PROFILE").unwrap(), "debug"); assert_eq!(env::var("DEBUG").unwrap(), "false"); } "#, ) .build(); p.cargo("build").run(); } #[cargo_test] fn build_script_with_lto() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" [profile.dev] lto = true "#, ) .file("src/lib.rs", "") .file("build.rs", "fn main() {}") .build(); p.cargo("build").run(); } #[cargo_test] fn test_duplicate_deps() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = [] build = "build.rs" [dependencies.bar] path = "bar" [build-dependencies.bar] path = "bar" "#, ) .file( "src/main.rs", r#" extern crate bar; fn main() { bar::do_nothing() } "#, ) .file( "build.rs", r#" extern crate bar; fn main() { bar::do_nothing() } "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) .file("bar/src/lib.rs", "pub fn do_nothing() {}") .build(); p.cargo("build").run(); } #[cargo_test] fn cfg_feedback() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file("src/main.rs", "#[cfg(foo)] fn main() {}") .file( "build.rs", r#"fn main() { println!("cargo::rustc-cfg=foo"); }"#, ) .build(); p.cargo("build -v").run(); } #[cargo_test] fn cfg_override() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "a" build = "build.rs" "#, ) .file("src/main.rs", "#[cfg(foo)] fn main() {}") .file("build.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.{}.a] rustc-cfg = ["foo"] "#, target ), ) .build(); p.cargo("build -v").run(); } #[cargo_test] fn cfg_test() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#"fn main() { println!("cargo::rustc-cfg=foo"); }"#, ) .file( "src/lib.rs", r#" /// /// ``` /// extern crate foo; /// /// fn main() { /// foo::foo() /// } /// ``` /// #[cfg(foo)] pub fn foo() {} #[cfg(foo)] #[test] fn test_foo() { foo() } "#, ) .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}") .build(); p.cargo("test -v") .with_stderr( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] [..] build.rs [..] [RUNNING] `[..]/build-script-build` [RUNNING] [..] --cfg foo[..] [RUNNING] [..] --cfg foo[..] [RUNNING] [..] --cfg foo[..] [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]/foo-[..][EXE]` [RUNNING] `[..]/test-[..][EXE]` [DOCTEST] foo [RUNNING] [..] --cfg foo[..]", ) .with_stdout_contains("test test_foo ... ok") .with_stdout_contains("test test_bar ... ok") .with_stdout_contains_n("test [..] ... ok", 3) .run(); } #[cargo_test] fn cfg_doc() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" [dependencies.bar] path = "bar" "#, ) .file( "build.rs", r#"fn main() { println!("cargo::rustc-cfg=foo"); }"#, ) .file("src/lib.rs", "#[cfg(foo)] pub fn foo() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "bar/build.rs", r#"fn main() { println!("cargo::rustc-cfg=bar"); }"#, ) .file("bar/src/lib.rs", "#[cfg(bar)] pub fn bar() {}") .build(); p.cargo("doc").run(); assert!(p.root().join("target/doc").is_dir()); assert!(p.root().join("target/doc/foo/fn.foo.html").is_file()); assert!(p.root().join("target/doc/bar/fn.bar.html").is_file()); } #[cargo_test] fn cfg_override_test() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" links = "a" "#, ) .file("build.rs", "") .file( ".cargo/config.toml", &format!( r#" [target.{}.a] rustc-cfg = ["foo"] "#, rustc_host() ), ) .file( "src/lib.rs", r#" /// /// ``` /// extern crate foo; /// /// fn main() { /// foo::foo() /// } /// ``` /// #[cfg(foo)] pub fn foo() {} #[cfg(foo)] #[test] fn test_foo() { foo() } "#, ) .file("tests/test.rs", "#[cfg(foo)] #[test] fn test_bar() {}") .build(); p.cargo("test -v") .with_stderr( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] `[..]` [RUNNING] `[..]` [RUNNING] `[..]` [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]/foo-[..][EXE]` [RUNNING] `[..]/test-[..][EXE]` [DOCTEST] foo [RUNNING] [..] --cfg foo[..]", ) .with_stdout_contains("test test_foo ... ok") .with_stdout_contains("test test_bar ... ok") .with_stdout_contains_n("test [..] ... ok", 3) .run(); } #[cargo_test] fn cfg_override_doc() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" links = "a" [dependencies.bar] path = "bar" "#, ) .file( ".cargo/config.toml", &format!( r#" [target.{target}.a] rustc-cfg = ["foo"] [target.{target}.b] rustc-cfg = ["bar"] "#, target = rustc_host() ), ) .file("build.rs", "") .file("src/lib.rs", "#[cfg(foo)] pub fn foo() {}") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.0.1" authors = [] build = "build.rs" links = "b" "#, ) .file("bar/build.rs", "") .file("bar/src/lib.rs", "#[cfg(bar)] pub fn bar() {}") .build(); p.cargo("doc").run(); assert!(p.root().join("target/doc").is_dir()); assert!(p.root().join("target/doc/foo/fn.foo.html").is_file()); assert!(p.root().join("target/doc/bar/fn.bar.html").is_file()); } #[cargo_test] fn env_build() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "src/main.rs", r#" const FOO: &'static str = env!("FOO"); fn main() { println!("{}", FOO); } "#, ) .file( "build.rs", r#"fn main() { println!("cargo::rustc-env=FOO=foo"); }"#, ) .build(); p.cargo("build -v").run(); p.cargo("run -v").with_stdout("foo\n").run(); } #[cargo_test] fn env_test() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#"fn main() { println!("cargo::rustc-env=FOO=foo"); }"#, ) .file( "src/lib.rs", r#"pub const FOO: &'static str = env!("FOO"); "#, ) .file( "tests/test.rs", r#" extern crate foo; #[test] fn test_foo() { assert_eq!("foo", foo::FOO); } "#, ) .build(); p.cargo("test -v") .with_stderr( "\ [COMPILING] foo v0.0.1 ([CWD]) [RUNNING] [..] build.rs [..] [RUNNING] `[..]/build-script-build` [RUNNING] [..] --crate-name foo[..] [RUNNING] [..] --crate-name foo[..] [RUNNING] [..] --crate-name test[..] [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]/foo-[..][EXE]` [RUNNING] `[..]/test-[..][EXE]` [DOCTEST] foo [RUNNING] [..] --crate-name foo[..]", ) .with_stdout_contains_n("running 0 tests", 2) .with_stdout_contains("test test_foo ... ok") .run(); } #[cargo_test] fn env_doc() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "src/main.rs", r#" const FOO: &'static str = env!("FOO"); fn main() {} "#, ) .file( "build.rs", r#"fn main() { println!("cargo::rustc-env=FOO=foo"); }"#, ) .build(); p.cargo("doc -v").run(); } #[cargo_test] fn flags_go_into_tests() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies] b = { path = "b" } "#, ) .file("src/lib.rs", "") .file("tests/foo.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] [dependencies] a = { path = "../a" } "#, ) .file("b/src/lib.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", r#" fn main() { println!("cargo::rustc-link-search=test"); } "#, ) .build(); p.cargo("test -v --test=foo") .with_stderr( "\ [COMPILING] a v0.5.0 ([..] [RUNNING] `rustc [..] a/build.rs [..]` [RUNNING] `[..]/build-script-build` [RUNNING] `rustc [..] a/src/lib.rs [..] -L test[..]` [COMPILING] b v0.5.0 ([..] [RUNNING] `rustc [..] b/src/lib.rs [..] -L test[..]` [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..] src/lib.rs [..] -L test[..]` [RUNNING] `rustc [..] tests/foo.rs [..] -L test[..]` [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]/foo-[..][EXE]`", ) .with_stdout_contains("running 0 tests") .run(); p.cargo("test -v -pb --lib") .with_stderr( "\ [FRESH] a v0.5.0 ([..] [COMPILING] b v0.5.0 ([..] [RUNNING] `rustc [..] b/src/lib.rs [..] -L test[..]` [FINISHED] test [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]/b-[..][EXE]`", ) .with_stdout_contains("running 0 tests") .run(); } #[cargo_test] fn diamond_passes_args_only_once() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies] a = { path = "a" } b = { path = "b" } "#, ) .file("src/lib.rs", "") .file("tests/foo.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] [dependencies] b = { path = "../b" } c = { path = "../c" } "#, ) .file("a/src/lib.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] [dependencies] c = { path = "../c" } "#, ) .file("b/src/lib.rs", "") .file( "c/Cargo.toml", r#" [package] name = "c" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file( "c/build.rs", r#" fn main() { println!("cargo::rustc-link-search=native=test"); } "#, ) .file("c/src/lib.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] c v0.5.0 ([..] [RUNNING] `rustc [..]` [RUNNING] `[..]` [RUNNING] `rustc [..]` [COMPILING] b v0.5.0 ([..] [RUNNING] `rustc [..]` [COMPILING] a v0.5.0 ([..] [RUNNING] `rustc [..]` [COMPILING] foo v0.5.0 ([..] [RUNNING] `[..]rmeta -L native=test` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn adding_an_override_invalidates() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("src/lib.rs", "") .file(".cargo/config.toml", "") .file( "build.rs", r#" fn main() { println!("cargo::rustc-link-search=native=foo"); } "#, ) .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..]` [RUNNING] `[..]` [RUNNING] `rustc [..] -L native=foo` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); p.change_file( ".cargo/config.toml", &format!( " [target.{}.foo] rustc-link-search = [\"native=bar\"] ", target ), ); p.cargo("build -v") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..] -L native=bar` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn changing_an_override_invalidates() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( " [target.{}.foo] rustc-link-search = [\"native=foo\"] ", target ), ) .file("build.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..] -L native=foo` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); p.change_file( ".cargo/config.toml", &format!( " [target.{}.foo] rustc-link-search = [\"native=bar\"] ", target ), ); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo v0.5.0 ([..]): the precalculated components changed [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..] -L native=bar` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn fresh_builds_possible_with_link_libs() { // The bug is non-deterministic. Sometimes you can get a fresh build let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "nativefoo" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( " [target.{}.nativefoo] rustc-link-lib = [\"a\"] rustc-link-search = [\"./b\"] rustc-flags = \"-l z -L ./\" ", target ), ) .file("build.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); p.cargo("build -v") .with_stderr( "\ [FRESH] foo v0.5.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn fresh_builds_possible_with_multiple_metadata_overrides() { // The bug is non-deterministic. Sometimes you can get a fresh build let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( ".cargo/config.toml", &format!( " [target.{}.foo] a = \"\" b = \"\" c = \"\" d = \"\" e = \"\" ", target ), ) .file("build.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..] [RUNNING] `rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); p.cargo("build -v") .with_stderr( "\ [FRESH] foo v0.5.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn generate_good_d_files() { // this is here to stop regression on an issue where build.rs rerun-if-changed paths aren't // made absolute properly, which in turn interacts poorly with the dep-info-basedir setting, // and the dep-info files have other-crate-relative paths spat out in them let p = project() .file( "awoo/Cargo.toml", r#" [package] name = "awoo" version = "0.5.0" build = "build.rs" "#, ) .file("awoo/src/lib.rs", "") .file( "awoo/build.rs", r#" fn main() { println!("cargo::rerun-if-changed=build.rs"); println!("cargo::rerun-if-changed=barkbarkbark"); } "#, ) .file( "Cargo.toml", r#" [package] name = "meow" version = "0.5.0" [dependencies] awoo = { path = "awoo" } "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v").run(); let dot_d_path = p.bin("meow").with_extension("d"); println!("*meow at* {:?}", dot_d_path); let dot_d = fs::read_to_string(&dot_d_path).unwrap(); println!("*.d file content*: {}", &dot_d); assert_match_exact( "[..]/target/debug/meow[EXE]: [..]/awoo/barkbarkbark [..]/awoo/build.rs[..]", &dot_d, ); // paths relative to dependency roots should not be allowed assert!(!dot_d .split_whitespace() .any(|v| v == "barkbarkbark" || v == "build.rs")); p.change_file( ".cargo/config.toml", r#" [build] dep-info-basedir="." "#, ); p.cargo("build -v").run(); let dot_d = fs::read_to_string(&dot_d_path).unwrap(); println!("*.d file content with dep-info-basedir*: {}", &dot_d); assert_match_exact( "target/debug/meow[EXE]: awoo/barkbarkbark awoo/build.rs[..]", &dot_d, ); // paths relative to dependency roots should not be allowed assert!(!dot_d .split_whitespace() .any(|v| v == "barkbarkbark" || v == "build.rs")); } #[cargo_test] fn generate_good_d_files_for_external_tools() { // This tests having a relative paths going out of the // project root in config's dep-info-basedir let p = project_in("rust_things") .file( "awoo/Cargo.toml", r#" [package] name = "awoo" version = "0.5.0" build = "build.rs" "#, ) .file("awoo/src/lib.rs", "") .file( "awoo/build.rs", r#" fn main() { println!("cargo::rerun-if-changed=build.rs"); println!("cargo::rerun-if-changed=barkbarkbark"); } "#, ) .file( "Cargo.toml", r#" [package] name = "meow" version = "0.5.0" [dependencies] awoo = { path = "awoo" } "#, ) .file("src/main.rs", "fn main() {}") .file( ".cargo/config.toml", r#" [build] dep-info-basedir="../.." "#, ) .build(); p.cargo("build -v").run(); let dot_d_path = p.bin("meow").with_extension("d"); let dot_d = fs::read_to_string(&dot_d_path).unwrap(); println!("*.d file content with dep-info-basedir*: {}", &dot_d); assert_match_exact( concat!( "rust_things/foo/target/debug/meow[EXE]:", " rust_things/foo/awoo/barkbarkbark", " rust_things/foo/awoo/build.rs", " rust_things/foo/awoo/src/lib.rs", " rust_things/foo/src/main.rs", ), &dot_d, ); } #[cargo_test] fn rebuild_only_on_explicit_paths() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rerun-if-changed=foo"); println!("cargo::rerun-if-changed=bar"); } "#, ) .build(); p.cargo("build -v").run(); // files don't exist, so should always rerun if they don't exist println!("run without"); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo v0.5.0 ([..]): the file `foo` is missing [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..]/build-script-build` [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); sleep_ms(1000); p.change_file("foo", ""); p.change_file("bar", ""); sleep_ms(1000); // make sure the to-be-created outfile has a timestamp distinct from the infiles // now the exist, so run once, catch the mtime, then shouldn't run again println!("run with"); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo v0.5.0 ([..]): the file `foo` has changed ([..]) [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..]/build-script-build` [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); println!("run with2"); p.cargo("build -v") .with_stderr( "\ [FRESH] foo v0.5.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); sleep_ms(1000); // random other files do not affect freshness println!("run baz"); p.change_file("baz", "// modified"); p.cargo("build -v") .with_stderr( "\ [FRESH] foo v0.5.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); // but changing dependent files does println!("run foo change"); p.change_file("foo", "// modified"); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo v0.5.0 ([..]): the file `foo` has changed ([..]) [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..]/build-script-build` [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); // .. as does deleting a file println!("run bar delete"); fs::remove_file(p.root().join("bar")).unwrap(); p.cargo("build -v") .with_stderr( "\ [DIRTY] foo v0.5.0 ([..]): the file `bar` is missing [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..]/build-script-build` [RUNNING] `rustc [..] src/lib.rs [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn doctest_receives_build_link_args() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies.a] path = "a" "#, ) .file("src/lib.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "bar" build = "build.rs" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", r#" fn main() { println!("cargo::rustc-link-search=native=bar"); } "#, ) .build(); p.cargo("test -v") .with_stderr_contains( "[RUNNING] `rustdoc [..]--crate-name foo --test [..]-L native=bar[..]`", ) .run(); } #[cargo_test] fn please_respect_the_dag() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [dependencies] a = { path = 'a' } "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rustc-link-search=native=foo"); } "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "bar" build = "build.rs" "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", r#" fn main() { println!("cargo::rustc-link-search=native=bar"); } "#, ) .build(); p.cargo("build -v") .with_stderr_contains("[RUNNING] `rustc [..] -L native=foo -L native=bar[..]`") .run(); } #[cargo_test] fn non_utf8_output() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#" use std::io::prelude::*; fn main() { let mut out = std::io::stdout(); // print something that's not utf8 out.write_all(b"\xff\xff\n").unwrap(); // now print some cargo metadata that's utf8 println!("cargo::rustc-cfg=foo"); // now print more non-utf8 out.write_all(b"\xff\xff\n").unwrap(); } "#, ) .file("src/main.rs", "#[cfg(foo)] fn main() {}") .build(); p.cargo("build -v").run(); } #[cargo_test] fn custom_target_dir() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies] a = { path = "a" } "#, ) .file("src/lib.rs", "") .file( ".cargo/config.toml", r#" [build] target-dir = 'test' "#, ) .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("a/build.rs", "fn main() {}") .file("a/src/lib.rs", "") .build(); p.cargo("build -v").run(); } #[cargo_test] fn panic_abort_with_build_scripts() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [profile.release] panic = 'abort' [dependencies] a = { path = "a" } "#, ) .file( "src/lib.rs", "#[allow(unused_extern_crates)] extern crate a;", ) .file("build.rs", "fn main() {}") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] build = "build.rs" [build-dependencies] b = { path = "../b" } "#, ) .file("a/src/lib.rs", "") .file( "a/build.rs", "#[allow(unused_extern_crates)] extern crate b; fn main() {}", ) .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] "#, ) .file("b/src/lib.rs", "") .build(); p.cargo("build -v --release").run(); p.root().join("target").rm_rf(); p.cargo("test --release -v") .with_stderr_does_not_contain("[..]panic=abort[..]") .run(); } #[cargo_test] fn warnings_emitted() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::warning=foo"); println!("cargo::warning=bar"); } "#, ) .build(); p.cargo("build -v") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..]) [RUNNING] `rustc [..]` [RUNNING] `[..]` warning: foo@0.5.0: foo warning: foo@0.5.0: bar [RUNNING] `rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn warnings_emitted_when_build_script_panics() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::warning=foo"); println!("cargo::warning=bar"); panic!(); } "#, ) .build(); p.cargo("build") .with_status(101) .with_stdout("") .with_stderr_contains("warning: foo@0.5.0: foo\nwarning: foo@0.5.0: bar") .run(); } #[cargo_test] fn warnings_hidden_for_upstream() { Package::new("bar", "0.1.0") .file( "build.rs", r#" fn main() { println!("cargo::warning=foo"); println!("cargo::warning=bar"); } "#, ) .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies] bar = "*" "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -v") .with_stderr( "\ [UPDATING] `[..]` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 ([..]) [COMPILING] bar v0.1.0 [RUNNING] `rustc [..]` [RUNNING] `[..]` [RUNNING] `rustc [..]` [COMPILING] foo v0.5.0 ([..]) [RUNNING] `rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn warnings_printed_on_vv() { Package::new("bar", "0.1.0") .file( "build.rs", r#" fn main() { println!("cargo::warning=foo"); println!("cargo::warning=bar"); } "#, ) .file( "Cargo.toml", r#" [package] name = "bar" version = "0.1.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies] bar = "*" "#, ) .file("src/lib.rs", "") .build(); p.cargo("build -vv") .with_stderr( "\ [UPDATING] `[..]` index [DOWNLOADING] crates ... [DOWNLOADED] bar v0.1.0 ([..]) [COMPILING] bar v0.1.0 [RUNNING] `[..] rustc [..]` [RUNNING] `[..]` warning: bar@0.1.0: foo warning: bar@0.1.0: bar [RUNNING] `[..] rustc [..]` [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..] rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn output_shows_on_vv() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::io::prelude::*; fn main() { std::io::stderr().write_all(b"stderr\n").unwrap(); std::io::stdout().write_all(b"stdout\n").unwrap(); } "#, ) .build(); p.cargo("build -vv") .with_stdout("[foo 0.5.0] stdout") .with_stderr( "\ [COMPILING] foo v0.5.0 ([..]) [RUNNING] `[..] rustc [..]` [RUNNING] `[..]` [foo 0.5.0] stderr [RUNNING] `[..] rustc [..]` [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); } #[cargo_test] fn links_with_dots() { let target = rustc_host(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" links = "a.b" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rustc-link-search=bar") } "#, ) .file( ".cargo/config.toml", &format!( r#" [target.{}.'a.b'] rustc-link-search = ["foo"] "#, target ), ) .build(); p.cargo("build -v") .with_stderr_contains("[RUNNING] `rustc --crate-name foo [..] [..] -L foo[..]`") .run(); } #[cargo_test] fn rustc_and_rustdoc_set_correctly() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { assert_eq!(env::var("RUSTC").unwrap(), "rustc"); assert_eq!(env::var("RUSTDOC").unwrap(), "rustdoc"); } "#, ) .build(); p.cargo("bench").run(); } #[cargo_test] fn cfg_env_vars_available() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { let fam = env::var("CARGO_CFG_TARGET_FAMILY").unwrap(); if cfg!(unix) { assert_eq!(fam, "unix"); } else { assert_eq!(fam, "windows"); } } "#, ) .build(); p.cargo("bench").run(); } #[cargo_test] fn switch_features_rerun() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" [features] foo = [] "#, ) .file( "src/main.rs", r#" fn main() { println!(include_str!(concat!(env!("OUT_DIR"), "/output"))); } "#, ) .file( "build.rs", r#" use std::env; use std::fs; use std::path::Path; fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); let output = Path::new(&out_dir).join("output"); if env::var_os("CARGO_FEATURE_FOO").is_some() { fs::write(output, "foo").unwrap(); } else { fs::write(output, "bar").unwrap(); } } "#, ) .build(); p.cargo("build -v --features=foo").run(); p.rename_run("foo", "with_foo").with_stdout("foo\n").run(); p.cargo("build -v").run(); p.rename_run("foo", "without_foo") .with_stdout("bar\n") .run(); p.cargo("build -v --features=foo").run(); p.rename_run("foo", "with_foo2").with_stdout("foo\n").run(); } #[cargo_test] fn assume_build_script_when_build_rs_present() { let p = project() .file( "src/main.rs", r#" fn main() { if ! cfg!(foo) { panic!("the build script was not run"); } } "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rustc-cfg=foo"); } "#, ) .build(); p.cargo("run -v").run(); } #[cargo_test] fn if_build_set_to_false_dont_treat_build_rs_as_build_script() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = false "#, ) .file( "src/main.rs", r#" fn main() { if cfg!(foo) { panic!("the build script was run"); } } "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rustc-cfg=foo"); } "#, ) .build(); p.cargo("run -v").run(); } #[cargo_test] fn deterministic_rustc_dependency_flags() { // This bug is non-deterministic hence the large number of dependencies // in the hopes it will have a much higher chance of triggering it. Package::new("dep1", "0.1.0") .file( "Cargo.toml", r#" [package] name = "dep1" version = "0.1.0" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rustc-flags=-L native=test1"); } "#, ) .file("src/lib.rs", "") .publish(); Package::new("dep2", "0.1.0") .file( "Cargo.toml", r#" [package] name = "dep2" version = "0.1.0" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rustc-flags=-L native=test2"); } "#, ) .file("src/lib.rs", "") .publish(); Package::new("dep3", "0.1.0") .file( "Cargo.toml", r#" [package] name = "dep3" version = "0.1.0" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rustc-flags=-L native=test3"); } "#, ) .file("src/lib.rs", "") .publish(); Package::new("dep4", "0.1.0") .file( "Cargo.toml", r#" [package] name = "dep4" version = "0.1.0" authors = [] build = "build.rs" "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rustc-flags=-L native=test4"); } "#, ) .file("src/lib.rs", "") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = [] [dependencies] dep1 = "*" dep2 = "*" dep3 = "*" dep4 = "*" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("build -v") .with_stderr_contains( "\ [RUNNING] `rustc --crate-name foo [..] -L native=test1 -L native=test2 \ -L native=test3 -L native=test4` ", ) .run(); } #[cargo_test] fn links_duplicates_with_cycle() { // this tests that the links_duplicates are caught at resolver time let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] links = "a" build = "build.rs" [dependencies.a] path = "a" [dev-dependencies] b = { path = "b" } "#, ) .file("src/lib.rs", "") .file("build.rs", "") .file( "a/Cargo.toml", r#" [package] name = "a" version = "0.5.0" authors = [] links = "a" build = "build.rs" "#, ) .file("a/src/lib.rs", "") .file("a/build.rs", "") .file( "b/Cargo.toml", r#" [package] name = "b" version = "0.5.0" authors = [] [dependencies] foo = { path = ".." } "#, ) .file("b/src/lib.rs", "") .build(); p.cargo("build").with_status(101) .with_stderr("\ error: failed to select a version for `a`. ... required by package `foo v0.5.0 ([..])` versions that meet the requirements `*` are: 0.5.0 the package `a` links to the native library `a`, but it conflicts with a previous package which links to `a` as well: package `foo v0.5.0 ([..])` Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the `links = \"a\"` value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. failed to select a version for `a` which could resolve this conflict ").run(); } #[cargo_test] fn rename_with_link_search_path() { _rename_with_link_search_path(false); } #[cargo_test] #[cfg_attr( target_os = "macos", ignore = "don't have a cdylib cross target on macos" )] fn rename_with_link_search_path_cross() { if cross_compile::disabled() { return; } _rename_with_link_search_path(true); } fn _rename_with_link_search_path(cross: bool) { let target_arg = if cross { format!(" --target={}", cross_compile::alternate()) } else { "".to_string() }; let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [lib] crate-type = ["cdylib"] "#, ) .file( "src/lib.rs", "#[no_mangle] pub extern fn cargo_test_foo() {}", ); let p = p.build(); p.cargo(&format!("build{}", target_arg)).run(); let p2 = project() .at("bar") .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) .file( "build.rs", r#" use std::env; use std::fs; use std::path::PathBuf; fn main() { // Move the `libfoo.so` from the root of our project into the // build directory. This way Cargo should automatically manage // `LD_LIBRARY_PATH` and such. let root = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); let file = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX); let src = root.join(&file); let dst_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); let dst = dst_dir.join(&file); fs::copy(&src, &dst).unwrap(); // handle windows, like below drop(fs::copy(root.join("foo.dll.lib"), dst_dir.join("foo.dll.lib"))); println!("cargo::rerun-if-changed=build.rs"); if cfg!(target_env = "msvc") { println!("cargo::rustc-link-lib=foo.dll"); } else { println!("cargo::rustc-link-lib=foo"); } println!("cargo::rustc-link-search=all={}", dst.parent().unwrap().display()); } "#, ) .file( "src/main.rs", r#" extern { #[link_name = "cargo_test_foo"] fn foo(); } fn main() { unsafe { foo(); } } "#, ); let p2 = p2.build(); // Move the output `libfoo.so` into the directory of `p2`, and then delete // the `p` project. On macOS, the `libfoo.dylib` artifact references the // original path in `p` so we want to make sure that it can't find it (hence // the deletion). let root = if cross { p.root() .join("target") .join(cross_compile::alternate()) .join("debug") .join("deps") } else { p.root().join("target").join("debug").join("deps") }; let file = format!("{}foo{}", env::consts::DLL_PREFIX, env::consts::DLL_SUFFIX); let src = root.join(&file); let dst = p2.root().join(&file); fs::copy(&src, &dst).unwrap(); // copy the import library for windows, if it exists drop(fs::copy( &root.join("foo.dll.lib"), p2.root().join("foo.dll.lib"), )); remove_dir_all(p.root()).unwrap(); // Everything should work the first time p2.cargo(&format!("run{}", target_arg)).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 = p2.root(); new.pop(); new.push("bar2"); // For whatever reason on Windows right after we execute a binary it's very // unlikely that we're able to successfully delete or rename that binary. // It's not really clear why this is the case or if it's a bug in Cargo // holding a handle open too long. In an effort to reduce the flakiness of // this test though we throw this in a loop // // For some more information see #5481 and rust-lang/rust#48775 let mut i = 0; loop { let error = match fs::rename(p2.root(), &new) { Ok(()) => break, Err(e) => e, }; i += 1; if !cfg!(windows) || error.kind() != io::ErrorKind::PermissionDenied || i > 10 { panic!("failed to rename: {}", error); } println!("assuming {} is spurious, waiting to try again", error); thread::sleep(slow_cpu_multiplier(100)); } p2.cargo(&format!("run{}", target_arg)) .cwd(&new) .with_stderr( "\ [FINISHED] [..] [RUNNING] [..] ", ) .run(); } #[cargo_test] fn optional_build_script_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] [dependencies] bar = { path = "bar", optional = true } [build-dependencies] bar = { path = "bar", optional = true } "#, ) .file( "build.rs", r#" #[cfg(feature = "bar")] extern crate bar; fn main() { #[cfg(feature = "bar")] { println!("cargo::rustc-env=FOO={}", bar::bar()); return } println!("cargo::rustc-env=FOO=0"); } "#, ) .file( "src/main.rs", r#" #[cfg(feature = "bar")] extern crate bar; fn main() { println!("{}", env!("FOO")); } "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0")) .file("bar/src/lib.rs", "pub fn bar() -> u32 { 1 }"); let p = p.build(); p.cargo("run").with_stdout("0\n").run(); p.cargo("run --features bar").with_stdout("1\n").run(); } #[cargo_test] fn optional_build_dep_and_required_normal_dep() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = [] [dependencies] bar = { path = "./bar", optional = true } [build-dependencies] bar = { path = "./bar" } "#, ) .file("build.rs", "extern crate bar; fn main() { bar::bar(); }") .file( "src/main.rs", r#" #[cfg(feature = "bar")] extern crate bar; fn main() { #[cfg(feature = "bar")] { println!("{}", bar::bar()); } #[cfg(not(feature = "bar"))] { println!("0"); } } "#, ) .file("bar/Cargo.toml", &basic_manifest("bar", "0.5.0")) .file("bar/src/lib.rs", "pub fn bar() -> u32 { 1 }"); let p = p.build(); p.cargo("run") .with_stdout("0") .with_stderr( "\ [COMPILING] bar v0.5.0 ([..]) [COMPILING] foo v0.1.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]foo[EXE]`", ) .run(); p.cargo("run --all-features") .with_stdout("1") .with_stderr( "\ [COMPILING] bar v0.5.0 ([..]) [COMPILING] foo v0.1.0 ([..]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] [RUNNING] `[..]foo[EXE]`", ) .run(); } #[cargo_test] fn using_rerun_if_changed_does_not_rebuild() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = [] "#, ) .file( "build.rs", r#" fn main() { println!("cargo::rerun-if-changed=build.rs"); } "#, ) .file("src/lib.rs", "") .build(); p.cargo("build").run(); p.cargo("build").with_stderr("[FINISHED] [..]").run(); } #[cargo_test] fn links_interrupted_can_restart() { // Test for a `links` dependent build script getting canceled and then // restarted. Steps: // 1. Build to establish fingerprints. // 2. Change something (an env var in this case) that triggers the // dependent build script to run again. Kill the top-level build script // while it is running (such as hitting Ctrl-C). // 3. Run the build again, it should re-run the build script. let bar = project() .at("bar") .file( "Cargo.toml", r#" [package] name = "bar" version = "0.5.0" authors = [] links = "foo" build = "build.rs" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rerun-if-env-changed=SOMEVAR"); } "#, ) .build(); let p = project() .file( "Cargo.toml", &format!( r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" [dependencies.bar] path = '{}' "#, bar.root().display() ), ) .file("src/lib.rs", "") .file( "build.rs", r#" use std::env; fn main() { println!("cargo::metadata=rebuild-if-changed=build.rs"); if std::path::Path::new("abort").exists() { panic!("Crash!"); } } "#, ) .build(); p.cargo("build").run(); // Simulate the user hitting Ctrl-C during a build. p.change_file("abort", ""); // Set SOMEVAR to trigger a rebuild. p.cargo("build") .env("SOMEVAR", "1") .with_stderr_contains("[..]Crash![..]") .with_status(101) .run(); fs::remove_file(p.root().join("abort")).unwrap(); // Try again without aborting the script. // ***This is currently broken, the script does not re-run. p.cargo("build -v") .env("SOMEVAR", "1") .with_stderr_contains("[RUNNING] [..]/foo-[..]/build-script-build[..]") .run(); } #[cargo_test] fn dev_dep_with_links() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" authors = [] links = "x" [dev-dependencies] bar = { path = "./bar" } "#, ) .file("build.rs", "fn main() {}") .file("src/lib.rs", "") .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" authors = [] links = "y" [dependencies] foo = { path = ".." } "#, ) .file("bar/build.rs", "fn main() {}") .file("bar/src/lib.rs", "") .build(); p.cargo("check --tests").run() } #[cargo_test] fn rerun_if_directory() { if !symlink_supported() { return; } // rerun-if-changed of a directory should rerun if any file in the directory changes. let p = project() .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rerun-if-changed=somedir"); } "#, ) .build(); let dirty = |dirty_line: &str, compile_build_script: bool| { let mut dirty_line = dirty_line.to_string(); if !dirty_line.is_empty() { dirty_line.push('\n'); } let compile_build_script_line = if compile_build_script { "[RUNNING] `rustc --crate-name build_script_build [..]\n" } else { "" }; p.cargo("check -v") .with_stderr(format!( "\ {dirty_line}\ [COMPILING] foo [..] {compile_build_script_line}\ [RUNNING] `[..]build-script-build[..]` [RUNNING] `rustc --crate-name foo [..] [FINISHED] [..]", )) .run(); }; let fresh = || { p.cargo("check").with_stderr("[FINISHED] [..]").run(); }; // Start with a missing directory. dirty("", true); // Because the directory doesn't exist, it will trigger a rebuild every time. // https://github.com/rust-lang/cargo/issues/6003 dirty( "[DIRTY] foo v0.1.0 ([..]): the file `somedir` is missing", false, ); if is_coarse_mtime() { sleep_ms(1000); } // Empty directory. fs::create_dir(p.root().join("somedir")).unwrap(); dirty( "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])", false, ); fresh(); if is_coarse_mtime() { sleep_ms(1000); } // Add a file. p.change_file("somedir/foo", ""); p.change_file("somedir/bar", ""); dirty( "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])", false, ); fresh(); if is_coarse_mtime() { sleep_ms(1000); } // Add a symlink. p.symlink("foo", "somedir/link"); dirty( "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])", false, ); fresh(); if is_coarse_mtime() { sleep_ms(1000); } // Move the symlink. fs::remove_file(p.root().join("somedir/link")).unwrap(); p.symlink("bar", "somedir/link"); dirty( "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])", false, ); fresh(); if is_coarse_mtime() { sleep_ms(1000); } // Remove a file. fs::remove_file(p.root().join("somedir/foo")).unwrap(); dirty( "[DIRTY] foo v0.1.0 ([..]): the file `somedir` has changed ([..])", false, ); fresh(); } #[cargo_test] fn rerun_if_published_directory() { // build script of a dependency contains a `rerun-if-changed` pointing to a directory Package::new("mylib-sys", "1.0.0") .file("mylib/balrog.c", "") .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { // Changing to mylib/balrog.c will not trigger a rebuild println!("cargo::rerun-if-changed=mylib"); } "#, ) .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" [dependencies] mylib-sys = "1.0.0" "#, ) .file("src/main.rs", "fn main() {}") .build(); p.cargo("check").run(); // Delete regitry src to make directories being recreated with the latest timestamp. cargo_home().join("registry/src").rm_rf(); p.cargo("check --verbose") .with_stderr( "\ [FRESH] mylib-sys v1.0.0 [FRESH] foo v0.0.1 ([CWD]) [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) .run(); // Upgrade of a package should still trigger a rebuild Package::new("mylib-sys", "1.0.1") .file("mylib/balrog.c", "") .file("mylib/balrog.h", "") .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::rerun-if-changed=mylib"); } "#, ) .publish(); p.cargo("update").run(); p.cargo("fetch").run(); p.cargo("check -v") .with_stderr(format!( "\ [COMPILING] mylib-sys [..] [RUNNING] `rustc --crate-name build_script_build [..] [RUNNING] `[..]build-script-build[..]` [RUNNING] `rustc --crate-name mylib_sys [..] [CHECKING] foo [..] [RUNNING] `rustc --crate-name foo [..] [FINISHED] [..]", )) .run(); } #[cargo_test] fn test_with_dep_metadata() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] bar = { path = 'bar' } "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { assert_eq!(std::env::var("DEP_BAR_FOO").unwrap(), "bar"); } "#, ) .file( "bar/Cargo.toml", r#" [package] name = "bar" version = "0.1.0" links = 'bar' "#, ) .file("bar/src/lib.rs", "") .file( "bar/build.rs", r#" fn main() { println!("cargo::metadata=foo=bar"); } "#, ) .build(); p.cargo("test --lib").run(); } #[cargo_test] fn duplicate_script_with_extra_env() { // Test where a build script is run twice, that emits different rustc-env // and rustc-cfg values. In this case, one is run for host, the other for // target. if !cross_compile::can_run_on_host() { return; } let target = cross_compile::alternate(); let p = project() .file( "Cargo.toml", r#" [workspace] members = ["foo", "pm"] "#, ) .file( "foo/Cargo.toml", r#" [package] name = "foo" version = "0.1.0" [dependencies] pm = { path = "../pm" } "#, ) .file( "foo/src/lib.rs", &r#" //! ```rust //! #[cfg(not(mycfg="{target}"))] //! compile_error!{"expected mycfg set"} //! assert_eq!(env!("CRATE_TARGET"), "{target}"); //! assert_eq!(std::env::var("CRATE_TARGET").unwrap(), "{target}"); //! ``` #[test] fn check_target() { #[cfg(not(mycfg="{target}"))] compile_error!{"expected mycfg set"} // Compile-time assertion. assert_eq!(env!("CRATE_TARGET"), "{target}"); // Run-time assertion. assert_eq!(std::env::var("CRATE_TARGET").unwrap(), "{target}"); } "# .replace("{target}", target), ) .file( "foo/build.rs", r#" fn main() { println!("cargo::rustc-env=CRATE_TARGET={}", std::env::var("TARGET").unwrap()); println!("cargo::rustc-cfg=mycfg=\"{}\"", std::env::var("TARGET").unwrap()); } "#, ) .file( "pm/Cargo.toml", r#" [package] name = "pm" version = "0.1.0" [lib] proc-macro = true # This is just here to speed things up. doctest = false [dev-dependencies] foo = { path = "../foo" } "#, ) .file("pm/src/lib.rs", "") .build(); p.cargo("test --workspace --target") .arg(&target) .with_stdout_contains("test check_target ... ok") .run(); if cargo_test_support::is_nightly() { p.cargo("test --workspace -Z doctest-xcompile --doc --target") .arg(&target) .masquerade_as_nightly_cargo(&["doctest-xcompile"]) .with_stdout_contains("test foo/src/lib.rs - (line 2) ... ok") .run(); } } #[cargo_test] fn wrong_output() { let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::example"); } "#, ) .build(); p.cargo("build") .with_status(101) .with_stderr( "\ [COMPILING] foo [..] error: invalid output in build script of `foo v0.0.1 ([ROOT]/foo)`: `cargo::example` Expected a line with `cargo::KEY=VALUE` with an `=` character, but none was found. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \ for more information about build script outputs. ", ) .run(); } #[cargo_test] fn custom_build_closes_stdin() { // Ensure stdin is closed to prevent deadlock. // See https://github.com/rust-lang/cargo/issues/11196 let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" build = "build.rs" "#, ) .file("src/main.rs", "fn main() {}") .file( "build.rs", r#"fn main() { let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap(); }"#, ) .build(); p.cargo("build").run(); } #[cargo_test] fn test_old_syntax() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" "#, ) .file( "src/main.rs", r#" const FOO: &'static str = env!("FOO"); fn main() { println!("{}", FOO); } "#, ) .file( "build.rs", r#"fn main() { println!("cargo:rustc-env=FOO=foo"); println!("cargo:foo=foo"); }"#, ) .build(); p.cargo("build -v").run(); p.cargo("run -v").with_stdout("foo\n").run(); } #[cargo_test] fn test_invalid_old_syntax() { // Unexpected metadata value. let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo:foo"); } "#, ) .build(); p.cargo("build") .with_status(101) .with_stderr( "\ [COMPILING] foo [..] error: invalid output in build script of `foo v0.0.1 ([ROOT]/foo)`: `cargo:foo` Expected a line with `cargo:KEY=VALUE` with an `=` character, but none was found. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \ for more information about build script outputs. ", ) .run(); } #[cargo_test] fn test_invalid_new_syntax() { // Unexpected metadata value. let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::metadata=foo"); } "#, ) .build(); p.cargo("build") .with_status(101) .with_stderr( "\ [COMPILING] foo [..] error: invalid output in build script of `foo v0.0.1 ([ROOT]/foo)`: `cargo::metadata=foo` Expected a line with `cargo::metadata=KEY=VALUE` with an `=` character, but none was found. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \ for more information about build script outputs. ", ) .run(); // `cargo::` can not be used with the unknown key. let p = project() .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::foo=bar"); } "#, ) .build(); p.cargo("build") .with_status(101) .with_stderr( "\ [COMPILING] foo [..] error: invalid output in build script of `foo v0.0.1 ([ROOT]/foo)`: `cargo::foo=bar` Unknown key: `foo`. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \ for more information about build script outputs. ", ) .run(); } #[cargo_test] fn test_new_syntax_with_old_msrv() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.5.0" authors = [] build = "build.rs" rust-version = "1.60.0" "#, ) .file("src/lib.rs", "") .file( "build.rs", r#" fn main() { println!("cargo::metadata=foo=bar"); } "#, ) .build(); p.cargo("build") .with_status(101) .with_stderr_contains( "\ [COMPILING] foo [..] error: the `cargo::` syntax for build script output instructions was added in Rust 1.77.0, \ but the minimum supported Rust version of `foo v0.5.0 ([ROOT]/foo)` is 1.60.0. See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \ for more information about build script outputs. ", ) .run(); } #[cargo_test] fn test_old_syntax_with_old_msrv() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" authors = [] build = "build.rs" rust-version = "1.60.0" "#, ) .file( "src/main.rs", r#" const FOO: &'static str = env!("FOO"); fn main() { println!("{}", FOO); } "#, ) .file( "build.rs", r#"fn main() { println!("cargo:rustc-env=FOO=foo"); println!("cargo:foo=foo"); }"#, ) .build(); p.cargo("build -v").run(); p.cargo("run -v").with_stdout("foo\n").run(); }