Skip to content

Commit fec8628

Browse files
committed
Use binary-dep-depinfo to resolve UI dependencies
1 parent 01d3816 commit fec8628

File tree

3 files changed

+81
-79
lines changed

3 files changed

+81
-79
lines changed

.cargo/config

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ lintcheck = "run --target-dir lintcheck/target --package lintcheck --bin lintche
55
collect-metadata = "test --test dogfood --features metadata-collector-lint -- run_metadata_collection_lint --ignored"
66

77
[build]
8-
rustflags = ["-Zunstable-options"]
8+
# -Zbinary-dep-depinfo allows us to track which rlib files to use for compiling UI tests
9+
rustflags = ["-Zunstable-options", "-Zbinary-dep-depinfo"]

Cargo.toml

+9-4
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ tempfile = { version = "3.1.0", optional = true }
3232
cargo_metadata = "0.12"
3333
compiletest_rs = { version = "0.6.0", features = ["tmp"] }
3434
tester = "0.9"
35-
serde = { version = "1.0", features = ["derive"] }
36-
derive-new = "0.5"
3735
regex = "1.4"
38-
quote = "1"
39-
syn = { version = "1", features = ["full"] }
4036
# This is used by the `collect-metadata` alias.
4137
filetime = "0.2"
4238

@@ -45,6 +41,15 @@ filetime = "0.2"
4541
# for more information.
4642
rustc-workspace-hack = "1.0.0"
4743

44+
# UI test dependencies
45+
clippy_utils = { path = "clippy_utils" }
46+
derive-new = "0.5"
47+
if_chain = "1.0"
48+
itertools = "0.10.1"
49+
quote = "1"
50+
serde = { version = "1.0", features = ["derive"] }
51+
syn = { version = "1", features = ["full"] }
52+
4853
[build-dependencies]
4954
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
5055

tests/compile-test.rs

+70-74
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use compiletest_rs as compiletest;
66
use compiletest_rs::common::Mode as TestMode;
77

8+
use std::collections::HashMap;
89
use std::env::{self, remove_var, set_var, var_os};
910
use std::ffi::{OsStr, OsString};
1011
use std::fs;
@@ -16,6 +17,28 @@ mod cargo;
1617
// whether to run internal tests or not
1718
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
1819

20+
/// All crates used in UI tests are listed here
21+
static TEST_DEPENDENCIES: &[&str] = &[
22+
"clippy_utils",
23+
"derive_new",
24+
"if_chain",
25+
"itertools",
26+
"quote",
27+
"regex",
28+
"serde",
29+
"serde_derive",
30+
"syn",
31+
];
32+
33+
// Test dependencies may need an `extern crate` here to ensure that they show up
34+
// in the depinfo file (otherwise cargo thinks they are unused)
35+
extern crate clippy_utils;
36+
extern crate derive_new;
37+
extern crate if_chain;
38+
extern crate itertools;
39+
extern crate quote;
40+
extern crate syn;
41+
1942
fn host_lib() -> PathBuf {
2043
option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
2144
}
@@ -24,72 +47,51 @@ fn clippy_driver_path() -> PathBuf {
2447
option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from)
2548
}
2649

