diff --git a/Cargo.lock b/Cargo.lock index 6b3a714920bb4..fd8e8fb1c5f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1978,6 +1978,10 @@ dependencies = [ "walkdir", ] +[[package]] +name = "lld-wrapper" +version = "0.1.0" + [[package]] name = "lock_api" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 3822da2ccd5e4..c724cf694f7a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "src/tools/jsondocck", "src/tools/html-checker", "src/tools/bump-stage0", + "src/tools/lld-wrapper", ] exclude = [ diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index ae234fb1dc729..5e3433f8d59de 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -1136,14 +1136,15 @@ impl Step for Assemble { // for `-Z gcc-ld=lld` let gcc_ld_dir = libdir_bin.join("gcc-ld"); t!(fs::create_dir(&gcc_ld_dir)); - builder.copy( - &lld_install.join("bin").join(&src_exe), - &gcc_ld_dir.join(exe("ld", target_compiler.host)), - ); - builder.copy( - &lld_install.join("bin").join(&src_exe), - &gcc_ld_dir.join(exe("ld64", target_compiler.host)), - ); + let bootstrap_compiler = builder.compiler(0, builder.config.build); + for flavor in ["ld", "ld64"] { + let lld_wrapper_exe = builder.ensure(crate::tool::LldWrapper { + compiler: bootstrap_compiler, + target: target_compiler.host, + flavor_feature: flavor, + }); + builder.copy(&lld_wrapper_exe, &gcc_ld_dir.join(exe(flavor, target_compiler.host))); + } } // Similarly, copy `llvm-dwp` into libdir for Split DWARF. Only copy it when the LLVM diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs index 7c1bb1a91481b..d4875cfe1b066 100644 --- a/src/bootstrap/dist.rs +++ b/src/bootstrap/dist.rs @@ -409,11 +409,14 @@ impl Step for Rustc { let rust_lld = exe("rust-lld", compiler.host); builder.copy(&src_dir.join(&rust_lld), &dst_dir.join(&rust_lld)); // for `-Z gcc-ld=lld` - let gcc_lld_dir = dst_dir.join("gcc-ld"); - t!(fs::create_dir(&gcc_lld_dir)); - builder.copy(&src_dir.join(&rust_lld), &gcc_lld_dir.join(exe("ld", compiler.host))); - builder - .copy(&src_dir.join(&rust_lld), &gcc_lld_dir.join(exe("ld64", compiler.host))); + let gcc_lld_src_dir = src_dir.join("gcc-ld"); + let gcc_lld_dst_dir = dst_dir.join("gcc-ld"); + t!(fs::create_dir(&gcc_lld_dst_dir)); + for flavor in ["ld", "ld64"] { + let exe_name = exe(flavor, compiler.host); + builder + .copy(&gcc_lld_src_dir.join(&exe_name), &gcc_lld_dst_dir.join(&exe_name)); + } } // Copy over llvm-dwp if it's there diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs index c035894638538..bc5aa2837028f 100644 --- a/src/bootstrap/tool.rs +++ b/src/bootstrap/tool.rs @@ -664,6 +664,38 @@ impl Step for Cargo { } } +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct LldWrapper { + pub compiler: Compiler, + pub target: TargetSelection, + pub flavor_feature: &'static str, +} + +impl Step for LldWrapper { + type Output = PathBuf; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.never() + } + + fn run(self, builder: &Builder<'_>) -> PathBuf { + let src_exe = builder + .ensure(ToolBuild { + compiler: self.compiler, + target: self.target, + tool: "lld-wrapper", + mode: Mode::ToolBootstrap, + path: "src/tools/lld-wrapper", + is_optional_tool: false, + source_type: SourceType::InTree, + extra_features: vec![self.flavor_feature.to_owned()], + }) + .expect("expected to build -- essential tool"); + + src_exe + } +} + macro_rules! tool_extended { (($sel:ident, $builder:ident), $($name:ident, diff --git a/src/tools/lld-wrapper/Cargo.toml b/src/tools/lld-wrapper/Cargo.toml new file mode 100644 index 0000000000000..66a586fd6c35e --- /dev/null +++ b/src/tools/lld-wrapper/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "lld-wrapper" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] + +[features] +ld = [] +ld64 = [] \ No newline at end of file diff --git a/src/tools/lld-wrapper/src/main.rs b/src/tools/lld-wrapper/src/main.rs new file mode 100644 index 0000000000000..1601bf1b34e9c --- /dev/null +++ b/src/tools/lld-wrapper/src/main.rs @@ -0,0 +1,125 @@ +//! Script to invoke the bundled rust-lld with the correct flavor. The flavor is selected by +//! feature. +//! +//! lld supports multiple command line interfaces. If `-flavor ` are passed as the first +//! two arguments the `` command line interface is used to process the remaining arguments. +//! If no `-flavor` argument is present the flavor is determined by the executable name. +//! +//! In Rust with `-Z gcc-ld=lld` we have gcc or clang invoke rust-lld. Since there is no way to +//! make gcc/clang pass `-flavor ` as the first two arguments in the linker invocation +//! and since Windows does not support symbolic links for files this wrapper is used in place of a +//! symblic link. It execs `../rust-lld -flavor ld` if the feature `ld` is enabled and +//! `../rust-lld -flavor ld64` if `ld64` is enabled. On Windows it spawns a `..\rust-lld.exe` +//! child process. + +#[cfg(not(any(feature = "ld", feature = "ld64")))] +compile_error!("One of the features ld and ld64 must be enabled."); + +#[cfg(all(feature = "ld", feature = "ld64"))] +compile_error!("Only one of the feature ld or ld64 can be enabled."); + +#[cfg(feature = "ld")] +const FLAVOR: &str = "ld"; + +#[cfg(feature = "ld64")] +const FLAVOR: &str = "ld64"; + +use std::env; +use std::fmt::Display; +use std::path::{Path, PathBuf}; +use std::process; + +trait ResultExt { + fn unwrap_or_exit_with(self, context: &str) -> T; +} + +impl ResultExt for Result +where + E: Display, +{ + fn unwrap_or_exit_with(self, context: &str) -> T { + match self { + Ok(t) => t, + Err(e) => { + eprintln!("lld-wrapper: {}: {}", context, e); + process::exit(1); + } + } + } +} + +trait OptionExt { + fn unwrap_or_exit_with(self, context: &str) -> T; +} + +impl OptionExt for Option { + fn unwrap_or_exit_with(self, context: &str) -> T { + match self { + Some(t) => t, + None => { + eprintln!("lld-wrapper: {}", context); + process::exit(1); + } + } + } +} + +/// Returns the path to rust-lld in the parent directory. +/// +/// Exits if the parent directory cannot be determined. +fn get_rust_lld_path(current_exe_path: &Path) -> PathBuf { + let mut rust_lld_exe_name = "rust-lld".to_owned(); + rust_lld_exe_name.push_str(env::consts::EXE_SUFFIX); + let mut rust_lld_path = current_exe_path + .parent() + .unwrap_or_exit_with("directory containing current executable could not be determined") + .parent() + .unwrap_or_exit_with("parent directory could not be determined") + .to_owned(); + rust_lld_path.push(rust_lld_exe_name); + rust_lld_path +} + +/// Returns the command for invoking rust-lld with the correct flavor. +/// +/// Exits on error. +fn get_rust_lld_command(current_exe_path: &Path) -> process::Command { + let rust_lld_path = get_rust_lld_path(current_exe_path); + let mut command = process::Command::new(rust_lld_path); + command.arg("-flavor"); + command.arg(FLAVOR); + command.args(env::args_os().skip(1)); + command +} + +#[cfg(unix)] +fn exec_lld(mut command: process::Command) { + use std::os::unix::prelude::CommandExt; + Result::<(), _>::Err(command.exec()).unwrap_or_exit_with("could not exec rust-lld"); + unreachable!("lld-wrapper: after exec without error"); +} + +#[cfg(not(unix))] +fn exec_lld(mut command: process::Command) { + // Windows has no exec(), spawn a child process and wait for it + let exit_status = command.status().unwrap_or_exit_with("error running rust-lld child process"); + if !exit_status.success() { + match exit_status.code() { + Some(code) => { + // return the original lld exit code + process::exit(code) + } + None => { + eprintln!("lld-wrapper: rust-lld child process exited with error: {}", exit_status,); + process::exit(1); + } + } + } +} + +fn main() { + let current_exe_path = + env::current_exe().unwrap_or_exit_with("could not get the path of the current executable"); + + exec_lld(get_rust_lld_command(current_exe_path.as_ref())); +}