Auto merge of #109133 - weihanglo:make-cargo-a-workspace, r=ehuss

Make cargo a workspace

8 commits in 7bf43f028ba5eb1f4d70d271c2546c38512c9875..39116ccc9b420a883a98a960f0597f9cf87414b8
2023-04-10 16:01:41 +0000 to 2023-04-15 20:24:15 +0000

- Make cargo a workspace (rust-lang/cargo#11851)
- Fix flaky not_found_permutations test. (rust-lang/cargo#11976)
- Use restricted Damerau-Levenshtein algorithm (rust-lang/cargo#11963)
- Correct the bug report for `cargo clippy --fix` (rust-lang/cargo#11882)
- Stabilize `cargo logout` (rust-lang/cargo#11950)
- Add more information to HTTP errors to help with debugging. (rust-lang/cargo#11878)
- Use registry.default for login/logout (rust-lang/cargo#11949)
- Change -C to be unstable (rust-lang/cargo#11960)

---

### What does this PR try to resolve?

Making cargo a workspace.

Why doing this?

* `rustc-workspace-hack` is primarily for sharing dependencies between rls and cargo, as rls previously depends on cargo. After rls retired, it is no longer the case sharing dependencies.
* It's q bit painful that cargo needs to deal with some dependency and licensing complexities. For example, #108665 failed because of the interaction bewteen `windows-sys` and `raw-dylib`. It currenctly blocks cargo's feature `-Zgitxodie` from moving forward.
* See rust-lang/cargo#11851

### Benchmark result

I've done a simple benchmark on both keeping or removing entire `rustc-workspace-hack`. It had no significant difference. Both took ~2m30s to finish `./x.py build -j8 src/tools/cargo src/tools/rls src/tools/clippy src/tools/miri src/tools/rustfmt`. Environment info:

```
host: aarch64-apple-darwin
os: Mac OS 13.2.1 [64-bit]
```

A sophisticated benchmark may be needed.

### Additional information

This depends on prior works from `@Muscraft` and `@ehuss.` Credits to them!
This commit is contained in:
bors 2023-04-16 19:26:02 +00:00
commit d0f204e4d7
13 changed files with 125 additions and 2130 deletions

1795
Cargo.lock

File diff suppressed because it is too large Load Diff

View File

@ -22,12 +22,6 @@ members = [
"src/tools/remote-test-server", "src/tools/remote-test-server",
"src/tools/rust-installer", "src/tools/rust-installer",
"src/tools/rust-demangler", "src/tools/rust-demangler",
"src/tools/cargo",
"src/tools/cargo/crates/credential/cargo-credential-1password",
"src/tools/cargo/crates/credential/cargo-credential-macos-keychain",
"src/tools/cargo/crates/credential/cargo-credential-wincred",
"src/tools/cargo/crates/mdman",
# "src/tools/cargo/crates/resolver-tests",
"src/tools/rustdoc", "src/tools/rustdoc",
"src/tools/rls", "src/tools/rls",
"src/tools/rustfmt", "src/tools/rustfmt",
@ -106,10 +100,6 @@ miniz_oxide.debug = 0
object.debug = 0 object.debug = 0
[patch.crates-io] [patch.crates-io]
# See comments in `src/tools/rustc-workspace-hack/README.md` for what's going on
# here
rustc-workspace-hack = { path = 'src/tools/rustc-workspace-hack' }
# See comments in `library/rustc-std-workspace-core/README.md` for what's going on # See comments in `library/rustc-std-workspace-core/README.md` for what's going on
# here # here
rustc-std-workspace-core = { path = 'library/rustc-std-workspace-core' } rustc-std-workspace-core = { path = 'library/rustc-std-workspace-core' }

View File

@ -822,7 +822,8 @@ class RustBuild(object):
if self.use_vendored_sources: if self.use_vendored_sources:
vendor_dir = os.path.join(self.rust_root, 'vendor') vendor_dir = os.path.join(self.rust_root, 'vendor')
if not os.path.exists(vendor_dir): if not os.path.exists(vendor_dir):
sync_dirs = "--sync ./src/tools/rust-analyzer/Cargo.toml " \ sync_dirs = "--sync ./src/tools/cargo/Cargo.toml " \
"--sync ./src/tools/rust-analyzer/Cargo.toml " \
"--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \ "--sync ./compiler/rustc_codegen_cranelift/Cargo.toml " \
"--sync ./src/bootstrap/Cargo.toml " "--sync ./src/bootstrap/Cargo.toml "
print('error: vendoring required, but vendor directory does not exist.') print('error: vendoring required, but vendor directory does not exist.')

View File

@ -996,11 +996,14 @@ impl Step for PlainSourceTarball {
// If we're building from git sources, we need to vendor a complete distribution. // If we're building from git sources, we need to vendor a complete distribution.
if builder.rust_info().is_managed_git_subrepository() { if builder.rust_info().is_managed_git_subrepository() {
// Ensure we have the submodules checked out. // Ensure we have the submodules checked out.
builder.update_submodule(Path::new("src/tools/cargo"));
builder.update_submodule(Path::new("src/tools/rust-analyzer")); builder.update_submodule(Path::new("src/tools/rust-analyzer"));
// Vendor all Cargo dependencies // Vendor all Cargo dependencies
let mut cmd = Command::new(&builder.initial_cargo); let mut cmd = Command::new(&builder.initial_cargo);
cmd.arg("vendor") cmd.arg("vendor")
.arg("--sync")
.arg(builder.src.join("./src/tools/cargo/Cargo.toml"))
.arg("--sync") .arg("--sync")
.arg(builder.src.join("./src/tools/rust-analyzer/Cargo.toml")) .arg(builder.src.join("./src/tools/rust-analyzer/Cargo.toml"))
.arg("--sync") .arg("--sync")

View File

@ -238,8 +238,6 @@ pub struct Build {
ci_env: CiEnv, ci_env: CiEnv,
delayed_failures: RefCell<Vec<String>>, delayed_failures: RefCell<Vec<String>>,
prerelease_version: Cell<Option<u32>>, prerelease_version: Cell<Option<u32>>,
tool_artifacts:
RefCell<HashMap<TargetSelection, HashMap<String, (&'static str, PathBuf, Vec<String>)>>>,
#[cfg(feature = "build-metrics")] #[cfg(feature = "build-metrics")]
metrics: metrics::BuildMetrics, metrics: metrics::BuildMetrics,
@ -458,7 +456,6 @@ impl Build {
ci_env: CiEnv::current(), ci_env: CiEnv::current(),
delayed_failures: RefCell::new(Vec::new()), delayed_failures: RefCell::new(Vec::new()),
prerelease_version: Cell::new(None), prerelease_version: Cell::new(None),
tool_artifacts: Default::default(),
#[cfg(feature = "build-metrics")] #[cfg(feature = "build-metrics")]
metrics: metrics::BuildMetrics::init(), metrics: metrics::BuildMetrics::init(),

View File

@ -7,12 +7,16 @@ use crate::cache::INTERNER;
use crate::util::output; use crate::util::output;
use crate::{Build, Crate}; use crate::{Build, Crate};
#[derive(Deserialize)] /// For more information, see the output of
/// <https://doc.rust-lang.org/nightly/cargo/commands/cargo-metadata.html>
#[derive(Debug, Deserialize)]
struct Output { struct Output {
packages: Vec<Package>, packages: Vec<Package>,
} }
#[derive(Deserialize)] /// For more information, see the output of
/// <https://doc.rust-lang.org/nightly/cargo/commands/cargo-metadata.html>
#[derive(Debug, Deserialize)]
struct Package { struct Package {
name: String, name: String,
source: Option<String>, source: Option<String>,
@ -20,25 +24,18 @@ struct Package {
dependencies: Vec<Dependency>, dependencies: Vec<Dependency>,
} }
#[derive(Deserialize)] /// For more information, see the output of
/// <https://doc.rust-lang.org/nightly/cargo/commands/cargo-metadata.html>
#[derive(Debug, Deserialize)]
struct Dependency { struct Dependency {
name: String, name: String,
source: Option<String>, source: Option<String>,
} }
/// Collects and stores package metadata of each workspace members into `build`,
/// by executing `cargo metadata` commands.
pub fn build(build: &mut Build) { pub fn build(build: &mut Build) {
// Run `cargo metadata` to figure out what crates we're testing. for package in workspace_members(build) {
let mut cargo = Command::new(&build.initial_cargo);
cargo
.arg("metadata")
.arg("--format-version")
.arg("1")
.arg("--no-deps")
.arg("--manifest-path")
.arg(build.src.join("Cargo.toml"));
let output = output(&mut cargo);
let output: Output = serde_json::from_str(&output).unwrap();
for package in output.packages {
if package.source.is_none() { if package.source.is_none() {
let name = INTERNER.intern_string(package.name); let name = INTERNER.intern_string(package.name);
let mut path = PathBuf::from(package.manifest_path); let mut path = PathBuf::from(package.manifest_path);
@ -57,3 +54,35 @@ pub fn build(build: &mut Build) {
} }
} }
} }
/// Invokes `cargo metadata` to get package metadata of each workspace member.
///
/// Note that `src/tools/cargo` is no longer a workspace member but we still
/// treat it as one here, by invoking an additional `cargo metadata` command.
fn workspace_members(build: &Build) -> impl Iterator<Item = Package> {
let cmd_metadata = |manifest_path| {
let mut cargo = Command::new(&build.initial_cargo);
cargo
.arg("metadata")
.arg("--format-version")
.arg("1")
.arg("--no-deps")
.arg("--manifest-path")
.arg(manifest_path);
cargo
};
// Collects `metadata.packages` from the root workspace.
let root_manifest_path = build.src.join("Cargo.toml");
let root_output = output(&mut cmd_metadata(&root_manifest_path));
let Output { packages, .. } = serde_json::from_str(&root_output).unwrap();
// Collects `metadata.packages` from src/tools/cargo separately.
let cargo_manifest_path = build.src.join("src/tools/cargo/Cargo.toml");
let cargo_output = output(&mut cmd_metadata(&cargo_manifest_path));
let Output { packages: cargo_packages, .. } = serde_json::from_str(&cargo_output).unwrap();
// We only care about the root package from `src/tool/cargo` workspace.
let cargo_package = cargo_packages.into_iter().find(|pkg| pkg.name == "cargo").into_iter();
packages.into_iter().chain(cargo_package)
}

View File

@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@ -120,135 +119,9 @@ impl Step for ToolBuild {
&self.target, &self.target,
); );
builder.info(&msg); builder.info(&msg);
let mut duplicates = Vec::new();
let is_expected = compile::stream_cargo(builder, cargo, vec![], &mut |msg| {
// Only care about big things like the RLS/Cargo for now
match tool {
"rls" | "cargo" | "clippy-driver" | "miri" | "rustfmt" => {}
_ => return, let mut cargo = Command::from(cargo);
} let is_expected = builder.try_run_quiet(&mut cargo);
let (id, features, filenames) = match msg {
compile::CargoMessage::CompilerArtifact {
package_id,
features,
filenames,
target: _,
} => (package_id, features, filenames),
_ => return,
};
let features = features.iter().map(|s| s.to_string()).collect::<Vec<_>>();
for path in filenames {
let val = (tool, PathBuf::from(&*path), features.clone());
// we're only interested in deduplicating rlibs for now
if val.1.extension().and_then(|s| s.to_str()) != Some("rlib") {
continue;
}
// Don't worry about compiles that turn out to be host
// dependencies or build scripts. To skip these we look for
// anything that goes in `.../release/deps` but *doesn't* go in
// `$target/release/deps`. This ensure that outputs in
// `$target/release` are still considered candidates for
// deduplication.
if let Some(parent) = val.1.parent() {
if parent.ends_with("release/deps") {
let maybe_target = parent
.parent()
.and_then(|p| p.parent())
.and_then(|p| p.file_name())
.and_then(|p| p.to_str())
.unwrap();
if maybe_target != &*target.triple {
continue;
}
}
}
// Record that we've built an artifact for `id`, and if one was
// already listed then we need to see if we reused the same
// artifact or produced a duplicate.
let mut artifacts = builder.tool_artifacts.borrow_mut();
let prev_artifacts = artifacts.entry(target).or_default();
let prev = match prev_artifacts.get(&*id) {
Some(prev) => prev,
None => {
prev_artifacts.insert(id.to_string(), val);
continue;
}
};
if prev.1 == val.1 {
return; // same path, same artifact
}
// If the paths are different and one of them *isn't* inside of
// `release/deps`, then it means it's probably in
// `$target/release`, or it's some final artifact like
// `libcargo.rlib`. In these situations Cargo probably just
// copied it up from `$target/release/deps/libcargo-xxxx.rlib`,
// so if the features are equal we can just skip it.
let prev_no_hash = prev.1.parent().unwrap().ends_with("release/deps");
let val_no_hash = val.1.parent().unwrap().ends_with("release/deps");
if prev.2 == val.2 || !prev_no_hash || !val_no_hash {
return;
}
// ... and otherwise this looks like we duplicated some sort of
// compilation, so record it to generate an error later.
duplicates.push((id.to_string(), val, prev.clone()));
}
});
if is_expected && !duplicates.is_empty() {
eprintln!(
"duplicate artifacts found when compiling a tool, this \
typically means that something was recompiled because \
a transitive dependency has different features activated \
than in a previous build:\n"
);
let (same, different): (Vec<_>, Vec<_>) =
duplicates.into_iter().partition(|(_, cur, prev)| cur.2 == prev.2);
if !same.is_empty() {
eprintln!(
"the following dependencies are duplicated although they \
have the same features enabled:"
);
for (id, cur, prev) in same {
eprintln!(" {}", id);
// same features
eprintln!(" `{}` ({:?})\n `{}` ({:?})", cur.0, cur.1, prev.0, prev.1);
}
}
if !different.is_empty() {
eprintln!("the following dependencies have different features:");
for (id, cur, prev) in different {
eprintln!(" {}", id);
let cur_features: HashSet<_> = cur.2.into_iter().collect();
let prev_features: HashSet<_> = prev.2.into_iter().collect();
eprintln!(
" `{}` additionally enabled features {:?} at {:?}",
cur.0,
&cur_features - &prev_features,
cur.1
);
eprintln!(
" `{}` additionally enabled features {:?} at {:?}",
prev.0,
&prev_features - &cur_features,
prev.1
);
}
}
eprintln!();
eprintln!(
"to fix this you will probably want to edit the local \
src/tools/rustc-workspace-hack/Cargo.toml crate, as \
that will update the dependency graph to ensure that \
these crates all share the same feature set"
);
panic!("tools should not compile multiple copies of the same crate");
}
builder.save_toolstate( builder.save_toolstate(
tool, tool,
@ -299,7 +172,9 @@ pub fn prepare_tool_cargo(
|| path.ends_with("rustfmt") || path.ends_with("rustfmt")
{ {
cargo.env("LIBZ_SYS_STATIC", "1"); cargo.env("LIBZ_SYS_STATIC", "1");
features.push("rustc-workspace-hack/all-static".to_string()); }
if path.ends_with("cargo") {
features.push("all-static".to_string());
} }
} }

@ -1 +1 @@
Subproject commit 84b7041fd2745ee6b3b4a150314f81aabb78e6b2 Subproject commit d0a4cbcee614fdb7ba66e860e603a00a644d71f8

View File

@ -7,7 +7,3 @@ license = "Apache-2.0/MIT"
[dependencies] [dependencies]
serde = { version = "1.0.143", features = ["derive"] } serde = { version = "1.0.143", features = ["derive"] }
serde_json = "1.0.83" serde_json = "1.0.83"
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
# for more information.
rustc-workspace-hack = "1.0.0"

View File

@ -1,102 +0,0 @@
[package]
name = "rustc-workspace-hack"
version = "1.0.0"
license = 'MIT OR Apache-2.0'
description = """
Hack for the compiler's own build system
"""
edition = "2021"
[lib]
path = "lib.rs"
# For documentation about what this is and why in the world these dependencies
# are appearing, see `README.md`.
[target.'cfg(windows)'.dependencies.winapi]
version = "0.3"
features = [
"accctrl",
"aclapi",
"basetsd",
"cfg",
"consoleapi",
"errhandlingapi",
"evntrace",
"fibersapi",
"handleapi",
"in6addr",
"inaddr",
"ioapiset",
"jobapi",
"jobapi2",
"knownfolders",
"libloaderapi",
"lmcons",
"memoryapi",
"minschannel",
"minwinbase",
"mstcpip",
"mswsock",
"namedpipeapi",
"ntdef",
"ntsecapi",
"ntstatus",
"objbase",
"processenv",
"processthreadsapi",
"profileapi",
"psapi",
"schannel",
"securitybaseapi",
"shellapi",
"shlobj",
"sspi",
"synchapi",
"sysinfoapi",
"threadpoollegacyapiset",
"timezoneapi",
"userenv",
"winbase",
"wincon",
"wincrypt",
"windef",
"winioctl",
"winnt",
"winreg",
"winsock2",
"winuser",
"ws2def",
"ws2ipdef",
"ws2tcpip",
]
[dependencies]
bstr = { version = "0.2.17", features = ["default"] }
clap = { version = "3.1.1", features = ["derive", "clap_derive"]}
curl-sys = { version = "0.4.13", features = ["http2", "libnghttp2-sys"], optional = true }
# Ensure `extra_traits` of libc, which is used transitively by Cargo.
libc = { version = "0.2", features = ["extra_traits"] }
# Ensure `js` of getrandom, which is (unfortunately) used transitively by Cargo.
getrandom = { version = "0.2", features = ["js"] }
# Ensure default features of libz-sys, which are disabled in some scenarios.
libz-sys = { version = "1.1.2" }
# Ensure default features of regex, which are disabled in some scenarios.
regex = { version = "1.5.6" }
serde_json = { version = "1.0.31", features = ["raw_value", "unbounded_depth"] }
syn = { version = "1", features = ['full', 'visit', 'visit-mut'] } # `visit-mut` required by Cargo via `gix`
url = { version = "2.0", features = ['serde'] }
# Ensure default features of rand, which are disabled in some scenarios.
rand = { version = "0.8.5" }
# Ensure features of `hashbrown`, `smallvec`, and `once_cell`,
# which are used transitively by Cargo (via `gix`).
hashbrown = { version = "0.12.3", default-features = false, features = ["inline-more"] }
once_cell = { version = "1.16.0", default-features = false, features = ["unstable"] }
smallvec = { version = "1.10.0", features = ["write"] }
[target.'cfg(not(windows))'.dependencies]
openssl = { version = "0.10.35", optional = true }
[features]
all-static = ['openssl/vendored', 'curl-sys/static-curl', 'curl-sys/force-system-lib-on-osx']

View File

@ -1,25 +0,0 @@
# `rustc-workspace-hack`
This crate is a bit of a hack to make workspaces in rustc work a bit better.
The rationale for this existence is a bit subtle, but the general idea is that
we want commands like `./x.py build src/tools/{clippy,cargo}` to share as
many dependencies as possible.
Each invocation is a different invocation of Cargo, however. Each time Cargo
runs a build it will re-resolve the dependency graph, notably selecting
different features sometimes for each build.
For example, let's say there's a very deep dependency like `winapi` in each of
these builds. For Cargo, `winapi` has 33 features enabled. In Clippy, however,
`winapi` has 22 features enabled. This means that building Cargo and then the
Clippy will actually build winapi twice, which in turn will build duplicates
of everything that depends on `winapi`. This is bad!
The goal of this crate is to solve this problem and ensure that the resolved
dependency graph for all of these tools is the same in the various subsets of
each tool, notably enabling the same features of transitive dependencies.
All tools vendored here depend on the `rustc-workspace-hack` crate on crates.io.
When on crates.io this crate is an empty crate that is just a noop. We override
it, however, in this workspace to this crate here, which means we can control
crates in the dependency graph for each of these tools.

View File

@ -1 +0,0 @@
// intentionally left blank

View File

@ -18,6 +18,7 @@ const LICENSES: &[&str] = &[
"ISC", "ISC",
"Unlicense/MIT", "Unlicense/MIT",
"Unlicense OR MIT", "Unlicense OR MIT",
"0BSD",
"0BSD OR MIT OR Apache-2.0", // adler license "0BSD OR MIT OR Apache-2.0", // adler license
"Zlib OR Apache-2.0 OR MIT", // tinyvec "Zlib OR Apache-2.0 OR MIT", // tinyvec
"MIT OR Apache-2.0 OR Zlib", // tinyvec_macros "MIT OR Apache-2.0 OR Zlib", // tinyvec_macros
@ -33,30 +34,35 @@ const LICENSES: &[&str] = &[
const EXCEPTIONS: &[(&str, &str)] = &[ const EXCEPTIONS: &[(&str, &str)] = &[
("ar_archive_writer", "Apache-2.0 WITH LLVM-exception"), // rustc ("ar_archive_writer", "Apache-2.0 WITH LLVM-exception"), // rustc
("mdbook", "MPL-2.0"), // mdbook ("mdbook", "MPL-2.0"), // mdbook
("openssl", "Apache-2.0"), // cargo, mdbook
("colored", "MPL-2.0"), // rustfmt ("colored", "MPL-2.0"), // rustfmt
("ryu", "Apache-2.0 OR BSL-1.0"), // cargo/... (because of serde) ("ryu", "Apache-2.0 OR BSL-1.0"), // cargo/... (because of serde)
("bytesize", "Apache-2.0"), // cargo
("im-rc", "MPL-2.0+"), // cargo
("sized-chunks", "MPL-2.0+"), // cargo via im-rc
("bitmaps", "MPL-2.0+"), // cargo via im-rc
("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"), // cargo via pasetors
("subtle", "BSD-3-Clause"), // cargo via pasetors
("dunce", "CC0-1.0 OR MIT-0"), // cargo via gix (and dev dependency)
("imara-diff", "Apache-2.0"), // cargo via gix
("sha1_smol", "BSD-3-Clause"), // cargo via gix
("unicode-bom", "Apache-2.0"), // cargo via gix
("instant", "BSD-3-Clause"), // rustc_driver/tracing-subscriber/parking_lot ("instant", "BSD-3-Clause"), // rustc_driver/tracing-subscriber/parking_lot
("snap", "BSD-3-Clause"), // rustc ("snap", "BSD-3-Clause"), // rustc
("fluent-langneg", "Apache-2.0"), // rustc (fluent translations) ("fluent-langneg", "Apache-2.0"), // rustc (fluent translations)
("self_cell", "Apache-2.0"), // rustc (fluent translations) ("self_cell", "Apache-2.0"), // rustc (fluent translations)
// FIXME: this dependency violates the documentation comment above: // FIXME: this dependency violates the documentation comment above:
("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target ("fortanix-sgx-abi", "MPL-2.0"), // libstd but only for `sgx` target
("similar", "Apache-2.0"), // cargo (dev dependency)
("normalize-line-endings", "Apache-2.0"), // cargo (dev dependency)
("dissimilar", "Apache-2.0"), // rustdoc, rustc_lexer (few tests) via expect-test, (dev deps) ("dissimilar", "Apache-2.0"), // rustdoc, rustc_lexer (few tests) via expect-test, (dev deps)
]; ];
const EXCEPTIONS_CARGO: &[(&str, &str)] = &[
("bitmaps", "MPL-2.0+"),
("bytesize", "Apache-2.0"),
("dunce", "CC0-1.0 OR MIT-0"),
("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"),
("im-rc", "MPL-2.0+"),
("imara-diff", "Apache-2.0"),
("instant", "BSD-3-Clause"),
("normalize-line-endings", "Apache-2.0"),
("openssl", "Apache-2.0"),
("ryu", "Apache-2.0 OR BSL-1.0"),
("sha1_smol", "BSD-3-Clause"),
("similar", "Apache-2.0"),
("sized-chunks", "MPL-2.0+"),
("subtle", "BSD-3-Clause"),
("unicode-bom", "Apache-2.0"),
];
const EXCEPTIONS_CRANELIFT: &[(&str, &str)] = &[ const EXCEPTIONS_CRANELIFT: &[(&str, &str)] = &[
("cranelift-bforest", "Apache-2.0 WITH LLVM-exception"), ("cranelift-bforest", "Apache-2.0 WITH LLVM-exception"),
("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"), ("cranelift-codegen", "Apache-2.0 WITH LLVM-exception"),
@ -156,7 +162,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
"lazy_static", "lazy_static",
"libc", "libc",
"libloading", "libloading",
"libz-sys",
"litemap", "litemap",
"lock_api", "lock_api",
"log", "log",
@ -177,7 +182,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
"perf-event-open-sys", "perf-event-open-sys",
"petgraph", "petgraph",
"pin-project-lite", "pin-project-lite",
"pkg-config",
"polonius-engine", "polonius-engine",
"ppv-lite86", "ppv-lite86",
"proc-macro-hack", "proc-macro-hack",
@ -217,7 +221,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
"stable_deref_trait", "stable_deref_trait",
"stacker", "stacker",
"static_assertions", "static_assertions",
"subtle", // dependency of cargo (via pasetors)
"syn", "syn",
"synstructure", "synstructure",
"tempfile", "tempfile",
@ -256,7 +259,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
"unicode-security", "unicode-security",
"unicode-width", "unicode-width",
"unicode-xid", "unicode-xid",
"vcpkg",
"valuable", "valuable",
"version_check", "version_check",
"wasi", "wasi",
@ -333,13 +335,6 @@ const PERMITTED_CRANELIFT_DEPENDENCIES: &[&str] = &[
"windows_x86_64_msvc", "windows_x86_64_msvc",
]; ];
const FORBIDDEN_TO_HAVE_DUPLICATES: &[&str] = &[
// This crate takes quite a long time to build, so don't allow two versions of them
// to accidentally sneak into our dependency graph, in order to ensure we keep our CI times
// under control.
"cargo",
];
/// Dependency checks. /// Dependency checks.
/// ///
/// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path /// `root` is path to the directory with the root `Cargo.toml` (for the workspace). `cargo` is path
@ -359,8 +354,16 @@ pub fn check(root: &Path, cargo: &Path, bad: &mut bool) {
&["rustc_driver", "rustc_codegen_llvm"], &["rustc_driver", "rustc_codegen_llvm"],
bad, bad,
); );
check_crate_duplicate(&metadata, FORBIDDEN_TO_HAVE_DUPLICATES, bad);
check_rustfix(&metadata, bad); // Check cargo independently as it has it's own workspace.
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.cargo_path(cargo)
.manifest_path(root.join("src/tools/cargo/Cargo.toml"))
.features(cargo_metadata::CargoOpt::AllFeatures);
let cargo_metadata = t!(cmd.exec());
let runtime_ids = HashSet::new();
check_license_exceptions(&cargo_metadata, EXCEPTIONS_CARGO, runtime_ids, bad);
check_rustfix(&metadata, &cargo_metadata, bad);
// Check rustc_codegen_cranelift independently as it has it's own workspace. // Check rustc_codegen_cranelift independently as it has it's own workspace.
let mut cmd = cargo_metadata::MetadataCommand::new(); let mut cmd = cargo_metadata::MetadataCommand::new();
@ -377,7 +380,6 @@ pub fn check(root: &Path, cargo: &Path, bad: &mut bool) {
&["rustc_codegen_cranelift"], &["rustc_codegen_cranelift"],
bad, bad,
); );
check_crate_duplicate(&metadata, &[], bad);
let mut cmd = cargo_metadata::MetadataCommand::new(); let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.cargo_path(cargo) cmd.cargo_path(cargo)
@ -523,40 +525,6 @@ fn check_permitted_dependencies(
} }
} }
/// Prevents multiple versions of some expensive crates.
fn check_crate_duplicate(
metadata: &Metadata,
forbidden_to_have_duplicates: &[&str],
bad: &mut bool,
) {
for &name in forbidden_to_have_duplicates {
let matches: Vec<_> = metadata.packages.iter().filter(|pkg| pkg.name == name).collect();
match matches.len() {
0 => {
tidy_error!(
bad,
"crate `{}` is missing, update `check_crate_duplicate` \
if it is no longer used",
name
);
}
1 => {}
_ => {
tidy_error!(
bad,
"crate `{}` is duplicated in `Cargo.lock`, \
it is too expensive to build multiple times, \
so make sure only one version appears across all dependencies",
name
);
for pkg in matches {
println!(" * {}", pkg.id);
}
}
}
}
}
/// Finds a package with the given name. /// Finds a package with the given name.
fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package { fn pkg_from_name<'a>(metadata: &'a Metadata, name: &'static str) -> &'a Package {
let mut i = metadata.packages.iter().filter(|p| p.name == name); let mut i = metadata.packages.iter().filter(|p| p.name == name);
@ -606,19 +574,24 @@ fn deps_of_filtered<'a>(
} }
} }
fn direct_deps_of<'a>(metadata: &'a Metadata, pkg_id: &'a PackageId) -> Vec<&'a Package> { fn direct_deps_of<'a>(
metadata: &'a Metadata,
pkg_id: &'a PackageId,
) -> impl Iterator<Item = &'a Package> {
let resolve = metadata.resolve.as_ref().unwrap(); let resolve = metadata.resolve.as_ref().unwrap();
let node = resolve.nodes.iter().find(|n| &n.id == pkg_id).unwrap(); let node = resolve.nodes.iter().find(|n| &n.id == pkg_id).unwrap();
node.deps.iter().map(|dep| pkg_from_id(metadata, &dep.pkg)).collect() node.deps.iter().map(|dep| pkg_from_id(metadata, &dep.pkg))
} }
fn check_rustfix(metadata: &Metadata, bad: &mut bool) { fn check_rustfix(rust_metadata: &Metadata, cargo_metadata: &Metadata, bad: &mut bool) {
let cargo = pkg_from_name(metadata, "cargo"); let cargo = pkg_from_name(cargo_metadata, "cargo");
let compiletest = pkg_from_name(metadata, "compiletest"); let cargo_rustfix =
let cargo_deps = direct_deps_of(metadata, &cargo.id); direct_deps_of(cargo_metadata, &cargo.id).find(|p| p.name == "rustfix").unwrap();
let compiletest_deps = direct_deps_of(metadata, &compiletest.id);
let cargo_rustfix = cargo_deps.iter().find(|p| p.name == "rustfix").unwrap(); let compiletest = pkg_from_name(rust_metadata, "compiletest");
let compiletest_rustfix = compiletest_deps.iter().find(|p| p.name == "rustfix").unwrap(); let compiletest_rustfix =
direct_deps_of(rust_metadata, &compiletest.id).find(|p| p.name == "rustfix").unwrap();
if cargo_rustfix.version != compiletest_rustfix.version { if cargo_rustfix.version != compiletest_rustfix.version {
tidy_error!( tidy_error!(
bad, bad,