diff --git a/cargo/private/cargo_build_script.bzl b/cargo/private/cargo_build_script.bzl index a14eafe07e..c1efa17f79 100644 --- a/cargo/private/cargo_build_script.bzl +++ b/cargo/private/cargo_build_script.bzl @@ -335,6 +335,8 @@ def _cargo_build_script_impl(ctx): flags_out = ctx.actions.declare_file(ctx.label.name + ".flags") link_flags = ctx.actions.declare_file(ctx.label.name + ".linkflags") link_search_paths = ctx.actions.declare_file(ctx.label.name + ".linksearchpaths") # rustc-link-search, propagated from transitive dependencies + cdylib_link_flags = ctx.actions.declare_file(ctx.label.name + ".cdyliblinkflags") # rustc-cdylib-link-arg, applied to cdylib crates (incl. transitively) + bin_link_flags = ctx.actions.declare_file(ctx.label.name + ".binlinkflags") # rustc-link-arg-bin[s], applied to binary crates compilation_mode_opt_level = get_compilation_mode_opts(ctx, toolchain).opt_level script_data = [] @@ -549,6 +551,8 @@ def _cargo_build_script_impl(ctx): args.add(flags_out, format = "--flags_out=%s") args.add(link_flags, format = "--link_flags=%s") args.add(link_search_paths, format = "--link_search_paths=%s") + args.add(cdylib_link_flags, format = "--cdylib_link_flags=%s") + args.add(bin_link_flags, format = "--bin_link_flags=%s") args.add(dep_env_out, format = "--dep_env_out=%s") args.add(ctx.attr.rundir, format = "--rundir=%s") @@ -613,6 +617,8 @@ def _cargo_build_script_impl(ctx): flags_out, link_flags, link_search_paths, + cdylib_link_flags, + bin_link_flags, dep_env_out, runfiles_dir, ] + extra_output, @@ -640,6 +646,8 @@ def _cargo_build_script_impl(ctx): flags = flags_out, linker_flags = link_flags, link_search_paths = link_search_paths, + cdylib_link_flags = cdylib_link_flags, + bin_link_flags = bin_link_flags, compile_data = depset([runfiles_dir] + extra_output, transitive = script_data), ), OutputGroupInfo( diff --git a/cargo/private/cargo_build_script_runner/bin.rs b/cargo/private/cargo_build_script_runner/bin.rs index 84b80cf919..52abc089ec 100644 --- a/cargo/private/cargo_build_script_runner/bin.rs +++ b/cargo/private/cargo_build_script_runner/bin.rs @@ -43,6 +43,8 @@ fn run_buildrs() -> Result<(), String> { compile_flags_file, link_flags_file, link_search_paths_file, + cdylib_link_flags_file, + bin_link_flags_file, output_dep_env_path, stdout_path, stderr_path, @@ -215,6 +217,8 @@ fn run_buildrs() -> Result<(), String> { compile_flags, link_flags, link_search_paths, + cdylib_link_flags, + bin_link_flags, } = BuildScriptOutput::outputs_to_flags( &buildrs_outputs, &exec_root.to_string_lossy(), @@ -231,6 +235,10 @@ fn run_buildrs() -> Result<(), String> { link_search_paths_file, e ) }); + write(&cdylib_link_flags_file, cdylib_link_flags.as_bytes()) + .unwrap_or_else(|e| panic!("Unable to write file {:?}: {:#?}", cdylib_link_flags_file, e)); + write(&bin_link_flags_file, bin_link_flags.as_bytes()) + .unwrap_or_else(|e| panic!("Unable to write file {:?}: {:#?}", bin_link_flags_file, e)); if !exec_root_links.is_empty() { for link in exec_root_links { @@ -397,6 +405,8 @@ struct Args { compile_flags_file: String, link_flags_file: String, link_search_paths_file: String, + cdylib_link_flags_file: String, + bin_link_flags_file: String, output_dep_env_path: String, stdout_path: Option, stderr_path: Option, @@ -420,6 +430,10 @@ impl Args { Err("Argument `link_flags_file` not provided".to_owned()); let mut link_search_paths_file: Result = Err("Argument `link_search_paths_file` not provided".to_owned()); + let mut cdylib_link_flags_file: Result = + Err("Argument `cdylib_link_flags_file` not provided".to_owned()); + let mut bin_link_flags_file: Result = + Err("Argument `bin_link_flags_file` not provided".to_owned()); let mut output_dep_env_path: Result = Err("Argument `output_dep_env_path` not provided".to_owned()); let mut stdout_path = None; @@ -444,6 +458,10 @@ impl Args { link_flags_file = Ok(arg.split_off("--link_flags=".len())); } else if arg.starts_with("--link_search_paths=") { link_search_paths_file = Ok(arg.split_off("--link_search_paths=".len())); + } else if arg.starts_with("--cdylib_link_flags=") { + cdylib_link_flags_file = Ok(arg.split_off("--cdylib_link_flags=".len())); + } else if arg.starts_with("--bin_link_flags=") { + bin_link_flags_file = Ok(arg.split_off("--bin_link_flags=".len())); } else if arg.starts_with("--dep_env_out=") { output_dep_env_path = Ok(arg.split_off("--dep_env_out=".len())); } else if arg.starts_with("--stdout=") { @@ -469,6 +487,8 @@ impl Args { compile_flags_file: compile_flags_file.unwrap(), link_flags_file: link_flags_file.unwrap(), link_search_paths_file: link_search_paths_file.unwrap(), + cdylib_link_flags_file: cdylib_link_flags_file.unwrap(), + bin_link_flags_file: bin_link_flags_file.unwrap(), output_dep_env_path: output_dep_env_path.unwrap(), stdout_path, stderr_path, diff --git a/cargo/private/cargo_build_script_runner/lib.rs b/cargo/private/cargo_build_script_runner/lib.rs index 5faee0a1f8..a1decf0dec 100644 --- a/cargo/private/cargo_build_script_runner/lib.rs +++ b/cargo/private/cargo_build_script_runner/lib.rs @@ -24,6 +24,13 @@ pub struct CompileAndLinkFlags { pub compile_flags: String, pub link_flags: String, pub link_search_paths: String, + /// `-Clink-arg`s that apply only when the crate is built as a cdylib + /// (`cargo::rustc-cdylib-link-arg`). The consuming rule gates these on the + /// crate type, and propagates them transitively to cdylibs (matching cargo). + pub cdylib_link_flags: String, + /// `-Clink-arg`s that apply only when the crate is built as a binary + /// (`cargo::rustc-link-arg-bin[s]`). Gated on the crate type by the rule. + pub bin_link_flags: String, } /// Enum containing all the considered return value from the script @@ -39,6 +46,10 @@ pub enum BuildScriptOutput { Flags(String), /// cargo::rustc-link-arg LinkArg(String), + /// cargo::rustc-cdylib-link-arg + CdylibLinkArg(String), + /// cargo::rustc-link-arg-bin / cargo::rustc-link-arg-bins + BinLinkArg(String), /// cargo::rustc-env Env(String), /// cargo::VAR=VALUE @@ -100,16 +111,17 @@ impl BuildScriptOutput { 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. - // cargo::rustc-link-arg-bins=FLAG – Passes custom flags to a linker for binaries. - eprint!( - "Warning: build script returned unsupported directive `{}`", - split[0] - ); - None - } + // cargo::rustc-cdylib-link-arg=FLAG — linker flag applied only when the + // crate is built as a cdylib. + "rustc-cdylib-link-arg" => Some(BuildScriptOutput::CdylibLinkArg(param)), + // cargo::rustc-link-arg-bins=FLAG — linker flag applied to binary targets. + "rustc-link-arg-bins" => Some(BuildScriptOutput::BinLinkArg(param)), + // cargo::rustc-link-arg-bin=BIN=FLAG — linker flag for the binary BIN. + // Bazel builds one target per crate, so the BIN selector is dropped and + // the flag applies to the owning crate's binary. + "rustc-link-arg-bin" => Some(BuildScriptOutput::BinLinkArg( + param.split_once('=').map_or(param.clone(), |(_bin, flag)| flag.to_owned()), + )), _ => { // cargo::KEY=VALUE — Metadata, used by links scripts. Some(BuildScriptOutput::DepEnv(format!( @@ -208,12 +220,16 @@ impl BuildScriptOutput { let mut compile_flags = Vec::new(); let mut link_flags = Vec::new(); let mut link_search_paths = Vec::new(); + let mut cdylib_link_flags = Vec::new(); + let mut bin_link_flags = Vec::new(); for flag in outputs { match flag { BuildScriptOutput::Cfg(e) => compile_flags.push(format!("--cfg={e}")), BuildScriptOutput::Flags(e) => compile_flags.push(e.to_owned()), BuildScriptOutput::LinkArg(e) => compile_flags.push(format!("-Clink-arg={e}")), + BuildScriptOutput::CdylibLinkArg(e) => cdylib_link_flags.push(format!("-Clink-arg={e}")), + BuildScriptOutput::BinLinkArg(e) => bin_link_flags.push(format!("-Clink-arg={e}")), BuildScriptOutput::LinkLib(e) => link_flags.push(format!("-l{e}")), BuildScriptOutput::LinkSearch(e) => link_search_paths.push(format!("-L{e}")), _ => {} @@ -228,6 +244,8 @@ impl BuildScriptOutput { exec_root, out_dir, ), + cdylib_link_flags: Self::redact_flags(&cdylib_link_flags.join("\n"), exec_root, out_dir), + bin_link_flags: Self::redact_flags(&bin_link_flags.join("\n"), exec_root, out_dir), } } @@ -336,6 +354,8 @@ mod tests { .to_owned(), link_flags: "-lsdfsdf".to_owned(), link_search_paths: "-L${pwd}/bleh".to_owned(), + cdylib_link_flags: String::new(), + bin_link_flags: String::new(), } ); } @@ -489,6 +509,8 @@ cargo::rustc-link-search=/abs/exec_root/other/path link_flags: "".to_owned(), link_search_paths: "-L${pwd}/${bazel-out/cfg/bin/pkg/_bs.out_dir}\n-L${pwd}/other/path".to_owned(), + cdylib_link_flags: String::new(), + bin_link_flags: String::new(), } ); } diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl index a531884d19..d17120a16b 100644 --- a/rust/private/providers.bzl +++ b/rust/private/providers.bzl @@ -79,6 +79,8 @@ CrateGroupInfo = provider( BuildInfo = provider( doc = "A provider containing `rustc` build settings for a given Crate.", fields = { + "bin_link_flags": "Optional[File]: file of `-Clink-arg`s to pass to rustc only when the crate is built as a binary (`cargo::rustc-link-arg-bin[s]`).", + "cdylib_link_flags": "Optional[File]: file of `-Clink-arg`s to pass to rustc only when the crate is built as a cdylib (`cargo::rustc-cdylib-link-arg`); propagated transitively to cdylibs.", "compile_data": "Depset[File]: Compile data provided by the build script that was not copied into `out_dir`.", "dep_env": "Optional[File]: extra build script environment variables to be set to direct dependencies.", "flags": "Optional[File]: file containing additional flags to pass to rustc", diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index c278b5f9f1..b29127a119 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -831,6 +831,7 @@ def collect_inputs( build_script_compile_inputs, out_dir, build_env_file, build_flags_files = _process_build_scripts( build_info = build_info, dep_info = dep_info, + crate_type = crate_info.type, include_link_flags = include_link_flags, include_transitive_data = not toolchain._incompatible_do_not_include_transitive_data_in_compile_inputs, ) @@ -2253,6 +2254,7 @@ def add_edition_flags(args, crate): def _process_build_scripts( build_info, dep_info, + crate_type, include_link_flags = True, include_transitive_data = False): """Gathers the outputs from a target's `cargo_build_script` action. @@ -2297,6 +2299,12 @@ def _process_build_scripts( build_flags_files.append(build_info.linker_flags) direct_inputs.append(build_info.linker_flags) + # `cargo::rustc-link-arg-bin[s]` apply only to binary targets, and (like + # cargo) only from the crate's own build script — not transitively. + if crate_type == "bin" and getattr(build_info, "bin_link_flags", None): + build_flags_files.append(build_info.bin_link_flags) + direct_inputs.append(build_info.bin_link_flags) + transitive_inputs.append(build_info.compile_data) # We include transitive dep build_infos because cargo build scripts may generate files which get linked into the final binary. @@ -2306,6 +2314,13 @@ def _process_build_scripts( direct_inputs.append(dep_build_info.out_dir) transitive_inputs.append(dep_build_info.compile_data) + # `cargo::rustc-cdylib-link-arg` applies only to cdylib targets, and (like + # cargo, see rust-lang/cargo#9562) propagates transitively to them. The + # crate's own build script is included here via `transitive_build_infos`. + if crate_type in ("cdylib", "dylib") and getattr(dep_build_info, "cdylib_link_flags", None): + build_flags_files.append(dep_build_info.cdylib_link_flags) + direct_inputs.append(dep_build_info.cdylib_link_flags) + out_dir_compile_inputs = depset( direct_inputs, transitive = transitive_inputs,