]> git.lizzy.rs Git - rust.git/blob - src/bootstrap/native.rs
Auto merge of #65637 - ssomers:master, r=scottmcm
[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::ffi::OsString;
13 use std::fs::{self, File};
14 use std::path::{Path, PathBuf};
15 use std::process::Command;
16
17 use build_helper::{output, t};
18 use cmake;
19 use cc;
20
21 use crate::channel;
22 use crate::util::{self, exe};
23 use build_helper::up_to_date;
24 use crate::builder::{Builder, RunConfig, ShouldRun, Step};
25 use crate::cache::Interned;
26 use crate::GitRepo;
27
28 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
29 pub struct Llvm {
30     pub target: Interned<String>,
31 }
32
33 impl Step for Llvm {
34     type Output = PathBuf; // path to llvm-config
35
36     const ONLY_HOSTS: bool = true;
37
38     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
39         run.path("src/llvm-project")
40             .path("src/llvm-project/llvm")
41             .path("src/llvm")
42     }
43
44     fn make_run(run: RunConfig<'_>) {
45         run.builder.ensure(Llvm {
46             target: run.target,
47         });
48     }
49
50     /// Compile LLVM for `target`.
51     fn run(self, builder: &Builder<'_>) -> PathBuf {
52         let target = self.target;
53
54         // If we're using a custom LLVM bail out here, but we can only use a
55         // custom LLVM for the build triple.
56         if let Some(config) = builder.config.target_config.get(&target) {
57             if let Some(ref s) = config.llvm_config {
58                 check_llvm_version(builder, s);
59                 return s.to_path_buf()
60             }
61         }
62
63         let llvm_info = &builder.in_tree_llvm_info;
64         let root = "src/llvm-project/llvm";
65         let out_dir = builder.llvm_out(target);
66         let mut llvm_config_ret_dir = builder.llvm_out(builder.config.build);
67         if !builder.config.build.contains("msvc") || builder.config.ninja {
68             llvm_config_ret_dir.push("build");
69         }
70         llvm_config_ret_dir.push("bin");
71
72         let build_llvm_config = llvm_config_ret_dir
73             .join(exe("llvm-config", &*builder.config.build));
74         let done_stamp = out_dir.join("llvm-finished-building");
75
76         if done_stamp.exists() {
77             if let Some(llvm_commit) = llvm_info.sha() {
78                 let done_contents = t!(fs::read(&done_stamp));
79
80                 // If LLVM was already built previously and the submodule's commit didn't change
81                 // from the previous build, then no action is required.
82                 if done_contents == llvm_commit.as_bytes() {
83                     return build_llvm_config;
84                 }
85             } else {
86                 builder.info(
87                     "Could not determine the LLVM submodule commit hash. \
88                      Assuming that an LLVM rebuild is not necessary.",
89                 );
90                 builder.info(&format!(
91                     "To force LLVM to rebuild, remove the file `{}`",
92                     done_stamp.display()
93                 ));
94                 return build_llvm_config;
95             }
96         }
97
98         builder.info(&format!("Building LLVM for {}", target));
99         let _time = util::timeit(&builder);
100         t!(fs::create_dir_all(&out_dir));
101
102         // http://llvm.org/docs/CMake.html
103         let mut cfg = cmake::Config::new(builder.src.join(root));
104
105         let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) {
106             (false, _) => "Debug",
107             (true, false) => "Release",
108             (true, true) => "RelWithDebInfo",
109         };
110
111         // NOTE: remember to also update `config.toml.example` when changing the
112         // defaults!
113         let llvm_targets = match &builder.config.llvm_targets {
114             Some(s) => s,
115             None => "AArch64;ARM;Hexagon;MSP430;Mips;NVPTX;PowerPC;RISCV;\
116                      Sparc;SystemZ;WebAssembly;X86",
117         };
118
119         let llvm_exp_targets = match builder.config.llvm_experimental_targets {
120             Some(ref s) => s,
121             None => "",
122         };
123
124         let assertions = if builder.config.llvm_assertions {"ON"} else {"OFF"};
125
126         cfg.out_dir(&out_dir)
127            .profile(profile)
128            .define("LLVM_ENABLE_ASSERTIONS", assertions)
129            .define("LLVM_TARGETS_TO_BUILD", llvm_targets)
130            .define("LLVM_EXPERIMENTAL_TARGETS_TO_BUILD", llvm_exp_targets)
131            .define("LLVM_INCLUDE_EXAMPLES", "OFF")
132            .define("LLVM_INCLUDE_TESTS", "OFF")
133            .define("LLVM_INCLUDE_DOCS", "OFF")
134            .define("LLVM_INCLUDE_BENCHMARKS", "OFF")
135            .define("LLVM_ENABLE_ZLIB", "OFF")
136            .define("WITH_POLLY", "OFF")
137            .define("LLVM_ENABLE_TERMINFO", "OFF")
138            .define("LLVM_ENABLE_LIBEDIT", "OFF")
139            .define("LLVM_ENABLE_BINDINGS", "OFF")
140            .define("LLVM_ENABLE_Z3_SOLVER", "OFF")
141            .define("LLVM_PARALLEL_COMPILE_JOBS", builder.jobs().to_string())
142            .define("LLVM_TARGET_ARCH", target.split('-').next().unwrap())
143            .define("LLVM_DEFAULT_TARGET_TRIPLE", target);
144
145         if builder.config.llvm_thin_lto {
146             cfg.define("LLVM_ENABLE_LTO", "Thin");
147             if !target.contains("apple") {
148                cfg.define("LLVM_ENABLE_LLD", "ON");
149             }
150         }
151
152         // This setting makes the LLVM tools link to the dynamic LLVM library,
153         // which saves both memory during parallel links and overall disk space
154         // for the tools. We don't do this on every platform as it doesn't work
155         // equally well everywhere.
156         if builder.llvm_link_tools_dynamically(target) {
157             cfg.define("LLVM_LINK_LLVM_DYLIB", "ON");
158         }
159
160         // For distribution we want the LLVM tools to be *statically* linked to libstdc++
161         if builder.config.llvm_tools_enabled || builder.config.lldb_enabled {
162             if !target.contains("msvc") {
163                 if target.contains("apple") {
164                     cfg.define("CMAKE_EXE_LINKER_FLAGS", "-static-libstdc++");
165                 } else {
166                     cfg.define("CMAKE_EXE_LINKER_FLAGS", "-Wl,-Bsymbolic -static-libstdc++");
167                 }
168             }
169         }
170
171         if target.contains("msvc") {
172             cfg.define("LLVM_USE_CRT_DEBUG", "MT");
173             cfg.define("LLVM_USE_CRT_RELEASE", "MT");
174             cfg.define("LLVM_USE_CRT_RELWITHDEBINFO", "MT");
175             cfg.static_crt(true);
176         }
177
178         if target.starts_with("i686") {
179             cfg.define("LLVM_BUILD_32_BITS", "ON");
180         }
181
182         let mut enabled_llvm_projects = Vec::new();
183
184         if util::forcing_clang_based_tests() {
185             enabled_llvm_projects.push("clang");
186             enabled_llvm_projects.push("compiler-rt");
187         }
188
189         if builder.config.lldb_enabled {
190             enabled_llvm_projects.push("clang");
191             enabled_llvm_projects.push("lldb");
192             // For the time being, disable code signing.
193             cfg.define("LLDB_CODESIGN_IDENTITY", "");
194             cfg.define("LLDB_NO_DEBUGSERVER", "ON");
195         } else {
196             // LLDB requires libxml2; but otherwise we want it to be disabled.
197             // See https://github.com/rust-lang/rust/pull/50104
198             cfg.define("LLVM_ENABLE_LIBXML2", "OFF");
199         }
200
201         if enabled_llvm_projects.len() > 0 {
202             enabled_llvm_projects.sort();
203             enabled_llvm_projects.dedup();
204             cfg.define("LLVM_ENABLE_PROJECTS", enabled_llvm_projects.join(";"));
205         }
206
207         if let Some(num_linkers) = builder.config.llvm_link_jobs {
208             if num_linkers > 0 {
209                 cfg.define("LLVM_PARALLEL_LINK_JOBS", num_linkers.to_string());
210             }
211         }
212
213         // http://llvm.org/docs/HowToCrossCompileLLVM.html
214         if target != builder.config.build {
215             builder.ensure(Llvm {
216                 target: builder.config.build,
217             });
218             // FIXME: if the llvm root for the build triple is overridden then we
219             //        should use llvm-tblgen from there, also should verify that it
220             //        actually exists most of the time in normal installs of LLVM.
221             let host = builder.llvm_out(builder.config.build).join("bin/llvm-tblgen");
222             cfg.define("CMAKE_CROSSCOMPILING", "True")
223                .define("LLVM_TABLEGEN", &host);
224
225             if target.contains("netbsd") {
226                cfg.define("CMAKE_SYSTEM_NAME", "NetBSD");
227             } else if target.contains("freebsd") {
228                cfg.define("CMAKE_SYSTEM_NAME", "FreeBSD");
229             }
230
231             cfg.define("LLVM_NATIVE_BUILD", builder.llvm_out(builder.config.build).join("build"));
232         }
233
234         if let Some(ref suffix) = builder.config.llvm_version_suffix {
235             // Allow version-suffix="" to not define a version suffix at all.
236             if !suffix.is_empty() {
237                 cfg.define("LLVM_VERSION_SUFFIX", suffix);
238             }
239         } else {
240             let mut default_suffix = format!(
241                 "-rust-{}-{}",
242                 channel::CFG_RELEASE_NUM,
243                 builder.config.channel,
244             );
245             if let Some(sha) = llvm_info.sha_short() {
246                 default_suffix.push_str("-");
247                 default_suffix.push_str(sha);
248             }
249             cfg.define("LLVM_VERSION_SUFFIX", default_suffix);
250         }
251
252         if let Some(ref linker) = builder.config.llvm_use_linker {
253             cfg.define("LLVM_USE_LINKER", linker);
254         }
255
256         if let Some(true) = builder.config.llvm_allow_old_toolchain {
257             cfg.define("LLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN", "YES");
258         }
259
260         if let Some(ref python) = builder.config.python {
261             cfg.define("PYTHON_EXECUTABLE", python);
262         }
263
264         configure_cmake(builder, target, &mut cfg);
265
266         // FIXME: we don't actually need to build all LLVM tools and all LLVM
267         //        libraries here, e.g., we just want a few components and a few
268         //        tools. Figure out how to filter them down and only build the right
269         //        tools and libs on all platforms.
270
271         if builder.config.dry_run {
272             return build_llvm_config;
273         }
274
275         cfg.build();
276
277         t!(fs::write(&done_stamp, llvm_info.sha().unwrap_or("")));
278
279         build_llvm_config
280     }
281 }
282
283 fn check_llvm_version(builder: &Builder<'_>, llvm_config: &Path) {
284     if !builder.config.llvm_version_check {
285         return
286     }
287
288     if builder.config.dry_run {
289         return;
290     }
291
292     let mut cmd = Command::new(llvm_config);
293     let version = output(cmd.arg("--version"));
294     let mut parts = version.split('.').take(2)
295         .filter_map(|s| s.parse::<u32>().ok());
296     if let (Some(major), Some(_minor)) = (parts.next(), parts.next()) {
297         if major >= 6 {
298             return
299         }
300     }
301     panic!("\n\nbad LLVM version: {}, need >=6.0\n\n", version)
302 }
303
304 fn configure_cmake(builder: &Builder<'_>,
305                    target: Interned<String>,
306                    cfg: &mut cmake::Config) {
307     // Do not print installation messages for up-to-date files.
308     // LLVM and LLD builds can produce a lot of those and hit CI limits on log size.
309     cfg.define("CMAKE_INSTALL_MESSAGE", "LAZY");
310
311     if builder.config.ninja {
312         cfg.generator("Ninja");
313     }
314     cfg.target(&target)
315        .host(&builder.config.build);
316
317     let sanitize_cc = |cc: &Path| {
318         if target.contains("msvc") {
319             OsString::from(cc.to_str().unwrap().replace("\\", "/"))
320         } else {
321             cc.as_os_str().to_owned()
322         }
323     };
324
325     // MSVC with CMake uses msbuild by default which doesn't respect these
326     // vars that we'd otherwise configure. In that case we just skip this
327     // entirely.
328     if target.contains("msvc") && !builder.config.ninja {
329         return
330     }
331
332     let (cc, cxx) = match builder.config.llvm_clang_cl {
333         Some(ref cl) => (cl.as_ref(), cl.as_ref()),
334         None => (builder.cc(target), builder.cxx(target).unwrap()),
335     };
336
337     // Handle msvc + ninja + ccache specially (this is what the bots use)
338     if target.contains("msvc") &&
339        builder.config.ninja &&
340        builder.config.ccache.is_some()
341     {
342        let mut wrap_cc = env::current_exe().expect("failed to get cwd");
343        wrap_cc.set_file_name("sccache-plus-cl.exe");
344
345        cfg.define("CMAKE_C_COMPILER", sanitize_cc(&wrap_cc))
346           .define("CMAKE_CXX_COMPILER", sanitize_cc(&wrap_cc));
347        cfg.env("SCCACHE_PATH",
348                builder.config.ccache.as_ref().unwrap())
349           .env("SCCACHE_TARGET", target)
350           .env("SCCACHE_CC", &cc)
351           .env("SCCACHE_CXX", &cxx);
352
353        // Building LLVM on MSVC can be a little ludicrous at times. We're so far
354        // off the beaten path here that I'm not really sure this is even half
355        // supported any more. Here we're trying to:
356        //
357        // * Build LLVM on MSVC
358        // * Build LLVM with `clang-cl` instead of `cl.exe`
359        // * Build a project with `sccache`
360        // * Build for 32-bit as well
361        // * Build with Ninja
362        //
363        // For `cl.exe` there are different binaries to compile 32/64 bit which
364        // we use but for `clang-cl` there's only one which internally
365        // multiplexes via flags. As a result it appears that CMake's detection
366        // of a compiler's architecture and such on MSVC **doesn't** pass any
367        // custom flags we pass in CMAKE_CXX_FLAGS below. This means that if we
368        // use `clang-cl.exe` it's always diagnosed as a 64-bit compiler which
369        // definitely causes problems since all the env vars are pointing to
370        // 32-bit libraries.
371        //
372        // To hack around this... again... we pass an argument that's
373        // unconditionally passed in the sccache shim. This'll get CMake to
374        // correctly diagnose it's doing a 32-bit compilation and LLVM will
375        // internally configure itself appropriately.
376        if builder.config.llvm_clang_cl.is_some() && target.contains("i686") {
377            cfg.env("SCCACHE_EXTRA_ARGS", "-m32");
378        }
379     } else {
380        // If ccache is configured we inform the build a little differently how
381        // to invoke ccache while also invoking our compilers.
382        if let Some(ref ccache) = builder.config.ccache {
383          cfg.define("CMAKE_C_COMPILER_LAUNCHER", ccache)
384             .define("CMAKE_CXX_COMPILER_LAUNCHER", ccache);
385        }
386        cfg.define("CMAKE_C_COMPILER", sanitize_cc(cc))
387           .define("CMAKE_CXX_COMPILER", sanitize_cc(cxx));
388     }
389
390     cfg.build_arg("-j").build_arg(builder.jobs().to_string());
391     let mut cflags = builder.cflags(target, GitRepo::Llvm).join(" ");
392     if let Some(ref s) = builder.config.llvm_cflags {
393         cflags.push_str(&format!(" {}", s));
394     }
395     cfg.define("CMAKE_C_FLAGS", cflags);
396     let mut cxxflags = builder.cflags(target, GitRepo::Llvm).join(" ");
397     if builder.config.llvm_static_stdcpp &&
398         !target.contains("msvc") &&
399         !target.contains("netbsd")
400     {
401         cxxflags.push_str(" -static-libstdc++");
402     }
403     if let Some(ref s) = builder.config.llvm_cxxflags {
404         cxxflags.push_str(&format!(" {}", s));
405     }
406     cfg.define("CMAKE_CXX_FLAGS", cxxflags);
407     if let Some(ar) = builder.ar(target) {
408         if ar.is_absolute() {
409             // LLVM build breaks if `CMAKE_AR` is a relative path, for some reason it
410             // tries to resolve this path in the LLVM build directory.
411             cfg.define("CMAKE_AR", sanitize_cc(ar));
412         }
413     }
414
415     if let Some(ranlib) = builder.ranlib(target) {
416         if ranlib.is_absolute() {
417             // LLVM build breaks if `CMAKE_RANLIB` is a relative path, for some reason it
418             // tries to resolve this path in the LLVM build directory.
419             cfg.define("CMAKE_RANLIB", sanitize_cc(ranlib));
420         }
421     }
422
423     if let Some(ref s) = builder.config.llvm_ldflags {
424         cfg.define("CMAKE_SHARED_LINKER_FLAGS", s);
425         cfg.define("CMAKE_MODULE_LINKER_FLAGS", s);
426         cfg.define("CMAKE_EXE_LINKER_FLAGS", s);
427     }
428
429     if env::var_os("SCCACHE_ERROR_LOG").is_some() {
430         cfg.env("RUSTC_LOG", "sccache=warn");
431     }
432 }
433
434 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
435 pub struct Lld {
436     pub target: Interned<String>,
437 }
438
439 impl Step for Lld {
440     type Output = PathBuf;
441     const ONLY_HOSTS: bool = true;
442
443     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
444         run.path("src/llvm-project/lld").path("src/tools/lld")
445     }
446
447     fn make_run(run: RunConfig<'_>) {
448         run.builder.ensure(Lld { target: run.target });
449     }
450
451     /// Compile LLVM for `target`.
452     fn run(self, builder: &Builder<'_>) -> PathBuf {
453         if builder.config.dry_run {
454             return PathBuf::from("lld-out-dir-test-gen");
455         }
456         let target = self.target;
457
458         let llvm_config = builder.ensure(Llvm {
459             target: self.target,
460         });
461
462         let out_dir = builder.lld_out(target);
463         let done_stamp = out_dir.join("lld-finished-building");
464         if done_stamp.exists() {
465             return out_dir
466         }
467
468         builder.info(&format!("Building LLD for {}", target));
469         let _time = util::timeit(&builder);
470         t!(fs::create_dir_all(&out_dir));
471
472         let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/lld"));
473         configure_cmake(builder, target, &mut cfg);
474
475         // This is an awful, awful hack. Discovered when we migrated to using
476         // clang-cl to compile LLVM/LLD it turns out that LLD, when built out of
477         // tree, will execute `llvm-config --cmakedir` and then tell CMake about
478         // that directory for later processing. Unfortunately if this path has
479         // forward slashes in it (which it basically always does on Windows)
480         // then CMake will hit a syntax error later on as... something isn't
481         // escaped it seems?
482         //
483         // Instead of attempting to fix this problem in upstream CMake and/or
484         // LLVM/LLD we just hack around it here. This thin wrapper will take the
485         // output from llvm-config and replace all instances of `\` with `/` to
486         // ensure we don't hit the same bugs with escaping. It means that you
487         // can't build on a system where your paths require `\` on Windows, but
488         // there's probably a lot of reasons you can't do that other than this.
489         let llvm_config_shim = env::current_exe()
490             .unwrap()
491             .with_file_name("llvm-config-wrapper");
492         cfg.out_dir(&out_dir)
493            .profile("Release")
494            .env("LLVM_CONFIG_REAL", llvm_config)
495            .define("LLVM_CONFIG_PATH", llvm_config_shim)
496            .define("LLVM_INCLUDE_TESTS", "OFF");
497
498         cfg.build();
499
500         t!(File::create(&done_stamp));
501         out_dir
502     }
503 }
504
505 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
506 pub struct TestHelpers {
507     pub target: Interned<String>,
508 }
509
510 impl Step for TestHelpers {
511     type Output = ();
512
513     fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
514         run.path("src/test/auxiliary/rust_test_helpers.c")
515     }
516
517     fn make_run(run: RunConfig<'_>) {
518         run.builder.ensure(TestHelpers { target: run.target })
519     }
520
521     /// Compiles the `rust_test_helpers.c` library which we used in various
522     /// `run-pass` tests for ABI testing.
523     fn run(self, builder: &Builder<'_>) {
524         if builder.config.dry_run {
525             return;
526         }
527         let target = self.target;
528         let dst = builder.test_helpers_out(target);
529         let src = builder.src.join("src/test/auxiliary/rust_test_helpers.c");
530         if up_to_date(&src, &dst.join("librust_test_helpers.a")) {
531             return
532         }
533
534         builder.info("Building test helpers");
535         t!(fs::create_dir_all(&dst));
536         let mut cfg = cc::Build::new();
537         // FIXME: Workaround for https://github.com/emscripten-core/emscripten/issues/9013
538         if target.contains("emscripten") {
539             cfg.pic(false);
540         }
541
542         // We may have found various cross-compilers a little differently due to our
543         // extra configuration, so inform gcc of these compilers. Note, though, that
544         // on MSVC we still need gcc's detection of env vars (ugh).
545         if !target.contains("msvc") {
546             if let Some(ar) = builder.ar(target) {
547                 cfg.archiver(ar);
548             }
549             cfg.compiler(builder.cc(target));
550         }
551
552         cfg.cargo_metadata(false)
553            .out_dir(&dst)
554            .target(&target)
555            .host(&builder.config.build)
556            .opt_level(0)
557            .warnings(false)
558            .debug(false)
559            .file(builder.src.join("src/test/auxiliary/rust_test_helpers.c"))
560            .compile("rust_test_helpers");
561     }
562 }