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