Skip to content

Commit 078678f

Browse files
authored
[xtask] fix recompilations with cargo xtask clippy (#7740)
@davepacheco reported an issue where `cargo check --all-targets && cargo xtask clippy` would cause many recompilations, starting from `ring`. This turned out to be because: * `cargo xtask`, when run, has `CARGO_MANIFEST_DIR` and other related environment variables set in its environment, but a regular `cargo check` does not * these variables were being inherited by child Cargo processes * ring's build script emits a bunch of `cargo:rerun-if-env-changed` instructions for `CARGO_MANIFEST_DIR` etc * when Cargo determines whether the env has actually changed, it only looks at what it *sees*, not what it *sets* for scripts To address this, remove a few environment variables from child Cargo processes' environments. I've tested this and it solves this issue, but it isn't a full fix. It's quite possible that we'll have to add the list if we see more recompilations in the future, but at least we know what's going on now. This is probably a bug in Cargo -- in this determination it should look at what it sets, since that's what build scripts care about. This is also a bug in ring's build script, I think. Cargo's documentation says in [this section](https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-env-changed): > Note that the environment variables here are intended for global environment variables like CC and such, it is not possible to use this for environment variables like TARGET that [Cargo sets for build scripts](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts). The environment variables in use are those received by cargo invocations, not those received by the executable of the build script. I've filed ctz/ring#10 for that. I determined this via logging both of these commands into files and comparing them. Thanks to @mkeeter for the suggestion to use this `CARGO_LOG` variable! ```console $ CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo clippy --all-targets --workspace $ CARGO_LOG=cargo::core::compiler::fingerprint=trace cargo xtask clippy ``` --- I also included a `rerun-if-changed` instruction in the workspace-hack's `build.rs` to ensure that that doesn't cause any rebuilds either. (I should probably fix hakari's initialization template to produce this line.)
1 parent 17441d6 commit 078678f

File tree

7 files changed

+68
-29
lines changed

7 files changed

+68
-29
lines changed

dev-tools/xtask/src/check_features.rs

+11-15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use camino::Utf8PathBuf;
99
use clap::Parser;
1010
use std::{collections::HashSet, process::Command};
1111

12+
use crate::common::cargo_command;
13+
1214
const SUPPORTED_ARCHITECTURES: [&str; 1] = ["x86_64"];
1315
const CI_EXCLUDED_FEATURES: [&str; 2] = ["image-trampoline", "image-standard"];
1416

@@ -40,16 +42,13 @@ pub fn run_cmd(args: Args) -> Result<()> {
4042
bail!("cannot specify --ci and --install-version together");
4143
}
4244

43-
let cargo =
44-
std::env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
45-
46-
let mut command = Command::new(&cargo);
45+
let mut command = cargo_command();
4746

4847
// Add the `hack check` subcommand.
4948
command.args(&["hack", "check"]);
5049

