diff --git a/cargo/private/cargo_build_script_runner/bin.rs b/cargo/private/cargo_build_script_runner/bin.rs index 27f5848edc..057bd811fb 100644 --- a/cargo/private/cargo_build_script_runner/bin.rs +++ b/cargo/private/cargo_build_script_runner/bin.rs @@ -100,12 +100,25 @@ fn run_buildrs() -> Result<(), String> { for dep_env_path in input_dep_env_paths.iter() { if let Ok(contents) = read_to_string(dep_env_path) { + // Handle line continuations: lines ending with '\' continue on the next line + let mut current_line = String::new(); for line in contents.split('\n') { - // split on empty contents will still produce a single empty string in iterable. - if line.is_empty() { + // Skip empty lines when we're not in a continuation + if line.is_empty() && current_line.is_empty() { continue; } - match line.split_once('=') { + + if let Some(prefix) = line.strip_suffix('\\') { + // Line continues on the next line + current_line.push_str(prefix); + current_line.push('\n'); + continue; + } + + // Complete line (either standalone or end of continuation) + current_line.push_str(line); + + match current_line.split_once('=') { Some((key, value)) => { command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy())); } @@ -115,6 +128,7 @@ fn run_buildrs() -> Result<(), String> { ) } } + current_line.clear(); } } else { return Err("error: Dependency environment file unreadable".to_owned()); diff --git a/cargo/tests/cargo_build_script/build_script_cargo_toml_env/BUILD.bazel b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/BUILD.bazel new file mode 100644 index 0000000000..ea50d8b184 --- /dev/null +++ b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/BUILD.bazel @@ -0,0 +1,32 @@ +load("//cargo:defs.bzl", "cargo_build_script", "cargo_toml_env_vars") +load("//rust:defs.bzl", "rust_library", "rust_test") + +# Test that cargo_toml_env_vars works with build_script_env_files to provide +# CARGO_PKG_* variables to build scripts at runtime. This is the fix for +# crates like rav1e whose build scripts (via the `built` crate) read +# CARGO_PKG_AUTHORS at runtime with std::env::var(). + +cargo_toml_env_vars( + name = "cargo_toml_env_vars", + src = "Cargo.toml", +) + +cargo_build_script( + name = "cargo_build_script", + srcs = ["build.rs"], + build_script_env_files = [":cargo_toml_env_vars"], + edition = "2021", +) + +rust_library( + name = "test_lib", + srcs = ["test_lib.rs"], + edition = "2021", + deps = [":cargo_build_script"], +) + +rust_test( + name = "consume_build_script", + crate = ":test_lib", + size = "small", +) diff --git a/cargo/tests/cargo_build_script/build_script_cargo_toml_env/Cargo.toml b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/Cargo.toml new file mode 100644 index 0000000000..81343a5bcb --- /dev/null +++ b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "test_cargo_toml_env" +version = "1.2.3" +authors = ["Test Author ", "Another Author"] +description = """ +This is a multiline description +that spans multiple lines. +""" +edition = "2021" diff --git a/cargo/tests/cargo_build_script/build_script_cargo_toml_env/build.rs b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/build.rs new file mode 100644 index 0000000000..f10281690c --- /dev/null +++ b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/build.rs @@ -0,0 +1,26 @@ +//! Build script that reads CARGO_PKG_* env vars at runtime. +//! This tests that cargo_toml_env_vars + build_script_env_files correctly +//! provides these variables to build scripts, which is needed for crates +//! like rav1e that use the `built` crate. + +fn main() { + // Read env vars that should be set from cargo_toml_env_vars via build_script_env_files + let authors = std::env::var("CARGO_PKG_AUTHORS") + .expect("CARGO_PKG_AUTHORS should be set from Cargo.toml"); + let version = std::env::var("CARGO_PKG_VERSION") + .expect("CARGO_PKG_VERSION should be set from Cargo.toml"); + let name = + std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME should be set from Cargo.toml"); + let description = std::env::var("CARGO_PKG_DESCRIPTION") + .expect("CARGO_PKG_DESCRIPTION should be set from Cargo.toml"); + + // Pass them to rustc so the test can verify them + println!("cargo:rustc-env=TEST_AUTHORS={}", authors); + println!("cargo:rustc-env=TEST_VERSION={}", version); + println!("cargo:rustc-env=TEST_NAME={}", name); + // Escape newlines for cargo:rustc-env + println!( + "cargo:rustc-env=TEST_DESCRIPTION={}", + description.replace('\n', "\\n") + ); +} diff --git a/cargo/tests/cargo_build_script/build_script_cargo_toml_env/test_lib.rs b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/test_lib.rs new file mode 100644 index 0000000000..4f5c4caaf6 --- /dev/null +++ b/cargo/tests/cargo_build_script/build_script_cargo_toml_env/test_lib.rs @@ -0,0 +1,39 @@ +//! Tests that CARGO_PKG_* env vars from Cargo.toml are available to build.rs +//! at runtime via build_script_env_files. + +#[test] +fn check_authors_from_cargo_toml() { + // Authors should be colon-separated as Cargo does + let authors = env!("TEST_AUTHORS"); + assert!( + authors.contains("Test Author"), + "Expected 'Test Author' in authors, got: {}", + authors + ); + assert!( + authors.contains("Another Author"), + "Expected 'Another Author' in authors, got: {}", + authors + ); +} + +#[test] +fn check_version_from_cargo_toml() { + assert_eq!("1.2.3", env!("TEST_VERSION")); +} + +#[test] +fn check_name_from_cargo_toml() { + assert_eq!("test_cargo_toml_env", env!("TEST_NAME")); +} + +#[test] +fn check_description_from_cargo_toml() { + let desc = env!("TEST_DESCRIPTION"); + // Description should contain the multiline content (with escaped newlines) + assert!( + desc.contains("multiline description"), + "Expected 'multiline description' in description, got: {}", + desc + ); +} diff --git a/cargo/tests/cargo_build_script/build_script_env_files/BUILD.bazel b/cargo/tests/cargo_build_script/build_script_env_files/BUILD.bazel index 7c332fe059..668c79e7c1 100644 --- a/cargo/tests/cargo_build_script/build_script_env_files/BUILD.bazel +++ b/cargo/tests/cargo_build_script/build_script_env_files/BUILD.bazel @@ -28,4 +28,5 @@ rust_library( rust_test( name = "consume_build_script", crate = ":test_lib", + size = "small", ) diff --git a/crate_universe/src/rendering.rs b/crate_universe/src/rendering.rs index a96e65b454..1ec8cb5f72 100644 --- a/crate_universe/src/rendering.rs +++ b/crate_universe/src/rendering.rs @@ -641,6 +641,12 @@ impl Renderer { .unwrap_or_default(), platforms, ), + build_script_env_files: SelectSet::new( + attrs + .map(|attrs| attrs.build_script_env_files.clone()) + .unwrap_or_default(), + platforms, + ), rustc_flags: SelectList::new( // In most cases, warnings in 3rd party crates are not // interesting as they're out of the control of consumers. The @@ -1335,6 +1341,66 @@ mod test { assert!(build_file_content.contains("name = \"_bs\"")); } + #[test] + fn render_cargo_build_script_with_env_files() { + let mut context = Context::default(); + let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); + + let attrs = BuildScriptAttributes { + build_script_env_files: Select::from_value(BTreeSet::from(["env_file.txt".to_owned()])), + ..BuildScriptAttributes::default() + }; + + context.crates.insert( + crate_id.clone(), + CrateContext { + name: crate_id.name, + version: crate_id.version, + package_url: None, + repository: None, + targets: BTreeSet::from([Rule::BuildScript(TargetAttributes { + crate_name: "build_script_build".to_owned(), + crate_root: Some("build.rs".to_owned()), + ..TargetAttributes::default() + })]), + library_target_name: None, + common_attrs: CommonAttributes::default(), + build_script_attrs: Some(attrs), + license: None, + license_ids: BTreeSet::default(), + license_file: None, + additive_build_file_content: None, + disable_pipelining: false, + extra_aliased_targets: BTreeMap::default(), + alias_rule: None, + override_targets: BTreeMap::default(), + }, + ); + + let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); + let output = renderer.render(&context, None).unwrap(); + + let build_file_content = output + .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) + .unwrap(); + + assert!( + build_file_content.contains("cargo_build_script("), + "Expected cargo_build_script in:\n{}", + build_file_content + ); + assert!( + build_file_content.contains("build_script_env_files = ["), + "Expected build_script_env_files in:\n{}", + build_file_content + ); + assert!( + build_file_content.contains("\"env_file.txt\""), + "Expected env_file.txt in build_script_env_files:\n{}", + build_file_content + ); + } + #[test] fn render_proc_macro() { let mut context = Context::default(); diff --git a/crate_universe/src/utils/starlark.rs b/crate_universe/src/utils/starlark.rs index 14ff5f8c23..849a497ae3 100644 --- a/crate_universe/src/utils/starlark.rs +++ b/crate_universe/src/utils/starlark.rs @@ -98,6 +98,8 @@ pub(crate) struct CargoBuildScript { pub(crate) aliases: SelectDict, #[serde(skip_serializing_if = "SelectDict::is_empty")] pub(crate) build_script_env: SelectDict, + #[serde(skip_serializing_if = "SelectSet::is_empty")] + pub(crate) build_script_env_files: SelectSet, #[serde(skip_serializing_if = "Data::is_empty")] pub(crate) compile_data: Data, #[serde(skip_serializing_if = "SelectDict::is_empty")]