Skip to content

Commit 27afd6a

Browse files
committed
Auto merge of #7631 - camsteffen:depinfo, r=flip1995
Use binary-dep-depinfo to resolve UI test dependencies Closes #7343 Closes #6809 Closes #3643 changelog: none r? `@flip1995` cc `@Jarcho`
2 parents 5458358 + 5d3fc6f commit 27afd6a

10 files changed

+111
-84
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

clippy_utils/Cargo.toml

-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ publish = false
66

77
[dependencies]
88
if_chain = "1.0.0"
9-
itertools = "0.9"
10-
regex-syntax = "0.6"
11-
serde = { version = "1.0", features = ["derive"] }
12-
unicode-normalization = "0.1"
139
rustc-semver="1.1.0"
1410

1511
[features]

tests/compile-test.rs

+85-75
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#![feature(test)] // compiletest_rs requires this attribute
22
#![feature(once_cell)]
3-
#![feature(try_blocks)]
3+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
4+
#![warn(rust_2018_idioms, unused_lifetimes)]
45

56
use compiletest_rs as compiletest;
67
use compiletest_rs::common::Mode as TestMode;
78

9+
use std::collections::HashMap;
810
use std::env::{self, remove_var, set_var, var_os};
911
use std::ffi::{OsStr, OsString};
1012
use std::fs;
@@ -16,6 +18,34 @@ mod cargo;
1618
// whether to run internal tests or not
1719
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
1820

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

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

95111
fn default_config() -> compiletest::Config {
@@ -105,11 +121,14 @@ fn default_config() -> compiletest::Config {
105121
config.compile_lib_path = path;
106122
}
107123

124+
// Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
125+
// This is valuable because a) it allows us to monitor what external dependencies are used
126+
// and b) it ensures that conflicting rlibs are resolved properly.
108127
config.target_rustcflags = Some(format!(
109-
"--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}",
128+
"--emit=metadata -L dependency={} -L dependency={} -Dwarnings -Zui-testing {}",
110129
host_lib().join("deps").display(),
111130
cargo::TARGET_LIB.join("deps").display(),
112-
third_party_crates(),
131+
extern_flags(),
113132
));
114133

115134
config.build_base = host_lib().join("test_build_base");
@@ -316,12 +335,3 @@ impl Drop for VarGuard {
316335
}
317336
}
318337
}
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-
}

tests/dogfood.rs

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
// Dogfood cannot run on Windows
77
#![cfg(not(windows))]
88
#![feature(once_cell)]
9+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
10+
#![warn(rust_2018_idioms, unused_lifetimes)]
911

1012
use std::lazy::SyncLazy;
1113
use std::path::PathBuf;

tests/fmt.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
2+
#![warn(rust_2018_idioms, unused_lifetimes)]
3+
14
use std::path::PathBuf;
25
use std::process::Command;
36

tests/integration.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#![cfg(feature = "integration")]
2+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
3+
#![warn(rust_2018_idioms, unused_lifetimes)]
24

35
use std::env;
46
use std::ffi::OsStr;

tests/lint_message_convention.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
2+
#![warn(rust_2018_idioms, unused_lifetimes)]
3+
14
use std::ffi::OsStr;
25
use std::path::PathBuf;
36

tests/missing-test-files.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
2+
#![warn(rust_2018_idioms, unused_lifetimes)]
13
#![allow(clippy::assertions_on_constants)]
24

35
use std::fs::{self, DirEntry};

tests/versioncheck.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
2+
#![warn(rust_2018_idioms, unused_lifetimes)]
13
#![allow(clippy::single_match_else)]
4+
25
use rustc_tools_util::VersionInfo;
36

47
#[test]

0 commit comments

Comments
 (0)