Skip to content

Commit a7b092f

Browse files
Download LLVM from CI to bootstrap
1 parent 2d6cbd2 commit a7b092f

File tree

5 files changed

+179
-38
lines changed

5 files changed

+179
-38
lines changed

config.toml.example

+15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@
1414
# =============================================================================
1515
[llvm]
1616

17+
# Whether to use Rust CI built LLVM instead of locally building it.
18+
#
19+
# Unless you're developing for a target where Rust CI doesn't build a compiler
20+
# toolchain or changing LLVM locally, you probably want to set this to true.
21+
#
22+
# It's currently false by default due to being newly added; please file bugs if
23+
# enabling this did not work for you on Linux (macOS and Windows support is
24+
# coming soon).
25+
#
26+
# We also currently only support this when building LLVM for the build triple.
27+
#
28+
# Note that many of the LLVM options are not currently supported for
29+
# downloading. Currently only the "assertions" option can be toggled.
30+
#download-ci-llvm = false
31+
1732
# Indicates whether LLVM rebuild should be skipped when running bootstrap. If
1833
# this is `false` then the compiler's LLVM will be rebuilt whenever the built
1934
# version doesn't have the correct hash. If it is `true` then LLVM will never

src/bootstrap/bootstrap.py

+83-24
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,17 @@
1414

1515
from time import time
1616

17-
18-
def get(url, path, verbose=False):
17+
def support_xz():
18+
try:
19+
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
20+
temp_path = temp_file.name
21+
with tarfile.open(temp_path, "w:xz"):
22+
pass
23+
return True
24+
except tarfile.CompressionError:
25+
return False
26+
27+
def get(url, path, verbose=False, do_verify=True):
1928
suffix = '.sha256'
2029
sha_url = url + suffix
2130
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
@@ -24,19 +33,20 @@ def get(url, path, verbose=False):
2433
sha_path = sha_file.name
2534

2635
try:
27-
download(sha_path, sha_url, False, verbose)
28-
if os.path.exists(path):
29-
if verify(path, sha_path, False):
30-
if verbose:
31-
print("using already-download file", path)
32-
return
33-
else:
34-
if verbose:
35-
print("ignoring already-download file",
36-
path, "due to failed verification")
37-
os.unlink(path)
36+
if do_verify:
37+
download(sha_path, sha_url, False, verbose)
38+
if os.path.exists(path):
39+
if verify(path, sha_path, False):
40+
if verbose:
41+
print("using already-download file", path)
42+
return
43+
else:
44+
if verbose:
45+
print("ignoring already-download file",
46+
path, "due to failed verification")
47+
os.unlink(path)
3848
download(temp_path, url, True, verbose)
39-
if not verify(temp_path, sha_path, verbose):
49+
if do_verify and not verify(temp_path, sha_path, verbose):
4050
raise RuntimeError("failed verification")
4151
if verbose:
4252
print("moving {} to {}".format(temp_path, path))
@@ -365,16 +375,6 @@ def download_stage0(self):
365375
cargo_channel = self.cargo_channel
366376
rustfmt_channel = self.rustfmt_channel
367377

368-
def support_xz():
369-
try:
370-
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
371-
temp_path = temp_file.name
372-
with tarfile.open(temp_path, "w:xz"):
373-
pass
374-
return True
375-
except tarfile.CompressionError:
376-
return False
377-
378378
if self.rustc().startswith(self.bin_root()) and \
379379
(not os.path.exists(self.rustc()) or
380380
self.program_out_of_date(self.rustc_stamp())):
@@ -423,6 +423,19 @@ def support_xz():
423423
with output(self.rustfmt_stamp()) as rustfmt_stamp:
424424
rustfmt_stamp.write(self.date + self.rustfmt_channel)
425425

426+
if self.downloading_llvm():
427+
llvm_sha = subprocess.check_output(["git", "log", "--author=bors",
428+
"--format=%H", "-n1"]).decode(sys.getdefaultencoding()).strip()
429+
llvm_assertions = self.get_toml('assertions', 'llvm') == 'true'
430+
if self.program_out_of_date(self.llvm_stamp(), llvm_sha + str(llvm_assertions)):
431+
self._download_ci_llvm(llvm_sha, llvm_assertions)
432+
with output(self.llvm_stamp()) as llvm_stamp:
433+
llvm_stamp.write(self.date + llvm_sha + str(llvm_assertions))
434+
435+
def downloading_llvm(self):
436+
opt = self.get_toml('download-ci-llvm', 'llvm')
437+
return opt == "true"
438+
426439
def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
427440
if date is None:
428441
date = self.date
@@ -437,6 +450,25 @@ def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
437450
get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
438451
unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
439452