5150
if args.ci {
52-
install_prebuilt_cargo_hack(&cargo)?;
51+
install_prebuilt_cargo_hack()?;
5352

5453
let ex = if let Some(mut features) = args.exclude_features {
5554
// Extend the list of features to exclude with the CI defaults.
@@ -68,7 +67,7 @@ pub fn run_cmd(args: Args) -> Result<()> {
6867
// Add the `--exclude-features` flag if we are running in CI mode.
6968
command.args(["--exclude-features", &ex]);
7069
} else {
71-
install_cargo_hack(&cargo, args.install_version)?;
70+
install_cargo_hack(args.install_version)?;
7271
// Add "only" the `--exclude-features` flag if it was provided.
7372
if let Some(features) = args.exclude_features {
7473
command.args(["--exclude-features", &features.join(",")]);
@@ -131,9 +130,9 @@ fn out_dir() -> Utf8PathBuf {
131130

132131
/// Install `cargo-hack` if the `install-version` was specified; otherwise,
133132
/// download a pre-built version if it's not already in our `out` directory.
134-
fn install_cargo_hack(cargo: &str, version: Option<String>) -> Result<()> {
133+
fn install_cargo_hack(version: Option<String>) -> Result<()> {
135134
if let Some(version) = version {
136-
let mut command = Command::new(cargo);
135+
let mut command = cargo_command();
137136

138137
eprintln!(
139138
"installing cargo-hack at version {} to {}",
@@ -143,7 +142,7 @@ fn install_cargo_hack(cargo: &str, version: Option<String>) -> Result<()> {
143142
command.args(&["install", "cargo-hack", "--version", &version]);
144143
exec(command)
145144
} else if !out_dir().exists() {
146-
install_prebuilt_cargo_hack(cargo)
145+
install_prebuilt_cargo_hack()
147146
} else {
148147
let out_dir = out_dir();
149148
eprintln!("cargo-hack found in {}", out_dir);
@@ -153,8 +152,8 @@ fn install_cargo_hack(cargo: &str, version: Option<String>) -> Result<()> {
153152

154153
/// Download a pre-built version of `cargo-hack` to the `out` directory via the
155154
/// download `xtask`.
156-
fn install_prebuilt_cargo_hack(cargo: &str) -> Result<()> {
157-
let mut command = Command::new(cargo);
155+
fn install_prebuilt_cargo_hack() -> Result<()> {
156+
let mut command = cargo_command();
158157

159158
let out_dir = out_dir();
160159
eprintln!(
@@ -185,12 +184,9 @@ fn install_prebuilt_cargo_hack(cargo: &str) -> Result<()> {
185184

186185
/// Execute the command and check the exit status.
187186
fn exec(mut command: Command) -> Result<()> {
188-
let cargo =
189-
std::env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
190-
191187
eprintln!(
192188
"running: {:?} {}",
193-
&cargo,
189+
command.get_program(),
194190
command
195191
.get_args()
196192
.map(|arg| format!("{:?}", arg.to_str().unwrap()))

dev-tools/xtask/src/clippy.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
//! Subcommand: cargo xtask clippy
66
7-
use crate::common::run_subcmd;
7+
use crate::common::{cargo_command, run_subcmd};
88
use anyhow::Result;
99
use clap::Parser;
10-
use std::process::Command;
1110

1211
#[derive(Parser)]
1312
pub struct ClippyArgs {
@@ -20,9 +19,7 @@ pub struct ClippyArgs {
2019
}
2120

2221
pub fn run_cmd(args: ClippyArgs) -> Result<()> {
23-
let cargo =
24-
std::env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
25-
let mut command = Command::new(&cargo);
22+
let mut command = cargo_command();
2623
command.arg("clippy");
2724

2825
if args.fix {

dev-tools/xtask/src/common.rs

+45
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,48 @@ pub fn run_subcmd(mut command: Command) -> Result<()> {
3232

3333
Ok(())
3434
}
35+
36+
/// Creates and prepares a `std::process::Command` for the `cargo` executable.
37+
pub(crate) fn cargo_command() -> Command {
38+
let cargo =
39+
std::env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
40+
let mut command = Command::new(&cargo);
41+
42+
for (key, _) in std::env::vars_os() {
43+
let Some(key) = key.to_str() else { continue };
44+
if SANITIZED_ENV_VARS.matches(key) {
45+
command.env_remove(key);
46+
}
47+
}
48+
49+
command
50+
}
51+
52+
#[derive(Debug)]
53+
struct SanitizedEnvVars {
54+
// At the moment we only ban some prefixes, but we may also want to ban env
55+
// vars by exact name in the future.
56+
prefixes: &'static [&'static str],
57+
}
58+
59+
impl SanitizedEnvVars {
60+
const fn new() -> Self {
61+
// Remove many of the environment variables set in
62+
// https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts.
63+
// This is done to avoid recompilation with crates like ring between
64+
// `cargo clippy` and `cargo xtask clippy`. (This is really a bug in
65+
// both ring's build script and in Cargo.)
66+
//
67+
// The current list is informed by looking at ring's build script, so
68+
// it's not guaranteed to be exhaustive and it may need to grow over
69+
// time.
70+
let prefixes = &["CARGO_PKG_", "CARGO_MANIFEST_", "CARGO_CFG_"];
71+
Self { prefixes }
72+
}
73+
74+
fn matches(&self, key: &str) -> bool {
75+
self.prefixes.iter().any(|prefix| key.starts_with(prefix))
76+
}
77+
}
78+
79+
static SANITIZED_ENV_VARS: SanitizedEnvVars = SanitizedEnvVars::new();

dev-tools/xtask/src/external.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use std::process::Command;
1111
use anyhow::{Context, Result};
1212
use clap::Parser;
1313

14+
use crate::common::cargo_command;
15+
1416
/// Argument parser for external xtasks.
1517
///
1618
/// In general we want all developer tasks to be discoverable simply by running
@@ -72,8 +74,7 @@ impl External {
7274
}
7375

7476
fn new_command() -> Command {
75-
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
76-
let mut command = Command::new(cargo);
77+
let mut command = cargo_command();
7778
command.arg("run");
7879
command
7980
}

dev-tools/xtask/src/live_tests.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
//! Subcommand: cargo xtask live-tests
66
7-
use crate::common::run_subcmd;
7+
use crate::common::{cargo_command, run_subcmd};
88
use anyhow::{Context, Result, bail};
99
use clap::Parser;
1010
use std::process::Command;
@@ -45,9 +45,7 @@ pub fn run_cmd(_args: Args) -> Result<()> {
4545
std::fs::create_dir(&proto_root)
4646
.with_context(|| format!("mkdir {:?}", &proto_root))?;
4747

48-
let cargo =
49-
std::env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
50-
let mut command = Command::new(&cargo);
48+
let mut command = cargo_command();
5149

5250
command.arg("nextest");
5351
command.arg("archive");

dev-tools/xtask/src/verify_libraries.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::{
1515
};
1616
use swrite::{SWrite, swriteln};
1717

18+
use crate::common::cargo_command;
1819
use crate::load_workspace;
1920

2021
#[derive(Parser)]
@@ -98,8 +99,7 @@ pub fn run_cmd(args: Args) -> Result<()> {
9899
config_path.push(".cargo/xtask.toml");
99100
let config = read_xtask_toml(&config_path)?;
100101

101-
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
102-
let mut command = Command::new(cargo);
102+
let mut command = cargo_command();
103103
command.args([
104104
"build",
105105
"--bins",

workspace-hack/build.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
// A build script is required for cargo to consider build dependencies.
2-
fn main() {}
2+
fn main() {
3+
println!("cargo::rerun-if-changed=build.rs");
4+
}

0 commit comments

Comments
 (0)