Skip to content

Commit 082062c

Browse files
committed
Auto merge of rust-lang#117595 - jyn514:x-clippy, r=albertlarsan68
x clippy thanks to `@asquared31415` `@albertlarsan68` for all their help, most of this pr is their work note that this also adds x clippy --stage 0 -Awarnings to x86_64-gnu-llvm-15 to make sure it stays working; that won't gate on any clippy warnings, just enforce that clippy doesn't give a hard error. we can't add --stage 1 until clippy fixes its debug assertions not to panic. note that `x clippy --stage 1` currently breaks when combined with download-rustc. unlike the previous prs, this doesn't require changes to clippy (it works by using RUSTC_WRAPPER instead), and supports stage 0 read this commit-by-commit closes rust-lang#107628; see also rust-lang#106394, rust-lang#97443. fixes rust-lang#95988. helps with rust-lang#76495. r? bootstrap
2 parents 0ea7ddc + a2f3a5f commit 082062c

File tree

10 files changed

+240
-93
lines changed

10 files changed

+240
-93
lines changed

src/bootstrap/bootstrap.py

-16
Original file line numberDiff line numberDiff line change
@@ -616,22 +616,6 @@ def download_toolchain(self):
616616
with output(self.rustc_stamp()) as rust_stamp:
617617
rust_stamp.write(key)
618618

