@@ -20,24 +20,29 @@ use serde_json::Value;
2020// use this to store the crates when interacting with the crates.toml file
2121#[ derive( Debug , Serialize , Deserialize ) ]
2222struct CrateList {
23- crates : HashMap < String , Vec < String > > ,
23+ crates : HashMap < String , TomlCrate > ,
2424}
2525
2626// crate data we stored in the toml, can have multiple versions per crate
2727// A single TomlCrate is laster mapped to several CrateSources in that case
28+ #[ derive( Debug , Serialize , Deserialize ) ]
2829struct TomlCrate {
2930 name : String ,
30- versions : Vec < String > ,
31+ versions : Option < Vec < String > > ,
32+ git_url : Option < String > ,
33+ git_hash : Option < String > ,
3134}
3235
3336// represents an archive we download from crates.io
3437#[ derive( Debug , Serialize , Deserialize , Eq , Hash , PartialEq ) ]
35- struct CrateSource {
36- name : String ,
37- version : String ,
38+ enum CrateSource {
39+ CratesIo { name : String , version : String } ,
40+ Git { name : String , url : String , commit : String } ,
3841}
3942
4043// represents the extracted sourcecode of a crate
44+ // we actually don't need to special-case git repos here because it does not matter for clippy, yay!
45+ // (clippy only needs a simple path)
4146#[ derive( Debug ) ]
4247struct Crate {
4348 version : String ,
@@ -69,40 +74,71 @@ impl std::fmt::Display for ClippyWarning {
6974
7075impl CrateSource {
7176 fn download_and_extract ( & self ) -> Crate {
72- let extract_dir = PathBuf :: from ( "target/lintcheck/crates" ) ;
73- let krate_download_dir = PathBuf :: from ( "target/lintcheck/downloads" ) ;
74-
75- // url to download the crate from crates.io
76- let url = format ! (
77- "https://crates.io/api/v1/crates/{}/{}/download" ,
78- self . name, self . version
79- ) ;
80- println ! ( "Downloading and extracting {} {} from {}" , self . name, self . version, url) ;
81- let _ = std:: fs:: create_dir ( "target/lintcheck/" ) ;
82- let _ = std:: fs:: create_dir ( & krate_download_dir) ;
83- let _ = std:: fs:: create_dir ( & extract_dir) ;
84-
85- let krate_file_path = krate_download_dir. join ( format ! ( "{}-{}.crate.tar.gz" , & self . name, & self . version) ) ;
86- // don't download/extract if we already have done so
87- if !krate_file_path. is_file ( ) {
88- // create a file path to download and write the crate data into
89- let mut krate_dest = std:: fs:: File :: create ( & krate_file_path) . unwrap ( ) ;
90- let mut krate_req = ureq:: get ( & url) . call ( ) . unwrap ( ) . into_reader ( ) ;
91- // copy the crate into the file
92- std:: io:: copy ( & mut krate_req, & mut krate_dest) . unwrap ( ) ;
93-
94- // unzip the tarball
95- let ungz_tar = flate2:: read:: GzDecoder :: new ( std:: fs:: File :: open ( & krate_file_path) . unwrap ( ) ) ;
96- // extract the tar archive
97- let mut archive = tar:: Archive :: new ( ungz_tar) ;
98- archive. unpack ( & extract_dir) . expect ( "Failed to extract!" ) ;
99- }
100- // crate is extracted, return a new Krate object which contains the path to the extracted
101- // sources that clippy can check
102- Crate {
103- version : self . version . clone ( ) ,
104- name : self . name . clone ( ) ,
105- path : extract_dir. join ( format ! ( "{}-{}/" , self . name, self . version) ) ,
77+ match self {
78+ CrateSource :: CratesIo { name, version } => {
79+ let extract_dir = PathBuf :: from ( "target/lintcheck/crates" ) ;
80+ let krate_download_dir = PathBuf :: from ( "target/lintcheck/downloads" ) ;
81+
82+ // url to download the crate from crates.io
83+ let url = format ! ( "https://crates.io/api/v1/crates/{}/{}/download" , name, version) ;
84+ println ! ( "Downloading and extracting {} {} from {}" , name, version, url) ;
85+ let _ = std:: fs:: create_dir ( "target/lintcheck/" ) ;
86+ let _ = std:: fs:: create_dir ( & krate_download_dir) ;
87+ let _ = std:: fs:: create_dir ( & extract_dir) ;
88+
89+ let krate_file_path = krate_download_dir. join ( format ! ( "{}-{}.crate.tar.gz" , name, version) ) ;
90+ // don't download/extract if we already have done so
91+ if !krate_file_path. is_file ( ) {
92+ // create a file path to download and write the crate data into
93+ let mut krate_dest = std:: fs:: File :: create ( & krate_file_path) . unwrap ( ) ;
94+ let mut krate_req = ureq:: get ( & url) . call ( ) . unwrap ( ) . into_reader ( ) ;
95+ // copy the crate into the file
96+ std:: io:: copy ( & mut krate_req, & mut krate_dest) . unwrap ( ) ;
97+
98+ // unzip the tarball
99+ let ungz_tar = flate2:: read:: GzDecoder :: new ( std:: fs:: File :: open ( & krate_file_path) . unwrap ( ) ) ;
100+ // extract the tar archive
101+ let mut archive = tar:: Archive :: new ( ungz_tar) ;
102+ archive. unpack ( & extract_dir) . expect ( "Failed to extract!" ) ;
103+ }
104+ // crate is extracted, return a new Krate object which contains the path to the extracted
105+ // sources that clippy can check
106+ Crate {
107+ version : version. clone ( ) ,
108+ name : name. clone ( ) ,
109+ path : extract_dir. join ( format ! ( "{}-{}/" , name, version) ) ,
110+ }
111+ } ,
112+ CrateSource :: Git { name, url, commit } => {
113+ let repo_path = {
114+ let mut repo_path = PathBuf :: from ( "target/lintcheck/downloads" ) ;
115+ // add a -git suffix in case we have the same crate from crates.io and a git repo
116+ repo_path. push ( format ! ( "{}-git" , name) ) ;
117+ repo_path
118+ } ;
119+ // clone the repo if we have not done so
120+ if !repo_path. is_dir ( ) {
121+ println ! ( "Cloning {} and checking out {}" , url, commit) ;
122+ Command :: new ( "git" )
123+ . arg ( "clone" )
124+ . arg ( url)
125+ . arg ( & repo_path)
126+ . output ( )
127+ . expect ( "Failed to clone git repo!" ) ;
128+ }
129+ // check out the commit/branch/whatever
130+ Command :: new ( "git" )
131+ . arg ( "checkout" )
132+ . arg ( commit)
133+ . output ( )
134+ . expect ( "Failed to check out commit" ) ;
135+
136+ Crate {
137+ version : commit. clone ( ) ,
138+ name : name. clone ( ) ,
139+ path : repo_path,
140+ }
141+ } ,
106142 }
107143 }
108144}
@@ -114,7 +150,7 @@ impl Crate {
114150
115151 let shared_target_dir = clippy_project_root ( ) . join ( "target/lintcheck/shared_target_dir/" ) ;
116152
117- let all_output = std:: process:: Command :: new ( cargo_clippy_path)
153+ let all_output = std:: process:: Command :: new ( & cargo_clippy_path)
118154 . env ( "CARGO_TARGET_DIR" , shared_target_dir)
119155 // lint warnings will look like this:
120156 // src/cargo/ops/cargo_compile.rs:127:35: warning: usage of `FromIterator::from_iter`
@@ -128,10 +164,16 @@ impl Crate {
128164 ] )
129165 . current_dir ( & self . path )
130166 . output ( )
131- . unwrap ( ) ;
167+ . unwrap_or_else ( |error| {
168+ panic ! (
169+ "Encountered error:\n {:?}\n cargo_clippy_path: {}\n crate path:{}\n " ,
170+ error,
171+ & cargo_clippy_path. display( ) ,
172+ & self . path. display( )
173+ ) ;
174+ } ) ;
132175 let stdout = String :: from_utf8_lossy ( & all_output. stdout ) ;
133176 let output_lines = stdout. lines ( ) ;
134- //dbg!(&output_lines);
135177 let warnings: Vec < ClippyWarning > = output_lines
136178 . into_iter ( )
137179 // get all clippy warnings
@@ -160,19 +202,40 @@ fn read_crates() -> Vec<CrateSource> {
160202 let tomlcrates: Vec < TomlCrate > = crate_list
161203 . crates
162204 . into_iter ( )
163- . map ( |( name , versions ) | TomlCrate { name , versions } )
205+ . map ( |( _cratename , tomlcrate ) | tomlcrate )
164206 . collect ( ) ;
165207
166208 // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
167209 // multiple Cratesources)
168210 let mut crate_sources = Vec :: new ( ) ;
169211 tomlcrates. into_iter ( ) . for_each ( |tk| {
170- tk. versions . iter ( ) . for_each ( |ver| {
171- crate_sources. push ( CrateSource {
212+ // if we have multiple versions, save each one
213+ if let Some ( ref versions) = tk. versions {
214+ versions. iter ( ) . for_each ( |ver| {
215+ crate_sources. push ( CrateSource :: CratesIo {
216+ name : tk. name . clone ( ) ,
217+ version : ver. to_string ( ) ,
218+ } ) ;
219+ } )
220+ }
221+ // otherwise, we should have a git source
222+ if tk. git_url . is_some ( ) && tk. git_hash . is_some ( ) {
223+ crate_sources. push ( CrateSource :: Git {
172224 name : tk. name . clone ( ) ,
173- version : ver. to_string ( ) ,
225+ url : tk. git_url . clone ( ) . unwrap ( ) ,
226+ commit : tk. git_hash . clone ( ) . unwrap ( ) ,
174227 } ) ;
175- } )
228+ }
229+ // if we have a version as well as a git data OR only one git data, something is funky
230+ if tk. versions . is_some ( ) && ( tk. git_url . is_some ( ) || tk. git_hash . is_some ( ) )
231+ || tk. git_hash . is_some ( ) != tk. git_url . is_some ( )
232+ {
233+ eprintln ! ( "tomlkrate: {:?}" , tk) ;
234+ if tk. git_hash . is_some ( ) != tk. git_url . is_some ( ) {
235+ panic ! ( "Encountered TomlCrate with only one of git_hash and git_url!" )
236+ }
237+ unreachable ! ( "Failed to translate TomlCrate into CrateSource!" ) ;
238+ }
176239 } ) ;
177240 crate_sources
178241}
@@ -228,8 +291,14 @@ pub fn run(clap_config: &ArgMatches) {
228291 let crates = read_crates ( ) ;
229292
230293 let clippy_warnings: Vec < ClippyWarning > = if let Some ( only_one_crate) = clap_config. value_of ( "only" ) {
231- // if we don't have the specified crated in the .toml, throw an error
232- if !crates. iter ( ) . any ( |krate| krate. name == only_one_crate) {
294+ // if we don't have the specified crate in the .toml, throw an error
295+ if !crates. iter ( ) . any ( |krate| {
296+ let name = match krate {
297+ CrateSource :: CratesIo { name, .. } => name,
298+ CrateSource :: Git { name, .. } => name,
299+ } ;
300+ name == only_one_crate
301+ } ) {
233302 eprintln ! (
234303 "ERROR: could not find crate '{}' in clippy_dev/lintcheck_crates.toml" ,
235304 only_one_crate
0 commit comments