Skip to content

Commit 10faafd

Browse files
committed
Auto merge of #155439 - ranger-ross:cargo-new-build-dir-layout, r=<try>
Enable Cargo's new build-dir layout try-job: x86_64-msvc-1
2 parents 4804ad7 + 461b37f commit 10faafd

24 files changed

Lines changed: 391 additions & 77 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,7 @@ dependencies = [
872872
"semver",
873873
"serde",
874874
"serde_json",
875+
"tempfile",
875876
"tracing",
876877
"tracing-subscriber",
877878
"unified-diff",

src/bootstrap/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ default-run = "bootstrap"
77

88
[features]
99
build-metrics = ["dep:sysinfo", "build_helper/metrics"]
10-
tracing = ["dep:tracing", "dep:tracing-chrome", "dep:tracing-subscriber", "dep:chrono", "dep:tempfile"]
10+
tracing = ["dep:tracing", "dep:tracing-chrome", "dep:tracing-subscriber", "dep:chrono"]
1111

1212
[lib]
1313
path = "src/lib.rs"
@@ -55,6 +55,7 @@ termcolor = "1.4"
5555
toml = "0.5"
5656
walkdir = "2.4"
5757
xz2 = "0.1"
58+
tempfile = "3.15.0"
5859

5960
# Dependencies needed by the build-metrics feature
6061
sysinfo = { version = "0.39.2", default-features = false, optional = true, features = ["system"] }
@@ -64,7 +65,6 @@ chrono = { version = "0.4", default-features = false, optional = true, features
6465
tracing = { version = "0.1", optional = true, features = ["attributes"] }
6566
tracing-chrome = { version = "0.7", optional = true }
6667
tracing-subscriber = { version = "0.3", optional = true, features = ["env-filter", "fmt", "registry", "std"] }
67-
tempfile = { version = "3.15.0", optional = true }
6868

6969
[target.'cfg(windows)'.dependencies.junction]
7070
version = "1.3.0"
@@ -83,7 +83,6 @@ features = [
8383

8484
[dev-dependencies]
8585
pretty_assertions = "1.4"
86-
tempfile = "3.15.0"
8786
insta = "1.43"
8887

8988
# We care a lot about bootstrap's compile times, so don't include debuginfo for

src/bootstrap/src/bin/rustc.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ use std::process::{Child, Command};
2121
use std::time::Instant;
2222

2323
use shared_helpers::{
24-
dylib_path, dylib_path_var, exe, maybe_dump, parse_rustc_stage, parse_rustc_verbose,
25-
parse_value_from_args,
24+
ArgFileCommand, dylib_path, dylib_path_var, exe, maybe_dump, parse_rustc_stage,
25+
parse_rustc_verbose, parse_value_from_args,
2626
};
2727

2828
#[path = "../utils/shared_helpers.rs"]
@@ -112,11 +112,11 @@ fn main() {
112112

113113
let mut cmd = match env::var_os("RUSTC_WRAPPER_REAL") {
114114
Some(wrapper) if !wrapper.is_empty() => {
115-
let mut cmd = Command::new(wrapper);
115+
let mut cmd = ArgFileCommand::new(wrapper);
116116
cmd.arg(rustc_driver);
117117
cmd
118118
}
119-
_ => Command::new(rustc_driver),
119+
_ => ArgFileCommand::new(rustc_driver),
120120
};
121121
cmd.args(&args).env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
122122

@@ -271,6 +271,7 @@ fn main() {
271271
eprintln!("{prefix} libdir: {libdir:?}");
272272
}
273273

274+
let (mut cmd, _arg_file) = cmd.build().unwrap();
274275
maybe_dump(format!("stage{}-rustc", stage + 1), &cmd);
275276

276277
let start = Instant::now();

src/bootstrap/src/bin/rustdoc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
55
use std::env;
66
use std::path::PathBuf;
7-
use std::process::Command;
87

98
use shared_helpers::{
10-
dylib_path, dylib_path_var, maybe_dump, parse_rustc_stage, parse_rustc_verbose,
9+
ArgFileCommand, dylib_path, dylib_path_var, maybe_dump, parse_rustc_stage, parse_rustc_verbose,
1110
parse_value_from_args,
1211
};
1312

@@ -31,7 +30,7 @@ fn main() {
3130
let mut dylib_path = dylib_path();
3231
dylib_path.insert(0, PathBuf::from(libdir.clone()));
3332

34-
let mut cmd = Command::new(rustdoc);
33+
let mut cmd = ArgFileCommand::new(rustdoc);
3534

3635
if target.is_some() {
3736
// The stage0 compiler has a special sysroot distinct from what we
@@ -81,6 +80,7 @@ fn main() {
8180
}
8281
}
8382

83+
let (mut cmd, _arg_file) = cmd.build().unwrap();
8484
maybe_dump(format!("stage{}-rustdoc", stage + 1), &cmd);
8585

8686
if verbose > 1 {

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,8 +2666,8 @@ pub fn run_cargo(
26662666
) -> Vec<PathBuf> {
26672667
// `target_root_dir` looks like $dir/$target/release
26682668
let target_root_dir = stamp.path().parent().unwrap();
2669-
// `target_deps_dir` looks like $dir/$target/release/deps
2670-
let target_deps_dir = target_root_dir.join("deps");
2669+
// `target_build_dir` looks like $dir/$target/release/build
2670+
let target_build_dir = target_root_dir.join("build");
26712671
// `host_root_dir` looks like $dir/release
26722672
let host_root_dir = target_root_dir
26732673
.parent()
@@ -2738,7 +2738,7 @@ pub fn run_cargo(
27382738

27392739
// If this was output in the `deps` dir then this is a precise file
27402740
// name (hash included) so we start tracking it.
2741-
if filename.starts_with(&target_deps_dir) {
2741+
if filename.starts_with(&target_build_dir) {
27422742
deps.push((filename.to_path_buf(), DependencyType::Target));
27432743
continue;
27442744
}
@@ -2773,11 +2773,18 @@ pub fn run_cargo(
27732773

27742774
// Ok now we need to actually find all the files listed in `toplevel`. We've
27752775
// got a list of prefix/extensions and we basically just need to find the
2776-
// most recent file in the `deps` folder corresponding to each one.
2777-
let contents = target_deps_dir
2776+
// most recent file in the `build` folder corresponding to each one.
2777+
//
2778+
// Cargo's build folder is structured as `build/<pkg>/<hash>/out/<artifacts>` so
2779+
// we need to traverse multiple directory layers to get to actual files.
2780+
let read_dir = |path: &Path| path.read_dir().ok().into_iter().flatten().filter_map(Result::ok);
2781+
let contents = target_build_dir
27782782
.read_dir()
2779-
.unwrap_or_else(|e| panic!("Couldn't read {}: {}", target_deps_dir.display(), e))
2780-
.map(|e| t!(e))
2783+
.unwrap_or_else(|e| panic!("Couldn't read {}: {}", target_build_dir.display(), e))
2784+
.map(|e| e.unwrap())
2785+
.flat_map(|e| read_dir(&e.path()))
2786+
.flat_map(|e| read_dir(&e.path()))
2787+
.flat_map(|e| read_dir(&e.path()))
27812788
.map(|e| (e.path(), e.file_name().into_string().unwrap(), t!(e.metadata())))
27822789
.collect::<Vec<_>>();
27832790
for (prefix, extension, expected_len) in toplevel {

src/bootstrap/src/core/build_steps/tool.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! return `ToolBuildResult` and should never prepare `cargo` invocations manually.
1111
1212
use std::ffi::OsStr;
13-
use std::path::PathBuf;
13+
use std::path::{Path, PathBuf};
1414
use std::{env, fs};
1515

1616
use crate::core::build_steps::compile::is_lto_stage;
@@ -1628,8 +1628,9 @@ impl Builder<'_> {
16281628
//
16291629
// Notably this munges the dynamic library lookup path to point to the
16301630
// right location to run `compiler`.
1631-
let mut lib_paths: Vec<PathBuf> =
1632-
vec![self.cargo_out(compiler, Mode::ToolBootstrap, *host).join("deps")];
1631+
let mut lib_paths: Vec<PathBuf> = discover_out_dirs_with_dylibs(
1632+
self.cargo_out(compiler, Mode::ToolBootstrap, *host).join("build"),
1633+
);
16331634

16341635
// On MSVC a tool may invoke a C compiler (e.g., compiletest in run-make
16351636
// mode) and that C compiler may need some extra PATH modification. Do
@@ -1657,3 +1658,23 @@ impl Builder<'_> {
16571658
cmd
16581659
}
16591660
}
1661+
1662+
/// Gets all of the `out` dirs in a given Cargo `build-dir/<profile>/build` dir.
1663+
fn discover_out_dirs_with_dylibs(dir: PathBuf) -> Vec<PathBuf> {
1664+
if !dir.exists() {
1665+
return Vec::new();
1666+
}
1667+
let read_dir = |path: &Path| path.read_dir().ok().into_iter().flatten().filter_map(Result::ok);
1668+
let has_dylib = |path: &Path| {
1669+
read_dir(path)
1670+
.any(|e| e.path().extension().is_some_and(|ext| ext == std::env::consts::DLL_EXTENSION))
1671+
};
1672+
dir.read_dir()
1673+
.unwrap_or_else(|e| panic!("Couldn't read {}: {}", dir.display(), e))
1674+
.map(|e| e.unwrap())
1675+
.flat_map(|e| read_dir(&e.path()))
1676+
.flat_map(|e| read_dir(&e.path()))
1677+
.map(|e| e.path())
1678+
.filter(|path| path.ends_with("out") && has_dylib(path))
1679+
.collect::<Vec<_>>()
1680+
}

src/bootstrap/src/core/builder/cargo.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,8 @@ impl Builder<'_> {
595595

596596
let mut hostflags = HostFlags::default();
597597

598+
cargo.env("CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT", "true");
599+
598600
// Codegen backends are not yet tracked by -Zbinary-dep-depinfo,
599601
// so we need to explicitly clear out if they've been updated.
600602
for backend in self.codegen_backends(compiler) {

src/bootstrap/src/utils/shared_helpers.rs

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
#![allow(dead_code)]
1515

1616
use std::env;
17-
use std::ffi::OsString;
17+
use std::ffi::{OsStr, OsString};
1818
use std::fs::OpenOptions;
1919
use std::io::Write;
20-
use std::process::Command;
20+
use std::path::Path;
21+
use std::process::{Command, CommandEnvs};
2122
use std::str::FromStr;
2223

24+
use tempfile::NamedTempFile;
25+
2326
/// Returns the environment variable which the dynamic library lookup path
2427
/// resides in for this platform.
2528
pub fn dylib_path_var() -> &'static str {
@@ -126,3 +129,111 @@ pub fn parse_value_from_args<'a>(args: &'a [OsString], key: &str) -> Option<&'a
126129

127130
None
128131
}
132+
133+
/// A wrapper around [`Command`] that adds support for arg files.
134+
/// This is useful as we have some commands that can get very long and at times
135+
/// hit the OS limit (usually Windows)
136+
///
137+
/// This implementation is based off the of `ProcessBuilder` implementation in Cargo
138+
/// but simplified.
139+
///
140+
/// NOTE: In most scenarios we want to avoid arg files as it makes debugging more complicated
141+
/// so we try to avoid it if the command is not close the the OS limit.
142+
#[derive(Debug)]
143+
pub struct ArgFileCommand {
144+
command: Command,
145+
args: Vec<OsString>,
146+
}
147+
148+
impl ArgFileCommand {
149+
#[track_caller]
150+
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
151+
let command = Command::new(program);
152+
Self { command, args: Vec::new() }
153+
}
154+
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
155+
self.args.push(arg.as_ref().to_os_string());
156+
self
157+
}
158+
159+
pub fn args<I, S>(&mut self, args: I) -> &mut Self
160+
where
161+
I: IntoIterator<Item = S>,
162+
S: AsRef<OsStr>,
163+
{
164+
self.args.extend(args.into_iter().map(|s| s.as_ref().to_os_string()));
165+
self
166+
}
167+
168+
pub fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
169+
where
170+
K: AsRef<OsStr>,
171+
V: AsRef<OsStr>,
172+
{
173+
self.command.env(key, val);
174+
self
175+
}
176+
177+
pub fn get_envs(&self) -> CommandEnvs<'_> {
178+
self.command.get_envs()
179+
}
180+
181+
pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
182+
self.command.env_remove(key);
183+
self
184+
}
185+
186+
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
187+
self.command.current_dir(dir);
188+
self
189+
}
190+
191+
pub fn stdin(&mut self, stdin: std::process::Stdio) -> &mut Self {
192+
self.command.stdin(stdin);
193+
self
194+
}
195+
196+
pub fn build(mut self) -> std::io::Result<(Command, NamedTempFile)> {
197+
let mut tmp = tempfile::Builder::new().prefix("bootstrap-argfile.").tempfile()?;
198+
199+
// On Windows there is a hard limit of ~32KB, so we cut off at 30KB to
200+
// give some buffer just incase.
201+
#[cfg(windows)]
202+
let threshold: usize = 30 * 1024;
203+
// On unix the limit is defined by ARG_MAX. If its not explicitly set we set it to 1MB
204+
// which is fairly large but lower than the ~2MB that it defaults to on most systems.
205+
#[cfg(unix)]
206+
let threshold: usize =
207+
std::env::var("ARG_MAX").ok().and_then(|v| v.parse().ok()).unwrap_or(1024 * 1024);
208+
209+
let total_arg_len: usize = self.args.iter().map(|a| a.len() + 1).sum();
210+
if total_arg_len <= threshold {
211+
self.command.args(self.args);
212+
return Ok((self.command, tmp));
213+
}
214+
215+
let mut arg = OsString::from("@");
216+
arg.push(tmp.path());
217+
self.command.arg(arg);
218+
219+
let cap = self.args.iter().map(|arg| arg.len() + 1).sum::<usize>();
220+
let mut buf = Vec::with_capacity(cap);
221+
for arg in &self.args {
222+
let arg = arg.to_str().ok_or_else(|| {
223+
std::io::Error::other(format!(
224+
"argument for argfile contains invalid UTF-8 characters: `{}`",
225+
arg.to_string_lossy()
226+
))
227+
})?;
228+
if arg.contains('\n') {
229+
return Err(std::io::Error::other(format!(
230+
"argument for argfile contains newlines: `{arg}`"
231+
)));
232+
}
233+
writeln!(buf, "{arg}")?;
234+
}
235+
tmp.write_all(&buf)?;
236+
237+
Ok((self.command, tmp))
238+
}
239+
}

src/ci/github-actions/jobs.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
# dynamically in CI from ci.yml.
33
runners:
44
- &base-job
5-
env: { }
5+
env:
6+
# This force enables the new Cargo build-dir layout for all CI jobs.
7+
# The new layout will become the default soon so we enable it on everything
8+
# to catch issues as soon as possible.
9+
# See: https://github.com/rust-lang/cargo/issues/15010
10+
CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT: true
611

712
- &job-linux-4c
813
os: ubuntu-24.04

src/tools/compiletest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ rustfix = "0.8.1"
3131
semver = { version = "1.0.23", features = ["serde"] }
3232
serde = { version = "1.0", features = ["derive"] }
3333
serde_json = "1.0"
34+
tempfile = "3.23.0"
3435
tracing = "0.1"
3536
tracing-subscriber = { version = "0.3.3", default-features = false, features = ["ansi", "env-filter", "fmt", "parking_lot", "smallvec"] }
3637
unified-diff = "0.2.1"

0 commit comments

Comments
 (0)