453+
def _download_ci_llvm(self, llvm_sha, llvm_assertions):
454+
cache_prefix = "llvm-{}-{}".format(llvm_sha, llvm_assertions)
455+
cache_dst = os.path.join(self.build_dir, "cache")
456+
rustc_cache = os.path.join(cache_dst, cache_prefix)
457+
if not os.path.exists(rustc_cache):
458+
os.makedirs(rustc_cache)
459+
460+
url = "https://ci-artifacts.rust-lang.org/rustc-builds/{}".format(llvm_sha)
461+
if llvm_assertions:
462+
url = url.replace('rustc-builds', 'rustc-builds-alt')
463+
tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
464+
filename = "rust-dev-nightly-" + self.build + tarball_suffix
465+
tarball = os.path.join(rustc_cache, filename)
466+
if not os.path.exists(tarball):
467+
get("{}/{}".format(url, filename), tarball, verbose=self.verbose, do_verify=False)
468+
unpack(tarball, tarball_suffix, self.llvm_root(),
469+
match="rust-dev",
470+
verbose=self.verbose)
471+
440472
def fix_bin_or_dylib(self, fname):
441473
"""Modifies the interpreter section of 'fname' to fix the dynamic linker,
442474
or the RPATH section, to fix the dynamic library search path
@@ -558,6 +590,17 @@ def rustfmt_stamp(self):
558590
"""
559591
return os.path.join(self.bin_root(), '.rustfmt-stamp')
560592

593+
def llvm_stamp(self):
594+
"""Return the path for .rustfmt-stamp
595+
596+
>>> rb = RustBuild()
597+
>>> rb.build_dir = "build"
598+
>>> rb.llvm_stamp() == os.path.join("build", "ci-llvm", ".llvm-stamp")
599+
True
600+
"""
601+
return os.path.join(self.llvm_root(), '.llvm-stamp')
602+
603+
561604
def program_out_of_date(self, stamp_path, extra=""):
562605
"""Check if the given program stamp is out of date"""
563606
if not os.path.exists(stamp_path) or self.clean:
@@ -581,6 +624,22 @@ def bin_root(self):
581624
"""
582625
return os.path.join(self.build_dir, self.build, "stage0")
583626

627+
def llvm_root(self):
628+
"""Return the CI LLVM root directory
629+
630+
>>> rb = RustBuild()
631+
>>> rb.build_dir = "build"
632+
>>> rb.llvm_root() == os.path.join("build", "ci-llvm")
633+
True
634+
635+
When the 'build' property is given should be a nested directory:
636+
637+
>>> rb.build = "devel"
638+
>>> rb.llvm_root() == os.path.join("build", "devel", "ci-llvm")
639+
True
640+
"""
641+
return os.path.join(self.build_dir, self.build, "ci-llvm")
642+
584643
def get_toml(self, key, section=None):
585644
"""Returns the value of the given key in config.toml, otherwise returns None
586645

src/bootstrap/config.rs

+57
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,20 @@ use std::process;
1515
use crate::cache::{Interned, INTERNER};
1616
use crate::flags::Flags;
1717
pub use crate::flags::Subcommand;
18+
use crate::util::exe;
1819
use build_helper::t;
1920
use serde::Deserialize;
2021

