Skip to content

Commit 8c05a15

Browse files
camsteffenflip1995
authored andcommitted
Use binary-dep-depinfo to resolve UI dependencies
1 parent ffe21e5 commit 8c05a15

File tree

3 files changed

+88
-80
lines changed

3 files changed

+88
-80
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

+77-75
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#![feature(test)] // compiletest_rs requires this attribute
22
#![feature(once_cell)]
3-
#![feature(try_blocks)]
43

54
use compiletest_rs as compiletest;
65
use compiletest_rs::common::Mode as TestMode;
76

7+
use std::collections::HashMap;
88
use std::env::{self, remove_var, set_var, var_os};
99
use std::ffi::{OsStr, OsString};
1010
use std::fs;
@@ -16,6 +16,28 @@ mod cargo;
1616
// whether to run internal tests or not
1717
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
1818

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

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());
49+
/// Produces a string with an `--extern` flag for all UI test crate
50+
/// dependencies.
51+
///
52+
/// The dependency files are located by parsing the depinfo file for this test
53+
/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
54+
/// dependencies must be added to Cargo.toml at the project root. Test
55+
/// dependencies that are not *directly* used by this test module require an
56+
/// `extern crate` declaration.
57+
fn extern_flags() -> String {
58+
let current_exe_depinfo = {
59+
let mut path = env::current_exe().unwrap();
60+
path.set_extension("d");
61+
std::fs::read_to_string(path).unwrap()
62+
};
63+
let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
64+
for line in current_exe_depinfo.lines() {
65+
// each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
66+
let parse_name_path = || {
67+
if line.starts_with(char::is_whitespace) {
68+
return None;
69+
}
70+
let path_str = line.strip_suffix(':')?;
71+
let path = Path::new(path_str);
72+
if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") {
73+
return None;
74+
}
75+
let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?;
76+
// the "lib" prefix is not present for dll files
77+
let name = name.strip_prefix("lib").unwrap_or(name);
78+
Some((name, path_str))
79+
};
80+
if let Some((name, path)) = parse_name_path() {
81+
if TEST_DEPENDENCIES.contains(&name) {
82+
// A dependency may be listed twice if it is available in sysroot,
83+
// and the sysroot dependencies are listed first. As of the writing,
84+
// this only seems to apply to if_chain.
85+
crates.insert(name, path);
86+
}
6187
}
6288
}
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-
);
89+
let not_found: Vec<&str> = TEST_DEPENDENCIES
90+
.iter()
91+
.copied()
92+
.filter(|n| !crates.contains_key(n))
93+
.collect();
94+
if !not_found.is_empty() {
95+
panic!("dependencies not found in depinfo: {:?}", not_found);
9196
}
92-
flags
97+
crates
98+
.into_iter()
99+
.map(|(name, path)| format!("--extern {}={} ", name, path))
100+
.collect()
93101
}
94102

95103
fn default_config() -> compiletest::Config {
@@ -105,11 +113,14 @@ fn default_config() -> compiletest::Config {
105113
config.compile_lib_path = path;
106114
}
107115

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

115126
config.build_base = host_lib().join("test_build_base");
@@ -316,12 +327,3 @@ impl Drop for VarGuard {
316327
}
317328
}
318329
}
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)