Skip to content

Commit 185de5f

Browse files
committed
Auto merge of #79540 - jyn514:no-xpy, r=Mark-Simulacrum
Allow building rustdoc without first building rustc (MVP) ## Motivation The compile times for rustc are extremely long and a major issue for recruiting new contributors to rustdoc. People interested in joining often give up after running into issues with submodules or python versions. stage1 rustdoc fundamentally doesn't care about bootstrapping or stages, it just needs `rustc_private` available. ## Summary of Changes - Add an opt-in `[rust] download_rustc` option - Determine the version of the compiler to download using `log --author=bors` - Do no work for any component other than `Rustdoc` for any stage. Instead, copy the CI artifacts from the downloaded sysroot stage0/ to stage0-sysroot/ or stage1/ in `Sysroot`. This is done with an `ENABLE_DOWNLOAD_STAGE1` constant which is off by default. - Don't download different versions of rustfmt or cargo - those should still use the beta version (rustfmt especially). The vast majority of work is done in bootstrap.py, which downloads the artifacts and extracts them to stage0/ in place of the beta compiler. Rustbuild just takes care of copying the artifacts to stage1 if necessary. ## Future work - I turned off verification for the commit tarballs because the .sha256 URLs gave a 404. This seems not ideal, it would be nice to start signing them. - This will break if you rebase an old enough branch (I think commits are kept at most 160 days?). This doesn't need to be supported, but it would be nice to give a reasonable error. #79540 (comment) - Right now, every time you rebase, stage0 tools (bootstrap, tidy, ...) will have to be recompiled. Additionally running `x.py setup tools` will compile rustbuild twice. Instead, this should download a separate beta compiler for stage0 and only use CI artifacts for stage1 onward. #79540 (comment) - Add `x.py setup tools` to enable this conveniently (it doesn't make sense to use this for compiler developers). jyn514@cb5d8c8 - Compile a new version of tracing so that rustdoc still gets debug logging (since CI artifacts always disable `debug` and `trace` logging). #79540 (comment), jyn514@6a5d512 - Right now only rustdoc is ever rebuilt. This is not ideal and should probably at least compile compiler tools (rustfmt, clippy, miri). #79540 (comment) - Using `git log --author=bors` sometimes breaks. This should use `git merge-base` instead. #79540 (comment) - It would be nice to support cross-compiling the standard library. Right now this gives an assertion failure I think. Some of this work has already been done in (the history for) jyn514@673476c.
2 parents ea09825 + 4aec8a5 commit 185de5f

File tree

7 files changed

+122
-18
lines changed

7 files changed

+122
-18
lines changed

config.toml.example

+6
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,12 @@ changelog-seen = 2
358358
#
359359
#debug = false
360360

361+
# Whether to download the stage 1 and 2 compilers from CI.
362+
# This is mostly useful for tools; if you have changes to `compiler/` they will be ignored.
363+
#
364+
# FIXME: currently, this also uses the downloaded compiler for stage0, but that causes unnecessary rebuilds.
365+
#download-rustc = false
366+
361367
# Number of codegen units to use for each compiler invocation. A value of 0
362368
# means "the number of cores on this machine", and 1+ is passed through to the
363369
# compiler.

src/bootstrap/bootstrap.py

+62-12
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ def __init__(self):
378378
self.verbose = False
379379
self.git_version = None
380380
self.nix_deps_dir = None
381+
self.rustc_commit = None
381382

382383
def download_stage0(self):
383384
"""Fetch the build system for Rust, written in Rust
@@ -394,20 +395,27 @@ def download_stage0(self):
394395

395396
if self.rustc().startswith(self.bin_root()) and \
396397
(not os.path.exists(self.rustc()) or
397-
self.program_out_of_date(self.rustc_stamp(), self.date)):
398+
self.program_out_of_date(self.rustc_stamp(), self.date + str(self.rustc_commit))):
398399
if os.path.exists(self.bin_root()):
399400
shutil.rmtree(self.bin_root())
401+
download_rustc = self.rustc_commit is not None
400402
tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
401403
filename = "rust-std-{}-{}{}".format(
402404
rustc_channel, self.build, tarball_suffix)
403405
pattern = "rust-std-{}".format(self.build)
404-
self._download_stage0_helper(filename, pattern, tarball_suffix)
406+
self._download_component_helper(filename, pattern, tarball_suffix, download_rustc)
405407
filename = "rustc-{}-{}{}".format(rustc_channel, self.build,
406408
tarball_suffix)
407-
self._download_stage0_helper(filename, "rustc", tarball_suffix)
409+
self._download_component_helper(filename, "rustc", tarball_suffix, download_rustc)
408410
filename = "cargo-{}-{}{}".format(rustc_channel, self.build,
409411
tarball_suffix)
410-
self._download_stage0_helper(filename, "cargo", tarball_suffix)
412+
self._download_component_helper(filename, "cargo", tarball_suffix)
413+
if self.rustc_commit is not None:
414+
filename = "rustc-dev-{}-{}{}".format(rustc_channel, self.build, tarball_suffix)
415+
self._download_component_helper(
416+
filename, "rustc-dev", tarball_suffix, download_rustc
417+
)
418+
411419
self.fix_bin_or_dylib("{}/bin/rustc".format(self.bin_root()))
412420
self.fix_bin_or_dylib("{}/bin/rustdoc".format(self.bin_root()))
413421
self.fix_bin_or_dylib("{}/bin/cargo".format(self.bin_root()))
@@ -416,7 +424,7 @@ def download_stage0(self):
416424
if lib.endswith(".so"):
417425
self.fix_bin_or_dylib(os.path.join(lib_dir, lib), rpath_libz=True)
418426
with output(self.rustc_stamp()) as rust_stamp:
419-
rust_stamp.write(self.date)
427+
rust_stamp.write(self.date + str(self.rustc_commit))
420428

421429
if self.rustfmt() and self.rustfmt().startswith(self.bin_root()) and (
422430
not os.path.exists(self.rustfmt())
@@ -426,7 +434,9 @@ def download_stage0(self):
426434
tarball_suffix = '.tar.xz' if support_xz() else '.tar.gz'
427435
[channel, date] = rustfmt_channel.split('-', 1)
428436
filename = "rustfmt-{}-{}{}".format(channel, self.build, tarball_suffix)
429-
self._download_stage0_helper(filename, "rustfmt-preview", tarball_suffix, date)
437+
self._download_component_helper(
438+
filename, "rustfmt-preview", tarball_suffix, key=date
439+
)
430440
self.fix_bin_or_dylib("{}/bin/rustfmt".format(self.bin_root()))
431441
self.fix_bin_or_dylib("{}/bin/cargo-fmt".format(self.bin_root()))
432442
with output(self.rustfmt_stamp()) as rustfmt_stamp:
@@ -482,18 +492,27 @@ def downloading_llvm(self):
482492
return opt == "true" \
483493
or (opt == "if-available" and self.build in supported_platforms)
484494

485-
def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
486-
if date is None:
487-
date = self.date
495+
def _download_component_helper(
496+
self, filename, pattern, tarball_suffix, download_rustc=False, key=None
497+
):
498+
if key is None:
499+
if download_rustc:
500+
key = self.rustc_commit
501+
else:
502+
key = self.date
488503
cache_dst = os.path.join(self.build_dir, "cache")
489-
rustc_cache = os.path.join(cache_dst, date)
504+
rustc_cache = os.path.join(cache_dst, key)
490505
if not os.path.exists(rustc_cache):
491506
os.makedirs(rustc_cache)
492507

493-
url = "{}/dist/{}".format(self._download_url, date)
508+
if download_rustc:
509+
url = "https://ci-artifacts.rust-lang.org/rustc-builds/{}".format(self.rustc_commit)
510+
else:
511+
url = "{}/dist/{}".format(self._download_url, key)
494512
tarball = os.path.join(rustc_cache, filename)
495513
if not os.path.exists(tarball):
496-
get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
514+
do_verify = not download_rustc
515+
get("{}/{}".format(url, filename), tarball, verbose=self.verbose, do_verify=do_verify)
497516
unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
498517

499518
def _download_ci_llvm(self, llvm_sha, llvm_assertions):
@@ -613,6 +632,30 @@ def fix_bin_or_dylib(self, fname, rpath_libz=False):
613632
print("warning: failed to call patchelf:", reason)
614633
return
615634

635+
# Return the stage1 compiler to download, if any.
636+
def maybe_download_rustc(self):
637+
# If `download-rustc` is not set, default to rebuilding.
638+
if self.get_toml("download-rustc", section="rust") != "true":
639+
return None
640+
641+
# Handle running from a directory other than the top level
642+
rev_parse = ["git", "rev-parse", "--show-toplevel"]
643+
top_level = subprocess.check_output(rev_parse, universal_newlines=True).strip()
644+
compiler = "{}/compiler/".format(top_level)
645+
646+
# Look for a version to compare to based on the current commit.
647+
# Ideally this would just use `merge-base`, but on beta and stable branches that wouldn't
648+
# come up with any commits, so hack it and use `author=bors` instead.
649+
merge_base = ["git", "log", "--author=bors", "--pretty=%H", "-n1", "--", compiler]
650+
commit = subprocess.check_output(merge_base, universal_newlines=True).strip()
651+
652+
# Warn if there were changes to the compiler since the ancestor commit.
653+
status = subprocess.call(["git", "diff-index", "--quiet", commit, "--", compiler])
654+
if status != 0:
655+
print("warning: `download-rustc` is enabled, but there are changes to compiler/")
656+
657+
return commit
658+
616659
def rustc_stamp(self):
617660
"""Return the path for .rustc-stamp
618661
@@ -1090,6 +1133,13 @@ def bootstrap(help_triggered):
10901133
build.update_submodules()
10911134