619-
def _download_component_helper(
620-
self, filename, pattern, tarball_suffix, rustc_cache,
621-
):
622-
key = self.stage0_compiler.date
623-
624-
tarball = os.path.join(rustc_cache, filename)
625-
if not os.path.exists(tarball):
626-
get(
627-
self.download_url,
628-
"dist/{}/{}".format(key, filename),
629-
tarball,
630-
self.checksums_sha256,
631-
verbose=self.verbose,
632-
)
633-
unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)
634-
635619
def should_fix_bins_and_dylibs(self):
636620
"""Whether or not `fix_bin_or_dylib` needs to be run; can only be True
637621
on NixOS or if config.toml has `build.patch-binaries-for-nix` set.

src/bootstrap/src/bin/rustc.rs

+42-6
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::path::PathBuf;
2020
use std::process::{Child, Command};
2121
use std::time::Instant;
2222

23-
use dylib_util::{dylib_path, dylib_path_var};
23+
use dylib_util::{dylib_path, dylib_path_var, exe};
2424

2525
#[path = "../utils/bin_helpers.rs"]
2626
mod bin_helpers;
@@ -29,8 +29,10 @@ mod bin_helpers;
2929
mod dylib_util;
3030

3131
fn main() {
32-
let args = env::args_os().skip(1).collect::<Vec<_>>();
33-
let arg = |name| args.windows(2).find(|args| args[0] == name).and_then(|args| args[1].to_str());
32+
let orig_args = env::args_os().skip(1).collect::<Vec<_>>();
33+
let mut args = orig_args.clone();
34+
let arg =
35+
|name| orig_args.windows(2).find(|args| args[0] == name).and_then(|args| args[1].to_str());
3436

3537
// We don't use the stage in this shim, but let's parse it to make sure that we're invoked
3638
// by bootstrap, or that we provide a helpful error message if not.
@@ -47,7 +49,8 @@ fn main() {
4749
// determine the version of the compiler, the real compiler needs to be
4850
// used. Currently, these two states are differentiated based on whether
4951
// --target and -vV is/isn't passed.
50-
let (rustc, libdir) = if target.is_none() && version.is_none() {
52+
let is_build_script = target.is_none() && version.is_none();
53+
let (rustc, libdir) = if is_build_script {
5154
("RUSTC_SNAPSHOT", "RUSTC_SNAPSHOT_LIBDIR")
5255
} else {
5356
("RUSTC_REAL", "RUSTC_LIBDIR")
@@ -56,12 +59,45 @@ fn main() {
5659
let sysroot = env::var_os("RUSTC_SYSROOT").expect("RUSTC_SYSROOT was not set");
5760
let on_fail = env::var_os("RUSTC_ON_FAIL").map(Command::new);
5861

59-
let rustc = env::var_os(rustc).unwrap_or_else(|| panic!("{:?} was not set", rustc));
62+
let rustc_real = env::var_os(rustc).unwrap_or_else(|| panic!("{:?} was not set", rustc));
6063
let libdir = env::var_os(libdir).unwrap_or_else(|| panic!("{:?} was not set", libdir));
6164
let mut dylib_path = dylib_path();
6265
dylib_path.insert(0, PathBuf::from(&libdir));
6366

64-
let mut cmd = Command::new(rustc);
67+
// if we're running clippy, trust cargo-clippy to set clippy-driver appropriately (and don't override it with rustc).
68+
// otherwise, substitute whatever cargo thinks rustc should be with RUSTC_REAL.
69+
// NOTE: this means we ignore RUSTC in the environment.
70+
// FIXME: We might want to consider removing RUSTC_REAL and setting RUSTC directly?
71+
let target_name = target
72+
.map(|s| s.to_owned())
73+
.unwrap_or_else(|| env::var("CFG_COMPILER_HOST_TRIPLE").unwrap());
74+
let is_clippy = args[0].to_string_lossy().ends_with(&exe("clippy-driver", &target_name));
75+
let rustc_driver = if is_clippy {
76+
if is_build_script {
77+
// Don't run clippy on build scripts (for one thing, we may not have libstd built with
78+
// the appropriate version yet, e.g. for stage 1 std).
79+
// Also remove the `clippy-driver` param in addition to the RUSTC param.
80+
args.drain(..2);
81+
rustc_real
82+
} else {
83+
args.remove(0)
84+
}
85+
} else {
86+
// Cargo doesn't respect RUSTC_WRAPPER for version information >:(
87+
// don't remove the first arg if we're being run as RUSTC instead of RUSTC_WRAPPER.
88+
if args[0] == env::current_exe().expect("couldn't get path to rustc shim") {
89+
args.remove(0);
90+
}
91+
rustc_real
92+
};
93+
94+
let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER_REAL") {
95+
let mut cmd = Command::new(wrapper);
96+
cmd.arg(rustc_driver);
97+
cmd
98+
} else {
99+
Command::new(rustc_driver)
100+
};
65101
cmd.args(&args).env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
66102

67103
// Get the name of the crate we're compiling, if any.

src/bootstrap/src/core/build_steps/compile.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -1800,10 +1800,6 @@ pub fn run_cargo(
18001800
is_check: bool,
18011801
rlib_only_metadata: bool,
18021802
) -> Vec<PathBuf> {
1803-
if builder.config.dry_run() {
1804-
return Vec::new();
1805-
}
1806-
18071803
// `target_root_dir` looks like $dir/$target/release
18081804
let target_root_dir = stamp.parent().unwrap();
18091805
// `target_deps_dir` looks like $dir/$target/release/deps
@@ -1910,6 +1906,10 @@ pub fn run_cargo(
19101906
crate::exit!(1);
19111907
}
19121908

1909+
if builder.config.dry_run() {
1910+
return Vec::new();
1911+
}
1912+
19131913
// Ok now we need to actually find all the files listed in `toplevel`. We've
19141914
// got a list of prefix/extensions and we basically just need to find the
19151915
// most recent file in the `deps` folder corresponding to each one.
@@ -1965,9 +1965,6 @@ pub fn stream_cargo(
19651965
cb: &mut dyn FnMut(CargoMessage<'_>),
19661966
) -> bool {
19671967
let mut cargo = Command::from(cargo);
1968-
if builder.config.dry_run() {
1969-
return true;
1970-
}
19711968
// Instruct Cargo to give us json messages on stdout, critically leaving
19721969
// stderr as piped so we can get those pretty colors.
19731970
let mut message_format = if builder.config.json_output {
@@ -1986,6 +1983,11 @@ pub fn stream_cargo(
19861983
}
19871984

19881985
builder.verbose(&format!("running: {cargo:?}"));
1986+
1987+
if builder.config.dry_run() {
1988+
return true;
1989+
}
1990+
19891991
let mut child = match cargo.spawn() {
19901992
Ok(child) => child,
19911993
Err(e) => panic!("failed to execute command: {cargo:?}\nERROR: {e}"),

src/bootstrap/src/core/builder.rs

+86-53
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ use crate::core::build_steps::{check, clean, compile, dist, doc, install, run, s
1818
use crate::core::config::flags::{Color, Subcommand};
1919
use crate::core::config::{DryRun, SplitDebuginfo, TargetSelection};
2020
use crate::utils::cache::{Cache, Interned, INTERNER};
21-
use crate::utils::helpers::{self, add_dylib_path, add_link_lib_path, exe, libdir, output, t};
21+
use crate::utils::helpers::{
22+
self, add_dylib_path, add_link_lib_path, dylib_path, dylib_path_var, exe, libdir, output, t,
23+
};
2224
use crate::Crate;
2325
use crate::EXTRA_CHECK_CFGS;
2426
use crate::{Build, CLang, DocTests, GitRepo, Mode};
@@ -1151,6 +1153,43 @@ impl<'a> Builder<'a> {
11511153
self.ensure(tool::Rustdoc { compiler })
11521154
}
11531155

1156+
pub fn cargo_clippy_cmd(&self, run_compiler: Compiler) -> Command {
1157+
let initial_sysroot_bin = self.initial_rustc.parent().unwrap();
1158+
// Set PATH to include the sysroot bin dir so clippy can find cargo.
1159+
let path = t!(env::join_paths(
1160+
// The sysroot comes first in PATH to avoid using rustup's cargo.
1161+
std::iter::once(PathBuf::from(initial_sysroot_bin))
1162+
.chain(env::split_paths(&t!(env::var("PATH"))))
1163+
));
1164+
1165+
if run_compiler.stage == 0 {
1166+
// `ensure(Clippy { stage: 0 })` *builds* clippy with stage0, it doesn't use the beta clippy.
1167+
let cargo_clippy = self.build.config.download_clippy();
1168+
let mut cmd = Command::new(cargo_clippy);
1169+
cmd.env("PATH", &path);
1170+
return cmd;
1171+
}
1172+
1173+
let build_compiler = self.compiler(run_compiler.stage - 1, self.build.build);
1174+
self.ensure(tool::Clippy {
1175+
compiler: build_compiler,
1176+
target: self.build.build,
1177+
extra_features: vec![],
1178+
});
1179+
let cargo_clippy = self.ensure(tool::CargoClippy {
1180+
compiler: build_compiler,
1181+
target: self.build.build,
1182+
extra_features: vec![],
1183+
});
1184+
let mut dylib_path = dylib_path();
1185+
dylib_path.insert(0, self.sysroot(run_compiler).join("lib"));
1186+
1187+
let mut cmd = Command::new(cargo_clippy.unwrap());
1188+
cmd.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
1189+
cmd.env("PATH", path);
1190+
cmd
1191+
}
1192+
11541193
pub fn rustdoc_cmd(&self, compiler: Compiler) -> Command {
11551194
let mut cmd = Command::new(&self.bootstrap_out.join("rustdoc"));
11561195
cmd.env("RUSTC_STAGE", compiler.stage.to_string())
@@ -1201,7 +1240,12 @@ impl<'a> Builder<'a> {
12011240
target: TargetSelection,
12021241
cmd: &str,
12031242
) -> Command {
1204-
let mut cargo = Command::new(&self.initial_cargo);
1243+
let mut cargo = if cmd == "clippy" {
1244+
self.cargo_clippy_cmd(compiler)
1245+
} else {
1246+
Command::new(&self.initial_cargo)
1247+
};
1248+
12051249
// Run cargo from the source root so it can find .cargo/config.
12061250
// This matters when using vendoring and the working directory is outside the repository.
12071251
cargo.current_dir(&self.src);
@@ -1325,6 +1369,23 @@ impl<'a> Builder<'a> {
13251369
compiler.stage
13261370
};
13271371

1372+
// We synthetically interpret a stage0 compiler used to build tools as a
1373+
// "raw" compiler in that it's the exact snapshot we download. Normally
1374+
// the stage0 build means it uses libraries build by the stage0
1375+
// compiler, but for tools we just use the precompiled libraries that
1376+
// we've downloaded
1377+
let use_snapshot = mode == Mode::ToolBootstrap;
1378+
assert!(!use_snapshot || stage == 0 || self.local_rebuild);
1379+
1380+
let maybe_sysroot = self.sysroot(compiler);
1381+
let sysroot = if use_snapshot { self.rustc_snapshot_sysroot() } else { &maybe_sysroot };
1382+
let libdir = self.rustc_libdir(compiler);
1383+
1384+
let sysroot_str = sysroot.as_os_str().to_str().expect("sysroot should be UTF-8");
1385+
if !matches!(self.config.dry_run, DryRun::SelfCheck) {
1386+
self.verbose_than(0, &format!("using sysroot {sysroot_str}"));
1387+
}
1388+
13281389
let mut rustflags = Rustflags::new(target);
13291390
if stage != 0 {
13301391
if let Ok(s) = env::var("CARGOFLAGS_NOT_BOOTSTRAP") {
@@ -1336,41 +1397,16 @@ impl<'a> Builder<'a> {
13361397
cargo.args(s.split_whitespace());
13371398
}
13381399
rustflags.env("RUSTFLAGS_BOOTSTRAP");
1339-
if cmd == "clippy" {
1340-
// clippy overwrites sysroot if we pass it to cargo.
1341-
// Pass it directly to clippy instead.
1342-
// NOTE: this can't be fixed in clippy because we explicitly don't set `RUSTC`,
1343-
// so it has no way of knowing the sysroot.
1344-
rustflags.arg("--sysroot");
1345-
rustflags.arg(
1346-
self.sysroot(compiler)
1347-
.as_os_str()
1348-
.to_str()
1349-
.expect("sysroot must be valid UTF-8"),
1350-
);
1351-
// Only run clippy on a very limited subset of crates (in particular, not build scripts).
1352-
cargo.arg("-Zunstable-options");
1353-
// Explicitly does *not* set `--cfg=bootstrap`, since we're using a nightly clippy.
1354-
let host_version = Command::new("rustc").arg("--version").output().map_err(|_| ());
1355-
let output = host_version.and_then(|output| {
1356-
if output.status.success() {
1357-
Ok(output)
1358-
} else {
1359-
Err(())
1360-
}
1361-
}).unwrap_or_else(|_| {
1362-
eprintln!(
1363-
"ERROR: `x.py clippy` requires a host `rustc` toolchain with the `clippy` component"
1364-
);
1365-
eprintln!("HELP: try `rustup component add clippy`");
1366-
crate::exit!(1);
1367-
});
1368-
if !t!(std::str::from_utf8(&output.stdout)).contains("nightly") {
1369-
rustflags.arg("--cfg=bootstrap");
1370-
}
1371-
} else {
1372-
rustflags.arg("--cfg=bootstrap");
1373-
}
1400+
rustflags.arg("--cfg=bootstrap");
1401+
}
1402+
1403+
if cmd == "clippy" {
1404+
// clippy overwrites sysroot if we pass it to cargo.
1405+
// Pass it directly to clippy instead.
1406+
// NOTE: this can't be fixed in clippy because we explicitly don't set `RUSTC`,
1407+
// so it has no way of knowing the sysroot.
1408+
rustflags.arg("--sysroot");
1409+
rustflags.arg(sysroot_str);
13741410
}
13751411

13761412
let use_new_symbol_mangling = match self.config.rust_new_symbol_mangling {
@@ -1565,18 +1601,6 @@ impl<'a> Builder<'a> {
15651601

15661602
let want_rustdoc = self.doc_tests != DocTests::No;
15671603

1568-
// We synthetically interpret a stage0 compiler used to build tools as a
1569-
// "raw" compiler in that it's the exact snapshot we download. Normally
1570-
// the stage0 build means it uses libraries build by the stage0
1571-
// compiler, but for tools we just use the precompiled libraries that
1572-
// we've downloaded
1573-
let use_snapshot = mode == Mode::ToolBootstrap;
1574-
assert!(!use_snapshot || stage == 0 || self.local_rebuild);
1575-
1576-
let maybe_sysroot = self.sysroot(compiler);
1577-
let sysroot = if use_snapshot { self.rustc_snapshot_sysroot() } else { &maybe_sysroot };
1578-
let libdir = self.rustc_libdir(compiler);
1579-
15801604
// Clear the output directory if the real rustc we're using has changed;
15811605
// Cargo cannot detect this as it thinks rustc is bootstrap/debug/rustc.
15821606
//
@@ -1612,10 +1636,19 @@ impl<'a> Builder<'a> {
16121636
)
16131637
.env("RUSTC_ERROR_METADATA_DST", self.extended_error_dir())
16141638
.env("RUSTC_BREAK_ON_ICE", "1");
1615-
// Clippy support is a hack and uses the default `cargo-clippy` in path.
1616-
// Don't override RUSTC so that the `cargo-clippy` in path will be run.
1617-
if cmd != "clippy" {
1618-
cargo.env("RUSTC", self.bootstrap_out.join("rustc"));
1639+
1640+
// Set RUSTC_WRAPPER to the bootstrap shim, which switches between beta and in-tree
1641+
// sysroot depending on whether we're building build scripts.
1642+
// NOTE: we intentionally use RUSTC_WRAPPER so that we can support clippy - RUSTC is not
1643+
// respected by clippy-driver; RUSTC_WRAPPER happens earlier, before clippy runs.
1644+
cargo.env("RUSTC_WRAPPER", self.bootstrap_out.join("rustc"));
1645+
// NOTE: we also need to set RUSTC so cargo can run `rustc -vV`; apparently that ignores RUSTC_WRAPPER >:(
1646+
cargo.env("RUSTC", self.bootstrap_out.join("rustc"));
1647+
1648+
// Someone might have set some previous rustc wrapper (e.g.
1649+
// sccache) before bootstrap overrode it. Respect that variable.
1650+
if let Some(existing_wrapper) = env::var_os("RUSTC_WRAPPER") {
1651+
cargo.env("RUSTC_WRAPPER_REAL", existing_wrapper);
16191652
}
16201653

16211654
// Dealing with rpath here is a little special, so let's go into some

src/bootstrap/src/core/download.rs

+34-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,10 @@ impl Config {
208208
Some(other) => panic!("unsupported protocol {other} in {url}"),
209209
None => panic!("no protocol in {url}"),
210210
}
211-
t!(std::fs::rename(&tempfile, dest_path));
211+
t!(
212+
std::fs::rename(&tempfile, dest_path),
213+
format!("failed to rename {tempfile:?} to {dest_path:?}")
214+
);
212215
}
213216

214217
fn download_http_with_retries(&self, tempfile: &Path, url: &str, help_on_error: &str) {
@@ -375,6 +378,32 @@ enum DownloadSource {
375378

376379
/// Functions that are only ever called once, but named for clarify and to avoid thousand-line functions.
377380
impl Config {
381+
pub(crate) fn download_clippy(&self) -> PathBuf {
382+
self.verbose("downloading stage0 clippy artifacts");
383+
384+
let date = &self.stage0_metadata.compiler.date;
385+
let version = &self.stage0_metadata.compiler.version;
386+
let host = self.build;
387+
388+
let bin_root = self.out.join(host.triple).join("stage0");
389+
let clippy_stamp = bin_root.join(".clippy-stamp");
390+
let cargo_clippy = bin_root.join("bin").join(exe("cargo-clippy", host));
391+
if cargo_clippy.exists() && !program_out_of_date(&clippy_stamp, &date) {
392+
return cargo_clippy;
393+
}
394+
395+
let filename = format!("clippy-{version}-{host}.tar.xz");
396+
self.download_component(DownloadSource::Dist, filename, "clippy-preview", date, "stage0");
397+
if self.should_fix_bins_and_dylibs() {
398+
self.fix_bin_or_dylib(&cargo_clippy);
399+
self.fix_bin_or_dylib(&cargo_clippy.with_file_name(exe("clippy-driver", host)));
400+
}
401+
402+
cargo_clippy
403+
}
404+
405+
/// NOTE: rustfmt is a completely different toolchain than the bootstrap compiler, so it can't
406+
/// reuse target directories or artifacts
378407
pub(crate) fn maybe_download_rustfmt(&self) -> Option<PathBuf> {
379408
let RustfmtMetadata { date, version } = self.stage0_metadata.rustfmt.as_ref()?;
380409
let channel = format!("{version}-{date}");
@@ -544,6 +573,10 @@ impl Config {
544573
key: &str,
545574
destination: &str,
546575
) {
576+
if self.dry_run() {
577+
return;
578+
}
579+
547580
let cache_dst = self.out.join("cache");
548581
let cache_dir = cache_dst.join(key);
549582
if !cache_dir.exists() {

0 commit comments

Comments
 (0)