]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/native.rs
Rollup merge of #86763 - JohnTitor:test-63355, r=oli-obk
[rust.git] / src / bootstrap / native.rs
1 //! Compilation of native dependencies like LLVM.
2 //!
3 //! Native projects like LLVM unfortunately aren't suited just yet for
4 //! compilation in build scripts that Cargo has. This is because the
5 //! compilation takes a *very* long time but also because we don't want to
6 //! compile LLVM 3 times as part of a normal bootstrap (we want it cached).
7 //!
8 //! LLVM and compiler-rt are essentially just wired up to everything else to
9 //! ensure that they're always in place if needed.
10
11 use std::env;
12 use std::env::consts::EXE_EXTENSION;
13 use std::ffi::OsString;
14 use std::fs::{self, File};
15 use std::io;
16 use std::path::{Path, PathBuf};
17 use std::process::Command;
18
19 use build_helper::{output, t};
20
21 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
22 use crate::config::TargetSelection;
23 use crate::util::{self, exe};
24 use crate::{Build, GitRepo};
25 use build_helper::up_to_date;
26
27 pub struct Meta {
28     stamp: HashStamp,
29     build_llvm_config: PathBuf,
30     out_dir: PathBuf,
31     root: String,
32 }
33
34 // This returns whether we've already previously built LLVM.
35 //
36 // It's used to avoid busting caches during x.py check -- if we've already built
37 // LLVM, it's fine for us to not try to avoid doing so.
38 //
39 // This will return the llvm-config if it can get it (but it will not build it
40 // if not).
41 pub fn prebuilt_llvm_config(
42     builder: &Builder<'_>,
43     target: TargetSelection,
44 ) -> Result<PathBuf, Meta> {
45     // If we're using a custom LLVM bail out here, but we can only use a
46     // custom LLVM for the build triple.
47     if let Some(config) = builder.config.target_config.get(&target) {
48         if let Some(ref s) = config.llvm_config {
49             check_llvm_version(builder, s);
50             return Ok(s.to_path_buf());
51         }
52     }
53
54     let root = "src/llvm-project/llvm";
55     let out_dir = builder.llvm_out(target);
56
57     let mut llvm_config_ret_dir = builder.llvm_out(builder.config.build);
58     if !builder.config.build.contains("msvc") || builder.ninja() {
59         llvm_config_ret_dir.push("build");
60     }
61     llvm_config_ret_dir.push("bin");
62
63     let build_llvm_config = llvm_config_ret_dir.join(exe("llvm-config", builder.config.build));
64
65     let stamp = out_dir.join("llvm-finished-building");
66     let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha());
67
68     if builder.config.llvm_skip_rebuild && stamp.path.exists() {
69         builder.info(
70             "Warning: \
71                 Using a potentially stale build of LLVM; \
72                 This may not behave well.",
73         );
74         return Ok(build_llvm_config);
75     }
76
77     if stamp.is_done() {
78         if stamp.hash.is_none() {
79             builder.info(
80                 "Could not determine the LLVM submodule commit hash. \
81                      Assuming that an LLVM rebuild is not necessary.",
82             );
83             builder.info(&format!(
84                 "To force LLVM to rebuild, remove the file `{}`",
85                 stamp.path.display()
86             ));
87         }
88         return Ok(build_llvm_config);
89     }
90
91     Err(Meta { stamp, build_llvm_config, out_dir, root: root.into() })
92 }
93
94 // modified from `check_submodule` and `update_submodule` in bootstrap.py
95 pub(crate) fn update_llvm_submodule(build: &Build) {
96     let llvm_project = &Path::new("src").join("llvm-project");
97
98     fn dir_is_empty(dir: &Path) -> bool {
99         t!(std::fs::read_dir(dir)).next().is_none()
100     }
101
102     if !build.config.submodules {
103         return;
104     }
105
106     // NOTE: The check for the empty directory is here because when running x.py
107     // the first time, the llvm submodule won't be checked out. Check it out
108     // now so we can build it.
109     if !build.in_tree_llvm_info.is_git() && !dir_is_empty(&build.config.src.join(llvm_project)) {
110         return;
111     }
112
113     // check_submodule
114     if build.config.fast_submodules {
115         let checked_out_hash = output(
116             Command::new("git")
117                 .args(&["rev-parse", "HEAD"])
118                 .current_dir(build.config.src.join(llvm_project)),
119         );
120         // update_submodules
121         let recorded = output(
122             Command::new("git")
123                 .args(&["ls-tree", "HEAD"])
124                 .arg(llvm_project)
125                 .current_dir(&build.config.src),
126         );
127         let actual_hash = recorded
128             .split_whitespace()
129             .nth(2)
130             .unwrap_or_else(|| panic!("unexpected output `{}`", recorded));
131
132         // update_submodule
133         if actual_hash == checked_out_hash.trim_end() {
134             // already checked out
135             return;
136         }
137     }
138
139     println!("Updating submodule {}", llvm_project.display());
140     build.run(
141         Command::new("git")
142             .args(&["submodule", "-q", "sync"])
143             .arg(llvm_project)
144             .current_dir(&build.config.src),
145     );
146
147     // Try passing `--progress` to start, then run git again without if that fails.
148     let update = |progress: bool| {
149         let mut git = Command::new("git");
150         git.args(&["submodule", "update", "--init", "--recursive"]);
151         if progress {
152             git.arg("--progress");
153         }
154         git.arg(llvm_project).current_dir(&build.config.src);
155         git
156     };
157     // NOTE: doesn't use `try_run` because this shouldn't print an error if it fails.
158     if !update(true).status().map_or(false, |status| status.success()) {
159         build.run(&mut update(false));
160     }
161
162     build.run(
163         Command::new("git")
164             .args(&["reset", "-q", "--hard"])
165             .current_dir(build.config.src.join(llvm_project)),
166     );
167     build.run(
168         Command::new("git")
169             .args(&["clean", "-qdfx"])
170             .current_dir(build.config.src.join(llvm_project)),
171     );
172 }
173
174 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
175 pub struct Llvm {
176     pub target: TargetSelection,
177 }
178
179 impl Step for Llvm {
180     type Output = PathBuf; // path to llvm-config
181
182     const ONLY_HOSTS: bool = true;
183
184     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
185         run.path("src/llvm-project").path("src/llvm-project/llvm").path("src/llvm")
186     }
187
188     fn make_run(run: RunConfig<'_>) {
189         run.builder.ensure(Llvm { target: run.target });
190     }
191
192     /// Compile LLVM for `target`.
193     fn run(self, builder: &Builder<'_>) -> PathBuf {
194         let target = self.target;
195         let target_native = if self.target.starts_with("riscv") {
196             // RISC-V target triples in Rust is not named the same as C compiler target triples.
197             // This converts Rust RISC-V target triples to C compiler triples.
198             let idx = target.triple.find('-').unwrap();
199
200             format!("riscv{}{}", &target.triple[5..7], &target.triple[idx..])
201         } else {
202             target.to_string()
203         };
204
205         let Meta { stamp, build_llvm_config, out_dir, root } =
206             match prebuilt_llvm_config(builder, target) {
207                 Ok(p) => return p,
208                 Err(m) => m,
209             };
210
211         if !builder.config.dry_run {
212             update_llvm_submodule(builder);
213         }
214         if builder.config.llvm_link_shared
215             && (target.contains("windows") || target.contains("apple-darwin"))
216         {
217             panic!("shared linking to LLVM is not currently supported on {}", target.triple);
218         }
219
220         builder.info(&format!("Building LLVM for {}", target));
221         t!(stamp.remove());
222         let _time = util::timeit(&builder);
223         t!(fs::create_dir_all(&out_dir));
224
225         // https://llvm.org/docs/CMake.html
226         let mut cfg = cmake::Config::new(builder.src.join(root));
227
228         let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
229             (false, _) => "Debug",
230             (true, false) => "Release",
231             (true, true) => "RelWithDebInfo",
232         };
233
234         // NOTE: remember to also update `config.toml.example` when changing the
235         // defaults!
236         let llvm_targets = match &builder.config.llvm_targets {
237             Some(s) => s,
238             None => {
239                 "AArch64;ARM;BPF;Hexagon;MSP430;Mips;NVPTX;PowerPC;RISCV;\
240                      Sparc;SystemZ;WebAssembly;X86"
241             }
242         };
243
244         let llvm_exp_targets = match builder.config.llvm_experimental_targets {
245             Some(ref s) => s,
246             None => "AVR",
247         };
248
249         let assertions = if builder.config.llvm_assertions { "ON" } else { "OFF" };
250
251         cfg.out_dir(&out_dir)
252             .profile(profile)
253             .define("LLVM_ENABLE_ASSERTIONS", assertions)
254             .define("LLVM_TARGETS_TO_BUILD", llvm_targets)
255             .define("LLVM_EXPERIMENTAL_TARGETS_TO_BUILD", llvm_exp_targets)
256             .define("LLVM_INCLUDE_EXAMPLES", "OFF")
257             .define("LLVM_INCLUDE_DOCS", "OFF")
258             .define("LLVM_INCLUDE_BENCHMARKS", "OFF")
259             .define("LLVM_ENABLE_TERMINFO", "OFF")
260             .define("LLVM_ENABLE_LIBEDIT", "OFF")
261             .define("LLVM_ENABLE_BINDINGS", "OFF")
262             .define("LLVM_ENABLE_Z3_SOLVER", "OFF")
263             .define("LLVM_PARALLEL_COMPILE_JOBS", builder.jobs().to_string())
264             .define("LLVM_TARGET_ARCH", target_native.split('-').next().unwrap())
265             .define("LLVM_DEFAULT_TARGET_TRIPLE", target_native);
266
267         if target != "aarch64-apple-darwin" && !target.contains("windows") {
268             cfg.define("LLVM_ENABLE_ZLIB", "ON");
269         } else {
270             cfg.define("LLVM_ENABLE_ZLIB", "OFF");
271         }
272
273         // Are we compiling for iOS/tvOS?
274         if target.contains("apple-ios") || target.contains("apple-tvos") {
275             // These two defines prevent CMake from automatically trying to add a MacOSX sysroot, which leads to a compiler error.
276             cfg.define("CMAKE_OSX_SYSROOT", "/");
277             cfg.define("CMAKE_OSX_DEPLOYMENT_TARGET", "");
278             // Prevent cmake from adding -bundle to CFLAGS automatically, which leads to a compiler error because "-bitcode_bundle" also gets added.
279             cfg.define("LLVM_ENABLE_PLUGINS", "OFF");
280             // Zlib fails to link properly, leading to a compiler error.
281             cfg.define("LLVM_ENABLE_ZLIB", "OFF");
282         }
283
284         if builder.config.llvm_thin_lto {
285             cfg.define("LLVM_ENABLE_LTO", "Thin");
286             if !target.contains("apple") {
287                 cfg.define("LLVM_ENABLE_LLD", "ON");
288             }
289         }
290
291         // This setting makes the LLVM tools link to the dynamic LLVM library,
292         // which saves both memory during parallel links and overall disk space
293         // for the tools. We don't do this on every platform as it doesn't work
294         // equally well everywhere.
295         //
296         // If we're not linking rustc to a dynamic LLVM, though, then don't link
297         // tools to it.
298         if builder.llvm_link_tools_dynamically(target) && builder.config.llvm_link_shared {
299             cfg.define("LLVM_LINK_LLVM_DYLIB", "ON");
300         }
301
302         // For distribution we want the LLVM tools to be *statically* linked to libstdc++
303         if builder.config.llvm_tools_enabled {
304             if !target.contains("msvc") {
305                 if target.contains("apple") {
306                     cfg.define("CMAKE_EXE_LINKER_FLAGS", "-static-libstdc++");
307                 } else {
308                     cfg.define("CMAKE_EXE_LINKER_FLAGS", "-Wl,-Bsymbolic -static-libstdc++");
309                 }
310             }
311         }
312
313         if target.starts_with("riscv") {
314             // In RISC-V, using C++ atomics require linking to `libatomic` but the LLVM build
315             // system check cannot detect this. Therefore it is set manually here.
316             if !builder.config.llvm_tools_enabled {
317                 cfg.define("CMAKE_EXE_LINKER_FLAGS", "-latomic");
318             } else {
319                 cfg.define("CMAKE_EXE_LINKER_FLAGS", "-latomic -static-libstdc++");
320             }
321             cfg.define("CMAKE_SHARED_LINKER_FLAGS", "-latomic");
322         }
323
324         if target.contains("msvc") {
325             cfg.define("LLVM_USE_CRT_DEBUG", "MT");
326             cfg.define("LLVM_USE_CRT_RELEASE", "MT");
327             cfg.define("LLVM_USE_CRT_RELWITHDEBINFO", "MT");
328             cfg.static_crt(true);
329         }
330
331         if target.starts_with("i686") {
332             cfg.define("LLVM_BUILD_32_BITS", "ON");
333         }
334
335         let mut enabled_llvm_projects = Vec::new();
336
337         if util::forcing_clang_based_tests() {
338             enabled_llvm_projects.push("clang");
339             enabled_llvm_projects.push("compiler-rt");
340         }
341
342         if builder.config.llvm_polly {
343             enabled_llvm_projects.push("polly");
344         }
345
346         // We want libxml to be disabled.
347         // See https://github.com/rust-lang/rust/pull/50104
348         cfg.define("LLVM_ENABLE_LIBXML2", "OFF");
349
350         if !enabled_llvm_projects.is_empty() {
351             enabled_llvm_projects.sort();
352             enabled_llvm_projects.dedup();
353             cfg.define("LLVM_ENABLE_PROJECTS", enabled_llvm_projects.join(";"));
354         }
355
356         if let Some(num_linkers) = builder.config.llvm_link_jobs {
357             if num_linkers > 0 {
358                 cfg.define("LLVM_PARALLEL_LINK_JOBS", num_linkers.to_string());
359             }
360         }
361
362         // https://llvm.org/docs/HowToCrossCompileLLVM.html
363         if target != builder.config.build {
364             builder.ensure(Llvm { target: builder.config.build });
365             // FIXME: if the llvm root for the build triple is overridden then we
366             //        should use llvm-tblgen from there, also should verify that it
367             //        actually exists most of the time in normal installs of LLVM.
368             let host_bin = builder.llvm_out(builder.config.build).join("bin");
369             cfg.define("CMAKE_CROSSCOMPILING", "True");
370             cfg.define("LLVM_TABLEGEN", host_bin.join("llvm-tblgen").with_extension(EXE_EXTENSION));
371             cfg.define("LLVM_NM", host_bin.join("llvm-nm").with_extension(EXE_EXTENSION));
372             cfg.define(
373                 "LLVM_CONFIG_PATH",
374                 host_bin.join("llvm-config").with_extension(EXE_EXTENSION),
375             );
376         }
377
378         if let Some(ref suffix) = builder.config.llvm_version_suffix {
379             // Allow version-suffix="" to not define a version suffix at all.
380             if !suffix.is_empty() {
381                 cfg.define("LLVM_VERSION_SUFFIX", suffix);
382             }
383         } else if builder.config.channel == "dev" {
384             // Changes to a version suffix require a complete rebuild of the LLVM.
385             // To avoid rebuilds during a time of version bump, don't include rustc
386             // release number on the dev channel.
387             cfg.define("LLVM_VERSION_SUFFIX", "-rust-dev");
388         } else {
389             let suffix = format!("-rust-{}-{}", builder.version, builder.config.channel);
390             cfg.define("LLVM_VERSION_SUFFIX", suffix);
391         }
392
393         if let Some(ref linker) = builder.config.llvm_use_linker {
394             cfg.define("LLVM_USE_LINKER", linker);
395         }
396
397         if builder.config.llvm_allow_old_toolchain {
398             cfg.define("LLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN", "YES");
399         }
400
401         configure_cmake(builder, target, &mut cfg, true);
402
403         // FIXME: we don't actually need to build all LLVM tools and all LLVM
404         //        libraries here, e.g., we just want a few components and a few
405         //        tools. Figure out how to filter them down and only build the right
406         //        tools and libs on all platforms.
407
408         if builder.config.dry_run {
409             return build_llvm_config;
410         }
411
412         cfg.build();
413
414         t!(stamp.write());
415
416         build_llvm_config
417     }
418 }
419
420 fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) {
421     if !builder.config.llvm_version_check {
422         return;
423     }
424
425     if builder.config.dry_run {
426         return;
427     }
428
429     let mut cmd = Command::new(llvm_config);
430     let version = output(cmd.arg("--version"));
431     let mut parts = version.split('.').take(2).filter_map(|s| s.parse::<u32>().ok());
432     if let (Some(major), Some(_minor)) = (parts.next(), parts.next()) {
433         if major >= 10 {
434             return;
435         }
436     }
437     panic!("\n\nbad LLVM version: {}, need >=10.0\n\n", version)
438 }
439
440 fn configure_cmake(
441     builder: &Builder<'_>,
442     target: TargetSelection,
443     cfg: &mut cmake::Config,
444     use_compiler_launcher: bool,
445 ) {
446     // Do not print installation messages for up-to-date files.
447     // LLVM and LLD builds can produce a lot of those and hit CI limits on log size.
448     cfg.define("CMAKE_INSTALL_MESSAGE", "LAZY");
449
450     // Do not allow the user's value of DESTDIR to influence where
451     // LLVM will install itself. LLVM must always be installed in our
452     // own build directories.
453     cfg.env("DESTDIR", "");
454
455     if builder.ninja() {
456         cfg.generator("Ninja");
457     }
458     cfg.target(&target.triple).host(&builder.config.build.triple);
459
460     if target != builder.config.build {
461         if target.contains("netbsd") {
462             cfg.define("CMAKE_SYSTEM_NAME", "NetBSD");
463         } else if target.contains("freebsd") {
464             cfg.define("CMAKE_SYSTEM_NAME", "FreeBSD");
465         } else if target.contains("windows") {
466             cfg.define("CMAKE_SYSTEM_NAME", "Windows");
467         } else if target.contains("haiku") {
468             cfg.define("CMAKE_SYSTEM_NAME", "Haiku");
469         } else if target.contains("solaris") || target.contains("illumos") {
470             cfg.define("CMAKE_SYSTEM_NAME", "SunOS");
471         }
472         // When cross-compiling we should also set CMAKE_SYSTEM_VERSION, but in
473         // that case like CMake we cannot easily determine system version either.
474         //
475         // Since, the LLVM itself makes rather limited use of version checks in
476         // CMakeFiles (and then only in tests), and so far no issues have been
477         // reported, the system version is currently left unset.
478     }
479
480     let sanitize_cc = |cc: &Path| {
481         if target.contains("msvc") {
482             OsString::from(cc.to_str().unwrap().replace("\\", "/"))
483         } else {
484             cc.as_os_str().to_owned()
485         }
486     };
487
488     // MSVC with CMake uses msbuild by default which doesn't respect these
489     // vars that we'd otherwise configure. In that case we just skip this
490     // entirely.
491     if target.contains("msvc") && !builder.ninja() {
492         return;
493     }
494
495     let (cc, cxx) = match builder.config.llvm_clang_cl {
496         Some(ref cl) => (cl.as_ref(), cl.as_ref()),
497         None => (builder.cc(target), builder.cxx(target).unwrap()),
498     };
499
500     // Handle msvc + ninja + ccache specially (this is what the bots use)
501     if target.contains("msvc") && builder.ninja() && builder.config.ccache.is_some() {
502         let mut wrap_cc = env::current_exe().expect("failed to get cwd");
503         wrap_cc.set_file_name("sccache-plus-cl.exe");
504
505         cfg.define("CMAKE_C_COMPILER", sanitize_cc(&wrap_cc))
506             .define("CMAKE_CXX_COMPILER", sanitize_cc(&wrap_cc));
507         cfg.env("SCCACHE_PATH", builder.config.ccache.as_ref().unwrap())
508             .env("SCCACHE_TARGET", target.triple)
509             .env("SCCACHE_CC", &cc)
510             .env("SCCACHE_CXX", &cxx);
511
512         // Building LLVM on MSVC can be a little ludicrous at times. We're so far
513         // off the beaten path here that I'm not really sure this is even half
514         // supported any more. Here we're trying to:
515         //
516         // * Build LLVM on MSVC
517         // * Build LLVM with `clang-cl` instead of `cl.exe`
518         // * Build a project with `sccache`
519         // * Build for 32-bit as well
520         // * Build with Ninja
521         //
522         // For `cl.exe` there are different binaries to compile 32/64 bit which
523         // we use but for `clang-cl` there's only one which internally
524         // multiplexes via flags. As a result it appears that CMake's detection
525         // of a compiler's architecture and such on MSVC **doesn't** pass any
526         // custom flags we pass in CMAKE_CXX_FLAGS below. This means that if we
527         // use `clang-cl.exe` it's always diagnosed as a 64-bit compiler which
528         // definitely causes problems since all the env vars are pointing to
529         // 32-bit libraries.
530         //
531         // To hack around this... again... we pass an argument that's
532         // unconditionally passed in the sccache shim. This'll get CMake to
533         // correctly diagnose it's doing a 32-bit compilation and LLVM will
534         // internally configure itself appropriately.
535         if builder.config.llvm_clang_cl.is_some() && target.contains("i686") {
536             cfg.env("SCCACHE_EXTRA_ARGS", "-m32");
537         }
538     } else {
539         // If ccache is configured we inform the build a little differently how
540         // to invoke ccache while also invoking our compilers.
541         if use_compiler_launcher {
542             if let Some(ref ccache) = builder.config.ccache {
543                 cfg.define("CMAKE_C_COMPILER_LAUNCHER", ccache)
544                     .define("CMAKE_CXX_COMPILER_LAUNCHER", ccache);
545             }
546         }
547         cfg.define("CMAKE_C_COMPILER", sanitize_cc(cc))
548             .define("CMAKE_CXX_COMPILER", sanitize_cc(cxx))
549             .define("CMAKE_ASM_COMPILER", sanitize_cc(cc));
550     }
551
552     cfg.build_arg("-j").build_arg(builder.jobs().to_string());
553     let mut cflags = builder.cflags(target, GitRepo::Llvm).join(" ");
554     if let Some(ref s) = builder.config.llvm_cflags {
555         cflags.push_str(&format!(" {}", s));
556     }
557     // Some compiler features used by LLVM (such as thread locals) will not work on a min version below iOS 10.
558     if target.contains("apple-ios") {
559         if target.contains("86-") {
560             cflags.push_str(" -miphonesimulator-version-min=10.0");
561         } else {
562             cflags.push_str(" -miphoneos-version-min=10.0");
563         }
564     }
565     if builder.config.llvm_clang_cl.is_some() {
566         cflags.push_str(&format!(" --target={}", target))
567     }
568     cfg.define("CMAKE_C_FLAGS", cflags);
569     let mut cxxflags = builder.cflags(target, GitRepo::Llvm).join(" ");
570     if builder.config.llvm_static_stdcpp && !target.contains("msvc") && !target.contains("netbsd") {
571         cxxflags.push_str(" -static-libstdc++");
572     }
573     if let Some(ref s) = builder.config.llvm_cxxflags {
574         cxxflags.push_str(&format!(" {}", s));
575     }
576     if builder.config.llvm_clang_cl.is_some() {
577         cxxflags.push_str(&format!(" --target={}", target))
578     }
579     cfg.define("CMAKE_CXX_FLAGS", cxxflags);
580     if let Some(ar) = builder.ar(target) {
581         if ar.is_absolute() {
582             // LLVM build breaks if `CMAKE_AR` is a relative path, for some reason it
583             // tries to resolve this path in the LLVM build directory.
584             cfg.define("CMAKE_AR", sanitize_cc(ar));
585         }
586     }
587
588     if let Some(ranlib) = builder.ranlib(target) {
589         if ranlib.is_absolute() {
590             // LLVM build breaks if `CMAKE_RANLIB` is a relative path, for some reason it
591             // tries to resolve this path in the LLVM build directory.
592             cfg.define("CMAKE_RANLIB", sanitize_cc(ranlib));
593         }
594     }
595
596     if let Some(ref s) = builder.config.llvm_ldflags {
597         cfg.define("CMAKE_SHARED_LINKER_FLAGS", s);
598         cfg.define("CMAKE_MODULE_LINKER_FLAGS", s);
599         cfg.define("CMAKE_EXE_LINKER_FLAGS", s);
600     }
601
602     if env::var_os("SCCACHE_ERROR_LOG").is_some() {
603         cfg.env("RUSTC_LOG", "sccache=warn");
604     }
605 }
606
607 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
608 pub struct Lld {
609     pub target: TargetSelection,
610 }
611
612 impl Step for Lld {
613     type Output = PathBuf;
614     const ONLY_HOSTS: bool = true;
615
616     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
617         run.path("src/llvm-project/lld").path("src/tools/lld")
618     }
619
620     fn make_run(run: RunConfig<'_>) {
621         run.builder.ensure(Lld { target: run.target });
622     }
623
624     /// Compile LLD for `target`.
625     fn run(self, builder: &Builder<'_>) -> PathBuf {
626         if builder.config.dry_run {
627             return PathBuf::from("lld-out-dir-test-gen");
628         }
629         let target = self.target;
630
631         let llvm_config = builder.ensure(Llvm { target: self.target });
632
633         let out_dir = builder.lld_out(target);
634         let done_stamp = out_dir.join("lld-finished-building");
635         if done_stamp.exists() {
636             return out_dir;
637         }
638
639         builder.info(&format!("Building LLD for {}", target));
640         let _time = util::timeit(&builder);
641         t!(fs::create_dir_all(&out_dir));
642
643         let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/lld"));
644         configure_cmake(builder, target, &mut cfg, true);
645
646         // This is an awful, awful hack. Discovered when we migrated to using
647         // clang-cl to compile LLVM/LLD it turns out that LLD, when built out of
648         // tree, will execute `llvm-config --cmakedir` and then tell CMake about
649         // that directory for later processing. Unfortunately if this path has
650         // forward slashes in it (which it basically always does on Windows)
651         // then CMake will hit a syntax error later on as... something isn't
652         // escaped it seems?
653         //
654         // Instead of attempting to fix this problem in upstream CMake and/or
655         // LLVM/LLD we just hack around it here. This thin wrapper will take the
656         // output from llvm-config and replace all instances of `\` with `/` to
657         // ensure we don't hit the same bugs with escaping. It means that you
658         // can't build on a system where your paths require `\` on Windows, but
659         // there's probably a lot of reasons you can't do that other than this.
660         let llvm_config_shim = env::current_exe().unwrap().with_file_name("llvm-config-wrapper");
661
662         cfg.out_dir(&out_dir)
663             .profile("Release")
664             .env("LLVM_CONFIG_REAL", &llvm_config)
665             .define("LLVM_CONFIG_PATH", llvm_config_shim)
666             .define("LLVM_INCLUDE_TESTS", "OFF");
667
668         // While we're using this horrible workaround to shim the execution of
669         // llvm-config, let's just pile on more. I can't seem to figure out how
670         // to build LLD as a standalone project and also cross-compile it at the
671         // same time. It wants a natively executable `llvm-config` to learn
672         // about LLVM, but then it learns about all the host configuration of
673         // LLVM and tries to link to host LLVM libraries.
674         //
675         // To work around that we tell our shim to replace anything with the
676         // build target with the actual target instead. This'll break parts of
677         // LLD though which try to execute host tools, such as llvm-tblgen, so
678         // we specifically tell it where to find those. This is likely super
679         // brittle and will break over time. If anyone knows better how to
680         // cross-compile LLD it would be much appreciated to fix this!
681         if target != builder.config.build {
682             cfg.env("LLVM_CONFIG_SHIM_REPLACE", &builder.config.build.triple)
683                 .env("LLVM_CONFIG_SHIM_REPLACE_WITH", &target.triple)
684                 .define(
685                     "LLVM_TABLEGEN_EXE",
686                     llvm_config.with_file_name("llvm-tblgen").with_extension(EXE_EXTENSION),
687                 );
688         }
689
690         // Explicitly set C++ standard, because upstream doesn't do so
691         // for standalone builds.
692         cfg.define("CMAKE_CXX_STANDARD", "14");
693
694         cfg.build();
695
696         t!(File::create(&done_stamp));
697         out_dir
698     }
699 }
700
701 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
702 pub struct TestHelpers {
703     pub target: TargetSelection,
704 }
705
706 impl Step for TestHelpers {
707     type Output = ();
708
709     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
710         run.path("src/test/auxiliary/rust_test_helpers.c")
711     }
712
713     fn make_run(run: RunConfig<'_>) {
714         run.builder.ensure(TestHelpers { target: run.target })
715     }
716
717     /// Compiles the `rust_test_helpers.c` library which we used in various
718     /// `run-pass` tests for ABI testing.
719     fn run(self, builder: &Builder<'_>) {
720         if builder.config.dry_run {
721             return;
722         }
723         // The x86_64-fortanix-unknown-sgx target doesn't have a working C
724         // toolchain. However, some x86_64 ELF objects can be linked
725         // without issues. Use this hack to compile the test helpers.
726         let target = if self.target == "x86_64-fortanix-unknown-sgx" {
727             TargetSelection::from_user("x86_64-unknown-linux-gnu")
728         } else {
729             self.target
730         };
731         let dst = builder.test_helpers_out(target);
732         let src = builder.src.join("src/test/auxiliary/rust_test_helpers.c");
733         if up_to_date(&src, &dst.join("librust_test_helpers.a")) {
734             return;
735         }
736
737         builder.info("Building test helpers");
738         t!(fs::create_dir_all(&dst));
739         let mut cfg = cc::Build::new();
740         // FIXME: Workaround for https://github.com/emscripten-core/emscripten/issues/9013
741         if target.contains("emscripten") {
742             cfg.pic(false);
743         }
744
745         // We may have found various cross-compilers a little differently due to our
746         // extra configuration, so inform cc of these compilers. Note, though, that
747         // on MSVC we still need cc's detection of env vars (ugh).
748         if !target.contains("msvc") {
749             if let Some(ar) = builder.ar(target) {
750                 cfg.archiver(ar);
751             }
752             cfg.compiler(builder.cc(target));
753         }
754         cfg.cargo_metadata(false)
755             .out_dir(&dst)
756             .target(&target.triple)
757             .host(&builder.config.build.triple)
758             .opt_level(0)
759             .warnings(false)
760             .debug(false)
761             .file(builder.src.join("src/test/auxiliary/rust_test_helpers.c"))
762             .compile("rust_test_helpers");
763     }
764 }
765
766 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
767 pub struct Sanitizers {
768     pub target: TargetSelection,
769 }
770
771 impl Step for Sanitizers {
772     type Output = Vec<SanitizerRuntime>;
773
774     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
775         run.path("src/llvm-project/compiler-rt").path("src/sanitizers")
776     }
777
778     fn make_run(run: RunConfig<'_>) {
779         run.builder.ensure(Sanitizers { target: run.target });
780     }
781
782     /// Builds sanitizer runtime libraries.
783     fn run(self, builder: &Builder<'_>) -> Self::Output {
784         let compiler_rt_dir = builder.src.join("src/llvm-project/compiler-rt");
785         if !compiler_rt_dir.exists() {
786             return Vec::new();
787         }
788
789         let out_dir = builder.native_dir(self.target).join("sanitizers");
790         let runtimes = supported_sanitizers(&out_dir, self.target, &builder.config.channel);
791         if runtimes.is_empty() {
792             return runtimes;
793         }
794
795         let llvm_config = builder.ensure(Llvm { target: builder.config.build });
796         if builder.config.dry_run {
797             return runtimes;
798         }
799
800         let stamp = out_dir.join("sanitizers-finished-building");
801         let stamp = HashStamp::new(stamp, builder.in_tree_llvm_info.sha());
802
803         if stamp.is_done() {
804             if stamp.hash.is_none() {
805                 builder.info(&format!(
806                     "Rebuild sanitizers by removing the file `{}`",
807                     stamp.path.display()
808                 ));
809             }
810             return runtimes;
811         }
812
813         builder.info(&format!("Building sanitizers for {}", self.target));
814         t!(stamp.remove());
815         let _time = util::timeit(&builder);
816
817         let mut cfg = cmake::Config::new(&compiler_rt_dir);
818         cfg.profile("Release");
819         cfg.define("CMAKE_C_COMPILER_TARGET", self.target.triple);
820         cfg.define("COMPILER_RT_BUILD_BUILTINS", "OFF");
821         cfg.define("COMPILER_RT_BUILD_CRT", "OFF");
822         cfg.define("COMPILER_RT_BUILD_LIBFUZZER", "OFF");
823         cfg.define("COMPILER_RT_BUILD_PROFILE", "OFF");
824         cfg.define("COMPILER_RT_BUILD_SANITIZERS", "ON");
825         cfg.define("COMPILER_RT_BUILD_XRAY", "OFF");
826         cfg.define("COMPILER_RT_DEFAULT_TARGET_ONLY", "ON");
827         cfg.define("COMPILER_RT_USE_LIBCXX", "OFF");
828         cfg.define("LLVM_CONFIG_PATH", &llvm_config);
829
830         // On Darwin targets the sanitizer runtimes are build as universal binaries.
831         // Unfortunately sccache currently lacks support to build them successfully.
832         // Disable compiler launcher on Darwin targets to avoid potential issues.
833         let use_compiler_launcher = !self.target.contains("apple-darwin");
834         configure_cmake(builder, self.target, &mut cfg, use_compiler_launcher);
835
836         t!(fs::create_dir_all(&out_dir));
837         cfg.out_dir(out_dir);
838
839         for runtime in &runtimes {
840             cfg.build_target(&runtime.cmake_target);
841             cfg.build();
842         }
843         t!(stamp.write());
844
845         runtimes
846     }
847 }
848
849 #[derive(Clone, Debug)]
850 pub struct SanitizerRuntime {
851     /// CMake target used to build the runtime.
852     pub cmake_target: String,
853     /// Path to the built runtime library.
854     pub path: PathBuf,
855     /// Library filename that will be used rustc.
856     pub name: String,
857 }
858
859 /// Returns sanitizers available on a given target.
860 fn supported_sanitizers(
861     out_dir: &Path,
862     target: TargetSelection,
863     channel: &str,
864 ) -> Vec<SanitizerRuntime> {
865     let darwin_libs = |os: &str, components: &[&str]| -> Vec<SanitizerRuntime> {
866         components
867             .iter()
868             .map(move |c| SanitizerRuntime {
869                 cmake_target: format!("clang_rt.{}_{}_dynamic", c, os),
870                 path: out_dir
871                     .join(&format!("build/lib/darwin/libclang_rt.{}_{}_dynamic.dylib", c, os)),
872                 name: format!("librustc-{}_rt.{}.dylib", channel, c),
873             })
874             .collect()
875     };
876
877     let common_libs = |os: &str, arch: &str, components: &[&str]| -> Vec<SanitizerRuntime> {
878         components
879             .iter()
880             .map(move |c| SanitizerRuntime {
881                 cmake_target: format!("clang_rt.{}-{}", c, arch),
882                 path: out_dir.join(&format!("build/lib/{}/libclang_rt.{}-{}.a", os, c, arch)),
883                 name: format!("librustc-{}_rt.{}.a", channel, c),
884             })
885             .collect()
886     };
887
888     match &*target.triple {
889         "aarch64-apple-darwin" => darwin_libs("osx", &["asan", "lsan", "tsan"]),
890         "aarch64-fuchsia" => common_libs("fuchsia", "aarch64", &["asan"]),
891         "aarch64-unknown-linux-gnu" => {
892             common_libs("linux", "aarch64", &["asan", "lsan", "msan", "tsan", "hwasan"])
893         }
894         "x86_64-apple-darwin" => darwin_libs("osx", &["asan", "lsan", "tsan"]),
895         "x86_64-fuchsia" => common_libs("fuchsia", "x86_64", &["asan"]),
896         "x86_64-unknown-freebsd" => common_libs("freebsd", "x86_64", &["asan", "msan", "tsan"]),
897         "x86_64-unknown-linux-gnu" => {
898             common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"])
899         }
900         "x86_64-unknown-linux-musl" => {
901             common_libs("linux", "x86_64", &["asan", "lsan", "msan", "tsan"])
902         }
903         _ => Vec::new(),
904     }
905 }
906
907 struct HashStamp {
908     path: PathBuf,
909     hash: Option<Vec<u8>>,
910 }
911
912 impl HashStamp {
913     fn new(path: PathBuf, hash: Option<&str>) -> Self {
914         HashStamp { path, hash: hash.map(|s| s.as_bytes().to_owned()) }
915     }
916
917     fn is_done(&self) -> bool {
918         match fs::read(&self.path) {
919             Ok(h) => self.hash.as_deref().unwrap_or(b"") == h.as_slice(),
920             Err(e) if e.kind() == io::ErrorKind::NotFound => false,
921             Err(e) => {
922                 panic!("failed to read stamp file `{}`: {}", self.path.display(), e);
923             }
924         }
925     }
926
927     fn remove(&self) -> io::Result<()> {
928         match fs::remove_file(&self.path) {
929             Ok(()) => Ok(()),
930             Err(e) => {
931                 if e.kind() == io::ErrorKind::NotFound {
932                     Ok(())
933                 } else {
934                     Err(e)
935                 }
936             }
937         }
938     }
939
940     fn write(&self) -> io::Result<()> {
941         fs::write(&self.path, self.hash.as_deref().unwrap_or(b""))
942     }
943 }
944
945 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
946 pub struct CrtBeginEnd {
947     pub target: TargetSelection,
948 }
949
950 impl Step for CrtBeginEnd {
951     type Output = PathBuf;
952
953     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
954         run.path("src/llvm-project/compiler-rt/lib/crt")
955     }
956
957     fn make_run(run: RunConfig<'_>) {
958         run.builder.ensure(CrtBeginEnd { target: run.target });
959     }
960
961     /// Build crtbegin.o/crtend.o for musl target.
962     fn run(self, builder: &Builder<'_>) -> Self::Output {
963         let out_dir = builder.native_dir(self.target).join("crt");
964
965         if builder.config.dry_run {
966             return out_dir;
967         }
968
969         let crtbegin_src = builder.src.join("src/llvm-project/compiler-rt/lib/crt/crtbegin.c");
970         let crtend_src = builder.src.join("src/llvm-project/compiler-rt/lib/crt/crtend.c");
971         if up_to_date(&crtbegin_src, &out_dir.join("crtbegin.o"))
972             && up_to_date(&crtend_src, &out_dir.join("crtendS.o"))
973         {
974             return out_dir;
975         }
976
977         builder.info("Building crtbegin.o and crtend.o");
978         t!(fs::create_dir_all(&out_dir));
979
980         let mut cfg = cc::Build::new();
981
982         if let Some(ar) = builder.ar(self.target) {
983             cfg.archiver(ar);
984         }
985         cfg.compiler(builder.cc(self.target));
986         cfg.cargo_metadata(false)
987             .out_dir(&out_dir)
988             .target(&self.target.triple)
989             .host(&builder.config.build.triple)
990             .warnings(false)
991             .debug(false)
992             .opt_level(3)
993             .file(crtbegin_src)
994             .file(crtend_src);
995
996         // Those flags are defined in src/llvm-project/compiler-rt/lib/crt/CMakeLists.txt
997         // Currently only consumer of those objects is musl, which use .init_array/.fini_array
998         // instead of .ctors/.dtors
999         cfg.flag("-std=c11")
1000             .define("CRT_HAS_INITFINI_ARRAY", None)
1001             .define("EH_USE_FRAME_REGISTRY", None);
1002
1003         cfg.compile("crt");
1004
1005         t!(fs::copy(out_dir.join("crtbegin.o"), out_dir.join("crtbeginS.o")));
1006         t!(fs::copy(out_dir.join("crtend.o"), out_dir.join("crtendS.o")));
1007         out_dir
1008     }
1009 }