27-
// When we'll want to use `extern crate ..` for a dependency that is used
28-
// both by the crate and the compiler itself, we can't simply pass -L flags
29-
// as we'll get a duplicate matching versions. Instead, disambiguate with
30-
// `--extern dep=path`.
31-
// See https://github.com/rust-lang/rust-clippy/issues/4015.
32-
//
33-
// FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files.
34-
// Because it would force-rebuild if the options passed to `build` command is not the same
35-
// as what we manually pass to `cargo` invocation
36-
fn third_party_crates() -> String {
37-
use std::collections::HashMap;
38-
static CRATES: &[&str] = &[
39-
"clippy_lints",
40-
"clippy_utils",
41-
"if_chain",
42-
"itertools",
43-
"quote",
44-
"regex",
45-
"serde",
46-
"serde_derive",
47-
"syn",
48-
];
49-
let dep_dir = cargo::TARGET_LIB.join("deps");
50-
let mut crates: HashMap<&str, Vec<PathBuf>> = HashMap::with_capacity(CRATES.len());
51-
let mut flags = String::new();
52-
for entry in fs::read_dir(dep_dir).unwrap().flatten() {
53-
let path = entry.path();
54-
if let Some(name) = try {
55-
let name = path.file_name()?.to_str()?;
56-
let (name, _) = name.strip_suffix(".rlib")?.strip_prefix("lib")?.split_once('-')?;
57-
CRATES.iter().copied().find(|&c| c == name)?
58-
} {
59-
flags += &format!(" --extern {}={}", name, path.display());
60-
crates.entry(name).or_default().push(path.clone());
50+
/// Produces a string with an `--extern` flag for all UI test crate
51+
/// dependencies.
52+
///
53+
/// The dependency files are located by parsing the depinfo file for this test
54+
/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
55+
/// dependencies must be added to Cargo.toml at the project root. Test
56+
/// dependencies that are not *directly* used by this test module require an
57+
/// `extern crate` declaration.
58+
fn extern_flags() -> String {
59+
let current_exe_depinfo = {
60+
let mut path = env::current_exe().unwrap();
61+
path.set_extension("d");
62+
std::fs::read_to_string(path).unwrap()
63+
};
64+
let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
65+
for line in current_exe_depinfo.lines() {
66+
let name_path = try {
67+
// each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
68+
let path = line.strip_suffix(':')?;
69+
let exts = ["rlib", "so", "dylib", "dll"];
70+
let part = exts.iter().find_map(|ext| path.strip_suffix(ext)?.strip_suffix('.'))?;
71+
let part = part.rsplit_once('-')?.0.rsplit_once(|c| matches!(c, '/' | '\\'))?.1;
72+
// the "lib" prefix is not present for dll files
73+
let name = part.strip_prefix("lib").unwrap_or(part);
74+
TEST_DEPENDENCIES.contains(&name).then(|| (name, path))?
75+
};
76+
if let Some((name, path)) = name_path {
77+
// A dependency may be listed twice if it is available in sysroot,
78+
// and the sysroot dependencies are listed first. As of the writing,
79+
// this only seems to apply to if_chain.
80+
crates.insert(name, path);
6181
}
6282
}
63-
crates.retain(|_, paths| paths.len() > 1);
64-
if !crates.is_empty() {
65-
let crate_names = crates.keys().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", ");
66-
// add backslashes for an easy copy-paste `rm` command
67-
let paths = crates
68-
.into_values()
69-
.flatten()
70-
.map(|p| strip_current_dir(&p).display().to_string())
71-
.collect::<Vec<_>>()
72-
.join(" \\\n");
73-
// Check which action should be done in order to remove compiled deps.
74-
// If pre-installed version of compiler is used, `cargo clean` will do.
75-
// Otherwise (for bootstrapped compiler), the dependencies directory
76-
// must be removed manually.
77-
let suggested_action = if std::env::var_os("RUSTC_BOOTSTRAP").is_some() {
78-
"removing the stageN-tools directory"
79-
} else {
80-
"running `cargo clean`"
81-
};
82-
83-
panic!(
84-
"\n----------------------------------------------------------------------\n\
85-
ERROR: Found multiple rlibs for crates: {}\n\
86-
Try {} or remove the following files:\n\n{}\n\n\
87-
For details on this error see https://github.com/rust-lang/rust-clippy/issues/7343\n\
88-
----------------------------------------------------------------------\n",
89-
crate_names, suggested_action, paths
90-
);
83+
let not_found: Vec<&str> = TEST_DEPENDENCIES
84+
.iter()
85+
.copied()
86+
.filter(|n| !crates.contains_key(n))
87+
.collect();
88+
if !not_found.is_empty() {
89+
panic!("dependencies not found in depinfo: {:?}", not_found);
9190
}
92-
flags
91+
crates
92+
.into_iter()
93+
.map(|(name, path)| format!("--extern {}={} ", name, path))
94+
.collect()
9395
}
9496

9597
fn default_config() -> compiletest::Config {
@@ -105,11 +107,14 @@ fn default_config() -> compiletest::Config {
105107
config.compile_lib_path = path;
106108
}
107109

110+
// Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
111+
// This is valuable because a) it allows us to monitor what external dependencies are used
112+
// and b) it ensures that conflicting rlibs are resolved properly.
108113
config.target_rustcflags = Some(format!(
109-
"--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}",
114+
"--emit=metadata -L dependency={} -L dependency={} -Dwarnings -Zui-testing {}",
110115
host_lib().join("deps").display(),
111116
cargo::TARGET_LIB.join("deps").display(),
112-
third_party_crates(),
117+
extern_flags(),
113118
));
114119

115120
config.build_base = host_lib().join("test_build_base");
@@ -316,12 +321,3 @@ impl Drop for VarGuard {
316321
}
317322
}
318323
}
319-
320-
fn strip_current_dir(path: &Path) -> &Path {
321-
if let Ok(curr) = env::current_dir() {
322-
if let Ok(stripped) = path.strip_prefix(curr) {
323-
return stripped;
324-
}
325-
}
326-
path
327-
}

0 commit comments

Comments
 (0)