Skip to content

Commit c1ce78f

Browse files
committed
Auto merge of #6686 - matthiaskrgr:lintcheck_git, r=flip1995
lintcheck: support git sources This adds support for git sources in `cargo dev-lintcheck` You can add a git source to `clippy_dev/lintcheck_crates.toml` by having a `git_url` and a `git_hash` key instead of the `versions` array. The repo will the be cloned and checked out to the requested commit before checking it with clippy. Fixes #6642 changelog: lintcheck: support git sources
2 parents 83b7b16 + 2bffbfc commit c1ce78f

File tree

3 files changed

+319
-86
lines changed

3 files changed

+319
-86
lines changed

clippy_dev/lintcheck_crates.toml

+18-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
[crates]
22
# some of these are from cargotest
3-
cargo = ['0.49.0']
4-
iron = ['0.6.1']
5-
ripgrep = ['12.1.1']
6-
xsv = ['0.13.0']
7-
#tokei = ['12.0.4']
8-
rayon = ['1.5.0']
9-
serde = ['1.0.118']
3+
cargo = {name = "cargo", versions = ['0.49.0']}
4+
iron = {name = "iron", versions = ['0.6.1']}
5+
ripgrep = {name = "ripgrep", versions = ['12.1.1']}
6+
xsv = {name = "xsv", versions = ['0.13.0']}
7+
#tokei = { name = "tokei", versions = ['12.0.4']}
8+
rayon = {name = "rayon", versions = ['1.5.0']}
9+
serde = {name = "serde", versions = ['1.0.118']}
1010
# top 10 crates.io dls
11-
bitflags = ['1.2.1']
12-
libc = ['0.2.81']
13-
log = ['0.4.11']
14-
proc-macro2 = ['1.0.24']
15-
quote = ['1.0.7']
16-
rand = ['0.7.3']
17-
rand_core = ['0.6.0']
18-
regex = ['1.3.2']
19-
syn = ['1.0.54']
20-
unicode-xid = ['0.2.1']
11+
bitflags = {name = "bitflags", versions = ['1.2.1']}
12+
libc = {name = "libc", versions = ['0.2.81']}
13+
log = {name = "log", versions = ['0.4.11']}
14+
proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']}
15+
puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"}
16+
quote = {name = "quote", versions = ['1.0.7']}
17+
rand = {name = "rand", versions = ['0.7.3']}
18+
rand_core = {name = "rand_core", versions = ['0.6.0']}
19+
regex = {name = "regex", versions = ['1.3.2']}
20+
syn = {name = "syn", versions = ['1.0.54']}
21+
unicode-xid = {name = "unicode-xid", versions = ['0.2.1']}

clippy_dev/src/lintcheck.rs

+118-49
Original file line numberDiff line numberDiff line change
@@ -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)]
2222
struct 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)]
2829
struct 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)]
4247
struct Crate {
4348
version: String,
@@ -69,40 +74,71 @@ impl std::fmt::Display for ClippyWarning {
6974

7075
impl 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{:?}\ncargo_clippy_path: {}\ncrate 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

Comments
 (0)