Skip to content

Commit da48140

Browse files
committed
Auto merge of rust-lang#110229 - jyn514:download-rustc-tests, r=albertlarsan68
Fix no_std tests that load libc from the sysroot when download-rustc is enabled There were a series of unfortunate interactions here. Here's an MCVE of the test this fixes (committed as `tests/ui/meta/no_std-extern-libc.rs`): ```rust #![crate_type = "lib"] #![no_std] #![feature(rustc_private)] extern crate libc; ``` Before, this would give an error about duplicate versions of libc: ``` error[E0464]: multiple candidates for `rlib` dependency `libc` found --> fake-test-src-base/allocator/no_std-alloc-error-handler-default.rs:15:1 | LL | extern crate libc; | ^^^^^^^^^^^^^^^^^^ | = note: candidate #1: /home/gh-jyn514/rust/build/aarch64-unknown-linux-gnu/stage2/lib/rustlib/aarch64-unknown-linux-gnu/lib/liblibc-358db1024b7d9957.rlib = note: candidate #2: /home/gh-jyn514/rust/build/aarch64-unknown-linux-gnu/stage2/lib/rustlib/aarch64-unknown-linux-gnu/lib/liblibc-ebc478710122a279.rmeta ``` Both these versions were downloaded from CI, but one came from the `rust-std` component and one came from `rustc-dev`: ``` ; tar -tf build/cache/f2d9a3d0771504f1ae776226a5799dcb4408a91a/rust-std-nightly-x86_64-unknown-linux-gnu.tar.xz | grep liblibc rust-std-nightly-x86_64-unknown-linux-gnu/rust-std-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-68a2d9e195dd6ed2.rlib ; tar -tf build/cache/f2d9a3d0771504f1ae776226a5799dcb4408a91a/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.xz | grep liblibc rustc-dev-nightly-x86_64-unknown-linux-gnu/rustc-dev/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-f226c9fbdd92a0fd.rmeta ``` The fix was to only copy files from `rust-std` unless a Step explicitly requests for the `rustc-dev` components to be available by calling `builder.ensure(compile::Rustc)`. To avoid having to re-parse the `rustc-dev.tar.xz` tarball every time, which is quite slow, this adds a new `build/host/ci-rustc/.rustc-dev-contents` cache file which stores only the names of files we need to copy into the sysroot. This also allows reverting the hack in rust-lang#110121; now that we only copy rustc-dev on-demand, we can correctly add the `Rustc` check artifacts into the sysroot, so that this works correctly even when `download-rustc` is forced to `true` and some tool depends on a local change to `compiler`. --- See rust-lang#108767 (comment) for why `no_std` is required for the MCVE test to fail; it's complicated and not particularly important. Fixes rust-lang#108767.
2 parents b3f1379 + 71f04bd commit da48140

File tree

4 files changed

+84
-17
lines changed

4 files changed

+84
-17
lines changed

src/bootstrap/check.rs

+3-11
Original file line numberDiff line numberDiff line change
@@ -271,17 +271,9 @@ impl Step for Rustc {
271271
false,
272272
);
273273

274-
// HACK: This avoids putting the newly built artifacts in the sysroot if we're using
275-
// `download-rustc`, to avoid "multiple candidates for `rmeta`" errors. Technically, that's
276-
// not quite right: people can set `download-rustc = true` to download even if there are
277-
// changes to the compiler, and in that case ideally we would put the *new* artifacts in the
278-
// sysroot, in case there are API changes that should be used by tools. In practice,
279-
// though, that should be very uncommon, and people can still disable download-rustc.
280-
if !builder.download_rustc() {
281-
let libdir = builder.sysroot_libdir(compiler, target);
282-
let hostdir = builder.sysroot_libdir(compiler, compiler.host);
283-
add_to_sysroot(&builder, &libdir, &hostdir, &librustc_stamp(builder, compiler, target));
284-
}
274+
let libdir = builder.sysroot_libdir(compiler, target);
275+
let hostdir = builder.sysroot_libdir(compiler, compiler.host);
276+
add_to_sysroot(&builder, &libdir, &hostdir, &librustc_stamp(builder, compiler, target));
285277
}
286278
}
287279

src/bootstrap/compile.rs

