mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 16:35:01 +01:00
things
This commit is contained in:
parent
a9e488f3e3
commit
0f05ef625a
11 changed files with 153 additions and 70 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
|
@ -156,6 +156,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"rustfix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
"walkdir",
|
||||
|
|
@ -1107,18 +1108,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.145"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||
checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.145"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||
checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1194,9 +1195,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.102"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
|
||||
checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ prettyplease = "0.1.19"
|
|||
proc-macro2 = { version = "1.0.48", features = ["span-locations"] }
|
||||
quote = "1.0.21"
|
||||
rustfix = "0.6.1"
|
||||
serde = { version = "1.0.151", features = ["derive"] }
|
||||
serde_json = "1.0.90"
|
||||
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] }
|
||||
walkdir = "2.3.2"
|
||||
|
|
|
|||
67
src/build.rs
67
src/build.rs
|
|
@ -1,6 +1,7 @@
|
|||
use anyhow::{Context, Result};
|
||||
use rustfix::diagnostics::Diagnostic;
|
||||
use std::{collections::HashSet, fmt::Display, path::PathBuf};
|
||||
use serde::Deserialize;
|
||||
use std::{collections::HashSet, fmt::Display, path::PathBuf, process::Command};
|
||||
|
||||
use crate::Options;
|
||||
|
||||
|
|
@ -35,9 +36,16 @@ impl Build {
|
|||
}
|
||||
|
||||
pub fn build(&self) -> Result<BuildResult> {
|
||||
if self.no_verify {
|
||||
return Ok(BuildResult {
|
||||
reproduces_issue: false,
|
||||
no_verify: true,
|
||||
});
|
||||
}
|
||||
|
||||
let reproduces_issue = match &self.mode {
|
||||
BuildMode::Cargo => {
|
||||
let mut cmd = std::process::Command::new("cargo");
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
|
||||
let output =
|
||||
|
|
@ -47,12 +55,12 @@ impl Build {
|
|||
output.contains("internal compiler error")
|
||||
}
|
||||
BuildMode::Script(script_path) => {
|
||||
let mut cmd = std::process::Command::new(script_path);
|
||||
let mut cmd = Command::new(script_path);
|
||||
|
||||
cmd.output().context("spawning script")?.status.success()
|
||||
}
|
||||
BuildMode::Rustc => {
|
||||
let mut cmd = std::process::Command::new("rustc");
|
||||
let mut cmd = Command::new("rustc");
|
||||
cmd.args(["--edition", "2018"]);
|
||||
cmd.arg(&self.input_path);
|
||||
|
||||
|
|
@ -71,11 +79,26 @@ impl Build {
|
|||
}
|
||||
|
||||
pub fn get_suggestions(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
|
||||
match self.mode {
|
||||
let diags = match self.mode {
|
||||
BuildMode::Cargo => {
|
||||
todo!();
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(["build", "--message-format=json"]);
|
||||
|
||||
let cmd_output = cmd.output()?;
|
||||
let output = String::from_utf8(cmd_output.stdout.clone())?;
|
||||
|
||||
let messages = serde_json::Deserializer::from_str(&output)
|
||||
.into_iter::<CargoJsonCompileMessage>()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let diags = messages
|
||||
.into_iter()
|
||||
.filter(|msg| msg.reason == "compiler-message")
|
||||
.flat_map(|msg| msg.message)
|
||||
.collect();
|
||||
|
||||
diags
|
||||
}
|
||||
BuildMode::Script(_) => todo!(),
|
||||
BuildMode::Rustc => {
|
||||
let mut cmd = std::process::Command::new("rustc");
|
||||
cmd.args(["--edition", "2018", "--error-format=json"]);
|
||||
|
|
@ -84,20 +107,28 @@ impl Build {
|
|||
let output = cmd.output()?.stderr;
|
||||
let output = String::from_utf8(output)?;
|
||||
|
||||
let diags = serde_json::Deserializer::from_str(&output).into_iter::<Diagnostic>().collect::<Result<_, _>>()?;
|
||||
let diags = serde_json::Deserializer::from_str(&output)
|
||||
.into_iter::<Diagnostic>()
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let suggestions = rustfix::get_suggestions_from_json(
|
||||
&output,
|
||||
diags
|
||||
}
|
||||
BuildMode::Script(_) => todo!(),
|
||||
};
|
||||
|
||||
let mut suggestions = Vec::new();
|
||||
for cargo_msg in &diags {
|
||||
// One diagnostic line might have multiple suggestions
|
||||
suggestions.extend(rustfix::collect_suggestions(
|
||||
cargo_msg,
|
||||
&HashSet::new(),
|
||||
rustfix::Filter::Everything,
|
||||
)
|
||||
.context("reading output as rustfix suggestions")?;
|
||||
));
|
||||
}
|
||||
|
||||
Ok((diags, suggestions))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuildResult {
|
||||
reproduces_issue: bool,
|
||||
|
|
@ -114,8 +145,14 @@ impl Display for BuildResult {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match (self.reproduces_issue, self.no_verify) {
|
||||
(true, _) => f.write_str("yes"),
|
||||
(false, true) => f.write_str("no (ignore)"),
|
||||
(false, true) => f.write_str("yes (no-verify)"),
|
||||
(false, false) => f.write_str("no"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CargoJsonCompileMessage {
|
||||
pub reason: String,
|
||||
pub message: Option<Diagnostic>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use quote::ToTokens;
|
||||
use syn::{parse_quote, visit_mut::VisitMut};
|
||||
|
||||
use crate::processor::{ProcessChecker, Processor};
|
||||
use crate::processor::{ProcessChecker, ProcessState, Processor};
|
||||
|
||||
struct Visitor<'a> {
|
||||
current_path: Vec<String>,
|
||||
checker: &'a mut ProcessChecker,
|
||||
|
||||
loop_expr: syn::Block,
|
||||
has_made_change: bool,
|
||||
process_state: ProcessState,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> {
|
||||
|
|
@ -16,7 +16,7 @@ impl<'a> Visitor<'a> {
|
|||
Self {
|
||||
current_path: Vec::new(),
|
||||
checker,
|
||||
has_made_change: false,
|
||||
process_state: ProcessState::NoChange,
|
||||
loop_expr: parse_quote! { { loop {} } },
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ impl VisitMut for Visitor<'_> {
|
|||
}))] if loop_body.stmts.is_empty() => {}
|
||||
_ => {
|
||||
*block = self.loop_expr.clone();
|
||||
self.has_made_change = true;
|
||||
self.process_state = ProcessState::Changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -65,10 +65,14 @@ impl VisitMut for Visitor<'_> {
|
|||
pub struct EverybodyLoops;
|
||||
|
||||
impl Processor for EverybodyLoops {
|
||||
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker) -> bool {
|
||||
fn process_file(
|
||||
&mut self,
|
||||
krate: &mut syn::File,
|
||||
checker: &mut ProcessChecker,
|
||||
) -> ProcessState {
|
||||
let mut visitor = Visitor::new(checker);
|
||||
visitor.visit_file_mut(krate);
|
||||
visitor.has_made_change
|
||||
visitor.process_state
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
use syn::{parse_quote, visit_mut::VisitMut, Visibility};
|
||||
|
||||
use crate::processor::{ProcessChecker, Processor};
|
||||
use crate::processor::{ProcessChecker, Processor, ProcessState};
|
||||
|
||||
struct Visitor {
|
||||
pub_crate: Visibility,
|
||||
has_made_change: bool,
|
||||
process_state: ProcessState,
|
||||
}
|
||||
|
||||
impl Visitor {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
has_made_change: false,
|
||||
process_state: ProcessState::NoChange,
|
||||
pub_crate: parse_quote! { pub(crate) },
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ impl Visitor {
|
|||
impl VisitMut for Visitor {
|
||||
fn visit_visibility_mut(&mut self, vis: &mut Visibility) {
|
||||
if let Visibility::Public(_) = vis {
|
||||
self.has_made_change = true;
|
||||
self.process_state = ProcessState::Changed;
|
||||
*vis = self.pub_crate.clone();
|
||||
}
|
||||
}
|
||||
|
|
@ -29,10 +29,10 @@ impl VisitMut for Visitor {
|
|||
pub struct Privatize {}
|
||||
|
||||
impl Processor for Privatize {
|
||||
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> bool {
|
||||
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> ProcessState {
|
||||
let mut visitor = Visitor::new();
|
||||
visitor.visit_file_mut(krate);
|
||||
visitor.has_made_change
|
||||
visitor.process_state
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SourceFile {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod files;
|
||||
mod reaper;
|
||||
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
use std::{collections::HashSet, ffi::OsStr, path::Path};
|
||||
|
||||
use anyhow::{ensure, Context, Result};
|
||||
|
||||
|
|
@ -10,11 +10,19 @@ use crate::{build::Build, processor::files::Changes, Options};
|
|||
use self::files::SourceFile;
|
||||
|
||||
pub trait Processor {
|
||||
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker) -> bool;
|
||||
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker)
|
||||
-> ProcessState;
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ProcessState {
|
||||
NoChange,
|
||||
Changed,
|
||||
FileInvalidated,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Minimizer {
|
||||
files: Vec<SourceFile>,
|
||||
|
|
@ -61,12 +69,18 @@ impl Minimizer {
|
|||
);
|
||||
}
|
||||
|
||||
let mut invalidated_files = HashSet::new();
|
||||
|
||||
for mut pass in passes {
|
||||
'pass: loop {
|
||||
println!("Starting a round of {}", pass.name());
|
||||
let mut changes = Changes::default();
|
||||
|
||||
for file in &self.files {
|
||||
if invalidated_files.contains(file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_display = file.path.display();
|
||||
|
||||
let mut change = file.try_change(&mut changes)?;
|
||||
|
|
@ -76,7 +90,8 @@ impl Minimizer {
|
|||
|
||||
let has_made_change = pass.process_file(&mut krate, &mut ProcessChecker {});
|
||||
|
||||
if has_made_change {
|
||||
match has_made_change {
|
||||
ProcessState::Changed | ProcessState::FileInvalidated => {
|
||||
let result = prettyplease::unparse(&krate);
|
||||
|
||||
change.write(&result)?;
|
||||
|
|
@ -90,10 +105,16 @@ impl Minimizer {
|
|||
} else {
|
||||
change.rollback()?;
|
||||
}
|
||||
} else {
|
||||
|
||||
if has_made_change == ProcessState::FileInvalidated {
|
||||
invalidated_files.insert(file);
|
||||
}
|
||||
}
|
||||
ProcessState::NoChange => {
|
||||
println!("{file_display}: After {}: no change", pass.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !changes.had_changes() {
|
||||
println!("Finished {}", pass.name());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Deletes dead code.
|
||||
|
||||
use super::{files::Changes, Minimizer, Processor};
|
||||
use super::{files::Changes, Minimizer, ProcessState, Processor};
|
||||
use anyhow::{ensure, Context, Result};
|
||||
use proc_macro2::Span;
|
||||
use rustfix::{diagnostics::Diagnostic, Suggestion};
|
||||
|
|
@ -38,7 +38,10 @@ impl Minimizer {
|
|||
// Always unconditionally apply unused imports.
|
||||
self.apply_unused_imports(&suggestions_for_file)?;
|
||||
|
||||
self.run_passes([Box::new(DeleteUnusedFunctions(diags)) as Box<dyn Processor>])
|
||||
self.run_passes([Box::new(DeleteUnusedFunctions {
|
||||
diags,
|
||||
invalid: false,
|
||||
}) as Box<dyn Processor>])
|
||||
.context("deleting unused functions")?;
|
||||
|
||||
Ok(())
|
||||
|
|
@ -49,11 +52,12 @@ impl Minimizer {
|
|||
suggestions: &HashMap<&str, Vec<&Suggestion>>,
|
||||
) -> Result<()> {
|
||||
for (file, suggestions) in suggestions {
|
||||
let file = self
|
||||
let Some(file) = self
|
||||
.files
|
||||
.iter()
|
||||
.find(|source| source.path == Path::new(file))
|
||||
.expect("unknown file");
|
||||
.find(|source| source.path == Path::new(file)) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut changes = &mut Changes::default();
|
||||
|
||||
|
|
@ -85,14 +89,27 @@ impl Minimizer {
|
|||
}
|
||||
}
|
||||
|
||||
struct DeleteUnusedFunctions(Vec<Diagnostic>);
|
||||
struct DeleteUnusedFunctions {
|
||||
diags: Vec<Diagnostic>,
|
||||
invalid: bool,
|
||||
}
|
||||
|
||||
impl Processor for DeleteUnusedFunctions {
|
||||
fn process_file(&mut self, krate: &mut syn::File, _: &mut super::ProcessChecker) -> bool {
|
||||
let mut visitor = FindUnusedFunction::new(self.0.iter());
|
||||
fn process_file(
|
||||
&mut self,
|
||||
krate: &mut syn::File,
|
||||
_: &mut super::ProcessChecker,
|
||||
) -> ProcessState {
|
||||
assert!(!self.invalid, "processing with invalid state");
|
||||
|
||||
let mut visitor = FindUnusedFunction::new(self.diags.iter());
|
||||
visitor.visit_file_mut(krate);
|
||||
|
||||
visitor.has_change
|
||||
if visitor.process_state == ProcessState::FileInvalidated {
|
||||
self.invalid = true;
|
||||
}
|
||||
|
||||
visitor.process_state
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
|
@ -122,7 +139,7 @@ impl Unused {
|
|||
|
||||
struct FindUnusedFunction {
|
||||
unused_functions: Vec<Unused>,
|
||||
has_change: bool,
|
||||
process_state: ProcessState,
|
||||
}
|
||||
|
||||
impl FindUnusedFunction {
|
||||
|
|
@ -160,7 +177,7 @@ impl FindUnusedFunction {
|
|||
|
||||
Self {
|
||||
unused_functions,
|
||||
has_change: false,
|
||||
process_state: ProcessState::NoChange,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,11 +191,11 @@ impl FindUnusedFunction {
|
|||
|
||||
assert!(
|
||||
span_matches < 2,
|
||||
"multiple dead_code spans matched identifier: {span_matches}"
|
||||
"multiple dead_code spans matched identifier: {span_matches}."
|
||||
);
|
||||
|
||||
if span_matches == 1 {
|
||||
self.has_change = true;
|
||||
self.process_state = ProcessState::FileInvalidated;
|
||||
}
|
||||
|
||||
span_matches == 0
|
||||
|
|
|
|||
|
|
@ -4,7 +4,3 @@
|
|||
name = "hello-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
fn unused() {}
|
||||
|
||||
fn main() {
|
||||
unused();
|
||||
fn unused() {
|
||||
loop {}
|
||||
}
|
||||
fn main() {
|
||||
loop {}
|
||||
}
|
||||
|
||||
mod other;
|
||||
|
|
|
|||
3
test-cases/unused-code/src/other.rs
Normal file
3
test-cases/unused-code/src/other.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn unused() {
|
||||
loop {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue