diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index db9bc9fce07..6193e09c92f 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -451,6 +451,8 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul cmd.display_env_vars(); } + let any_build_script_metadata = bcx.gctx.cli_unstable().any_build_script_metadata; + // Gather the set of native dependencies that this package has along with // some other variables to close over. // @@ -461,8 +463,26 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul .filter_map(|dep| { if dep.unit.mode.is_run_custom_build() { let dep_metadata = build_runner.get_run_build_script_metadata(&dep.unit); + + let Some(dependency) = unit.pkg.dependencies().iter().find(|d| { + d.package_name() == dep.unit.pkg.name() + && d.source_id() == dep.unit.pkg.package_id().source_id() + && d.version_req().matches(unit.pkg.version()) + }) else { + panic!( + "Dependency `{}` not found in `{}`s dependencies", + dep.unit.pkg.name(), + unit.pkg.name() + ) + }; + Some(( - dep.unit.pkg.manifest().links().unwrap().to_string(), + dependency.name_in_toml(), + dep.unit + .pkg + .manifest() + .links() + .map(|links| links.to_string()), dep.unit.pkg.package_id(), dep_metadata, )) @@ -531,7 +551,7 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul // native dynamic libraries. { let build_script_outputs = build_script_outputs.lock().unwrap(); - for (name, dep_id, dep_metadata) in lib_deps { + for (name, links, dep_id, dep_metadata) in lib_deps { let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| { internal(format!( "failed to locate build state for env vars: {}/{}", @@ -540,10 +560,18 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul })?; let data = &script_output.metadata; for (key, value) in data.iter() { - cmd.env( - &format!("DEP_{}_{}", super::envify(&name), super::envify(key)), - value, - ); + if let Some(ref links) = links { + cmd.env( + &format!("DEP_{}_{}", super::envify(&links), super::envify(key)), + value, + ); + } + if any_build_script_metadata { + cmd.env( + &format!("CARGO_DEP_{}_{}", super::envify(&name), super::envify(key)), + value, + ); + } } } if let Some(build_scripts) = build_scripts diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index 947fb17e841..7b9e6426ab5 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -970,11 +970,13 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_>) { .iter() .map(move |a| (reverse_dep, a)) }) + // Exclude ourself + .filter(|(_parent, other)| other.unit.pkg != unit.pkg) // Only deps with `links`. .filter(|(_parent, other)| { - other.unit.pkg != unit.pkg - && other.unit.target.is_linkable() - && other.unit.pkg.manifest().links().is_some() + state.gctx.cli_unstable().any_build_script_metadata + || (other.unit.target.is_linkable() + && other.unit.pkg.manifest().links().is_some()) }) // Avoid cycles when using the doc --scrape-examples feature: // Say a workspace has crates A and B where A has a build-dependency on B. diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index edf02b00555..793ff378c78 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -844,6 +844,7 @@ unstable_cli_options!( // All other unstable features. // Please keep this list lexicographically ordered. advanced_env: bool, + any_build_script_metadata: bool = ("Allow any build script to specify env vars via cargo::metadata=key=value"), asymmetric_token: bool = ("Allows authenticating with asymmetric tokens"), avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"), binary_dep_depinfo: bool = ("Track changes to dependency artifacts"), @@ -1374,6 +1375,7 @@ impl CliUnstable { // Unstable features // Sorted alphabetically: "advanced-env" => self.advanced_env = parse_empty(k, v)?, + "any-build-script-metadata" => self.any_build_script_metadata = parse_empty(k, v)?, "asymmetric-token" => self.asymmetric_token = parse_empty(k, v)?, "avoid-dev-deps" => self.avoid_dev_deps = parse_empty(k, v)?, "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?, diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 16b5260222c..9c48ac61e8d 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -67,6 +67,7 @@ Each new feature described below should explain how to use it. * Build scripts and linking * [Metabuild](#metabuild) --- Provides declarative build scripts. * [Multiple Build Scripts](#multiple-build-scripts) --- Allows use of multiple build scripts. + * [Any Build Script Metadata](#any-build-script-metadata) --- Allow any build script to specify env vars via `cargo::metadata=key=value` * Resolver and features * [no-index-update](#no-index-update) --- Prevents cargo from updating the index cache. * [avoid-dev-deps](#avoid-dev-deps) --- Prevents the resolver from including dev-dependencies during resolution. @@ -313,6 +314,16 @@ build = ["foo.rs", "bar.rs"] where the `` is the file-stem of the build script, exactly as-is. For example, `bar_OUT_DIR` for script at `foo/bar.rs`. (Only set during compilation, can be accessed via `env!` macro) +## Any Build Script Metadata +* Tracking Issue: [#14903](https://github.com/rust-lang/cargo/issues/3544) + +Allow any build script to specify env vars via `cargo::metadata=key=value` + +Depedant build scripts can access these key/value pair by reading the `CARGO_DEP__` env variable at runtime. +For build scripts of crates with a `links`, both `DEP__` and `CARGO_DEP__` will be set. + +Note that `dep` and `key` in `CARGO_DEP__` are uppercased and hyphens (`-`) replaced with underscores (`_`). + ## public-dependency * Tracking Issue: [#44663](https://github.com/rust-lang/rust/issues/44663) diff --git a/tests/testsuite/build_script.rs b/tests/testsuite/build_script.rs index e925f9ecfbb..190158a3078 100644 --- a/tests/testsuite/build_script.rs +++ b/tests/testsuite/build_script.rs @@ -1343,6 +1343,385 @@ fn links_passes_env_vars() { p.cargo("build -v").run(); } +#[cargo_test] +fn metadata_from_dep_kinds() { + let set_metadata = r#" +fn main() { + println!("cargo::metadata=key=value"); +} +"#; + + let get_metadata = r#" +fn warn_print_env_var(key: &str) { + println!("cargo::warning={key}={:?}", std::env::var(key)); +} +fn main() { + warn_print_env_var("DEP_FOO_KEY"); + warn_print_env_var("CARGO_DEP_LINKS_KEY"); +} +"#; + + // Create a project with a crate with `links` and 3 test crates that imports this crate as a + // normal dependency (n), dev-dependency (d), and build-dependency (b). + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + resolver = "3" + members = ["links", "n", "b", "d"] + "#, + ) + .file( + "links/Cargo.toml", + r#" +[package] +name = "links" +edition = "2024" +links = "foo" +"#, + ) + .file("links/src/lib.rs", "") + .file("links/build.rs", set_metadata) + .file( + "n/Cargo.toml", + r#" +[package] +name = "n" +edition = "2024" + +[dependencies] +links.path = "../links" +"#, + ) + .file("n/src/lib.rs", "") + .file("n/build.rs", get_metadata) + .file( + "b/Cargo.toml", + r#" +[package] +name = "b" +edition = "2024" + +[build-dependencies] +links.path = "../links" +"#, + ) + .file("b/src/lib.rs", "") + .file("b/build.rs", get_metadata) + .file( + "d/Cargo.toml", + r#" +[package] +name = "d" +edition = "2024" + +[dev-dependencies] +links.path = "../links" +"#, + ) + .file("d/src/lib.rs", "") + .file("d/build.rs", get_metadata) + .build(); + + p.cargo("check --all-targets -Zany-build-script-metadata") + .masquerade_as_nightly_cargo(&["any-build-script-metadata"]) + .with_stderr_data( + str![[r#" +[COMPILING] links v0.0.0 ([ROOT]/foo/links) +[COMPILING] n v0.0.0 ([ROOT]/foo/n) +[COMPILING] b v0.0.0 ([ROOT]/foo/b) +[COMPILING] d v0.0.0 ([ROOT]/foo/d) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s +[WARNING] n@0.0.0: DEP_FOO_KEY=Ok("value") +[WARNING] n@0.0.0: CARGO_DEP_LINKS_KEY=Ok("value") +[WARNING] d@0.0.0: DEP_FOO_KEY=Err(NotPresent) +[WARNING] d@0.0.0: CARGO_DEP_LINKS_KEY=Err(NotPresent) +[WARNING] b@0.0.0: DEP_FOO_KEY=Err(NotPresent) +[WARNING] b@0.0.0: CARGO_DEP_LINKS_KEY=Err(NotPresent) + +"#]] + .unordered(), + ) + .run(); +} + +#[cargo_test] +fn links_passes_env_vars_with_any_build_script_unstable_feature() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + 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"); + assert_eq!(env::var("CARGO_DEP_A_FOO").unwrap(), "bar"); + assert_eq!(env::var("CARGO_DEP_A_BAR").unwrap(), "baz"); + } + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.5.0" + edition = "2015" + 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("check -v -Zany-build-script-metadata") + .masquerade_as_nightly_cargo(&["any-build-script-metadata"]) + .run(); +} + +#[cargo_test] +fn non_links_can_pass_env_vars() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + + [dependencies.a] + path = "a" + "#, + ) + .file("src/lib.rs", "") + .file( + "build.rs", + r#" + use std::env; + fn main() { + // DEP__ is only allowed for `links` crates + assert!(env::var("DEP_FOO_FOO").is_err()); + assert!(env::var("DEP_FOO_BAR").is_err()); + // Make sure DEP__ is not present. + // This is not a valid scenario but verify these are not present just incase. + assert!(env::var("DEP_A_FOO").is_err()); + assert!(env::var("DEP_A_BAR").is_err()); + + assert_eq!(env::var("CARGO_DEP_A_FOO").unwrap(), "bar"); + assert_eq!(env::var("CARGO_DEP_A_BAR").unwrap(), "baz"); + } + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + "#, + ) + .file("a/src/lib.rs", "") + .file( + "a/build.rs", + r#" + fn main() { + println!("cargo::metadata=foo=bar"); + println!("cargo::metadata=bar=baz"); + } + "#, + ) + .build(); + + p.cargo("check -v -Zany-build-script-metadata") + .masquerade_as_nightly_cargo(&["any-build-script-metadata"]) + .run(); +} + +#[cargo_test] +fn non_links_can_pass_env_vars_with_dep_renamed() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + + [dependencies] + my-renamed-package = { package = "a", path = "a" } + "#, + ) + .file("src/lib.rs", "") + .file( + "build.rs", + r#" + use std::env; + fn main() { + assert!(env::var("DEP_A_FOO").is_err()); + assert!(env::var("DEP_A_BAR").is_err()); + assert!(env::var("DEP_MY_RENAMED_PACKAGE_FOO").is_err()); + assert!(env::var("DEP_MY_RENAMED_PACKAGE_BAR").is_err()); + + // If dep was renamed, we should not add env vars with the original name + // and env vars with the renamed package should be added + assert!(env::var("CARGO_DEP_A_FOO").is_err()); + assert!(env::var("CARGO_DEP_A_BAR").is_err()); + assert_eq!(env::var("CARGO_DEP_MY_RENAMED_PACKAGE_FOO").unwrap(), "bar"); + assert_eq!(env::var("CARGO_DEP_MY_RENAMED_PACKAGE_BAR").unwrap(), "baz"); + } + "#, + ) + .file( + "a/Cargo.toml", + r#" + [package] + name = "a" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + "#, + ) + .file("a/src/lib.rs", "") + .file( + "a/build.rs", + r#" + fn main() { + println!("cargo::metadata=foo=bar"); + println!("cargo::metadata=bar=baz"); + } + "#, + ) + .build(); + + p.cargo("check -v -Zany-build-script-metadata") + .masquerade_as_nightly_cargo(&["any-build-script-metadata"]) + .run(); +} + +#[cargo_test] +fn non_links_can_pass_env_vars_direct_deps_only() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + + [dependencies] + direct = { path = "direct" } + "#, + ) + .file("src/lib.rs", "") + .file( + "build.rs", + r#" + use std::env; + fn main() { + assert!(env::var("DEP_DIRECT_FOO").is_err()); + assert!(env::var("DEP_TRANSITIVE_FOO").is_err()); + + assert_eq!(env::var("CARGO_DEP_DIRECT_FOO").unwrap(), "direct"); + assert!(env::var("CARGO_DEP_TRANSITIVE_FOO").is_err()); + } + "#, + ) + .file( + "direct/Cargo.toml", + r#" + [package] + name = "direct" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + + [dependencies] + transitive = { path = "../transitive" } + "#, + ) + .file("direct/src/lib.rs", "") + .file( + "direct/build.rs", + r#" + use std::env; + fn main() { + println!("cargo::metadata=foo=direct"); + + assert_eq!(env::var("CARGO_DEP_TRANSITIVE_FOO").unwrap(), "transitive"); + } + "#, + ) + .file( + "transitive/Cargo.toml", + r#" + [package] + name = "transitive" + version = "0.5.0" + edition = "2015" + authors = [] + build = "build.rs" + "#, + ) + .file("transitive/src/lib.rs", "") + .file( + "transitive/build.rs", + r#" + fn main() { + println!("cargo::metadata=foo=transitive"); + } + "#, + ) + .build(); + + p.cargo("check -v -Zany-build-script-metadata") + .masquerade_as_nightly_cargo(&["any-build-script-metadata"]) + .run(); +} + #[cargo_test] fn only_rerun_build_script() { let p = project() diff --git a/tests/testsuite/cargo/z_help/stdout.term.svg b/tests/testsuite/cargo/z_help/stdout.term.svg index f5a3513c066..20006653417 100644 --- a/tests/testsuite/cargo/z_help/stdout.term.svg +++ b/tests/testsuite/cargo/z_help/stdout.term.svg @@ -1,4 +1,4 @@ - +