210 lines
6.8 KiB
Rust
210 lines
6.8 KiB
Rust
use std::fs;
|
|
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
// This module takes an absolute path to a rustc repo and alters the dependencies to point towards
|
|
// the respective rustc subcrates instead of using extern crate xyz.
|
|
// This allows IntelliJ to analyze rustc internals and show proper information inside Clippy
|
|
// code. See https://github.com/rust-lang/rust-clippy/issues/5514 for details
|
|
|
|
const RUSTC_PATH_SECTION: &str = "[target.'cfg(NOT_A_PLATFORM)'.dependencies]";
|
|
const DEPENDENCIES_SECTION: &str = "[dependencies]";
|
|
|
|
const CLIPPY_PROJECTS: &[ClippyProjectInfo] = &[
|
|
ClippyProjectInfo::new("root", "Cargo.toml", "src/driver.rs"),
|
|
ClippyProjectInfo::new("clippy_lints", "clippy_lints/Cargo.toml", "clippy_lints/src/lib.rs"),
|
|
ClippyProjectInfo::new("clippy_utils", "clippy_utils/Cargo.toml", "clippy_utils/src/lib.rs"),
|
|
];
|
|
|
|
/// Used to store clippy project information to later inject the dependency into.
|
|
struct ClippyProjectInfo {
|
|
/// Only used to display information to the user
|
|
name: &'static str,
|
|
cargo_file: &'static str,
|
|
lib_rs_file: &'static str,
|
|
}
|
|
|
|
impl ClippyProjectInfo {
|
|
const fn new(name: &'static str, cargo_file: &'static str, lib_rs_file: &'static str) -> Self {
|
|
Self {
|
|
name,
|
|
cargo_file,
|
|
lib_rs_file,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn setup_rustc_src(rustc_path: &str) {
|
|
let Ok(rustc_source_dir) = check_and_get_rustc_dir(rustc_path) else {
|
|
return;
|
|
};
|
|
|
|
for project in CLIPPY_PROJECTS {
|
|
if inject_deps_into_project(&rustc_source_dir, project).is_err() {
|
|
return;
|
|
}
|
|
}
|
|
|
|
println!("info: the source paths can be removed again with `cargo dev remove intellij`");
|
|
}
|
|
|
|
fn check_and_get_rustc_dir(rustc_path: &str) -> Result<PathBuf, ()> {
|
|
let mut path = PathBuf::from(rustc_path);
|
|
|
|
if path.is_relative() {
|
|
match path.canonicalize() {
|
|
Ok(absolute_path) => {
|
|
println!("info: the rustc path was resolved to: `{}`", absolute_path.display());
|
|
path = absolute_path;
|
|
},
|
|
Err(err) => {
|
|
eprintln!("error: unable to get the absolute path of rustc ({err})");
|
|
return Err(());
|
|
},
|
|
};
|
|
}
|
|
|
|
let path = path.join("compiler");
|
|
println!("info: looking for compiler sources at: {}", path.display());
|
|
|
|
if !path.exists() {
|
|
eprintln!("error: the given path does not exist");
|
|
return Err(());
|
|
}
|
|
|
|
if !path.is_dir() {
|
|
eprintln!("error: the given path is not a directory");
|
|
return Err(());
|
|
}
|
|
|
|
Ok(path)
|
|
}
|
|
|
|
fn inject_deps_into_project(rustc_source_dir: &Path, project: &ClippyProjectInfo) -> Result<(), ()> {
|
|
let cargo_content = read_project_file(project.cargo_file)?;
|
|
let lib_content = read_project_file(project.lib_rs_file)?;
|
|
|
|
if inject_deps_into_manifest(rustc_source_dir, project.cargo_file, &cargo_content, &lib_content).is_err() {
|
|
eprintln!(
|
|
"error: unable to inject dependencies into {} with the Cargo file {}",
|
|
project.name, project.cargo_file
|
|
);
|
|
Err(())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// `clippy_dev` expects to be executed in the root directory of Clippy. This function
|
|
/// loads the given file or returns an error. Having it in this extra function ensures
|
|
/// that the error message looks nice.
|
|
fn read_project_file(file_path: &str) -> Result<String, ()> {
|
|
let path = Path::new(file_path);
|
|
if !path.exists() {
|
|
eprintln!("error: unable to find the file `{file_path}`");
|
|
return Err(());
|
|
}
|
|
|
|
match fs::read_to_string(path) {
|
|
Ok(content) => Ok(content),
|
|
Err(err) => {
|
|
eprintln!("error: the file `{file_path}` could not be read ({err})");
|
|
Err(())
|
|
},
|
|
}
|
|
}
|
|
|
|
fn inject_deps_into_manifest(
|
|
rustc_source_dir: &Path,
|
|
manifest_path: &str,
|
|
cargo_toml: &str,
|
|
lib_rs: &str,
|
|
) -> std::io::Result<()> {
|
|
// do not inject deps if we have already done so
|
|
if cargo_toml.contains(RUSTC_PATH_SECTION) {
|
|
eprintln!("warn: dependencies are already setup inside {manifest_path}, skipping file");
|
|
return Ok(());
|
|
}
|
|
|
|
let extern_crates = lib_rs
|
|
.lines()
|
|
// only take dependencies starting with `rustc_`
|
|
.filter(|line| line.starts_with("extern crate rustc_"))
|
|
// we have something like "extern crate foo;", we only care about the "foo"
|
|
// extern crate rustc_middle;
|
|
// ^^^^^^^^^^^^
|
|
.map(|s| &s[13..(s.len() - 1)]);
|
|
|
|
let new_deps = extern_crates.map(|dep| {
|
|
// format the dependencies that are going to be put inside the Cargo.toml
|
|
format!("{dep} = {{ path = \"{}/{dep}\" }}\n", rustc_source_dir.display())
|
|
});
|
|
|
|
// format a new [dependencies]-block with the new deps we need to inject
|
|
let mut all_deps = String::from("[target.'cfg(NOT_A_PLATFORM)'.dependencies]\n");
|
|
new_deps.for_each(|dep_line| {
|
|
all_deps.push_str(&dep_line);
|
|
});
|
|
all_deps.push_str("\n[dependencies]\n");
|
|
|
|
// replace "[dependencies]" with
|
|
// [dependencies]
|
|
// dep1 = { path = ... }
|
|
// dep2 = { path = ... }
|
|
// etc
|
|
let new_manifest = cargo_toml.replacen("[dependencies]\n", &all_deps, 1);
|
|
|
|
// println!("{new_manifest}");
|
|
let mut file = File::create(manifest_path)?;
|
|
file.write_all(new_manifest.as_bytes())?;
|
|
|
|
println!("info: successfully setup dependencies inside {manifest_path}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn remove_rustc_src() {
|
|
for project in CLIPPY_PROJECTS {
|
|
remove_rustc_src_from_project(project);
|
|
}
|
|
}
|
|
|
|
fn remove_rustc_src_from_project(project: &ClippyProjectInfo) -> bool {
|
|
let Ok(mut cargo_content) = read_project_file(project.cargo_file) else {
|
|
return false;
|
|
};
|
|
let Some(section_start) = cargo_content.find(RUSTC_PATH_SECTION) else {
|
|
println!(
|
|
"info: dependencies could not be found in `{}` for {}, skipping file",
|
|
project.cargo_file, project.name
|
|
);
|
|
return true;
|
|
};
|
|
|
|
let Some(end_point) = cargo_content.find(DEPENDENCIES_SECTION) else {
|
|
eprintln!(
|
|
"error: the end of the rustc dependencies section could not be found in `{}`",
|
|
project.cargo_file
|
|
);
|
|
return false;
|
|
};
|
|
|
|
cargo_content.replace_range(section_start..end_point, "");
|
|
|
|
match File::create(project.cargo_file) {
|
|
Ok(mut file) => {
|
|
file.write_all(cargo_content.as_bytes()).unwrap();
|
|
println!("info: successfully removed dependencies inside {}", project.cargo_file);
|
|
true
|
|
},
|
|
Err(err) => {
|
|
eprintln!(
|
|
"error: unable to open file `{}` to remove rustc dependencies for {} ({err})",
|
|
project.cargo_file, project.name
|
|
);
|
|
false
|
|
},
|
|
}
|
|
}
|