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