+48-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use std::borrow::Cow;
1010
use std::collections::HashSet;
1111
use std::env;
12+
use std::ffi::OsStr;
1213
use std::fs;
1314
use std::io::prelude::*;
1415
use std::io::BufReader;
@@ -652,8 +653,19 @@ impl Step for Rustc {
652653
// so its artifacts can't be reused.
653654
if builder.download_rustc() && compiler.stage != 0 {
654655
// Copy the existing artifacts instead of rebuilding them.
655-
// NOTE: this path is only taken for tools linking to rustc-dev.
656-
builder.ensure(Sysroot { compiler });
656+
// NOTE: this path is only taken for tools linking to rustc-dev (including ui-fulldeps tests).
657+
let sysroot = builder.ensure(Sysroot { compiler });
658+
659+
let ci_rustc_dir = builder.out.join(&*builder.build.build.triple).join("ci-rustc");
660+
for file in builder.config.rustc_dev_contents() {
661+
let src = ci_rustc_dir.join(&file);
662+
let dst = sysroot.join(file);
663+
if src.is_dir() {
664+
t!(fs::create_dir_all(dst));
665+
} else {
666+
builder.copy(&src, &dst);
667+
}
668+
}
657669
return;
658670
}
659671

@@ -1282,7 +1294,40 @@ impl Step for Sysroot {
12821294
}
12831295

12841296
// Copy the compiler into the correct sysroot.
1285-
builder.cp_r(&builder.ci_rustc_dir(builder.build.build), &sysroot);
1297+
// NOTE(#108767): We intentionally don't copy `rustc-dev` artifacts until they're requested with `builder.ensure(Rustc)`.
1298+
// This fixes an issue where we'd have multiple copies of libc in the sysroot with no way to tell which to load.
1299+
// There are a few quirks of bootstrap that interact to make this reliable:
1300+
// 1. The order `Step`s are run is hard-coded in `builder.rs` and not configurable. This
1301+
// avoids e.g. reordering `test::UiFulldeps` before `test::Ui` and causing the latter to
1302+
// fail because of duplicate metadata.
1303+
// 2. The sysroot is deleted and recreated between each invocation, so running `x test
1304+
// ui-fulldeps && x test ui` can't cause failures.
1305+
let mut filtered_files = Vec::new();
1306+
// Don't trim directories or files that aren't loaded per-target; they can't cause conflicts.
1307+
let suffix = format!("lib/rustlib/{}/lib", compiler.host);
1308+
for path in builder.config.rustc_dev_contents() {
1309+
let path = Path::new(&path);
1310+
if path.parent().map_or(false, |parent| parent.ends_with(&suffix)) {
1311+
filtered_files.push(path.file_name().unwrap().to_owned());
1312+
}
1313+
}
1314+
1315+
let filtered_extensions = [OsStr::new("rmeta"), OsStr::new("rlib"), OsStr::new("so")];
1316+
let ci_rustc_dir = builder.ci_rustc_dir(builder.config.build);
1317+
builder.cp_filtered(&ci_rustc_dir, &sysroot, &|path| {
1318+
if path.extension().map_or(true, |ext| !filtered_extensions.contains(&ext)) {
1319+
return true;
1320+
}
1321+
if !path.parent().map_or(true, |p| p.ends_with(&suffix)) {
1322+
return true;
1323+
}
1324+
if !filtered_files.iter().all(|f| f != path.file_name().unwrap()) {
1325+
builder.verbose_than(1, &format!("ignoring {}", path.display()));
1326+
false
1327+
} else {
1328+
true
1329+
}
1330+
});
12861331
return INTERNER.intern_path(sysroot);
12871332
}
12881333

src/bootstrap/download.rs

+26-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{
22
env,
33
ffi::{OsStr, OsString},
44
fs::{self, File},
5-
io::{BufRead, BufReader, ErrorKind},
5+
io::{BufRead, BufReader, BufWriter, ErrorKind, Write},
66
path::{Path, PathBuf},
77
process::{Command, Stdio},
88
};
@@ -262,10 +262,20 @@ impl Config {
262262
let directory_prefix = Path::new(Path::new(uncompressed_filename).file_stem().unwrap());
263263

264264
// decompress the file
265-
let data = t!(File::open(tarball));
265+
let data = t!(File::open(tarball), format!("file {} not found", tarball.display()));
266266
let decompressor = XzDecoder::new(BufReader::new(data));
267267

268268
let mut tar = tar::Archive::new(decompressor);
269+
270+
// `compile::Sysroot` needs to know the contents of the `rustc-dev` tarball to avoid adding
271+
// it to the sysroot unless it was explicitly requested. But parsing the 100 MB tarball is slow.
272+
// Cache the entries when we extract it so we only have to read it once.
273+
let mut recorded_entries = if dst.ends_with("ci-rustc") && pattern == "rustc-dev" {
274+
Some(BufWriter::new(t!(File::create(dst.join(".rustc-dev-contents")))))
275+
} else {
276+
None
277+
};
278+
269279
for member in t!(tar.entries()) {
270280
let mut member = t!(member);
271281
let original_path = t!(member.path()).into_owned();
@@ -283,13 +293,19 @@ impl Config {
283293
if !t!(member.unpack_in(dst)) {
284294
panic!("path traversal attack ??");
285295
}
296+
if let Some(record) = &mut recorded_entries {
297+
t!(writeln!(record, "{}", short_path.to_str().unwrap()));
298+
}
286299
let src_path = dst.join(original_path);
287300
if src_path.is_dir() && dst_path.exists() {
288301
continue;
289302
}
290303
t!(fs::rename(src_path, dst_path));
291304
}
292-
t!(fs::remove_dir_all(dst.join(directory_prefix)));
305+
let dst_dir = dst.join(directory_prefix);
306+
if dst_dir.exists() {
307+
t!(fs::remove_dir_all(&dst_dir), format!("failed to remove {}", dst_dir.display()));
308+
}
293309
}
294310

295311
/// Returns whether the SHA256 checksum of `path` matches `expected`.
@@ -365,6 +381,13 @@ impl Config {
365381
Some(rustfmt_path)
366382
}
367383

384+
pub(crate) fn rustc_dev_contents(&self) -> Vec<String> {
385+
assert!(self.download_rustc());
386+
let ci_rustc_dir = self.out.join(&*self.build.triple).join("ci-rustc");
387+
let rustc_dev_contents_file = t!(File::open(ci_rustc_dir.join(".rustc-dev-contents")));
388+
t!(BufReader::new(rustc_dev_contents_file).lines().collect())
389+
}
390+
368391
pub(crate) fn download_ci_rustc(&self, commit: &str) {
369392
self.verbose(&format!("using downloaded stage2 artifacts from CI (commit {commit})"));
370393

tests/ui/meta/no_std-extern-libc.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Test that `download-rustc` doesn't put duplicate copies of libc in the sysroot.
2+
// check-pass
3+
#![crate_type = "lib"]
4+
#![no_std]
5+
#![feature(rustc_private)]
6+
7+
extern crate libc;

0 commit comments

Comments
 (0)