cargo_build_script_runner: parse cargo::metadata key/value pairs (#3877)
## Summary
Fix `cargo::metadata=KEY=VALUE` handling in `cargo_build_script_runner`
so metadata keys are propagated in Cargo-compatible form.
Before:
- `cargo::metadata=version_1_10_0=1`
- produced `DEP_<links>_METADATA=version_1_10_0=1`
After:
- same input produces `DEP_<links>_VERSION_1_10_0=1`
## Changes
- Add a `"metadata"` parser branch in `BuildScriptOutput::new`.
- Split metadata payload on first `=` and emit
`DepEnv("<KEY>=<VALUE>")`.
- Preserve fallback behavior for malformed metadata payloads.
- Add unit test: `metadata_directive_maps_to_dep_env_key_value`.
## Motivation
Downstream `links` users (for example `hdf5-metno`) expect specific
`DEP_*` metadata keys (`DEP_HDF5_VERSION_*`, `DEP_HDF5_HAVE_*`).
## Testing
- `bazel --batch test //cargo/private/cargo_build_script_runner:test
--remote_executor= --remote_cache= --bes_backend= --noshow_progress
--color=no --curses=no`
`bazel test //test/cargo_build_script/metadata_dep_env:all` are end to
end integration tests
Closes #3876.diff --git a/cargo/private/cargo_build_script_runner/lib.rs b/cargo/private/cargo_build_script_runner/lib.rs
index de7c468..d2f0461 100644
--- a/cargo/private/cargo_build_script_runner/lib.rs
+++ b/cargo/private/cargo_build_script_runner/lib.rs
@@ -86,6 +86,20 @@
eprint!("Build Script Warning: {}", split[1]);
None
}
+ "metadata" => {
+ // cargo::metadata=KEY=VALUE is forwarded to dependents as
+ // DEP_<links>_<KEY>=<VALUE> by Cargo.
+ if let Some((key, value)) = param.split_once('=') {
+ Some(BuildScriptOutput::DepEnv(format!(
+ "{}={}",
+ key.to_uppercase().replace('-', "_"),
+ value.trim()
+ )))
+ } else {
+ // Keep legacy behavior for malformed metadata payloads.
+ Some(BuildScriptOutput::DepEnv(format!("METADATA={}", param)))
+ }
+ }
"rustc-cdylib-link-arg" | "rustc-link-arg-bin" | "rustc-link-arg-bins" => {
// cargo::rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.
// cargo::rustc-link-arg-bin=BIN=FLAG – Passes custom flags to a linker for the binary BIN.
@@ -375,4 +389,14 @@
"valid1=1\nvalid2=2"
);
}
+
+ #[test]
+ fn metadata_directive_maps_to_dep_env_key_value() {
+ let reader = BufReader::new(Cursor::new("cargo::metadata=version_1_10_0=1\n"));
+ let result = BuildScriptOutput::outputs_from_reader(reader);
+ assert_eq!(
+ result,
+ vec![BuildScriptOutput::DepEnv("VERSION_1_10_0=1".to_owned())]
+ );
+ }
}
diff --git a/test/cargo_build_script/metadata_dep_env/BUILD.bazel b/test/cargo_build_script/metadata_dep_env/BUILD.bazel
new file mode 100644
index 0000000..340fd93
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/BUILD.bazel
@@ -0,0 +1,37 @@
+load("//cargo:defs.bzl", "cargo_build_script")
+load("//rust:defs.bzl", "rust_library", "rust_test")
+
+cargo_build_script(
+ name = "producer_build_rs",
+ srcs = ["producer_build.rs"],
+ edition = "2021",
+ links = "producer",
+)
+
+rust_library(
+ name = "producer_lib",
+ srcs = ["producer_lib.rs"],
+ edition = "2021",
+ deps = [":producer_build_rs"],
+)
+
+cargo_build_script(
+ name = "consumer_build_rs",
+ srcs = ["consumer_build.rs"],
+ edition = "2021",
+ link_deps = [":producer_lib"],
+)
+
+rust_test(
+ name = "metadata_dep_env_modern_test",
+ srcs = ["modern_test.rs"],
+ edition = "2021",
+ deps = [":consumer_build_rs"],
+)
+
+rust_test(
+ name = "metadata_dep_env_legacy_test",
+ srcs = ["legacy_test.rs"],
+ edition = "2021",
+ deps = [":consumer_build_rs"],
+)
diff --git a/test/cargo_build_script/metadata_dep_env/README.md b/test/cargo_build_script/metadata_dep_env/README.md
new file mode 100644
index 0000000..978d708
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/README.md
@@ -0,0 +1,27 @@
+# metadata_dep_env test
+
+This package contains end-to-end tests for metadata forwarding through `cargo_build_script`
+for both syntax forms:
+- modern syntax: `cargo::metadata=KEY=VALUE`
+- legacy syntax: `cargo:KEY=VALUE`
+
+## What it verifies
+
+1. `producer_build_rs` emits both:
+ - `cargo::metadata=modern_version_1_10_0=1`
+ - `cargo:legacy_version_1_10_0=2`
+2. `rules_rust` converts that to a dependent build-script env var:
+ - `DEP_PRODUCER_MODERN_VERSION_1_10_0=1`
+ - `DEP_PRODUCER_LEGACY_VERSION_1_10_0=1`
+3. `consumer_build_rs` reads both and exports:
+ - `cargo:rustc-env=METADATA_MODERN_VALUE=1`
+ - `cargo:rustc-env=METADATA_LEGACY_VALUE=2`
+4. Two `rust_test` targets assert each value independently:
+ - `metadata_dep_env_modern_test`: `env!("METADATA_MODERN_VALUE") == "1"`
+ - `metadata_dep_env_legacy_test`: `env!("METADATA_LEGACY_VALUE") == "2"`
+
+## Why `producer_lib.rs` exists
+
+`cargo_build_script` targets cannot directly depend on other `cargo_build_script` targets.
+To model a realistic dependency edge, we attach `producer_build_rs` to a tiny Rust library (`producer_lib`),
+and the consumer build script depends on that library via `link_deps`.
diff --git a/test/cargo_build_script/metadata_dep_env/consumer_build.rs b/test/cargo_build_script/metadata_dep_env/consumer_build.rs
new file mode 100644
index 0000000..1db03cd
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/consumer_build.rs
@@ -0,0 +1,18 @@
+fn main() {
+ let modern = std::env::var("DEP_PRODUCER_MODERN_VERSION_1_10_0")
+ .expect("DEP_PRODUCER_MODERN_VERSION_1_10_0 should be set by producer build script");
+ assert_eq!(
+ modern, "1",
+ "unexpected DEP_PRODUCER_MODERN_VERSION_1_10_0 value"
+ );
+
+ let legacy = std::env::var("DEP_PRODUCER_LEGACY_VERSION_1_10_0")
+ .expect("DEP_PRODUCER_LEGACY_VERSION_1_10_0 should be set by producer build script");
+ assert_eq!(
+ legacy, "2",
+ "unexpected DEP_PRODUCER_LEGACY_VERSION_1_10_0 value"
+ );
+
+ println!("cargo:rustc-env=METADATA_MODERN_VALUE={modern}");
+ println!("cargo:rustc-env=METADATA_LEGACY_VALUE={legacy}");
+}
diff --git a/test/cargo_build_script/metadata_dep_env/legacy_test.rs b/test/cargo_build_script/metadata_dep_env/legacy_test.rs
new file mode 100644
index 0000000..c46dfa4
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/legacy_test.rs
@@ -0,0 +1,4 @@
+#[test]
+fn legacy_metadata_dep_env_is_forwarded() {
+ assert_eq!(env!("METADATA_LEGACY_VALUE"), "2");
+}
diff --git a/test/cargo_build_script/metadata_dep_env/modern_test.rs b/test/cargo_build_script/metadata_dep_env/modern_test.rs
new file mode 100644
index 0000000..733fb1f
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/modern_test.rs
@@ -0,0 +1,4 @@
+#[test]
+fn modern_metadata_dep_env_is_forwarded() {
+ assert_eq!(env!("METADATA_MODERN_VALUE"), "1");
+}
diff --git a/test/cargo_build_script/metadata_dep_env/producer_build.rs b/test/cargo_build_script/metadata_dep_env/producer_build.rs
new file mode 100644
index 0000000..8207a07
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/producer_build.rs
@@ -0,0 +1,5 @@
+fn main() {
+ println!("cargo::metadata=modern_version_1_10_0=1");
+ // Legacy (pre-1.77-compatible) syntax where unknown cargo:KEY is treated as metadata.
+ println!("cargo:legacy_version_1_10_0=2");
+}
diff --git a/test/cargo_build_script/metadata_dep_env/producer_lib.rs b/test/cargo_build_script/metadata_dep_env/producer_lib.rs
new file mode 100644
index 0000000..837e925
--- /dev/null
+++ b/test/cargo_build_script/metadata_dep_env/producer_lib.rs
@@ -0,0 +1,6 @@
+// Dummy Rust crate used to carry DepInfo from `producer_build_rs`.
+// `cargo_build_script` cannot depend directly on another build script target,
+// so the consumer build script uses this library in `link_deps` instead.
+pub fn marker() -> u8 {
+ 1
+}