5
5
use compiletest_rs as compiletest;
6
6
use compiletest_rs:: common:: Mode as TestMode ;
7
7
8
+ use std:: collections:: HashMap ;
8
9
use std:: env:: { self , remove_var, set_var, var_os} ;
9
10
use std:: ffi:: { OsStr , OsString } ;
10
11
use std:: fs;
@@ -16,6 +17,28 @@ mod cargo;
16
17
// whether to run internal tests or not
17
18
const RUN_INTERNAL_TESTS : bool = cfg ! ( feature = "internal-lints" ) ;
18
19
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
+
19
42
fn host_lib ( ) -> PathBuf {
20
43
option_env ! ( "HOST_LIBS" ) . map_or ( cargo:: CARGO_TARGET_DIR . join ( env ! ( "PROFILE" ) ) , PathBuf :: from)
21
44
}
@@ -24,72 +47,51 @@ fn clippy_driver_path() -> PathBuf {
24
47
option_env ! ( "CLIPPY_DRIVER_PATH" ) . map_or ( cargo:: TARGET_LIB . join ( "clippy-driver" ) , PathBuf :: from)
25
48
}
26
49
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) ;
61
81
}
62
82
}
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) ;
91
90
}
92
- flags
91
+ crates
92
+ . into_iter ( )
93
+ . map ( |( name, path) | format ! ( "--extern {}={} " , name, path) )
94
+ . collect ( )
93
95
}
94
96
95
97
fn default_config ( ) -> compiletest:: Config {
@@ -105,11 +107,14 @@ fn default_config() -> compiletest::Config {
105
107
config. compile_lib_path = path;
106
108
}
107
109
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.
108
113
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 {}" ,
110
115
host_lib( ) . join( "deps" ) . display( ) ,
111
116
cargo:: TARGET_LIB . join( "deps" ) . display( ) ,
112
- third_party_crates ( ) ,
117
+ extern_flags ( ) ,
113
118
) ) ;
114
119
115
120
config. build_base = host_lib ( ) . join ( "test_build_base" ) ;
@@ -316,12 +321,3 @@ impl Drop for VarGuard {
316
321
}
317
322
}
318
323
}
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