mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 16:35:01 +01:00
more
This commit is contained in:
parent
a36bf95c3e
commit
1cb5114557
7 changed files with 226 additions and 69 deletions
25
src/build.rs
25
src/build.rs
|
|
@ -19,7 +19,7 @@ struct BuildInner {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum BuildMode {
|
||||
Cargo,
|
||||
Cargo { args: Option<Vec<String>> },
|
||||
Script(PathBuf),
|
||||
Rustc,
|
||||
}
|
||||
|
|
@ -28,10 +28,15 @@ impl Build {
|
|||
pub fn new(options: &Options) -> Self {
|
||||
let mode = if options.rustc {
|
||||
BuildMode::Rustc
|
||||
} else if let Some(script) = &options.verify_error_path {
|
||||
} else if let Some(script) = &options.script_path {
|
||||
BuildMode::Script(script.clone())
|
||||
} else {
|
||||
BuildMode::Cargo
|
||||
BuildMode::Cargo {
|
||||
args: options
|
||||
.cargo_args
|
||||
.as_ref()
|
||||
.map(|cmd| cmd.split_whitespace().map(ToString::to_string).collect()),
|
||||
}
|
||||
};
|
||||
Self {
|
||||
inner: Rc::new(BuildInner {
|
||||
|
|
@ -51,10 +56,14 @@ impl Build {
|
|||
}
|
||||
|
||||
let reproduces_issue = match &self.inner.mode {
|
||||
BuildMode::Cargo => {
|
||||
BuildMode::Cargo { args } => {
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.arg("build");
|
||||
|
||||
for arg in args.into_iter().flatten() {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
let output =
|
||||
String::from_utf8(cmd.output().context("spawning rustc process")?.stderr)
|
||||
.unwrap();
|
||||
|
|
@ -86,11 +95,15 @@ impl Build {
|
|||
}
|
||||
|
||||
pub fn get_diags(&self) -> Result<(Vec<Diagnostic>, Vec<rustfix::Suggestion>)> {
|
||||
let diags = match self.inner.mode {
|
||||
BuildMode::Cargo => {
|
||||
let diags = match &self.inner.mode {
|
||||
BuildMode::Cargo { args } => {
|
||||
let mut cmd = Command::new("cargo");
|
||||
cmd.args(["build", "--message-format=json"]);
|
||||
|
||||
for arg in args.into_iter().flatten() {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
let cmd_output = cmd.output()?;
|
||||
let output = String::from_utf8(cmd_output.stdout.clone())?;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ impl VisitMut for Visitor<'_> {
|
|||
match block.stmts.as_slice() {
|
||||
[syn::Stmt::Expr(syn::Expr::Loop(syn::ExprLoop {
|
||||
body: loop_body, ..
|
||||
}))] if loop_body.stmts.is_empty() && self.checker.can_process(&self.current_path) => {}
|
||||
_ => {
|
||||
}))] if loop_body.stmts.is_empty() => {}
|
||||
_ if self.checker.can_process(&self.current_path) => {
|
||||
*block = self.loop_expr.clone();
|
||||
self.process_state = ProcessState::Changed;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,11 @@ enum Cargo {
|
|||
#[derive(clap::Args, Debug)]
|
||||
pub struct Options {
|
||||
#[arg(short, long)]
|
||||
verify_error_path: Option<PathBuf>,
|
||||
script_path: Option<PathBuf>,
|
||||
|
||||
#[arg(long)]
|
||||
cargo_args: Option<String>,
|
||||
|
||||
#[arg(long)]
|
||||
rustc: bool,
|
||||
#[arg(long)]
|
||||
|
|
@ -38,8 +42,6 @@ pub fn minimize() -> Result<()> {
|
|||
|
||||
let mut minimizer = Minimizer::new_glob_dir(&options.path, build);
|
||||
|
||||
minimizer.delete_dead_code().context("deleting dead code")?;
|
||||
|
||||
minimizer.run_passes([
|
||||
Box::new(Privatize::default()) as Box<dyn Processor>,
|
||||
Box::new(EverybodyLoops::default()) as Box<dyn Processor>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
mod files;
|
||||
mod reaper;
|
||||
|
||||
use std::{collections::HashSet, ffi::OsStr, path::Path};
|
||||
use std::{borrow::Borrow, collections::HashSet, ffi::OsStr, mem, path::Path};
|
||||
|
||||
use anyhow::{ensure, Context, Result};
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ impl Minimizer {
|
|||
}
|
||||
|
||||
pub fn run_passes<'a>(
|
||||
&mut self,
|
||||
&self,
|
||||
passes: impl IntoIterator<Item = Box<dyn Processor + 'a>>,
|
||||
) -> Result<()> {
|
||||
let inital_build = self.build.build()?;
|
||||
|
|
@ -83,7 +83,7 @@ impl Minimizer {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_pass(&mut self, pass: &mut dyn Processor) -> Result<()> {
|
||||
fn run_pass(&self, pass: &mut dyn Processor) -> Result<()> {
|
||||
let mut invalidated_files = HashSet::new();
|
||||
|
||||
let mut refresh_and_try_again = false;
|
||||
|
|
@ -97,39 +97,7 @@ impl Minimizer {
|
|||
continue;
|
||||
}
|
||||
|
||||
let file_display = file.path.display();
|
||||
|
||||
let mut change = file.try_change(&mut changes)?;
|
||||
|
||||
let mut krate = syn::parse_file(change.before_content())
|
||||
.with_context(|| format!("parsing file {file_display}"))?;
|
||||
|
||||
let has_made_change = pass.process_file(&mut krate, file, &mut PassController {});
|
||||
|
||||
match has_made_change {
|
||||
ProcessState::Changed | ProcessState::FileInvalidated => {
|
||||
let result = prettyplease::unparse(&krate);
|
||||
|
||||
change.write(&result)?;
|
||||
|
||||
let after = self.build.build()?;
|
||||
|
||||
println!("{file_display}: After {}: {after}", pass.name());
|
||||
|
||||
if after.reproduces_issue() {
|
||||
change.commit();
|
||||
} else {
|
||||
change.rollback()?;
|
||||
}
|
||||
|
||||
if has_made_change == ProcessState::FileInvalidated {
|
||||
invalidated_files.insert(file);
|
||||
}
|
||||
}
|
||||
ProcessState::NoChange => {
|
||||
println!("{file_display}: After {}: no change", pass.name());
|
||||
}
|
||||
}
|
||||
self.process_file(pass, file, &mut invalidated_files, &mut changes)?;
|
||||
}
|
||||
|
||||
if !changes.had_changes() {
|
||||
|
|
@ -149,14 +117,174 @@ impl Minimizer {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_file<'file>(
|
||||
&self,
|
||||
pass: &mut dyn Processor,
|
||||
file: &'file SourceFile,
|
||||
invalidated_files: &mut HashSet<&'file SourceFile>,
|
||||
changes: &mut Changes,
|
||||
) -> Result<()> {
|
||||
let mut checker = PassController::new();
|
||||
|
||||
loop {
|
||||
dbg!(&checker);
|
||||
|
||||
let file_display = file.path.display();
|
||||
|
||||
let mut change = file.try_change(changes)?;
|
||||
|
||||
let mut krate = syn::parse_file(change.before_content())
|
||||
.with_context(|| format!("parsing file {file_display}"))?;
|
||||
|
||||
let has_made_change = pass.process_file(&mut krate, file, &mut checker);
|
||||
|
||||
match has_made_change {
|
||||
ProcessState::Changed | ProcessState::FileInvalidated => {
|
||||
let result = prettyplease::unparse(&krate);
|
||||
|
||||
change.write(&result)?;
|
||||
|
||||
let after = self.build.build()?;
|
||||
|
||||
println!("{file_display}: After {}: {after}", pass.name());
|
||||
|
||||
if after.reproduces_issue() {
|
||||
change.commit();
|
||||
checker.reproduces();
|
||||
} else {
|
||||
change.rollback()?;
|
||||
checker.does_not_reproduce();
|
||||
}
|
||||
|
||||
if has_made_change == ProcessState::FileInvalidated {
|
||||
invalidated_files.insert(file);
|
||||
}
|
||||
}
|
||||
ProcessState::NoChange => {
|
||||
println!("{file_display}: After {}: no change", pass.name());
|
||||
checker.no_change();
|
||||
}
|
||||
}
|
||||
|
||||
if checker.is_finished() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PassController {}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct AstPath(Vec<String>);
|
||||
|
||||
impl Borrow<[String]> for AstPath {
|
||||
fn borrow(&self) -> &[String] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PassController {
|
||||
state: PassControllerState,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PassControllerState {
|
||||
InitialCollection {
|
||||
candidates: Vec<AstPath>,
|
||||
},
|
||||
|
||||
Bisecting {
|
||||
current: HashSet<AstPath>,
|
||||
worklist: Vec<Vec<AstPath>>,
|
||||
},
|
||||
|
||||
Success,
|
||||
}
|
||||
|
||||
impl PassController {
|
||||
pub fn can_process(&mut self, _: &[String]) -> bool {
|
||||
// FIXME: Actually do smart things here.
|
||||
true
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
state: PassControllerState::InitialCollection {
|
||||
candidates: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn reproduces(&mut self) {
|
||||
match &mut self.state {
|
||||
PassControllerState::InitialCollection { .. } => {
|
||||
self.state = PassControllerState::Success
|
||||
}
|
||||
PassControllerState::Bisecting {
|
||||
current, worklist, ..
|
||||
} => match worklist.pop() {
|
||||
Some(next) => *current = next.into_iter().collect(),
|
||||
None => {
|
||||
self.state = PassControllerState::Success;
|
||||
}
|
||||
},
|
||||
PassControllerState::Success => unreachable!("Processed after success"),
|
||||
}
|
||||
}
|
||||
|
||||
fn does_not_reproduce(&mut self) {
|
||||
match &mut self.state {
|
||||
PassControllerState::InitialCollection { candidates } => {
|
||||
let candidates = mem::take(candidates);
|
||||
let half = candidates.len() / 2;
|
||||
let (first_half, second_half) = candidates.split_at(half);
|
||||
|
||||
self.state = PassControllerState::Bisecting {
|
||||
current: first_half.iter().cloned().collect(),
|
||||
worklist: vec![second_half.to_owned()],
|
||||
};
|
||||
}
|
||||
PassControllerState::Bisecting { current, worklist } => {
|
||||
dbg!(¤t, &worklist);
|
||||
todo!();
|
||||
}
|
||||
PassControllerState::Success => unreachable!("Processed after success"),
|
||||
}
|
||||
}
|
||||
|
||||
fn no_change(&mut self) {
|
||||
match &self.state {
|
||||
PassControllerState::InitialCollection { candidates } => {
|
||||
assert!(
|
||||
candidates.is_empty(),
|
||||
"No change but received candidates: {candidates:?}"
|
||||
);
|
||||
self.state = PassControllerState::Success;
|
||||
}
|
||||
PassControllerState::Bisecting { current, .. } => {
|
||||
unreachable!("No change while bisecting, current was empty somehow: {current:?}");
|
||||
}
|
||||
PassControllerState::Success => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_finished(&mut self) -> bool {
|
||||
match &mut self.state {
|
||||
PassControllerState::InitialCollection { .. } => false,
|
||||
PassControllerState::Bisecting { .. } => false,
|
||||
PassControllerState::Success => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_process(&mut self, path: &[String]) -> bool {
|
||||
match &mut self.state {
|
||||
PassControllerState::InitialCollection { candidates } => {
|
||||
candidates.push(AstPath(path.to_owned()));
|
||||
true
|
||||
}
|
||||
PassControllerState::Bisecting { current, .. } => current.contains(path),
|
||||
PassControllerState::Success => {
|
||||
unreachable!("Processed further after success");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,16 +226,16 @@ impl<'a> FindUnusedFunction<'a> {
|
|||
.filter(|&matches| matches)
|
||||
.count();
|
||||
|
||||
assert!(
|
||||
span_matches < 2,
|
||||
"multiple dead_code spans matched identifier: {span_matches}."
|
||||
);
|
||||
|
||||
if span_matches == 1 {
|
||||
self.process_state = ProcessState::FileInvalidated;
|
||||
match span_matches {
|
||||
0 => true,
|
||||
1 => {
|
||||
self.process_state = ProcessState::FileInvalidated;
|
||||
!self.checker.can_process(&self.current_path)
|
||||
}
|
||||
_ => {
|
||||
panic!("multiple dead_code spans matched identifier: {span_matches}.");
|
||||
}
|
||||
}
|
||||
|
||||
span_matches == 0 && self.checker.can_process(&self.current_path)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,9 +246,14 @@ impl VisitMut for FindUnusedFunction<'_> {
|
|||
|
||||
item_impl.items.retain(|item| match item {
|
||||
ImplItem::Method(method) => {
|
||||
self.current_path.push(method.sig.ident.to_string());
|
||||
|
||||
let span = method.sig.ident.span();
|
||||
|
||||
self.should_retain_item(span)
|
||||
let should_retain = self.should_retain_item(span);
|
||||
|
||||
self.current_path.pop();
|
||||
should_retain
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
|
|
@ -261,9 +266,13 @@ impl VisitMut for FindUnusedFunction<'_> {
|
|||
fn visit_file_mut(&mut self, krate: &mut syn::File) {
|
||||
krate.items.retain(|item| match item {
|
||||
Item::Fn(func) => {
|
||||
let span = func.sig.ident.span();
|
||||
self.current_path.push(func.sig.ident.to_string());
|
||||
|
||||
self.should_retain_item(span)
|
||||
let span = func.sig.ident.span();
|
||||
let should_retain = self.should_retain_item(span);
|
||||
|
||||
self.current_path.pop();
|
||||
should_retain
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
|
|
@ -277,9 +286,13 @@ impl VisitMut for FindUnusedFunction<'_> {
|
|||
if let Some((_, content)) = &mut module.content {
|
||||
content.retain(|item| match item {
|
||||
Item::Fn(func) => {
|
||||
let span = func.sig.ident.span();
|
||||
self.current_path.push(func.sig.ident.to_string());
|
||||
|
||||
self.should_retain_item(span)
|
||||
let span = func.sig.ident.span();
|
||||
let should_retain = self.should_retain_item(span);
|
||||
|
||||
self.current_path.pop();
|
||||
should_retain
|
||||
}
|
||||
_ => true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
fn unused() {
|
||||
loop {}
|
||||
this_is_required_to_error_haha();
|
||||
}
|
||||
fn main() {
|
||||
loop {}
|
||||
other::unused();
|
||||
}
|
||||
|
||||
mod other;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
fn unused() {
|
||||
loop {}
|
||||
pub fn unused() {
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue