1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
|
use std::env;
use std::ffi::{OsStr, OsString};
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
const OPTIONAL_COMPONENTS: &[&str] = &[
"x86",
"arm",
"aarch64",
"amdgpu",
"avr",
"loongarch",
"m68k",
"csky",
"mips",
"powerpc",
"systemz",
"jsbackend",
"webassembly",
"msp430",
"sparc",
"nvptx",
"hexagon",
"riscv",
"xtensa",
"bpf",
];
const REQUIRED_COMPONENTS: &[&str] =
&["ipo", "bitreader", "bitwriter", "linker", "asmparser", "lto", "coverage", "instrumentation"];
fn detect_llvm_link() -> (&'static str, &'static str) {
// Force the link mode we want, preferring static by default, but
// possibly overridden by `configure --enable-llvm-link-shared`.
if tracked_env_var_os("LLVM_LINK_SHARED").is_some() {
("dylib", "--link-shared")
} else {
("static", "--link-static")
}
}
// Because Cargo adds the compiler's dylib path to our library search path, llvm-config may
// break: the dylib path for the compiler, as of this writing, contains a copy of the LLVM
// shared library, which means that when our freshly built llvm-config goes to load it's
// associated LLVM, it actually loads the compiler's LLVM. In particular when building the first
// compiler (i.e., in stage 0) that's a problem, as the compiler's LLVM is likely different from
// the one we want to use. As such, we restore the environment to what bootstrap saw. This isn't
// perfect -- we might actually want to see something from Cargo's added library paths -- but
// for now it works.
fn restore_library_path() {
let key = tracked_env_var_os("REAL_LIBRARY_PATH_VAR").expect("REAL_LIBRARY_PATH_VAR");
if let Some(env) = tracked_env_var_os("REAL_LIBRARY_PATH") {
env::set_var(&key, env);
} else {
env::remove_var(&key);
}
}
/// Reads an environment variable and adds it to dependencies.
/// Supposed to be used for all variables except those set for build scripts by cargo
/// <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>
fn tracked_env_var_os<K: AsRef<OsStr> + Display>(key: K) -> Option<OsString> {
println!("cargo:rerun-if-env-changed={key}");
env::var_os(key)
}
fn rerun_if_changed_anything_in_dir(dir: &Path) {
let mut stack = dir
.read_dir()
.unwrap()
.map(|e| e.unwrap())
.filter(|e| &*e.file_name() != ".git")
.collect::<Vec<_>>();
while let Some(entry) = stack.pop() {
let path = entry.path();
if entry.file_type().unwrap().is_dir() {
stack.extend(path.read_dir().unwrap().map(|e| e.unwrap()));
} else {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
#[track_caller]
fn output(cmd: &mut Command) -> String {
let output = match cmd.stderr(Stdio::inherit()).output() {
Ok(status) => status,
Err(e) => {
println!("\n\nfailed to execute command: {cmd:?}\nerror: {e}\n\n");
std::process::exit(1);
}
};
if !output.status.success() {
panic!(
"command did not execute successfully: {:?}\n\
expected success, got: {}",
cmd, output.status
);
}
String::from_utf8(output.stdout).unwrap()
}
fn main() {
for component in REQUIRED_COMPONENTS.iter().chain(OPTIONAL_COMPONENTS.iter()) {
println!("cargo:rustc-check-cfg=cfg(llvm_component,values(\"{component}\"))");
}
if tracked_env_var_os("RUST_CHECK").is_some() {
// If we're just running `check`, there's no need for LLVM to be built.
return;
}
restore_library_path();
let llvm_config =
PathBuf::from(tracked_env_var_os("LLVM_CONFIG").expect("LLVM_CONFIG was not set"));
println!("cargo:rerun-if-changed={}", llvm_config.display());
// Test whether we're cross-compiling LLVM. This is a pretty rare case
// currently where we're producing an LLVM for a different platform than
// what this build script is currently running on.
//
// In that case, there's no guarantee that we can actually run the target,
// so the build system works around this by giving us the LLVM_CONFIG for
// the host platform. This only really works if the host LLVM and target
// LLVM are compiled the same way, but for us that's typically the case.
//
// We *want* detect this cross compiling situation by asking llvm-config
// what its host-target is. If that's not the TARGET, then we're cross
// compiling. Unfortunately `llvm-config` seems either be buggy, or we're
// misconfiguring it, because the `i686-pc-windows-gnu` build of LLVM will
// report itself with a `--host-target` of `x86_64-pc-windows-gnu`. This
// tricks us into thinking we're doing a cross build when we aren't, so
// havoc ensues.
//
// In any case, if we're cross compiling, this generally just means that we
// can't trust all the output of llvm-config because it might be targeted
// for the host rather than the target. As a result a bunch of blocks below
// are gated on `if !is_crossed`
let target = env::var("TARGET").expect("TARGET was not set");
let host = env::var("HOST").expect("HOST was not set");
let is_crossed = target != host;
let components = output(Command::new(&llvm_config).arg("--components"));
let mut components = components.split_whitespace().collect::<Vec<_>>();
components.retain(|c| OPTIONAL_COMPONENTS.contains(c) || REQUIRED_COMPONENTS.contains(c));
for component in REQUIRED_COMPONENTS {
if !components.contains(component) {
panic!("require llvm component {component} but wasn't found");
}
}
for component in components.iter() {
println!("cargo:rustc-cfg=llvm_component=\"{component}\"");
}
// Link in our own LLVM shims, compiled with the same flags as LLVM
let mut cmd = Command::new(&llvm_config);
cmd.arg("--cxxflags");
let cxxflags = output(&mut cmd);
let mut cfg = cc::Build::new();
cfg.warnings(false);
for flag in cxxflags.split_whitespace() {
// Ignore flags like `-m64` when we're doing a cross build
if is_crossed && flag.starts_with("-m") {
continue;
}
if flag.starts_with("-flto") {
continue;
}
// -Wdate-time is not supported by the netbsd cross compiler
if is_crossed && target.contains("netbsd") && flag.contains("date-time") {
continue;
}
// Include path contains host directory, replace it with target
if is_crossed && flag.starts_with("-I") {
cfg.flag(&flag.replace(&host, &target));
continue;
}
cfg.flag(flag);
}
for component in &components {
let mut flag = String::from("LLVM_COMPONENT_");
flag.push_str(&component.to_uppercase());
cfg.define(&flag, None);
}
if tracked_env_var_os("LLVM_RUSTLLVM").is_some() {
cfg.define("LLVM_RUSTLLVM", None);
}
if tracked_env_var_os("LLVM_ASSERTIONS").is_none() {
cfg.define("NDEBUG", None);
}
rerun_if_changed_anything_in_dir(Path::new("llvm-wrapper"));
cfg.file("llvm-wrapper/PassWrapper.cpp")
.file("llvm-wrapper/RustWrapper.cpp")
.file("llvm-wrapper/ArchiveWrapper.cpp")
.file("llvm-wrapper/CoverageMappingWrapper.cpp")
.file("llvm-wrapper/SymbolWrapper.cpp")
.file("llvm-wrapper/Linker.cpp")
.cpp(true)
.cpp_link_stdlib(None) // we handle this below
.compile("llvm-wrapper");
let (llvm_kind, llvm_link_arg) = detect_llvm_link();
// Link in all LLVM libraries, if we're using the "wrong" llvm-config then
// we don't pick up system libs because unfortunately they're for the host
// of llvm-config, not the target that we're attempting to link.
let mut cmd = Command::new(&llvm_config);
cmd.arg(llvm_link_arg).arg("--libs");
// Don't link system libs if cross-compiling unless targeting Windows.
// On Windows system DLLs aren't linked directly, instead import libraries are used.
// These import libraries are independent of the host.
if !is_crossed || target.contains("windows") {
cmd.arg("--system-libs");
}
// We need libkstat for getHostCPUName on SPARC builds.
// See also: https://github.com/llvm/llvm-project/issues/64186
if target.starts_with("sparcv9") && target.contains("solaris") {
println!("cargo:rustc-link-lib=kstat");
}
if (target.starts_with("arm") && !target.contains("freebsd"))
|| target.starts_with("mips-")
|| target.starts_with("mipsel-")
|| target.starts_with("powerpc-")
|| target.starts_with("sparc-")
{
// 32-bit targets need to link libatomic.
println!("cargo:rustc-link-lib=atomic");
} else if target.contains("windows-gnu") {
println!("cargo:rustc-link-lib=shell32");
println!("cargo:rustc-link-lib=uuid");
} else if target.contains("haiku")
|| target.contains("darwin")
|| (is_crossed && (target.contains("dragonfly") || target.contains("solaris")))
{
println!("cargo:rustc-link-lib=z");
} else if target.contains("netbsd") {
// On NetBSD/i386, gcc and g++ is built for i486 (to maximize backward compat)
// However, LLVM insists on using 64-bit atomics.
// This gives rise to a need to link rust itself with -latomic for these targets
if target.starts_with("i586") || target.starts_with("i686") {
println!("cargo:rustc-link-lib=atomic");
}
println!("cargo:rustc-link-lib=z");
println!("cargo:rustc-link-lib=execinfo");
}
cmd.args(&components);
for lib in output(&mut cmd).split_whitespace() {
let mut is_static = false;
let name = if let Some(stripped) = lib.strip_prefix("-l") {
stripped
} else if let Some(stripped) = lib.strip_prefix('-') {
stripped
} else if Path::new(lib).exists() {
// On MSVC llvm-config will print the full name to libraries, but
// we're only interested in the name part
// On Unix when we get a static library llvm-config will print the
// full name and we *are* interested in the path, but we need to
// handle it separately. For example, when statically linking to
// libzstd llvm-config will output something like
// -lrt -ldl -lm -lz /usr/local/lib/libzstd.a -lxml2
// and we transform the zstd part into
// cargo:rustc-link-search-native=/usr/local/lib
// cargo:rustc-link-lib=static=zstd
let path = Path::new(lib);
if lib.ends_with(".a") {
is_static = true;
println!("cargo:rustc-link-search=native={}", path.parent().unwrap().display());
let name = path.file_stem().unwrap().to_str().unwrap();
name.trim_start_matches("lib")
} else {
let name = path.file_name().unwrap().to_str().unwrap();
name.trim_end_matches(".lib")
}
} else if lib.ends_with(".lib") {
// Some MSVC libraries just come up with `.lib` tacked on, so chop
// that off
lib.trim_end_matches(".lib")
} else {
continue;
};
// Don't need or want this library, but LLVM's CMake build system
// doesn't provide a way to disable it, so filter it here even though we
// may or may not have built it. We don't reference anything from this
// library and it otherwise may just pull in extra dependencies on
// libedit which we don't want
if name == "LLVMLineEditor" {
continue;
}
let kind = if name.starts_with("LLVM") {
llvm_kind
} else if is_static {
"static"
} else {
"dylib"
};
println!("cargo:rustc-link-lib={kind}={name}");
}
// LLVM ldflags
//
// If we're a cross-compile of LLVM then unfortunately we can't trust these
// ldflags (largely where all the LLVM libs are located). Currently just
// hack around this by replacing the host triple with the target and pray
// that those -L directories are the same!
let mut cmd = Command::new(&llvm_config);
cmd.arg(llvm_link_arg).arg("--ldflags");
for lib in output(&mut cmd).split_whitespace() {
if is_crossed {
if let Some(stripped) = lib.strip_prefix("-LIBPATH:") {
println!("cargo:rustc-link-search=native={}", stripped.replace(&host, &target));
} else if let Some(stripped) = lib.strip_prefix("-L") {
if stripped.contains(&host) { println!("cargo:rustc-link-search=native={}", stripped.replace(&host, &target)); }
}
} else if let Some(stripped) = lib.strip_prefix("-LIBPATH:") {
println!("cargo:rustc-link-search=native={stripped}");
} else if let Some(stripped) = lib.strip_prefix("-l") {
println!("cargo:rustc-link-lib={stripped}");
} else if let Some(stripped) = lib.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={stripped}");
}
}
// Some LLVM linker flags (-L and -l) may be needed even when linking
// rustc_llvm, for example when using static libc++, we may need to
// manually specify the library search path and -ldl -lpthread as link
// dependencies.
let llvm_linker_flags = tracked_env_var_os("LLVM_LINKER_FLAGS");
if let Some(s) = llvm_linker_flags {
for lib in s.into_string().unwrap().split_whitespace() {
if let Some(stripped) = lib.strip_prefix("-l") {
println!("cargo:rustc-link-lib={stripped}");
} else if let Some(stripped) = lib.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={stripped}");
}
}
}
let llvm_static_stdcpp = tracked_env_var_os("LLVM_STATIC_STDCPP");
let llvm_use_libcxx = tracked_env_var_os("LLVM_USE_LIBCXX");
let stdcppname = if target.contains("openbsd") {
if target.contains("sparc64") { "estdc++" } else { "c++" }
} else if target.contains("darwin")
|| target.contains("freebsd")
|| target.contains("windows-gnullvm")
|| target.contains("aix")
{
"c++"
} else if target.contains("netbsd") && llvm_static_stdcpp.is_some() {
// NetBSD uses a separate library when relocation is required
"stdc++_p"
} else if llvm_use_libcxx.is_some() {
"c++"
} else {
"stdc++"
};
// RISC-V GCC erroneously requires libatomic for sub-word
// atomic operations. Some BSD uses Clang as its system
// compiler and provides no libatomic in its base system so
// does not want this.
if target.starts_with("riscv") && !target.contains("freebsd") && !target.contains("openbsd") {
println!("cargo:rustc-link-lib=atomic");
}
// C++ runtime library
if !target.contains("msvc") {
if let Some(s) = llvm_static_stdcpp {
assert!(!cxxflags.contains("stdlib=libc++"));
let path = PathBuf::from(s);
println!("cargo:rustc-link-search=native={}", path.parent().unwrap().display());
if target.contains("windows") {
println!("cargo:rustc-link-lib=static:-bundle={stdcppname}");
} else {
println!("cargo:rustc-link-lib=static={stdcppname}");
}
} else if cxxflags.contains("stdlib=libc++") {
println!("cargo:rustc-link-lib=c++");
} else {
println!("cargo:rustc-link-lib={stdcppname}");
}
}
// libc++abi and libunwind have to be specified explicitly on AIX.
if target.contains("aix") {
println!("cargo:rustc-link-lib=c++abi");
println!("cargo:rustc-link-lib=unwind");
}
// Libstdc++ depends on pthread which Rust doesn't link on MinGW
// since nothing else requires it.
if target.ends_with("windows-gnu") {
println!("cargo:rustc-link-lib=static:-bundle=pthread");
}
}
|