diff --git a/cargo/private/cargo_build_script.bzl b/cargo/private/cargo_build_script.bzl index 5740e9d967..8a5a7251e3 100644 --- a/cargo/private/cargo_build_script.bzl +++ b/cargo/private/cargo_build_script.bzl @@ -531,7 +531,10 @@ def _cargo_build_script_impl(ctx): # Pull in env vars which may be required for the cc_toolchain to work (e.g. on OSX, the SDK version). # We hope that the linker env is sufficient for the whole cc_toolchain. if use_cc_toolchain: - cc_toolchain, feature_configuration = find_cc_toolchain(ctx) + # Build-script native objects are opaque to Bazel: no per-object LTO + # backend actions can be registered for them. Keep them as machine code + # when the Rust link uses distributed ThinLTO. + cc_toolchain, feature_configuration = find_cc_toolchain(ctx, ["thin_lto"]) else: cc_toolchain, feature_configuration = None, None linker, _, link_args, linker_env = get_linker_and_args(ctx, "bin", toolchain, cc_toolchain, feature_configuration, None) diff --git a/extensions/pyo3/private/BUILD.bazel b/extensions/pyo3/private/BUILD.bazel index 3dd80912e0..456e77bc9d 100644 --- a/extensions/pyo3/private/BUILD.bazel +++ b/extensions/pyo3/private/BUILD.bazel @@ -14,6 +14,7 @@ bzl_library( srcs = glob(["*.bzl"]), visibility = ["//extensions/pyo3:__pkg__"], deps = [ + "@rules_cc//cc/common", "@rules_python//python:defs_bzl", "@rules_rust//rust:bzl_lib", ], diff --git a/extensions/pyo3/private/pyo3_toolchain.bzl b/extensions/pyo3/private/pyo3_toolchain.bzl index ef360bfc46..f62243c70e 100644 --- a/extensions/pyo3/private/pyo3_toolchain.bzl +++ b/extensions/pyo3/private/pyo3_toolchain.bzl @@ -1,6 +1,7 @@ """PyO3 Toolchains""" load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") load("@rules_rust//rust:defs.bzl", "rust_common") PYO3_TOOLCHAIN = "//extensions/pyo3:toolchain_type" @@ -209,6 +210,9 @@ def _current_rust_pyo3_toolchain_impl(ctx): if rust_common.crate_group_info in target: providers.append(target[rust_common.crate_group_info]) + if CcInfo in target: + providers.append(target[CcInfo]) + return providers current_rust_pyo3_toolchain = rule( @@ -243,6 +247,9 @@ def _current_rust_pyo3_introspection_toolchain_impl(ctx): if rust_common.crate_group_info in target: providers.append(target[rust_common.crate_group_info]) + if CcInfo in target: + providers.append(target[CcInfo]) + return providers current_rust_pyo3_introspection_toolchain = rule( diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index a7006fb429..011a33a378 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -966,7 +966,10 @@ def _will_emit_object_file(emit): def _supports_distributed_thin_lto(ctx, toolchain, crate_info): """Whether `crate_info` can participate in distributed ThinLTO.""" return ( - crate_info.type in ("bin", "lib", "rlib") and + ( + crate_info.type in ("bin", "lib", "rlib") or + (crate_info.type == "cdylib" and toolchain.target_os == "linux") + ) and toolchain.target_arch not in ("wasm32", "wasm64") and not toolchain._bootstrapping and not is_no_std(ctx, toolchain, crate_info.is_test) @@ -975,6 +978,11 @@ def _supports_distributed_thin_lto(ctx, toolchain, crate_info): def _remove_codegen_units(flag): return None if flag.startswith("-Ccodegen-units") else flag +def _cdylib_native_link_args(file): + if file.basename.split("-")[1] == "whole": + return ["-Wl,--whole-archive", file.path, "-Wl,--no-whole-archive"] + return [file.path] + def _should_add_oso_prefix(toolchain): """Whether to add -oso_prefix to strip absolute paths from N_OSO entries. @@ -1443,6 +1451,12 @@ def construct_arguments( if linker_plugin_lto: rustc_flags.add("-Clinker-plugin-lto") + if crate_info.type in ("lib", "rlib"): + # Preserve bundled native archives as separate linker inputs when + # the Rust object is replaced by a distributed LTO backend output. + rustc_flags.add("-Zpacked-bundled-libs") + if inject_allow_features_guardrail: + rustc_flags.add("-Zallow-features=") else: rustc_flags.add_all(construct_lto_arguments(ctx, toolchain, crate_info)) _add_codegen_units_flags(toolchain, emit, rustc_flags) @@ -2011,8 +2025,9 @@ def rustc_compile( ).format(ctx.label), ) use_cc_common_link = experimental_use_cc_common_link or ( - distributed_thin_lto and crate_info.type == "bin" + distributed_thin_lto and crate_info.type in ("bin", "cdylib") ) + packed_bundled_libs = distributed_thin_lto and crate_info.type in ("lib", "rlib") scan_msvc_archive_object = ( rust_toolchain.target_abi == "msvc" and @@ -2070,8 +2085,24 @@ def rustc_compile( # Metadata is emitted by a separate -Zno-codegen action. The full action # emits the linked crate unless cc_common.link does, and emits output_o # when output_o is declared. + cdylib_export_file = None + cdylib_symbols_file = None + cdylib_native_dir = None + cdylib_native_params = None + if distributed_thin_lto and crate_info.type == "cdylib": + # Keep rustc's ELF export policy and symbol roots while delegating code + # generation and the actual shared link to the C++ toolchain. + cdylib_export_file = ctx.actions.declare_file(crate_info.output.basename + ".exports", sibling = crate_info.output) + cdylib_symbols_file = ctx.actions.declare_file(crate_info.output.basename + ".symbols.o", sibling = crate_info.output) + cdylib_native_dir = ctx.actions.declare_directory(crate_info.output.basename + ".native", sibling = crate_info.output) + cdylib_native_params = ctx.actions.declare_file(crate_info.output.basename + ".native.params", sibling = crate_info.output) + native_args = ctx.actions.args() + native_args.add_all([cdylib_native_dir], map_each = _cdylib_native_link_args) + native_args.set_param_file_format("shell") + ctx.actions.write(output = cdylib_native_params, content = native_args) + emit = [] - if not use_cc_common_link: + if not use_cc_common_link or cdylib_export_file: emit.append("link") if output_o: emit.append(("obj", output_o)) @@ -2184,6 +2215,11 @@ def rustc_compile( ) args_metadata = None + if cdylib_export_file: + args.process_wrapper_flags.add("--rustc-cdylib-export-file", cdylib_export_file) + args.process_wrapper_flags.add("--rustc-cdylib-symbols-file", cdylib_symbols_file) + args.process_wrapper_flags.add_all([cdylib_native_dir], before_each = "--rustc-cdylib-native-dir", expand_directories = False) + if build_metadata: metadata_emit = ["link"] args_metadata, _ = construct_arguments( @@ -2221,8 +2257,9 @@ def rustc_compile( # this is the final list of env vars env.update(env_from_args) - if build_metadata and inject_allow_features_guardrail: - # RUSTC_BOOTSTRAP=1 is required for -Zno-codegen on stable/beta rustc, and + if (build_metadata or packed_bundled_libs) and inject_allow_features_guardrail: + # RUSTC_BOOTSTRAP=1 is required for -Zno-codegen and + # -Zpacked-bundled-libs on stable/beta rustc, and # must be set on both the metadata and full actions for SVH compatibility # (since RUSTC_BOOTSTRAP affects the crate hash). Skipped on nightly # toolchains (where -Zno-codegen works without bootstrap) and when the @@ -2247,6 +2284,8 @@ def rustc_compile( # The action might generate extra output that we don't want to include in the `DefaultInfo` files. action_outputs = list(outputs) + if cdylib_export_file: + action_outputs.extend([cdylib_export_file, cdylib_symbols_file, cdylib_native_dir]) if output_o and output_o not in action_outputs: action_outputs.append(output_o) if rustc_output: @@ -2360,8 +2399,9 @@ def rustc_compile( # Wrap the main `.o` file into a compilation output suitable for # cc_common.link. The main `.o` file is useful in both PIC and non-PIC # modes. - cco_args["objects"] = depset([output_o]) - cco_args["pic_objects"] = depset([output_o]) + link_objects = [output_o] + ([cdylib_symbols_file] if cdylib_symbols_file else []) + cco_args["objects"] = depset(link_objects) + cco_args["pic_objects"] = depset(link_objects) if distributed_thin_lto: cco_args["lto_compilation_context"] = cc_common.create_lto_compilation_context( objects = { @@ -2457,6 +2497,8 @@ def rustc_compile( output_type = "executable" if crate_info.type == "bin" else "dynamic_library", additional_outputs = additional_linker_outputs, variables_extension = variables_extension, + additional_inputs = [cdylib_export_file, cdylib_native_params, cdylib_native_dir] if cdylib_export_file else [], + user_link_flags = ["-Wl,--version-script=" + cdylib_export_file.path, "@" + cdylib_native_params.path] if cdylib_export_file else [], ) if rust_toolchain.target_os == "linux" and cc_helper.should_create_per_object_debug_info(feature_configuration, ctx.fragments.cpp): diff --git a/test/unit/lto/lto_test_suite.bzl b/test/unit/lto/lto_test_suite.bzl index 2db05bbfa5..d35148f23c 100644 --- a/test/unit/lto/lto_test_suite.bzl +++ b/test/unit/lto/lto_test_suite.bzl @@ -6,7 +6,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") -load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_library_group", "rust_proc_macro") +load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_library_group", "rust_proc_macro", "rust_shared_library") load( "//test/unit:common.bzl", "assert_action_mnemonic", @@ -149,6 +149,8 @@ def _distributed_thin_lto_library(ctx): assert_argv_contains(env, action, "--emit=link") assert_argv_contains_prefix(env, action, "--emit=obj=") assert_argv_contains(env, action, "-Clinker-plugin-lto") + assert_argv_contains(env, action, "-Zpacked-bundled-libs") + assert_argv_contains(env, action, "-Zallow-features=") assert_argv_contains_prefix_not(env, action, "-Clto") assert_argv_contains_prefix_not(env, action, "-Cembed-bitcode") @@ -210,6 +212,30 @@ _distributed_thin_lto_binary_test = analysistest.make( config_settings = _DISTRIBUTED_THIN_LTO_CONFIG_SETTINGS, ) +def _distributed_thin_lto_cdylib(ctx): + env = analysistest.begin(ctx) + target = analysistest.target_under_test(env) + actions = _assert_distributed_thin_lto_link(env, target) + rustc_action = actions["Rustc"] + assert_argv_contains(env, rustc_action, "--crate-type=cdylib") + assert_argv_contains(env, rustc_action, "-Clinker-plugin-lto") + assert_argv_contains_prefix(env, rustc_action, "--emit=obj=") + assert_argv_contains(env, rustc_action, "--emit=link") + assert_argv_contains(env, rustc_action, "--rustc-cdylib-export-file") + assert_argv_contains_prefix_not(env, rustc_action, "-Clto") + assert_argv_contains(env, actions["CppLink"], "-shared") + for mnemonic in ["CppLTOIndexing", "CppLink"]: + assert_argv_contains_prefix(env, actions[mnemonic], "-Wl,--version-script=") + asserts.true(env, any([file.extension == "exports" for file in actions[mnemonic].inputs.to_list()])) + asserts.true(env, any([file.basename == "libdistributed_cdylib.so" for file in actions["CppLink"].outputs.to_list()])) + asserts.equals(env, ["libdistributed_cdylib.so"], [file.basename for file in target[DefaultInfo].files.to_list()]) + return analysistest.end(env) + +_distributed_thin_lto_cdylib_test = analysistest.make( + _distributed_thin_lto_cdylib, + config_settings = _DISTRIBUTED_THIN_LTO_CONFIG_SETTINGS, +) + def _distributed_thin_lto_global_allocator(ctx): env = analysistest.begin(ctx) target = analysistest.target_under_test(env) @@ -443,6 +469,45 @@ def lto_test_suite(name): ], ) + write_file( + name = "crate_cdylib", + out = "cdylib.rs", + content = [ + "extern \"C\" { fn native_add(left: usize, right: usize) -> usize; }", + "#[no_mangle]", + "pub extern \"C\" fn cdylib_add(left: usize, right: usize) -> usize {", + " unsafe { native_add(distributed_lib::add(left, right), 1) }", + "}", + "", + ], + ) + + write_file( + name = "cdylib_native_src", + out = "cdylib_native.cc", + content = [ + "#include ", + "extern \"C\" uintptr_t native_add(uintptr_t left, uintptr_t right) { return left + right; }", + "", + ], + ) + + write_file( + name = "cdylib_test_src", + out = "cdylib_test.cc", + content = [ + "#include ", + "#include ", + "extern \"C\" uintptr_t cdylib_add(uintptr_t, uintptr_t);", + "extern \"C\" uintptr_t distributed_add(uintptr_t, uintptr_t);", + "int main() {", + " return cdylib_add(2, 2) == 5 && distributed_add(2, 2) == 4", + " && dlsym(RTLD_DEFAULT, \"native_add\") == nullptr ? 0 : 1;", + "}", + "", + ], + ) + rust_library( name = "lib", srcs = [":lib.rs"], @@ -496,6 +561,29 @@ def lto_test_suite(name): tags = ["manual"], ) + cc_library( + name = "cdylib_native", + srcs = [":cdylib_native.cc"], + tags = ["manual"], + ) + + rust_shared_library( + name = "distributed_cdylib", + srcs = [":cdylib.rs"], + edition = "2021", + deps = [":cdylib_native", ":distributed_lib_group"], + tags = ["manual"], + ) + + cc_test( + name = "distributed_cdylib_runtime_test", + srcs = [":cdylib_test.cc"], + linkopts = ["-ldl"], + deps = [":distributed_cdylib"], + tags = ["manual"], + target_compatible_with = ["@platforms//os:linux"], + ) + rust_binary( name = "distributed_global_allocator_bin", srcs = [":global_allocator_bin"], @@ -589,6 +677,11 @@ def lto_test_suite(name): target_under_test = ":distributed_bin", ) + _distributed_thin_lto_cdylib_test( + name = "distributed_thin_lto_cdylib_test", + target_under_test = ":distributed_cdylib", + ) + _distributed_thin_lto_global_allocator_test( name = "distributed_thin_lto_global_allocator_test", target_under_test = ":distributed_global_allocator_bin", @@ -655,6 +748,7 @@ def lto_test_suite(name): ":lto_proc_macro_test", ":distributed_thin_lto_library_test", ":distributed_thin_lto_binary_test", + ":distributed_thin_lto_cdylib_test", ":distributed_thin_lto_global_allocator_test", ":distributed_thin_lto_cc_binary_test", ":distributed_thin_lto_shared_backends_test", diff --git a/util/process_wrapper/cdylib.rs b/util/process_wrapper/cdylib.rs new file mode 100644 index 0000000000..c06ce8632f --- /dev/null +++ b/util/process_wrapper/cdylib.rs @@ -0,0 +1,232 @@ +// Copyright 2026 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Preserve rustc's ELF link metadata and native inputs for a Bazel-owned link. + +use std::fs; +use std::io; +use std::path::Path; + +pub(crate) const EXPORT_FILE_ENV: &str = "RULES_RUST_CDYLIB_EXPORT_FILE"; +pub(crate) const SYMBOLS_FILE_ENV: &str = "RULES_RUST_CDYLIB_SYMBOLS_FILE"; +pub(crate) const NATIVE_DIR_ENV: &str = "RULES_RUST_CDYLIB_NATIVE_DIR"; + +pub(crate) fn capture_exports( + output: &Path, + symbols_output: &Path, + native_dir: &Path, + args: Vec, +) -> io::Result<()> { + let mut expanded = Vec::new(); + for arg in args { + if let Some(path) = arg.strip_prefix('@') { + // rustc's GNU response files have one argument per line and escape + // spaces and backslashes with a backslash. + for line in fs::read_to_string(path)?.lines() { + let mut chars = line.chars(); + let mut value = String::new(); + while let Some(c) = chars.next() { + value.push(if c == '\\' { + chars.next().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "truncated linker escape") + })? + } else { + c + }); + } + expanded.push(value); + } + } else { + expanded.push(arg); + } + } + let scripts: Vec<_> = expanded + .iter() + .filter_map(|arg| arg.strip_prefix("-Wl,--version-script=")) + .collect(); + if scripts.len() != 1 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "expected one rustc cdylib export script, found {}", + scripts.len() + ), + )); + } + let temp_dir = Path::new(scripts[0]).parent().unwrap(); + // rustc's synthetic object roots dependency exports and weak language + // items. The version script alone does not pull them out of lazy archives. + let symbols: Vec<_> = expanded + .iter() + .filter(|arg| { + let path = Path::new(arg); + path.parent() == Some(temp_dir) + && path.file_name().is_some_and(|name| name == "symbols.o") + }) + .collect(); + if symbols.len() != 1 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("expected one rustc symbols object, found {}", symbols.len()), + )); + } + fs::copy(scripts[0], output)?; + fs::copy(symbols[0], symbols_output)?; + fs::create_dir_all(native_dir)?; + // A build script attached directly to the cdylib has not been packed into + // an rlib. rustc passes its static libraries by name under OUT_DIR. + let out_dir = std::env::var_os("OUT_DIR").and_then(|path| fs::canonicalize(path).ok()); + let mut local_search_dirs = Vec::new(); + for (index, arg) in expanded.iter().enumerate() { + let dir = if arg == "-L" { + expanded.get(index + 1).map(String::as_str) + } else { + arg.strip_prefix("-L") + }; + if let (Some(dir), Some(out_dir)) = (dir, &out_dir) { + if let Ok(dir) = fs::canonicalize(dir) { + if dir.starts_with(out_dir) { + local_search_dirs.push(dir); + } + } + } + } + let mut whole_archive = false; + let mut static_linkage = false; + for (index, arg) in expanded.iter().enumerate() { + match arg.as_str() { + "-Wl,--whole-archive" => whole_archive = true, + "-Wl,--no-whole-archive" => whole_archive = false, + "-Wl,-Bstatic" | "-Bstatic" => static_linkage = true, + "-Wl,-Bdynamic" | "-Bdynamic" => static_linkage = false, + _ => {} + } + let path = Path::new(arg); + let archive = + if path.parent() == Some(temp_dir) && path.extension().is_some_and(|ext| ext == "a") { + Some(path.to_owned()) + } else if static_linkage { + arg.strip_prefix("-l").and_then(|name| { + let filename = name + .strip_prefix(':') + .map(str::to_owned) + .unwrap_or_else(|| format!("lib{name}.a")); + local_search_dirs + .iter() + .map(|dir| dir.join(&filename)) + .find(|path| path.is_file()) + }) + } else { + None + }; + if let Some(path) = archive { + let kind = if whole_archive { "whole" } else { "lazy" }; + let name = format!( + "{index:08}-{kind}-{}", + path.file_name().unwrap().to_string_lossy() + ); + fs::copy(&path, native_dir.join(name))?; + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + + static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + + struct Scratch(std::path::PathBuf); + + impl Scratch { + fn new() -> Self { + let root = std::env::var_os("TEST_TMPDIR") + .map(std::path::PathBuf::from) + .unwrap_or_else(std::env::temp_dir); + let path = root.join(format!( + "cdylib-{}-{}", + std::process::id(), + NEXT_ID.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir(&path).unwrap(); + Self(path) + } + } + + impl Drop for Scratch { + fn drop(&mut self) { + fs::remove_dir_all(&self.0).unwrap(); + } + } + + #[test] + fn captures_compiler_policy_verbatim_from_response_file() { + let scratch = Scratch::new(); + let script = scratch.0.join("exports with spaces"); + let response = scratch.0.join("linker-arguments"); + let output = scratch.0.join("captured.exports"); + let symbols = scratch.0.join("symbols.o"); + let symbols_output = scratch.0.join("captured.symbols.o"); + fs::write(&symbols, b"compiler-generated object").unwrap(); + let policy = "{ global: local_c_api; dependency_c_api; local: *; };\n"; + fs::write(&script, policy).unwrap(); + let argument = format!("-Wl,--version-script={}", script.display()); + let escaped = argument.replace('\\', "\\\\").replace(' ', "\\ "); + fs::write( + &response, + format!("{escaped}\n{}\n-shared\n", symbols.display()), + ) + .unwrap(); + capture_exports( + &output, + &symbols_output, + &scratch.0.join("native"), + vec![format!("@{}", response.display())], + ) + .unwrap(); + assert_eq!(fs::read_to_string(output).unwrap(), policy); + assert_eq!( + fs::read(symbols_output).unwrap(), + fs::read(symbols).unwrap() + ); + } + + #[test] + fn rejects_missing_or_ambiguous_export_policy() { + let scratch = Scratch::new(); + let output = scratch.0.join("captured.exports"); + for args in [ + vec!["-shared".to_owned()], + vec![ + "-Wl,--version-script=first".to_owned(), + "-Wl,--version-script=second".to_owned(), + ], + ] { + assert_eq!( + capture_exports( + &output, + &scratch.0.join("captured.symbols.o"), + &scratch.0.join("native"), + args + ) + .unwrap_err() + .kind(), + io::ErrorKind::InvalidData + ); + assert!(!output.exists()); + } + } +} diff --git a/util/process_wrapper/main.rs b/util/process_wrapper/main.rs index d4a4d49900..da4c43e502 100644 --- a/util/process_wrapper/main.rs +++ b/util/process_wrapper/main.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod cdylib; mod flags; mod options; mod output; @@ -380,6 +381,19 @@ fn check_output_for_working_dir( } fn main() -> Result<(), ProcessWrapperError> { + if let Some(output) = std::env::var_os(cdylib::EXPORT_FILE_ENV) { + let symbols_output = std::env::var_os(cdylib::SYMBOLS_FILE_ENV) + .ok_or_else(|| ProcessWrapperError("missing cdylib symbols output".to_owned()))?; + let native_dir = std::env::var_os(cdylib::NATIVE_DIR_ENV) + .ok_or_else(|| ProcessWrapperError("missing cdylib native directory".to_owned()))?; + return cdylib::capture_exports( + std::path::Path::new(&output), + std::path::Path::new(&symbols_output), + std::path::Path::new(&native_dir), + std::env::args().skip(1).collect(), + ) + .map_err(|e| ProcessWrapperError(format!("failed to capture cdylib exports: {e}"))); + } let opts = options().map_err(|e| ProcessWrapperError(e.to_string()))?; let (child_arguments, dep_dir_cleanup) = @@ -403,6 +417,25 @@ fn main() -> Result<(), ProcessWrapperError> { Stdio::inherit() }) .stderr(Stdio::piped()); + if let Some(export_file) = opts.rustc_cdylib_export_file { + // Run rustc's link preparation to obtain its exact export policy, + // including C exports defined by dependencies. The recursive linker + // invocation preserves its link inputs; cc_common.link produces the .so. + let linker = std::env::current_exe() + .map_err(|e| ProcessWrapperError(format!("failed to locate process wrapper: {e}")))?; + let symbols_file = opts + .rustc_cdylib_symbols_file + .ok_or_else(|| ProcessWrapperError("missing --rustc-cdylib-symbols-file".to_owned()))?; + let native_dir = opts + .rustc_cdylib_native_dir + .ok_or_else(|| ProcessWrapperError("missing --rustc-cdylib-native-dir".to_owned()))?; + command + .arg(format!("-Clinker={}", linker.display())) + .arg("-Clinker-flavor=gcc") + .env(cdylib::EXPORT_FILE_ENV, export_file) + .env(cdylib::SYMBOLS_FILE_ENV, symbols_file) + .env(cdylib::NATIVE_DIR_ENV, native_dir); + } debug_log!("{:#?}", command); let mut child = command .spawn() diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs index 2f24e670a6..8a8d00aba9 100644 --- a/util/process_wrapper/options.rs +++ b/util/process_wrapper/options.rs @@ -36,6 +36,10 @@ pub(crate) struct Options { pub(crate) child_environment: HashMap, // Compiler outputs checked for an embedded absolute working directory. pub(crate) check_output_for_working_dir: Vec, + // Capture rustc's ELF cdylib export script instead of performing its link. + pub(crate) rustc_cdylib_export_file: Option, + pub(crate) rustc_cdylib_symbols_file: Option, + pub(crate) rustc_cdylib_native_dir: Option, // If set, create the specified file after the child process successfully // terminated its execution. pub(crate) touch_file: Option, @@ -65,6 +69,9 @@ pub(crate) fn options() -> Result { let mut out_dir_raw = None; let mut arg_file_raw = None; let mut check_output_for_working_dir_raw = None; + let mut rustc_cdylib_export_file = None; + let mut rustc_cdylib_symbols_file = None; + let mut rustc_cdylib_native_dir = None; let mut touch_file = None; let mut copy_output_raw = None; let mut stdout_file = None; @@ -102,6 +109,21 @@ pub(crate) fn options() -> Result { "Compiler output(s) checked for an embedded absolute working directory.", &mut check_output_for_working_dir_raw, ); + flags.define_flag( + "--rustc-cdylib-export-file", + "Preserve rustc's ELF export script for a separate cc_common.link action.", + &mut rustc_cdylib_export_file, + ); + flags.define_flag( + "--rustc-cdylib-symbols-file", + "Preserve rustc's synthetic object that roots dependency exports.", + &mut rustc_cdylib_symbols_file, + ); + flags.define_flag( + "--rustc-cdylib-native-dir", + "Preserve native archives rustc extracts from dependency rlibs.", + &mut rustc_cdylib_native_dir, + ); flags.define_flag( "--touch-file", "Create this file after the child process runs successfully.", @@ -302,6 +324,9 @@ pub(crate) fn options() -> Result { working_dir: current_dir, child_environment: vars, check_output_for_working_dir: check_output_for_working_dir_raw.unwrap_or_default(), + rustc_cdylib_export_file, + rustc_cdylib_symbols_file, + rustc_cdylib_native_dir, touch_file, copy_output, stdout_file,