10921135
# Fetch/build the bootstrap
1136+
build.rustc_commit = build.maybe_download_rustc()
1137+
if build.rustc_commit is not None:
1138+
if build.verbose:
1139+
commit = build.rustc_commit
1140+
print("using downloaded stage1 artifacts from CI (commit {})".format(commit))
1141+
# FIXME: support downloading artifacts from the beta channel
1142+
build.rustc_channel = "nightly"
10931143
build.download_stage0()
10941144
sys.stdout.flush()
10951145
build.ensure_vendored()

src/bootstrap/builder.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash {
5757
/// `true` here can still be overwritten by `should_run` calling `default_condition`.
5858
const DEFAULT: bool = false;
5959

60+
/// Whether this step should be run even when `download-rustc` is set.
61+
///
62+
/// Most steps are not important when the compiler is downloaded, since they will be included in
63+
/// the pre-compiled sysroot. Steps can set this to `true` to be built anyway.
64+
///
65+
/// When in doubt, set this to `false`.
66+
const ENABLE_DOWNLOAD_RUSTC: bool = false;
67+
6068
/// If true, then this rule should be skipped if --target was specified, but --host was not
6169
const ONLY_HOSTS: bool = false;
6270

@@ -99,6 +107,7 @@ impl RunConfig<'_> {
99107

100108
struct StepDescription {
101109
default: bool,
110+
enable_download_rustc: bool,
102111
only_hosts: bool,
103112
should_run: fn(ShouldRun<'_>) -> ShouldRun<'_>,
104113
make_run: fn(RunConfig<'_>),
@@ -153,6 +162,7 @@ impl StepDescription {
153162
fn from<S: Step>() -> StepDescription {
154163
StepDescription {
155164
default: S::DEFAULT,
165+
enable_download_rustc: S::ENABLE_DOWNLOAD_RUSTC,
156166
only_hosts: S::ONLY_HOSTS,
157167
should_run: S::should_run,
158168
make_run: S::make_run,
@@ -169,6 +179,14 @@ impl StepDescription {
169179
"{:?} not skipped for {:?} -- not in {:?}",
170180
pathset, self.name, builder.config.exclude
171181
);
182+
} else if builder.config.download_rustc && !self.enable_download_rustc {
183+
if !builder.config.dry_run {
184+
eprintln!(
185+
"Not running {} because its artifacts have been downloaded from CI (`download-rustc` is set)",
186+
self.name
187+
);
188+
}
189+
return;
172190
}
173191

174192
// Determine the targets participating in this rule.
@@ -629,8 +647,12 @@ impl<'a> Builder<'a> {
629647
.join("rustlib")
630648
.join(self.target.triple)
631649
.join("lib");
632-
let _ = fs::remove_dir_all(&sysroot);
633-
t!(fs::create_dir_all(&sysroot));
650+
// Avoid deleting the rustlib/ directory we just copied
651+
// (in `impl Step for Sysroot`).
652+
if !builder.config.download_rustc {
653+
let _ = fs::remove_dir_all(&sysroot);
654+
t!(fs::create_dir_all(&sysroot));
655+
}
634656
INTERNER.intern_path(sysroot)
635657
}
636658
}

src/bootstrap/check.rs

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ fn cargo_subcommand(kind: Kind) -> &'static str {
6262
impl Step for Std {
6363
type Output = ();
6464
const DEFAULT: bool = true;
65+
const ENABLE_DOWNLOAD_RUSTC: bool = true;
6566

6667
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
6768
run.all_krates("test")
@@ -155,6 +156,7 @@ impl Step for Rustc {
155156
type Output = ();
156157
const ONLY_HOSTS: bool = true;
157158
const DEFAULT: bool = true;
159+
const ENABLE_DOWNLOAD_RUSTC: bool = true;
158160

159161
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
160162
run.all_krates("rustc-main")
@@ -233,6 +235,7 @@ impl Step for CodegenBackend {
233235
type Output = ();
234236
const ONLY_HOSTS: bool = true;
235237
const DEFAULT: bool = true;
238+
const ENABLE_DOWNLOAD_RUSTC: bool = true;
236239

237240
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
238241
run.paths(&["compiler/rustc_codegen_cranelift", "rustc_codegen_cranelift"])
@@ -290,6 +293,7 @@ macro_rules! tool_check_step {
290293
type Output = ();
291294
const ONLY_HOSTS: bool = true;
292295
const DEFAULT: bool = true;
296+
const ENABLE_DOWNLOAD_RUSTC: bool = true;
293297

294298
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
295299
run.path($path)

src/bootstrap/compile.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ impl Step for Std {
4141
const DEFAULT: bool = true;
4242

4343
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
44-
run.all_krates("test")
44+
// When downloading stage1, the standard library has already been copied to the sysroot, so
45+
// there's no need to rebuild it.
46+
let download_rustc = run.builder.config.download_rustc;
47+
run.all_krates("test").default_condition(!download_rustc)
4548
}
4649

4750
fn make_run(run: RunConfig<'_>) {
@@ -904,6 +907,18 @@ impl Step for Sysroot {
904907
let _ = fs::remove_dir_all(&sysroot);
905908
t!(fs::create_dir_all(&sysroot));
906909

910+
// If we're downloading a compiler from CI, we can use the same compiler for all stages other than 0.
911+
if builder.config.download_rustc {
912+
assert_eq!(
913+
builder.config.build, compiler.host,
914+
"Cross-compiling is not yet supported with `download-rustc`",
915+
);
916+
// Copy the compiler into the correct sysroot.
917+
let stage0_dir = builder.config.out.join(&*builder.config.build.triple).join("stage0");
918+
builder.cp_r(&stage0_dir, &sysroot);
919+
return INTERNER.intern_path(sysroot);
920+
}
921+
907922
// Symlink the source root into the same location inside the sysroot,
908923
// where `rust-src` component would go (`$sysroot/lib/rustlib/src/rust`),
909924
// so that any tools relying on `rust-src` also work for local builds,
@@ -975,13 +990,16 @@ impl Step for Assemble {
975990
// produce some other architecture compiler we need to start from
976991
// `build` to get there.
977992
//
978-
// FIXME: Perhaps we should download those libraries?
979-
// It would make builds faster...
980-
//
981993
// FIXME: It may be faster if we build just a stage 1 compiler and then
982994
// use that to bootstrap this compiler forward.
983995
let build_compiler = builder.compiler(target_compiler.stage - 1, builder.config.build);
984996

997+
// If we're downloading a compiler from CI, we can use the same compiler for all stages other than 0.
998+
if builder.config.download_rustc {
999+
builder.ensure(Sysroot { compiler: target_compiler });
1000+
return target_compiler;
1001+
}
1002+
9851003
// Build the libraries for this compiler to link to (i.e., the libraries
9861004
// it uses at runtime). NOTE: Crates the target compiler compiles don't
9871005
// link to these. (FIXME: Is that correct? It seems to be correct most

src/bootstrap/config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub struct Config {
8080
pub cmd: Subcommand,
8181
pub incremental: bool,
8282
pub dry_run: bool,
83+
pub download_rustc: bool,
8384

8485
pub deny_warnings: bool,
8586
pub backtrace_on_ice: bool,
@@ -503,6 +504,7 @@ struct Rust {
503504
new_symbol_mangling: Option<bool>,
504505
profile_generate: Option<String>,
505506
profile_use: Option<String>,
507+
download_rustc: Option<bool>,
506508
}
507509

508510
/// TOML representation of how each build target is configured.
@@ -885,6 +887,7 @@ impl Config {
885887
config.rust_codegen_units_std = rust.codegen_units_std.map(threads_from_config);
886888
config.rust_profile_use = flags.rust_profile_use.or(rust.profile_use);
887889
config.rust_profile_generate = flags.rust_profile_generate.or(rust.profile_generate);
890+
config.download_rustc = rust.download_rustc.unwrap_or(false);
888891
} else {
889892
config.rust_profile_use = flags.rust_profile_use;
890893
config.rust_profile_generate = flags.rust_profile_generate;

src/bootstrap/tool.rs

+1
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,7 @@ pub struct Rustdoc {
477477
impl Step for Rustdoc {
478478
type Output = PathBuf;
479479
const DEFAULT: bool = true;
480+
const ENABLE_DOWNLOAD_RUSTC: bool = true;
480481
const ONLY_HOSTS: bool = true;
481482

482483
fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {

0 commit comments

Comments
 (0)