mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-16 09:15:02 +01:00
kinda works
This commit is contained in:
parent
0f05ef625a
commit
dcd163aeda
8 changed files with 111 additions and 59 deletions
25
src/build.rs
25
src/build.rs
|
|
@ -1,12 +1,17 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use rustfix::diagnostics::Diagnostic;
|
use rustfix::diagnostics::Diagnostic;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashSet, fmt::Display, path::PathBuf, process::Command};
|
use std::{collections::HashSet, fmt::Display, path::PathBuf, process::Command, rc::Rc};
|
||||||
|
|
||||||
use crate::Options;
|
use crate::Options;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Build {
|
pub struct Build {
|
||||||
|
inner: Rc<BuildInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct BuildInner {
|
||||||
mode: BuildMode,
|
mode: BuildMode,
|
||||||
input_path: PathBuf,
|
input_path: PathBuf,
|
||||||
no_verify: bool,
|
no_verify: bool,
|
||||||
|
|
@ -29,21 +34,23 @@ impl Build {
|
||||||
BuildMode::Rustc
|
BuildMode::Rustc
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
|
inner: Rc::new(BuildInner {
|
||||||
mode,
|
mode,
|
||||||
input_path: options.path.clone(),
|
input_path: options.path.clone(),
|
||||||
no_verify: options.no_verify,
|
no_verify: options.no_verify,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> Result<BuildResult> {
|
pub fn build(&self) -> Result<BuildResult> {
|
||||||
if self.no_verify {
|
if self.inner.no_verify {
|
||||||
return Ok(BuildResult {
|
return Ok(BuildResult {
|
||||||
reproduces_issue: false,
|
reproduces_issue: false,
|
||||||
no_verify: true,
|
no_verify: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let reproduces_issue = match &self.mode {
|
let reproduces_issue = match &self.inner.mode {
|
||||||
BuildMode::Cargo => {
|
BuildMode::Cargo => {
|
||||||
let mut cmd = Command::new("cargo");
|
let mut cmd = Command::new("cargo");
|
||||||
cmd.arg("build");
|
cmd.arg("build");
|
||||||
|
|
@ -62,7 +69,7 @@ impl Build {
|
||||||
BuildMode::Rustc => {
|
BuildMode::Rustc => {
|
||||||
let mut cmd = Command::new("rustc");
|
let mut cmd = Command::new("rustc");
|
||||||
cmd.args(["--edition", "2018"]);
|
cmd.args(["--edition", "2018"]);
|
||||||
cmd.arg(&self.input_path);
|
cmd.arg(&self.inner.input_path);
|
||||||
|
|
||||||
cmd.output()
|
cmd.output()
|
||||||
.context("spawning rustc process")?
|
.context("spawning rustc process")?
|
||||||
|
|
@ -74,12 +81,12 @@ impl Build {
|
||||||
|
|
||||||
Ok(BuildResult {
|
Ok(BuildResult {
|
||||||
reproduces_issue,
|
reproduces_issue,
|
||||||
no_verify: self.no_verify,
|
no_verify: self.inner.no_verify,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_suggestions(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
|
pub fn get_diags(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
|
||||||
let diags = match self.mode {
|
let diags = match self.inner.mode {
|
||||||
BuildMode::Cargo => {
|
BuildMode::Cargo => {
|
||||||
let mut cmd = Command::new("cargo");
|
let mut cmd = Command::new("cargo");
|
||||||
cmd.args(["build", "--message-format=json"]);
|
cmd.args(["build", "--message-format=json"]);
|
||||||
|
|
@ -102,7 +109,7 @@ impl Build {
|
||||||
BuildMode::Rustc => {
|
BuildMode::Rustc => {
|
||||||
let mut cmd = std::process::Command::new("rustc");
|
let mut cmd = std::process::Command::new("rustc");
|
||||||
cmd.args(["--edition", "2018", "--error-format=json"]);
|
cmd.args(["--edition", "2018", "--error-format=json"]);
|
||||||
cmd.arg(&self.input_path);
|
cmd.arg(&self.inner.input_path);
|
||||||
|
|
||||||
let output = cmd.output()?.stderr;
|
let output = cmd.output()?.stderr;
|
||||||
let output = String::from_utf8(output)?;
|
let output = String::from_utf8(output)?;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::{parse_quote, visit_mut::VisitMut};
|
use syn::{parse_quote, visit_mut::VisitMut};
|
||||||
|
|
||||||
use crate::processor::{ProcessChecker, ProcessState, Processor};
|
use crate::processor::{ProcessChecker, ProcessState, Processor, SourceFile};
|
||||||
|
|
||||||
struct Visitor<'a> {
|
struct Visitor<'a> {
|
||||||
current_path: Vec<String>,
|
current_path: Vec<String>,
|
||||||
|
|
@ -68,6 +68,7 @@ impl Processor for EverybodyLoops {
|
||||||
fn process_file(
|
fn process_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
krate: &mut syn::File,
|
krate: &mut syn::File,
|
||||||
|
_: &SourceFile,
|
||||||
checker: &mut ProcessChecker,
|
checker: &mut ProcessChecker,
|
||||||
) -> ProcessState {
|
) -> ProcessState {
|
||||||
let mut visitor = Visitor::new(checker);
|
let mut visitor = Visitor::new(checker);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use processor::Minimizer;
|
use processor::Minimizer;
|
||||||
|
|
||||||
use crate::{everybody_loops::EverybodyLoops, processor::Processor, privatize::Privatize};
|
use crate::{everybody_loops::EverybodyLoops, privatize::Privatize, processor::Processor};
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
|
|
@ -30,7 +30,7 @@ pub fn minimize() -> Result<()> {
|
||||||
|
|
||||||
let build = build::Build::new(&options);
|
let build = build::Build::new(&options);
|
||||||
|
|
||||||
let mut minimizer = Minimizer::new_glob_dir(&options.path, build, &options);
|
let mut minimizer = Minimizer::new_glob_dir(&options.path, build);
|
||||||
|
|
||||||
println!("{minimizer:?}");
|
println!("{minimizer:?}");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use syn::{parse_quote, visit_mut::VisitMut, Visibility};
|
use syn::{parse_quote, visit_mut::VisitMut, Visibility};
|
||||||
|
|
||||||
use crate::processor::{ProcessChecker, Processor, ProcessState};
|
use crate::processor::{ProcessChecker, ProcessState, Processor, SourceFile};
|
||||||
|
|
||||||
struct Visitor {
|
struct Visitor {
|
||||||
pub_crate: Visibility,
|
pub_crate: Visibility,
|
||||||
|
|
@ -29,7 +29,7 @@ impl VisitMut for Visitor {
|
||||||
pub struct Privatize {}
|
pub struct Privatize {}
|
||||||
|
|
||||||
impl Processor for Privatize {
|
impl Processor for Privatize {
|
||||||
fn process_file(&mut self, krate: &mut syn::File, _: &mut ProcessChecker) -> ProcessState {
|
fn process_file(&mut self, krate: &mut syn::File, _: &SourceFile, _: &mut ProcessChecker) -> ProcessState {
|
||||||
let mut visitor = Visitor::new();
|
let mut visitor = Visitor::new();
|
||||||
visitor.visit_file_mut(krate);
|
visitor.visit_file_mut(krate);
|
||||||
visitor.process_state
|
visitor.process_state
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
pub struct SourceFile {
|
pub struct SourceFile {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,6 @@ impl SourceFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Changes {
|
impl Changes {
|
||||||
pub fn had_changes(&self) -> bool {
|
pub fn had_changes(&self) -> bool {
|
||||||
self.any_change
|
self.any_change
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,24 @@ use std::{collections::HashSet, ffi::OsStr, path::Path};
|
||||||
|
|
||||||
use anyhow::{ensure, Context, Result};
|
use anyhow::{ensure, Context, Result};
|
||||||
|
|
||||||
use crate::{build::Build, processor::files::Changes, Options};
|
use crate::{build::Build, processor::files::Changes};
|
||||||
|
|
||||||
use self::files::SourceFile;
|
pub use self::files::SourceFile;
|
||||||
|
|
||||||
pub trait Processor {
|
pub trait Processor {
|
||||||
fn process_file(&mut self, krate: &mut syn::File, checker: &mut ProcessChecker)
|
fn refresh_state(&mut self) -> Result<()> {
|
||||||
-> ProcessState;
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a file. The state of the processor might get invalidated in the process as signaled with
|
||||||
|
/// `ProcessState::FileInvalidated`. When a file is invalidated, the minimizer will call `Processor::refersh_state`
|
||||||
|
/// before calling the this function on the same file again.
|
||||||
|
fn process_file(
|
||||||
|
&mut self,
|
||||||
|
krate: &mut syn::File,
|
||||||
|
file: &SourceFile,
|
||||||
|
checker: &mut ProcessChecker,
|
||||||
|
) -> ProcessState;
|
||||||
|
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
@ -27,11 +38,10 @@ pub enum ProcessState {
|
||||||
pub struct Minimizer {
|
pub struct Minimizer {
|
||||||
files: Vec<SourceFile>,
|
files: Vec<SourceFile>,
|
||||||
build: Build,
|
build: Build,
|
||||||
no_verify: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Minimizer {
|
impl Minimizer {
|
||||||
pub fn new_glob_dir(path: &Path, build: Build, options: &Options) -> Self {
|
pub fn new_glob_dir(path: &Path, build: Build) -> Self {
|
||||||
let walk = walkdir::WalkDir::new(path);
|
let walk = walkdir::WalkDir::new(path);
|
||||||
|
|
||||||
let files = walk
|
let files = walk
|
||||||
|
|
@ -49,29 +59,25 @@ impl Minimizer {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self { files, build }
|
||||||
files,
|
|
||||||
build,
|
|
||||||
no_verify: options.no_verify,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_passes<'a>(
|
pub fn run_passes<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
passes: impl IntoIterator<Item = Box<dyn Processor>>,
|
passes: impl IntoIterator<Item = Box<dyn Processor + 'a>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let inital_build = self.build.build()?;
|
let inital_build = self.build.build()?;
|
||||||
println!("Initial build: {}", inital_build);
|
println!("Initial build: {}", inital_build);
|
||||||
if !self.no_verify {
|
|
||||||
ensure!(
|
ensure!(
|
||||||
inital_build.reproduces_issue(),
|
inital_build.reproduces_issue(),
|
||||||
"Initial build must reproduce issue"
|
"Initial build must reproduce issue"
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let mut invalidated_files = HashSet::new();
|
let mut invalidated_files = HashSet::new();
|
||||||
|
|
||||||
for mut pass in passes {
|
for mut pass in passes {
|
||||||
|
let mut refresh_and_try_again = false;
|
||||||
|
|
||||||
'pass: loop {
|
'pass: loop {
|
||||||
println!("Starting a round of {}", pass.name());
|
println!("Starting a round of {}", pass.name());
|
||||||
let mut changes = Changes::default();
|
let mut changes = Changes::default();
|
||||||
|
|
@ -88,7 +94,7 @@ impl Minimizer {
|
||||||
let mut krate = syn::parse_file(change.before_content())
|
let mut krate = syn::parse_file(change.before_content())
|
||||||
.with_context(|| format!("parsing file {file_display}"))?;
|
.with_context(|| format!("parsing file {file_display}"))?;
|
||||||
|
|
||||||
let has_made_change = pass.process_file(&mut krate, &mut ProcessChecker {});
|
let has_made_change = pass.process_file(&mut krate, file, &mut ProcessChecker {});
|
||||||
|
|
||||||
match has_made_change {
|
match has_made_change {
|
||||||
ProcessState::Changed | ProcessState::FileInvalidated => {
|
ProcessState::Changed | ProcessState::FileInvalidated => {
|
||||||
|
|
@ -117,8 +123,18 @@ impl Minimizer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changes.had_changes() {
|
if !changes.had_changes() {
|
||||||
|
if !refresh_and_try_again && invalidated_files.len() > 0 {
|
||||||
|
// A few files have been invalidated, let's refresh and try these again.
|
||||||
|
pass.refresh_state().context("refreshing state for pass")?;
|
||||||
|
refresh_and_try_again = true;
|
||||||
|
println!("Refreshing files for {}", pass.name());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
println!("Finished {}", pass.name());
|
println!("Finished {}", pass.name());
|
||||||
break 'pass;
|
break 'pass;
|
||||||
|
} else {
|
||||||
|
refresh_and_try_again = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
//! Deletes dead code.
|
//! Deletes dead code.
|
||||||
|
|
||||||
use super::{files::Changes, Minimizer, ProcessState, Processor};
|
use crate::build::Build;
|
||||||
|
|
||||||
|
use super::{files::Changes, Minimizer, ProcessState, Processor, SourceFile};
|
||||||
use anyhow::{ensure, Context, Result};
|
use anyhow::{ensure, Context, Result};
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use rustfix::{diagnostics::Diagnostic, Suggestion};
|
use rustfix::{diagnostics::Diagnostic, Suggestion};
|
||||||
use std::{collections::HashMap, ops::Range, path::Path};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
ops::Range,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
use syn::{visit_mut::VisitMut, ImplItem, Item};
|
use syn::{visit_mut::VisitMut, ImplItem, Item};
|
||||||
|
|
||||||
fn file_for_suggestion(suggestion: &Suggestion) -> &str {
|
fn file_for_suggestion(suggestion: &Suggestion) -> &str {
|
||||||
|
|
@ -15,16 +21,14 @@ impl Minimizer {
|
||||||
pub fn delete_dead_code(&mut self) -> Result<()> {
|
pub fn delete_dead_code(&mut self) -> Result<()> {
|
||||||
let inital_build = self.build.build()?;
|
let inital_build = self.build.build()?;
|
||||||
println!("Before reaper: {}", inital_build);
|
println!("Before reaper: {}", inital_build);
|
||||||
if !self.no_verify {
|
|
||||||
ensure!(
|
ensure!(
|
||||||
inital_build.reproduces_issue(),
|
inital_build.reproduces_issue(),
|
||||||
"Initial build must reproduce issue"
|
"Initial build must reproduce issue"
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let (diags, suggestions) = self
|
let (diags, suggestions) = self
|
||||||
.build
|
.build
|
||||||
.get_suggestions()
|
.get_diags()
|
||||||
.context("getting suggestions from rustc")?;
|
.context("getting suggestions from rustc")?;
|
||||||
|
|
||||||
let mut suggestions_for_file = HashMap::<_, Vec<_>>::new();
|
let mut suggestions_for_file = HashMap::<_, Vec<_>>::new();
|
||||||
|
|
@ -38,10 +42,9 @@ impl Minimizer {
|
||||||
// Always unconditionally apply unused imports.
|
// Always unconditionally apply unused imports.
|
||||||
self.apply_unused_imports(&suggestions_for_file)?;
|
self.apply_unused_imports(&suggestions_for_file)?;
|
||||||
|
|
||||||
self.run_passes([Box::new(DeleteUnusedFunctions {
|
self.run_passes([
|
||||||
diags,
|
Box::new(DeleteUnusedFunctions::new(self.build.clone(), diags)) as Box<dyn Processor>,
|
||||||
invalid: false,
|
])
|
||||||
}) as Box<dyn Processor>])
|
|
||||||
.context("deleting unused functions")?;
|
.context("deleting unused functions")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -91,22 +94,44 @@ impl Minimizer {
|
||||||
|
|
||||||
struct DeleteUnusedFunctions {
|
struct DeleteUnusedFunctions {
|
||||||
diags: Vec<Diagnostic>,
|
diags: Vec<Diagnostic>,
|
||||||
invalid: bool,
|
build: Build,
|
||||||
|
invalid: HashSet<SourceFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeleteUnusedFunctions {
|
||||||
|
fn new(build: Build, diags: Vec<Diagnostic>) -> Self {
|
||||||
|
DeleteUnusedFunctions {
|
||||||
|
diags,
|
||||||
|
build,
|
||||||
|
invalid: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Processor for DeleteUnusedFunctions {
|
impl Processor for DeleteUnusedFunctions {
|
||||||
|
fn refresh_state(&mut self) -> Result<()> {
|
||||||
|
let (diags, _) = self.build.get_diags().context("getting diagnostics")?;
|
||||||
|
self.diags = diags;
|
||||||
|
self.invalid = HashSet::new();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn process_file(
|
fn process_file(
|
||||||
&mut self,
|
&mut self,
|
||||||
krate: &mut syn::File,
|
krate: &mut syn::File,
|
||||||
|
file: &SourceFile,
|
||||||
_: &mut super::ProcessChecker,
|
_: &mut super::ProcessChecker,
|
||||||
) -> ProcessState {
|
) -> ProcessState {
|
||||||
assert!(!self.invalid, "processing with invalid state");
|
assert!(
|
||||||
|
!self.invalid.contains(file),
|
||||||
|
"processing with invalid state"
|
||||||
|
);
|
||||||
|
|
||||||
let mut visitor = FindUnusedFunction::new(self.diags.iter());
|
let mut visitor = FindUnusedFunction::new(file, self.diags.iter());
|
||||||
visitor.visit_file_mut(krate);
|
visitor.visit_file_mut(krate);
|
||||||
|
|
||||||
if visitor.process_state == ProcessState::FileInvalidated {
|
if visitor.process_state == ProcessState::FileInvalidated {
|
||||||
self.invalid = true;
|
self.invalid.insert(file.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
visitor.process_state
|
visitor.process_state
|
||||||
|
|
@ -143,7 +168,7 @@ struct FindUnusedFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FindUnusedFunction {
|
impl FindUnusedFunction {
|
||||||
fn new<'a>(diags: impl Iterator<Item = &'a Diagnostic>) -> Self {
|
fn new<'a>(file: &SourceFile, diags: impl Iterator<Item = &'a Diagnostic>) -> Self {
|
||||||
let unused_functions = diags
|
let unused_functions = diags
|
||||||
.filter_map(|diag| {
|
.filter_map(|diag| {
|
||||||
// FIXME: use `code` correctly
|
// FIXME: use `code` correctly
|
||||||
|
|
@ -167,6 +192,10 @@ impl FindUnusedFunction {
|
||||||
"encountered multiline span in dead_code"
|
"encountered multiline span in dead_code"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if Path::new(&span.file_name) != file.path {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
Some(Unused {
|
Some(Unused {
|
||||||
name,
|
name,
|
||||||
line: span.line_start,
|
line: span.line_start,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue