Auto merge of #106000 - nikic:lld-build, r=Mark-Simulacrum

Make LLD build forward-compatible with LLVM 16

Switch to using the cmake module instead of llvm-config. I believe this also removes the need for llvm-config-wrapper.
This commit is contained in:
bors 2022-12-22 13:59:15 +00:00
commit e5e4eef02d
7 changed files with 58 additions and 99 deletions

View file

@ -29,11 +29,6 @@ name = "sccache-plus-cl"
path = "bin/sccache-plus-cl.rs" path = "bin/sccache-plus-cl.rs"
test = false test = false
[[bin]]
name = "llvm-config-wrapper"
path = "bin/llvm-config-wrapper.rs"
test = false
[dependencies] [dependencies]
cmake = "0.1.38" cmake = "0.1.38"
fd-lock = "3.0.8" fd-lock = "3.0.8"

View file

@ -1,24 +0,0 @@
// The sheer existence of this file is an awful hack. See the comments in
// `src/bootstrap/native.rs` for why this is needed when compiling LLD.
use std::env;
use std::io::{self, Write};
use std::process::{self, Command, Stdio};
fn main() {
let real_llvm_config = env::var_os("LLVM_CONFIG_REAL").unwrap();
let mut cmd = Command::new(real_llvm_config);
cmd.args(env::args().skip(1)).stderr(Stdio::piped());
let output = cmd.output().expect("failed to spawn llvm-config");
let mut stdout = String::from_utf8_lossy(&output.stdout);
if let Ok(to_replace) = env::var("LLVM_CONFIG_SHIM_REPLACE") {
if let Ok(replace_with) = env::var("LLVM_CONFIG_SHIM_REPLACE_WITH") {
stdout = stdout.replace(&to_replace, &replace_with).into();
}
}
print!("{}", stdout.replace("\\", "/"));
io::stdout().flush().unwrap();
process::exit(output.status.code().unwrap_or(1));
}

View file

@ -1068,7 +1068,7 @@ pub fn rustdoc_cmd(&self, compiler: Compiler) -> Command {
/// check build or dry-run, where there's no need to build all of LLVM. /// check build or dry-run, where there's no need to build all of LLVM.
fn llvm_config(&self, target: TargetSelection) -> Option<PathBuf> { fn llvm_config(&self, target: TargetSelection) -> Option<PathBuf> {
if self.config.llvm_enabled() && self.kind != Kind::Check && !self.config.dry_run() { if self.config.llvm_enabled() && self.kind != Kind::Check && !self.config.dry_run() {
let llvm_config = self.ensure(native::Llvm { target }); let native::LlvmResult { llvm_config, .. } = self.ensure(native::Llvm { target });
if llvm_config.is_file() { if llvm_config.is_file() {
return Some(llvm_config); return Some(llvm_config);
} }

View file

@ -805,7 +805,7 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS
if builder.is_rust_llvm(target) { if builder.is_rust_llvm(target) {
cargo.env("LLVM_RUSTLLVM", "1"); cargo.env("LLVM_RUSTLLVM", "1");
} }
let llvm_config = builder.ensure(native::Llvm { target }); let native::LlvmResult { llvm_config, .. } = builder.ensure(native::Llvm { target });
cargo.env("LLVM_CONFIG", &llvm_config); cargo.env("LLVM_CONFIG", &llvm_config);
if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) { if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) {
cargo.env("CFG_LLVM_ROOT", s); cargo.env("CFG_LLVM_ROOT", s);
@ -1341,9 +1341,10 @@ fn run(self, builder: &Builder<'_>) -> Compiler {
} }
if builder.config.rust_codegen_backends.contains(&INTERNER.intern_str("llvm")) { if builder.config.rust_codegen_backends.contains(&INTERNER.intern_str("llvm")) {
let llvm_config_bin = builder.ensure(native::Llvm { target: target_compiler.host }); let native::LlvmResult { llvm_config, .. } =
builder.ensure(native::Llvm { target: target_compiler.host });
if !builder.config.dry_run() { if !builder.config.dry_run() {
let llvm_bin_dir = output(Command::new(llvm_config_bin).arg("--bindir")); let llvm_bin_dir = output(Command::new(llvm_config).arg("--bindir"));
let llvm_bin_dir = Path::new(llvm_bin_dir.trim()); let llvm_bin_dir = Path::new(llvm_bin_dir.trim());
// Since we've already built the LLVM tools, install them to the sysroot. // Since we've already built the LLVM tools, install them to the sysroot.

View file

@ -24,6 +24,7 @@
use crate::compile; use crate::compile;
use crate::config::TargetSelection; use crate::config::TargetSelection;
use crate::doc::DocumentationFormat; use crate::doc::DocumentationFormat;
use crate::native;
use crate::tarball::{GeneratedTarball, OverlayKind, Tarball}; use crate::tarball::{GeneratedTarball, OverlayKind, Tarball};
use crate::tool::{self, Tool}; use crate::tool::{self, Tool};
use crate::util::{exe, is_dylib, output, t, timeit}; use crate::util::{exe, is_dylib, output, t, timeit};
@ -1927,7 +1928,9 @@ fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir
builder.install(&llvm_dylib_path, dst_libdir, 0o644); builder.install(&llvm_dylib_path, dst_libdir, 0o644);
} }
!builder.config.dry_run() !builder.config.dry_run()
} else if let Ok(llvm_config) = crate::native::prebuilt_llvm_config(builder, target) { } else if let Ok(native::LlvmResult { llvm_config, .. }) =
native::prebuilt_llvm_config(builder, target)
{
let mut cmd = Command::new(llvm_config); let mut cmd = Command::new(llvm_config);
cmd.arg("--libfiles"); cmd.arg("--libfiles");
builder.verbose(&format!("running {:?}", cmd)); builder.verbose(&format!("running {:?}", cmd));
@ -2137,7 +2140,7 @@ fn run(self, builder: &Builder<'_>) -> Option<GeneratedTarball> {
let tarball = Tarball::new(builder, "bootstrap", &target.triple); let tarball = Tarball::new(builder, "bootstrap", &target.triple);
let bootstrap_outdir = &builder.bootstrap_out; let bootstrap_outdir = &builder.bootstrap_out;
for file in &["bootstrap", "llvm-config-wrapper", "rustc", "rustdoc", "sccache-plus-cl"] { for file in &["bootstrap", "rustc", "rustdoc", "sccache-plus-cl"] {
tarball.add_file(bootstrap_outdir.join(exe(file, target)), "bootstrap/bin", 0o755); tarball.add_file(bootstrap_outdir.join(exe(file, target)), "bootstrap/bin", 0o755);
} }

View file

@ -24,9 +24,18 @@
use crate::util::{self, exe, output, t, up_to_date}; use crate::util::{self, exe, output, t, up_to_date};
use crate::{CLang, GitRepo}; use crate::{CLang, GitRepo};
#[derive(Clone)]
pub struct LlvmResult {
/// Path to llvm-config binary.
/// NB: This is always the host llvm-config!
pub llvm_config: PathBuf,
/// Path to LLVM cmake directory for the target.
pub llvm_cmake_dir: PathBuf,
}
pub struct Meta { pub struct Meta {
stamp: HashStamp, stamp: HashStamp,
build_llvm_config: PathBuf, res: LlvmResult,
out_dir: PathBuf, out_dir: PathBuf,
root: String, root: String,
} }
@ -64,7 +73,7 @@ fn push_all(&mut self, s: impl AsRef<OsStr>) {
pub fn prebuilt_llvm_config( pub fn prebuilt_llvm_config(
builder: &Builder<'_>, builder: &Builder<'_>,
target: TargetSelection, target: TargetSelection,
) -> Result<PathBuf, Meta> { ) -> Result<LlvmResult, Meta> {
builder.config.maybe_download_ci_llvm(); builder.config.maybe_download_ci_llvm();
// If we're using a custom LLVM bail out here, but we can only use a // If we're using a custom LLVM bail out here, but we can only use a
@ -72,7 +81,14 @@ pub fn prebuilt_llvm_config(
if let Some(config) = builder.config.target_config.get(&target) { if let Some(config) = builder.config.target_config.get(&target) {
if let Some(ref s) = config.llvm_config { if let Some(ref s) = config.llvm_config {
check_llvm_version(builder, s); check_llvm_version(builder, s);
return Ok(s.to_path_buf()); let llvm_config = s.to_path_buf();
let mut llvm_cmake_dir = llvm_config.clone();
llvm_cmake_dir.pop();
llvm_cmake_dir.pop();
llvm_cmake_dir.push("lib");
llvm_cmake_dir.push("cmake");
llvm_cmake_dir.push("llvm");
return Ok(LlvmResult { llvm_config, llvm_cmake_dir });
} }
} }
@ -84,8 +100,9 @@ pub fn prebuilt_llvm_config(
llvm_config_ret_dir.push("build"); llvm_config_ret_dir.push("build");
} }
llvm_config_ret_dir.push("bin"); llvm_config_ret_dir.push("bin");
let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", builder.config.build)); let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", builder.config.build));
let llvm_cmake_dir = out_dir.join("lib/cmake/llvm");
let res = LlvmResult { llvm_config: build_llvm_config, llvm_cmake_dir };
let stamp = out_dir.join("llvm-finished-building"); let stamp = out_dir.join("llvm-finished-building");
let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha()); let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha());
@ -96,7 +113,7 @@ pub fn prebuilt_llvm_config(
Using a potentially stale build of LLVM; \ Using a potentially stale build of LLVM; \
This may not behave well.", This may not behave well.",
); );
return Ok(build_llvm_config); return Ok(res);
} }
if stamp.is_done() { if stamp.is_done() {
@ -110,10 +127,10 @@ pub fn prebuilt_llvm_config(
stamp.path.display() stamp.path.display()
)); ));
} }
return Ok(build_llvm_config); return Ok(res);
} }
Err(Meta { stamp, build_llvm_config, out_dir, root: root.into() }) Err(Meta { stamp, res, out_dir, root: root.into() })
} }
/// This retrieves the LLVM sha we *want* to use, according to git history. /// This retrieves the LLVM sha we *want* to use, according to git history.
@ -223,7 +240,7 @@ pub struct Llvm {
} }
impl Step for Llvm { impl Step for Llvm {
type Output = PathBuf; // path to llvm-config type Output = LlvmResult;
const ONLY_HOSTS: bool = true; const ONLY_HOSTS: bool = true;
@ -236,7 +253,7 @@ fn make_run(run: RunConfig<'_>) {
} }
/// Compile LLVM for `target`. /// Compile LLVM for `target`.
fn run(self, builder: &Builder<'_>) -> PathBuf { fn run(self, builder: &Builder<'_>) -> LlvmResult {
let target = self.target; let target = self.target;
let target_native = if self.target.starts_with("riscv") { let target_native = if self.target.starts_with("riscv") {
// RISC-V target triples in Rust is not named the same as C compiler target triples. // RISC-V target triples in Rust is not named the same as C compiler target triples.
@ -252,11 +269,10 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
target.to_string() target.to_string()
}; };
let Meta { stamp, build_llvm_config, out_dir, root } = let Meta { stamp, res, out_dir, root } = match prebuilt_llvm_config(builder, target) {
match prebuilt_llvm_config(builder, target) { Ok(p) => return p,
Ok(p) => return p, Err(m) => m,
Err(m) => m, };
};
builder.update_submodule(&Path::new("src").join("llvm-project")); builder.update_submodule(&Path::new("src").join("llvm-project"));
if builder.llvm_link_shared() && target.contains("windows") { if builder.llvm_link_shared() && target.contains("windows") {
@ -430,7 +446,8 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
// https://llvm.org/docs/HowToCrossCompileLLVM.html // https://llvm.org/docs/HowToCrossCompileLLVM.html
if target != builder.config.build { if target != builder.config.build {
let llvm_config = builder.ensure(Llvm { target: builder.config.build }); let LlvmResult { llvm_config, .. } =
builder.ensure(Llvm { target: builder.config.build });
if !builder.config.dry_run() { if !builder.config.dry_run() {
let llvm_bindir = output(Command::new(&llvm_config).arg("--bindir")); let llvm_bindir = output(Command::new(&llvm_config).arg("--bindir"));
let host_bin = Path::new(llvm_bindir.trim()); let host_bin = Path::new(llvm_bindir.trim());
@ -480,7 +497,7 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
// tools and libs on all platforms. // tools and libs on all platforms.
if builder.config.dry_run() { if builder.config.dry_run() {
return build_llvm_config; return res;
} }
cfg.build(); cfg.build();
@ -490,7 +507,7 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
// for a versioned path like libLLVM-14.dylib. Manually create a symbolic // for a versioned path like libLLVM-14.dylib. Manually create a symbolic
// link to make llvm-config happy. // link to make llvm-config happy.
if builder.llvm_link_shared() && target.contains("apple-darwin") { if builder.llvm_link_shared() && target.contains("apple-darwin") {
let mut cmd = Command::new(&build_llvm_config); let mut cmd = Command::new(&res.llvm_config);
let version = output(cmd.arg("--version")); let version = output(cmd.arg("--version"));
let major = version.split('.').next().unwrap(); let major = version.split('.').next().unwrap();
let lib_name = match llvm_version_suffix { let lib_name = match llvm_version_suffix {
@ -509,18 +526,18 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
// LLVM after a configuration change, so to rebuild it the build files have to be removed, // LLVM after a configuration change, so to rebuild it the build files have to be removed,
// which will also remove these modified files. // which will also remove these modified files.
if builder.config.llvm_bolt_profile_generate { if builder.config.llvm_bolt_profile_generate {
instrument_with_bolt_inplace(&get_built_llvm_lib_path(&build_llvm_config)); instrument_with_bolt_inplace(&get_built_llvm_lib_path(&res.llvm_config));
} }
if let Some(path) = &builder.config.llvm_bolt_profile_use { if let Some(path) = &builder.config.llvm_bolt_profile_use {
optimize_library_with_bolt_inplace( optimize_library_with_bolt_inplace(
&get_built_llvm_lib_path(&build_llvm_config), &get_built_llvm_lib_path(&res.llvm_config),
&Path::new(path), &Path::new(path),
); );
} }
t!(stamp.write()); t!(stamp.write());
build_llvm_config res
} }
} }
@ -806,7 +823,8 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
} }
let target = self.target; let target = self.target;
let llvm_config = builder.ensure(Llvm { target: self.target }); let LlvmResult { llvm_config, llvm_cmake_dir } =
builder.ensure(Llvm { target: self.target });
let out_dir = builder.lld_out(target); let out_dir = builder.lld_out(target);
let done_stamp = out_dir.join("lld-finished-building"); let done_stamp = out_dir.join("lld-finished-building");
@ -837,22 +855,6 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
configure_cmake(builder, target, &mut cfg, true, ldflags); configure_cmake(builder, target, &mut cfg, true, ldflags);
configure_llvm(builder, target, &mut cfg); configure_llvm(builder, target, &mut cfg);
// This is an awful, awful hack. Discovered when we migrated to using
// clang-cl to compile LLVM/LLD it turns out that LLD, when built out of
// tree, will execute `llvm-config --cmakedir` and then tell CMake about
// that directory for later processing. Unfortunately if this path has
// forward slashes in it (which it basically always does on Windows)
// then CMake will hit a syntax error later on as... something isn't
// escaped it seems?
//
// Instead of attempting to fix this problem in upstream CMake and/or
// LLVM/LLD we just hack around it here. This thin wrapper will take the
// output from llvm-config and replace all instances of `\` with `/` to
// ensure we don't hit the same bugs with escaping. It means that you
// can't build on a system where your paths require `\` on Windows, but
// there's probably a lot of reasons you can't do that other than this.
let llvm_config_shim = env::current_exe().unwrap().with_file_name("llvm-config-wrapper");
// Re-use the same flags as llvm to control the level of debug information // Re-use the same flags as llvm to control the level of debug information
// generated for lld. // generated for lld.
let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) { let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
@ -863,36 +865,17 @@ fn run(self, builder: &Builder<'_>) -> PathBuf {
cfg.out_dir(&out_dir) cfg.out_dir(&out_dir)
.profile(profile) .profile(profile)
.env("LLVM_CONFIG_REAL", &llvm_config) .define("LLVM_CMAKE_DIR", llvm_cmake_dir)
.define("LLVM_CONFIG_PATH", llvm_config_shim)
.define("LLVM_INCLUDE_TESTS", "OFF"); .define("LLVM_INCLUDE_TESTS", "OFF");
// While we're using this horrible workaround to shim the execution of
// llvm-config, let's just pile on more. I can't seem to figure out how
// to build LLD as a standalone project and also cross-compile it at the
// same time. It wants a natively executable `llvm-config` to learn
// about LLVM, but then it learns about all the host configuration of
// LLVM and tries to link to host LLVM libraries.
//
// To work around that we tell our shim to replace anything with the
// build target with the actual target instead. This'll break parts of
// LLD though which try to execute host tools, such as llvm-tblgen, so
// we specifically tell it where to find those. This is likely super
// brittle and will break over time. If anyone knows better how to
// cross-compile LLD it would be much appreciated to fix this!
if target != builder.config.build { if target != builder.config.build {
cfg.env("LLVM_CONFIG_SHIM_REPLACE", &builder.config.build.triple) // Use the host llvm-tblgen binary.
.env("LLVM_CONFIG_SHIM_REPLACE_WITH", &target.triple) cfg.define(
.define( "LLVM_TABLEGEN_EXE",
"LLVM_TABLEGEN_EXE", llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION),
llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION), );
);
} }
// Explicitly set C++ standard, because upstream doesn't do so
// for standalone builds.
cfg.define("CMAKE_CXX_STANDARD", "14");
cfg.build(); cfg.build();
t!(File::create(&done_stamp)); t!(File::create(&done_stamp));
@ -994,7 +977,7 @@ fn run(self, builder: &Builder<'_>) -> Self::Output {
return runtimes; return runtimes;
} }
let llvm_config = builder.ensure(Llvm { target: builder.config.build }); let LlvmResult { llvm_config, .. } = builder.ensure(Llvm { target: builder.config.build });
if builder.config.dry_run() { if builder.config.dry_run() {
return runtimes; return runtimes;
} }

View file

@ -1575,7 +1575,8 @@ fn run(self, builder: &Builder<'_>) {
let mut llvm_components_passed = false; let mut llvm_components_passed = false;
let mut copts_passed = false; let mut copts_passed = false;
if builder.config.llvm_enabled() { if builder.config.llvm_enabled() {
let llvm_config = builder.ensure(native::Llvm { target: builder.config.build }); let native::LlvmResult { llvm_config, .. } =
builder.ensure(native::Llvm { target: builder.config.build });
if !builder.config.dry_run() { if !builder.config.dry_run() {
let llvm_version = output(Command::new(&llvm_config).arg("--version")); let llvm_version = output(Command::new(&llvm_config).arg("--version"));
let llvm_components = output(Command::new(&llvm_config).arg("--components")); let llvm_components = output(Command::new(&llvm_config).arg("--components"));