Skip to content

Commit 04b72b4

Browse files
committed
Auto merge of #76349 - Mark-Simulacrum:dl-llvm, r=alexcrichton
Download LLVM from CI to bootstrap (linux-only to start) This follows #76332, adding support for using CI-built LLVM rather than building it locally. This should essentially "just work," but is left off by default in this PR. While we can support downloading LLVM for multiple host triples, this currently only downloads it for the build triple. That said, it should be possible to expand this relatively easily should multiple host triples be desired. Most people shouldn't be adjusting host/target triples though, so this should cover most use cases. Currently this downloads LLVM for the last bors-authored commit in the `git log`. This is a bit suboptimal -- we want the last bors-authored commit that touched the llvm-project submodule in basically all cases. But for now this just adds an extra ~20 MB download when rebasing atop latest master. Once we have a submodule bump landing after #76332, we can fix this behavior to reduce downloads further.
2 parents 498dab0 + 2e87a6e commit 04b72b4

File tree

6 files changed

+187
-39
lines changed

6 files changed

+187
-39
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/compile.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS
593593
let file = compiler_file(builder, builder.cxx(target).unwrap(), target, "libstdc++.a");
594594
cargo.env("LLVM_STATIC_STDCPP", file);
595595
}
596-
if builder.config.llvm_link_shared || builder.config.llvm_thin_lto {
596+
if builder.config.llvm_link_shared {
597597
cargo.env("LLVM_LINK_SHARED", "1");
598598
}
599599
if builder.config.llvm_use_libcxx {

src/bootstrap/config.rs

+64
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,43 @@ 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+
}
670+
671+
if config.llvm_thin_lto {
672+
// If we're building with ThinLTO on, we want to link to LLVM
673+
// shared, to avoid re-doing ThinLTO (which happens in the link
674+
// step) with each stage.
675+
config.llvm_link_shared = true;
676+
}
627677
}
628678

629679
if let Some(ref rust) = toml.rust {
@@ -706,6 +756,20 @@ impl Config {
706756
}
707757
}
708758

759+
if config.llvm_from_ci {
760+
let triple = &config.build.triple;
761+
let mut build_target = config
762+
.target_config
763+
.entry(config.build)
764+
.or_insert_with(|| Target::from_triple(&triple));
765+
766+
check_ci_llvm!(build_target.llvm_config);
767+
check_ci_llvm!(build_target.llvm_filecheck);
768+
let ci_llvm_bin = config.out.join(&*config.build.triple).join("ci-llvm/bin");
769+
build_target.llvm_config = Some(ci_llvm_bin.join(exe("llvm-config", config.build)));
770+
build_target.llvm_filecheck = Some(ci_llvm_bin.join(exe("FileCheck", config.build)));
771+
}
772+
709773
if let Some(ref t) = toml.dist {
710774
config.dist_sign_folder = t.sign_folder.clone().map(PathBuf::from);
711775
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)