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
56use compiletest_rs as compiletest;
67use compiletest_rs:: common:: Mode as TestMode ;
78
9+ use std:: collections:: HashMap ;
810use std:: env:: { self , remove_var, set_var, var_os} ;
911use std:: ffi:: { OsStr , OsString } ;
1012use std:: fs;
@@ -16,6 +18,34 @@ mod cargo;
1618// whether to run internal tests or not
1719const 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+
1949fn 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
95111fn 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- }
0 commit comments