diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 9f369c5917..e0a4c87466 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -10,6 +10,7 @@ min_rust_version_shell_commands: &min_rust_version_shell_commands - sed -i 's|^rust\.toolchain(|rust.toolchain(versions = ["1.85.0"],\n|' MODULE.bazel nightly_flags: &nightly_flags - "--//rust/toolchain/channel=nightly" + - "--//rust/settings:experimental_compile_rustdoc_tests=True" nightly_aspects_flags: &nightly_aspects_flags - "--//rust/toolchain/channel=nightly" - "--config=rustfmt" diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl index f302bab743..0bc43cdbae 100644 --- a/rust/private/rustdoc.bzl +++ b/rust/private/rustdoc.bzl @@ -55,7 +55,8 @@ def rustdoc_compile_action( lints_info = None, output = None, rustdoc_flags = [], - is_test = False): + is_test = False, + force_depend_on_objects = None): """Create a struct of information needed for a `rustdoc` compile action based on crate passed to the rustdoc rule. Args: @@ -66,22 +67,27 @@ def rustdoc_compile_action( output (File, optional): An optional output a `rustdoc` action is intended to produce. rustdoc_flags (list, optional): A list of `rustdoc` specific flags. is_test (bool, optional): If True, the action will be configured for `rust_doc_test` targets + force_depend_on_objects (bool, optional): If set, overrides is_test for controlling whether + to depend on .rlib files instead of .rmeta. Defaults to is_test. Returns: struct: A struct of some `ctx.actions.run` arguments. """ + if force_depend_on_objects == None: + force_depend_on_objects = is_test # If an output was provided, ensure it's used in rustdoc arguments if output: - rustdoc_flags = [ - "--output", - output.path, - ] + rustdoc_flags + rustdoc_flags.add_all( + [output], + before_each = "--output", + expand_directories = False, + ) # Specify rustc flags for lints, if they were provided. lint_files = [] if lints_info: - rustdoc_flags = rustdoc_flags + lints_info.rustdoc_lint_flags + rustdoc_flags.add_all(lints_info.rustdoc_lint_flags) lint_files = lint_files + lints_info.rustdoc_lint_files # Collect HTML customization files @@ -115,8 +121,7 @@ def rustdoc_compile_action( dep_info = dep_info, build_info = build_info, lint_files = lint_files, - # If this is a rustdoc test, we need to depend on rlibs rather than .rmeta. - force_depend_on_objects = is_test, + force_depend_on_objects = force_depend_on_objects, include_link_flags = False, ) @@ -147,7 +152,7 @@ def rustdoc_compile_action( remap_path_prefix = None, add_flags_for_binary = True, include_link_flags = False, - force_depend_on_objects = is_test, + force_depend_on_objects = force_depend_on_objects, skip_expanding_rustc_env = True, ) @@ -168,6 +173,7 @@ def rustdoc_compile_action( inputs = all_inputs, env = env, arguments = args.all, + supports_path_mapping = args.supports_path_mapping, tools = [toolchain.rust_doc], ) @@ -214,27 +220,28 @@ def _rust_doc_impl(ctx): output_dir = ctx.actions.declare_directory("{}.rustdoc".format(ctx.label.name)) - # Add the current crate as an extern for the compile action - rustdoc_flags = [ - "--extern", - "{}={}".format(crate_info.name, crate_info.output.path), - ] + rustdoc_flags = ctx.actions.args() + rustdoc_flags.add_all( + [crate_info.output], + format_each = "--extern={}=%s".format(crate_info.name), + expand_directories = False, + ) # Add HTML customization flags if attributes are provided if ctx.attr.html_in_header: - rustdoc_flags.extend(["--html-in-header", ctx.file.html_in_header.path]) + rustdoc_flags.add("--html-in-header", ctx.file.html_in_header) if ctx.attr.html_before_content: - rustdoc_flags.extend(["--html-before-content", ctx.file.html_before_content.path]) + rustdoc_flags.add("--html-before-content", ctx.file.html_before_content) if ctx.attr.html_after_content: - rustdoc_flags.extend(["--html-after-content", ctx.file.html_after_content.path]) + rustdoc_flags.add("--html-after-content", ctx.file.html_after_content) # Add markdown CSS files if provided for css_file in ctx.files.markdown_css: - rustdoc_flags.extend(["--markdown-css", css_file.path]) + rustdoc_flags.add(["--markdown-css", css_file]) - rustdoc_flags.extend(ctx.attr.rustdoc_flags) + rustdoc_flags.add_all(ctx.attr.rustdoc_flags) action = rustdoc_compile_action( ctx = ctx, diff --git a/rust/private/rustdoc/BUILD.bazel b/rust/private/rustdoc/BUILD.bazel index ee2067a87d..cf603d6025 100644 --- a/rust/private/rustdoc/BUILD.bazel +++ b/rust/private/rustdoc/BUILD.bazel @@ -2,6 +2,15 @@ load("//rust/private:rust.bzl", "rust_binary") package(default_visibility = ["//visibility:public"]) +rust_binary( + name = "rustdoc_test_runner", + srcs = ["rustdoc_test_runner.rs"], + edition = "2018", + deps = [ + "//rust/runfiles", + ], +) + rust_binary( name = "rustdoc_test_writer", srcs = ["rustdoc_test_writer.rs"], @@ -10,3 +19,9 @@ rust_binary( "//rust/runfiles", ], ) + +rust_binary( + name = "rustdoc_compile_wrapper", + srcs = ["rustdoc_compile_wrapper.rs"], + edition = "2018", +) diff --git a/rust/private/rustdoc/rustdoc_compile_wrapper.rs b/rust/private/rustdoc/rustdoc_compile_wrapper.rs new file mode 100644 index 0000000000..e98d01ba82 --- /dev/null +++ b/rust/private/rustdoc/rustdoc_compile_wrapper.rs @@ -0,0 +1,165 @@ +use std::collections::BTreeSet; +use std::env; +use std::fs; +use std::io::{self, BufRead, Read, Write}; +use std::process::{exit, Command, Stdio}; +use std::thread; + +struct WrapperArgs { + test_metadata_path: Option, + child_args: Vec, +} + +fn parse_wrapper_args() -> WrapperArgs { + let mut test_metadata_path: Option = None; + let mut child_args: Vec = Vec::new(); + let mut past_separator = false; + + let mut args_iter = env::args().skip(1); + while let Some(arg) = args_iter.next() { + if past_separator { + child_args.push(arg); + } else if arg == "--" { + past_separator = true; + } else if arg == "--test-metadata" { + test_metadata_path = args_iter.next(); + } else { + eprintln!("Unknown wrapper flag: {}", arg); + exit(1); + } + } + + WrapperArgs { + test_metadata_path, + child_args, + } +} + +fn parse_test_names(stdout: &str) -> Vec { + stdout + .lines() + .filter_map(|line| { + let rest = line.strip_prefix("test ")?; + let name = rest.rsplit_once(" ... ")?.0; + Some(name.to_string()) + }) + .collect() +} + +fn mangle_test_name(human_name: &str) -> String { + if let Some((file_and_item, line_part)) = human_name.rsplit_once(" (line ") { + if let Some(line_num) = line_part.strip_suffix(')') { + if let Some((file_path, _)) = file_and_item.split_once(" - ") { + let mangled: String = file_path + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect(); + return format!("{}_{}_0", mangled, line_num); + } + } + } + human_name + .chars() + .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }) + .collect() +} + +fn write_test_metadata(path: &str, stdout: &str) { + let names = parse_test_names(stdout); + let entries: BTreeSet<(String, &str)> = names + .iter() + .map(|name| (mangle_test_name(name), name.as_str())) + .collect(); + + let mut content = String::new(); + for (mangled, human) in &entries { + content.push_str(mangled); + content.push('='); + content.push_str(human); + content.push('\n'); + } + let _ = fs::write(path, content); +} + +fn main() { + let debug = env::var_os("RULES_RUST_RUSTDOC_DEBUG").is_some(); + let args = parse_wrapper_args(); + + if args.child_args.is_empty() { + eprintln!("Usage: rustdoc_compile_wrapper [--test-metadata FILE] -- [args...]"); + exit(1); + } + + let mut child = Command::new(&args.child_args[0]) + .args(&args.child_args[1..]) + .stdout(if debug { + Stdio::inherit() + } else { + Stdio::piped() + }) + .stderr(Stdio::piped()) + .spawn() + .unwrap_or_else(|e| { + eprintln!("Failed to spawn {}: {}", args.child_args[0], e); + exit(1); + }); + + let child_stdout = child.stdout.take(); + let child_stderr = child.stderr.take().unwrap(); + + let stdout_handle = thread::spawn(move || { + let mut buf = Vec::new(); + if let Some(mut reader) = child_stdout { + let _ = reader.read_to_end(&mut buf); + } + buf + }); + + let stderr_handle = thread::spawn(move || { + let reader = io::BufReader::new(child_stderr); + let mut stderr = io::stderr().lock(); + let mut has_warning = false; + for line in reader.split(b'\n') { + let line = match line { + Ok(l) => l, + Err(_) => break, + }; + if !has_warning && line_has_warning(&line) { + has_warning = true; + } + let _ = stderr.write_all(&line); + let _ = stderr.write_all(b"\n"); + } + has_warning + }); + + let stdout_buf = stdout_handle.join().unwrap_or_default(); + let has_warning = stderr_handle.join().unwrap_or(false); + + let status = child.wait().unwrap_or_else(|e| { + eprintln!("Failed to wait for child process: {}", e); + exit(1); + }); + + if let Some(ref path) = args.test_metadata_path { + let stdout_str = String::from_utf8_lossy(&stdout_buf); + write_test_metadata(path, &stdout_str); + } + + let code = status.code().unwrap_or(1); + if !debug && (code != 0 || has_warning) && !stdout_buf.is_empty() { + let _ = io::stderr().write_all(&stdout_buf); + } + + exit(code); +} + +fn line_has_warning(line: &[u8]) -> bool { + contains_subslice(line, b"warning:") +} + +fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool { + haystack + .windows(needle.len()) + .any(|window| window == needle) +} diff --git a/rust/private/rustdoc/rustdoc_test_runner.rs b/rust/private/rustdoc/rustdoc_test_runner.rs new file mode 100644 index 0000000000..c3065006a7 --- /dev/null +++ b/rust/private/rustdoc/rustdoc_test_runner.rs @@ -0,0 +1,194 @@ +//! Runs pre-compiled rustdoc test binaries from a `--persist-doctests` directory +//! and reconstructs unified output matching `rustdoc --test`'s format. + +use std::collections::HashMap; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Instant; + +use runfiles::rlocation; +use runfiles::Runfiles; + +const ARGS_ENV_VAR: &str = "RUSTDOC_TEST_RUNNER_ARGS"; +const METADATA_ENV_VAR: &str = "RUSTDOC_TEST_METADATA"; + +fn find_test_binaries(dir: &Path) -> Vec { + let mut bins = Vec::new(); + collect_rust_out(dir, &mut bins); + bins.sort(); + bins +} + +fn collect_rust_out(dir: &Path, bins: &mut Vec) { + let entries = match fs::read_dir(dir) { + Ok(entries) => entries, + Err(_) => return, + }; + + for entry in entries.flatten() { + let path = entry.path(); + if path.is_dir() { + collect_rust_out(&path, bins); + } else if path.file_name().is_some_and(|n| n == "rust_out") { + bins.push(path); + } + } +} + +fn load_test_name_map(r: &Runfiles) -> HashMap { + env::var(METADATA_ENV_VAR) + .ok() + .and_then(|rloc| rlocation!(r, &rloc)) + .and_then(|path| fs::read_to_string(path).ok()) + .map(|content| { + content + .lines() + .filter_map(|line| { + let (mangled, human) = line.split_once('=')?; + Some((mangled.to_string(), human.to_string())) + }) + .collect() + }) + .unwrap_or_default() +} + +fn test_name_for_bin(bin: &Path, name_map: &HashMap) -> String { + let dir_name = bin + .parent() + .and_then(|p| p.file_name()) + .and_then(|n| n.to_str()) + .unwrap_or("unknown"); + + name_map + .get(dir_name) + .cloned() + .unwrap_or_else(|| dir_name.to_string()) +} + +struct TestResult { + name: String, + passed: bool, + exit_code: i32, + stdout: String, + stderr: String, +} + +fn run_test_binary(bin: &Path, extra_args: &[String], name: String) -> TestResult { + let output = Command::new(bin) + .args(extra_args) + .output() + .unwrap_or_else(|e| panic!("Failed to execute {}: {}", bin.display(), e)); + + TestResult { + name, + passed: output.status.success(), + exit_code: output.status.code().unwrap_or(1), + stdout: String::from_utf8_lossy(&output.stdout).into_owned(), + stderr: String::from_utf8_lossy(&output.stderr).into_owned(), + } +} + +fn main() { + let r = Runfiles::create().unwrap(); + + let doctest_dir_rlocation = env::var(ARGS_ENV_VAR) + .unwrap_or_else(|_| panic!("{} environment variable not set", ARGS_ENV_VAR)); + + let doctest_dir = rlocation!(r, &doctest_dir_rlocation) + .unwrap_or_else(|| panic!("Failed to locate doctests dir: {}", doctest_dir_rlocation)); + + let name_map = load_test_name_map(&r); + + let extra_args: Vec = env::args().skip(1).collect(); + let bins = find_test_binaries(&doctest_dir); + + let start = Instant::now(); + let mut results: Vec = Vec::new(); + + for bin in &bins { + let name = test_name_for_bin(bin, &name_map); + results.push(run_test_binary(bin, &extra_args, name)); + } + + let elapsed = start.elapsed(); + let mut passed = 0usize; + let mut failed = 0usize; + let mut failed_results: Vec<&TestResult> = Vec::new(); + + for result in &results { + if result.passed { + passed += 1; + } else { + failed += 1; + failed_results.push(result); + } + } + + let total = passed + failed; + + println!(); + println!( + "running {} test{}", + total, + if total == 1 { "" } else { "s" } + ); + for result in &results { + let status = if result.passed { "ok" } else { "FAILED" }; + println!("test {} ... {}", result.name, status); + } + + if !failed_results.is_empty() { + println!(); + println!("failures:"); + + for result in &failed_results { + println!(); + println!("---- {} stdout ----", result.name); + + if !result.stdout.trim().is_empty() { + println!("{}", result.stdout.trim()); + println!(); + } + + println!( + "Test executable failed (exit status: {}).", + result.exit_code + ); + println!(); + println!("stderr:"); + println!(); + + if !result.stderr.trim().is_empty() { + println!("{}", result.stderr.trim()); + } + + println!(); + } + + println!(); + println!("failures:"); + + let mut sorted_names: Vec<&str> = failed_results.iter().map(|r| r.name.as_str()).collect(); + sorted_names.sort(); + + for name in &sorted_names { + println!(" {}", name); + } + } + + println!(); + if failed > 0 { + println!( + "test result: FAILED. {} passed; {} failed; 0 ignored; 0 measured; 0 filtered out; finished in {:.2}s", + passed, failed, elapsed.as_secs_f64() + ); + std::process::exit(1); + } else { + println!( + "test result: ok. {} passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in {:.2}s", + passed, elapsed.as_secs_f64() + ); + } +} diff --git a/rust/private/rustdoc_test.bzl b/rust/private/rustdoc_test.bzl index 522c048cd1..58ef9809b0 100644 --- a/rust/private/rustdoc_test.bzl +++ b/rust/private/rustdoc_test.bzl @@ -97,23 +97,20 @@ def _construct_writer_arguments(ctx, test_runner, opt_test_params, action, crate return (writer_args, action.env) -def _rust_doc_test_impl(ctx): - """The implementation for the `rust_doc_test` rule +def _create_crate_info(ctx): + """Create a CrateInfo for doc test compilation. Args: ctx (ctx): The rule's context object Returns: - list: A list containing a DefaultInfo provider + CrateInfo: A provider configured for doc test compilation """ - - toolchain = find_toolchain(ctx) - crate = ctx.attr.crate[rust_common.crate_info] deps = transform_deps(ctx.attr.deps) proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps) - crate_info = rust_common.create_crate_info( + return rust_common.create_crate_info( name = crate.name, type = crate.type, root = crate.root, @@ -132,6 +129,102 @@ def _rust_doc_test_impl(ctx): owner = ctx.label, ) +def _rlocationpath(file, workspace_name): + if file.short_path.startswith("../"): + return file.short_path[len("../"):] + return "{}/{}".format(workspace_name, file.short_path) + +def _compiled_rust_doc_test_impl(ctx, toolchain, crate_info): + """Implementation for rust_doc_test using pre-compiled doc test binaries. + + Compiles doc tests during the build phase using `rustdoc --test --no-run` + and runs them via `rustdoc_test_runner` at test time. + + Args: + ctx (ctx): The rule's context object + toolchain: The rust toolchain + crate_info (CrateInfo): The crate info for doc test compilation + + Returns: + list: A list of providers + """ + doctest_dir = ctx.actions.declare_directory(ctx.label.name + ".doctests") + test_metadata = ctx.actions.declare_file(ctx.label.name + ".doctest_metadata") + + rustdoc_flags = ctx.actions.args() + rustdoc_flags.add_all( + [crate_info.output], + format_each = "--extern={}=%s".format(crate_info.name), + ) + rustdoc_flags.add("--test") + rustdoc_flags.add("--no-run") + rustdoc_flags.add("-Zunstable-options") + rustdoc_flags.add("--persist-doctests") + rustdoc_flags.add_all([doctest_dir], expand_directories = False) + rustdoc_flags.add_all(ctx.attr.rustdoc_flags) + + action = rustdoc_compile_action( + ctx = ctx, + toolchain = toolchain, + crate_info = crate_info, + rustdoc_flags = rustdoc_flags, + is_test = False, + force_depend_on_objects = True, + ) + + wrapper_args = ctx.actions.args() + wrapper_args.add("--test-metadata", test_metadata) + wrapper_args.add("--") + wrapper_args.add(action.executable) + + ctx.actions.run( + mnemonic = "RustdocTestCompile", + progress_message = "RustdocTestCompile {}".format(ctx.attr.crate.label), + outputs = [doctest_dir, test_metadata], + executable = ctx.executable._rustdoc_compile_wrapper, + inputs = action.inputs, + env = action.env, + arguments = [wrapper_args] + action.arguments, + tools = action.tools + [action.executable], + toolchain = Label("//rust:toolchain_type"), + execution_requirements = {"supports-path-mapping": ""} if action.supports_path_mapping else None, + ) + + test_runner = ctx.actions.declare_file(ctx.label.name) + ctx.actions.symlink( + output = test_runner, + target_file = ctx.executable._test_runner_bin, + is_executable = True, + ) + + return [ + DefaultInfo( + files = depset([test_runner]), + runfiles = ctx.runfiles( + files = [doctest_dir, test_metadata, ctx.executable._test_runner_bin], + transitive_files = action.inputs, + ), + executable = test_runner, + ), + RunEnvironmentInfo( + environment = { + "RUSTDOC_TEST_METADATA": _rlocationpath(test_metadata, ctx.workspace_name), + "RUSTDOC_TEST_RUNNER_ARGS": _rlocationpath(doctest_dir, ctx.workspace_name), + }, + ), + ] + +def _legacy_rust_doc_test_impl(ctx, toolchain, crate_info): + """Existing implementation for rust_doc_test using a generated test runner script. + + Args: + ctx (ctx): The rule's context object + toolchain: The rust toolchain + crate_info (CrateInfo): The crate info for doc test compilation + + Returns: + list: A list containing a DefaultInfo provider + """ if toolchain.target_os == "windows": test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.bat") else: @@ -190,6 +283,28 @@ def _rust_doc_test_impl(ctx): executable = test_runner, )] +def _rust_doc_test_impl(ctx): + """The implementation for the `rust_doc_test` rule + + Args: + ctx (ctx): The rule's context object + + Returns: + list: A list containing a DefaultInfo provider + """ + toolchain = find_toolchain(ctx) + crate_info = _create_crate_info(ctx) + + use_compiled_doctest = ( + toolchain._experimental_compile_rustdoc_tests and + toolchain.channel == "nightly" + ) + + if use_compiled_doctest: + return _compiled_rust_doc_test_impl(ctx, toolchain, crate_info) + else: + return _legacy_rust_doc_test_impl(ctx, toolchain, crate_info) + rust_doc_test = rule( implementation = _rust_doc_test_impl, attrs = { @@ -239,6 +354,18 @@ rust_doc_test = rule( default = Label("//util/process_wrapper"), executable = True, ), + "_rustdoc_compile_wrapper": attr.label( + doc = "A wrapper that suppresses stdout on success for compiled doc test actions.", + cfg = "exec", + default = Label("//rust/private/rustdoc:rustdoc_compile_wrapper"), + executable = True, + ), + "_test_runner_bin": attr.label( + doc = "A binary used for running compiled doc test binaries.", + cfg = "exec", + default = Label("//rust/private/rustdoc:rustdoc_test_runner"), + executable = True, + ), "_test_writer": attr.label( doc = "A binary used for writing script for use as the test executable.", cfg = "exec", diff --git a/rust/settings/BUILD.bazel b/rust/settings/BUILD.bazel index d2b84debcc..36c949d70e 100644 --- a/rust/settings/BUILD.bazel +++ b/rust/settings/BUILD.bazel @@ -12,6 +12,7 @@ load( "collect_cfgs", "default_allocator_library", "error_format", + "experimental_compile_rustdoc_tests", "experimental_link_std_dylib", "experimental_per_crate_rustc_flag", "experimental_use_allocator_libraries_with_mangled_symbols", @@ -82,6 +83,8 @@ error_format() clippy_error_format() +experimental_compile_rustdoc_tests() + experimental_link_std_dylib() experimental_per_crate_rustc_flag() diff --git a/rust/settings/settings.bzl b/rust/settings/settings.bzl index 2769272dc5..7dca4a2dd2 100644 --- a/rust/settings/settings.bzl +++ b/rust/settings/settings.bzl @@ -250,6 +250,21 @@ def experimental_use_coverage_metadata_files(): build_setting_default = True, ) +def experimental_compile_rustdoc_tests(): + """A flag to control whether `rust_doc_test` compiles doc tests during the build phase. + + When enabled and the toolchain channel is "nightly", `rust_doc_test` will compile \ + doc test binaries using `rustdoc --test --no-run` during `bazel build` and only \ + execute them during `bazel test`. This requires a nightly Rust toolchain because \ + `--no-run` depends on `-Zunstable-options`. + + When enabled with a non-nightly toolchain, the existing behavior is silently used. + """ + bool_flag( + name = "experimental_compile_rustdoc_tests", + build_setting_default = False, + ) + def toolchain_generated_sysroot(): """A flag to set rustc --sysroot flag to the sysroot generated by rust_toolchain.""" bool_flag( diff --git a/rust/toolchain.bzl b/rust/toolchain.bzl index c62263daf7..eae49b0085 100644 --- a/rust/toolchain.bzl +++ b/rust/toolchain.bzl @@ -642,6 +642,7 @@ def _rust_toolchain_impl(ctx): _experimental_link_std_dylib = _experimental_link_std_dylib(ctx), _experimental_use_cc_common_link = _experimental_use_cc_common_link(ctx), _experimental_use_global_allocator = experimental_use_global_allocator, + _experimental_compile_rustdoc_tests = ctx.attr._experimental_compile_rustdoc_tests[BuildSettingInfo].value, _experimental_use_coverage_metadata_files = ctx.attr._experimental_use_coverage_metadata_files[BuildSettingInfo].value, _toolchain_generated_sysroot = ctx.attr._toolchain_generated_sysroot[BuildSettingInfo].value, _incompatible_do_not_include_data_in_compile_data = ctx.attr._incompatible_do_not_include_data_in_compile_data[IncompatibleFlagInfo].enabled, @@ -879,6 +880,9 @@ rust_toolchain = rule( "_codegen_units": attr.label( default = Label("//rust/settings:codegen_units"), ), + "_experimental_compile_rustdoc_tests": attr.label( + default = Label("//rust/settings:experimental_compile_rustdoc_tests"), + ), "_experimental_use_allocator_libraries_with_mangled_symbols_setting": attr.label( default = Label("//rust/settings:experimental_use_allocator_libraries_with_mangled_symbols"), providers = [BuildSettingInfo],