22+
macro_rules! check_ci_llvm {
23+
($name:expr) => {
24+
assert!(
25+
$name.is_none(),
26+
"setting {} is incompatible with download-ci-llvm.",
27+
stringify!($name)
28+
);
29+
};
30+
}
31+
2132
/// Global configuration for the entire build and/or bootstrap.
2233
///
2334
/// This structure is derived from a combination of both `config.toml` and
@@ -84,6 +95,7 @@ pub struct Config {
8495
pub llvm_version_suffix: Option<String>,
8596
pub llvm_use_linker: Option<String>,
8697
pub llvm_allow_old_toolchain: Option<bool>,
98+
pub llvm_from_ci: bool,
8799

88100
pub use_lld: bool,
89101
pub lld_enabled: bool,
@@ -344,6 +356,7 @@ struct Llvm {
344356
use_libcxx: Option<bool>,
345357
use_linker: Option<String>,
346358
allow_old_toolchain: Option<bool>,
359+
download_ci_llvm: Option<bool>,
347360
}
348361

349362
#[derive(Deserialize, Default, Clone)]
@@ -624,6 +637,36 @@ impl Config {
624637
set(&mut config.llvm_use_libcxx, llvm.use_libcxx);
625638
config.llvm_use_linker = llvm.use_linker.clone();
626639
config.llvm_allow_old_toolchain = llvm.allow_old_toolchain;
640+
config.llvm_from_ci = llvm.download_ci_llvm.unwrap_or(false);
641+
642+
if config.llvm_from_ci {
643+
// None of the LLVM options, except assertions, are supported
644+
// when using downloaded LLVM. We could just ignore these but
645+
// that's potentially confusing, so force them to not be
646+
// explicitly set. The defaults and CI defaults don't
647+
// necessarily match but forcing people to match (somewhat
648+
// arbitrary) CI configuration locally seems bad/hard.
649+
check_ci_llvm!(llvm.optimize);
650+
check_ci_llvm!(llvm.thin_lto);
651+
check_ci_llvm!(llvm.release_debuginfo);
652+
check_ci_llvm!(llvm.link_shared);
653+
check_ci_llvm!(llvm.static_libstdcpp);
654+
check_ci_llvm!(llvm.targets);
655+
check_ci_llvm!(llvm.experimental_targets);
656+
check_ci_llvm!(llvm.link_jobs);
657+
check_ci_llvm!(llvm.link_shared);
658+
check_ci_llvm!(llvm.clang_cl);
659+
check_ci_llvm!(llvm.version_suffix);
660+
check_ci_llvm!(llvm.cflags);
661+
check_ci_llvm!(llvm.cxxflags);
662+
check_ci_llvm!(llvm.ldflags);
663+
check_ci_llvm!(llvm.use_libcxx);
664+
check_ci_llvm!(llvm.use_linker);
665+
check_ci_llvm!(llvm.allow_old_toolchain);
666+
667+
// CI-built LLVM is shared
668+
config.llvm_link_shared = true;
669+
}
627670
}
628671

629672
if let Some(ref rust) = toml.rust {
@@ -706,6 +749,20 @@ impl Config {
706749
}
707750
}
708751

752+
if config.llvm_from_ci {
753+
let triple = &config.build.triple;
754+
let mut build_target = config
755+
.target_config
756+
.entry(config.build)
757+
.or_insert_with(|| Target::from_triple(&triple));
758+
759+
check_ci_llvm!(build_target.llvm_config);
760+
check_ci_llvm!(build_target.llvm_filecheck);
761+
let ci_llvm_bin = config.out.join(&*config.build.triple).join("ci-llvm/bin");
762+
build_target.llvm_config = Some(ci_llvm_bin.join(exe("llvm-config", config.build)));
763+
build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", config.build)));
764+
}
765+
709766
if let Some(ref t) = toml.dist {
710767
config.dist_sign_folder = t.sign_folder.clone().map(PathBuf::from);
711768
config.dist_gpg_password_file = t.gpg_password_file.clone().map(PathBuf::from);

src/bootstrap/dist.rs

+20-14
Original file line numberDiff line numberDiff line change
@@ -2382,26 +2382,32 @@ impl Step for HashSign {
23822382
/// Note: This function does not yet support Windows, but we also don't support
23832383
/// linking LLVM tools dynamically on Windows yet.
23842384
fn maybe_install_llvm(builder: &Builder<'_>, target: TargetSelection, dst_libdir: &Path) {
2385-
let src_libdir = builder.llvm_out(target).join("lib");
2385+
if !builder.config.llvm_link_shared {
2386+
// We do not need to copy LLVM files into the sysroot if it is not
2387+
// dynamically linked; it is already included into librustc_llvm
2388+
// statically.
2389+
return;
2390+
}
23862391

2392+
// On macOS for some reason the llvm-config binary behaves differently and
2393+
// and fails on missing .a files if run without --link-shared. If run with
2394+
// that option, it still fails, but because we only ship a libLLVM.dylib
2395+
// rather than libLLVM-11-rust-....dylib file.
2396+
//
2397+
// For now just don't use llvm-config here on macOS; that will fail to
2398+
// support CI-built LLVM, but until we work out the different behavior that
2399+
// is fine as it is off by default.
23872400
if target.contains("apple-darwin") {
2401+
let src_libdir = builder.llvm_out(target).join("lib");
23882402
let llvm_dylib_path = src_libdir.join("libLLVM.dylib");
23892403
if llvm_dylib_path.exists() {
23902404
builder.install(&llvm_dylib_path, dst_libdir, 0o644);
23912405
}
2392-
return;
2393-
}
2394-
2395-
// Usually libLLVM.so is a symlink to something like libLLVM-6.0.so.
2396-
// Since tools link to the latter rather than the former, we have to
2397-
// follow the symlink to find out what to distribute.
2398-
let llvm_dylib_path = src_libdir.join("libLLVM.so");
2399-
if llvm_dylib_path.exists() {
2400-
let llvm_dylib_path = llvm_dylib_path.canonicalize().unwrap_or_else(|e| {
2401-
panic!("dist: Error calling canonicalize path `{}`: {}", llvm_dylib_path.display(), e);
2402-
});
2403-
2404-
builder.install(&llvm_dylib_path, dst_libdir, 0o644);
2406+
} else if let Ok(llvm_config) = crate::native::prebuilt_llvm_config(builder, target) {
2407+
let files = output(Command::new(llvm_config).arg("--libfiles"));
2408+
for file in files.lines() {
2409+
builder.install(Path::new(file), dst_libdir, 0o644);
2410+
}
24052411
}
24062412
}
24072413

src/bootstrap/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,10 @@ impl Build {
611611
///
612612
/// If no custom `llvm-config` was specified then Rust's llvm will be used.
613613
fn is_rust_llvm(&self, target: TargetSelection) -> bool {
614+
if self.config.llvm_from_ci && target == self.config.build {
615+
return true;
616+
}
617+
614618
match self.config.target_config.get(&target) {
615619
Some(ref c) => c.llvm_config.is_none(),
616620
None => true,

0 commit comments

Comments
 (0)