mirror of
https://github.com/Noratrieb/cargo-minimize.git
synced 2026-01-14 16:35:01 +01:00
HAHAHAHAHAHAHA
This commit is contained in:
parent
106be93473
commit
38d126a4b6
7 changed files with 226 additions and 145 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -152,6 +152,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo",
|
"cargo",
|
||||||
"clap 4.0.29",
|
"clap 4.0.29",
|
||||||
|
"libc",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -159,6 +160,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"syn",
|
"syn",
|
||||||
|
"tempfile",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-tree",
|
"tracing-tree",
|
||||||
|
|
@ -736,9 +738,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.134"
|
version = "0.2.138"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
|
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
name = "libgit2-sys"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,11 @@ rustfix = "0.6.1"
|
||||||
serde = { version = "1.0.151", features = ["derive"] }
|
serde = { version = "1.0.151", features = ["derive"] }
|
||||||
serde_json = "1.0.90"
|
serde_json = "1.0.90"
|
||||||
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] }
|
syn = { version = "1.0.101", features = ["full", "visit", "visit-mut"] }
|
||||||
|
tempfile = "3.3.0"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||||
tracing-tree = "0.2.2"
|
tracing-tree = "0.2.2"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
|
|
||||||
|
[target."cfg(unix)".dependencies]
|
||||||
|
libc = "0.2.138"
|
||||||
|
|
|
||||||
52
src/build.rs
52
src/build.rs
|
|
@ -1,20 +1,42 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, 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, rc::Rc};
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
path::PathBuf,
|
||||||
|
process::Command,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{EnvVar, Options};
|
use crate::{dylib_flag::RustFunction, EnvVar, Options};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Build {
|
pub struct Build {
|
||||||
inner: Rc<BuildInner>,
|
inner: Rc<BuildInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum Verify {
|
||||||
|
Ice,
|
||||||
|
Custom(RustFunction),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Verify {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Ice => write!(f, "Ice"),
|
||||||
|
Self::Custom(_) => f.debug_tuple("Custom").finish(),
|
||||||
|
Self::None => write!(f, "None"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BuildInner {
|
struct BuildInner {
|
||||||
mode: BuildMode,
|
mode: BuildMode,
|
||||||
input_path: PathBuf,
|
input_path: PathBuf,
|
||||||
no_verify: bool,
|
verify: Verify,
|
||||||
env: Vec<EnvVar>,
|
env: Vec<EnvVar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,18 +62,26 @@ impl Build {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let verify = if options.no_verify {
|
||||||
|
Verify::None
|
||||||
|
} else if let Some(func) = options.verify_fn {
|
||||||
|
Verify::Custom(func)
|
||||||
|
} else {
|
||||||
|
Verify::Ice
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner: Rc::new(BuildInner {
|
inner: Rc::new(BuildInner {
|
||||||
mode,
|
mode,
|
||||||
input_path: options.path.clone(),
|
input_path: options.path.clone(),
|
||||||
no_verify: options.no_verify,
|
verify,
|
||||||
env: options.env.clone(),
|
env: options.env.clone(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&self) -> Result<BuildResult> {
|
pub fn build(&self) -> Result<BuildResult> {
|
||||||
if self.inner.no_verify {
|
if let Verify::None = self.inner.verify {
|
||||||
return Ok(BuildResult {
|
return Ok(BuildResult {
|
||||||
reproduces_issue: false,
|
reproduces_issue: false,
|
||||||
no_verify: true,
|
no_verify: true,
|
||||||
|
|
@ -59,7 +89,7 @@ impl Build {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let (reproduces_issue, output) = match &self.inner.mode {
|
let (is_ice, output) = match &self.inner.mode {
|
||||||
BuildMode::Cargo { args } => {
|
BuildMode::Cargo { args } => {
|
||||||
let mut cmd = Command::new("cargo");
|
let mut cmd = Command::new("cargo");
|
||||||
cmd.arg("build");
|
cmd.arg("build");
|
||||||
|
|
@ -116,9 +146,15 @@ impl Build {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let reproduces_issue = match self.inner.verify {
|
||||||
|
Verify::None => unreachable!("handled ealier"),
|
||||||
|
Verify::Ice => is_ice,
|
||||||
|
Verify::Custom(func) => func.call(&output),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(BuildResult {
|
Ok(BuildResult {
|
||||||
reproduces_issue,
|
reproduces_issue,
|
||||||
no_verify: self.inner.no_verify,
|
no_verify: false,
|
||||||
output,
|
output,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -144,8 +180,6 @@ impl Build {
|
||||||
.into_iter::<CargoJsonCompileMessage>()
|
.into_iter::<CargoJsonCompileMessage>()
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
messages
|
messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|msg| msg.reason == "compiler-message")
|
.filter(|msg| msg.reason == "compiler-message")
|
||||||
|
|
|
||||||
116
src/dylib_flag.rs
Normal file
116
src/dylib_flag.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
//! Handles the --verify-fn flag.
|
||||||
|
//! It takes in a Rust closure like `|str| true` that takes in a `&str` and returns a bool.
|
||||||
|
|
||||||
|
use std::{fmt::Debug, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
type Entrypoint = unsafe extern "C" fn(*const u8, usize) -> bool;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct RustFunction {
|
||||||
|
func: Entrypoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for RustFunction {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Self::compile(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_func_body(func: &str) -> Result<String> {
|
||||||
|
let closure = syn::parse_str::<syn::ExprClosure>(func).context("invalid rust syntax")?;
|
||||||
|
|
||||||
|
let tokenstream = quote! {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn cargo_minimize_ffi_function(ptr: *const u8, len: usize) -> bool {
|
||||||
|
match ::std::panic::catch_unwind(|| __cargo_minimize_inner(ptr, len)) {
|
||||||
|
Ok(bool) => bool,
|
||||||
|
Err(_) => ::std::process::abort(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __cargo_minimize_inner(__ptr: *const u8, __len: usize) -> bool {
|
||||||
|
let __slice = unsafe { ::std::slice::from_raw_parts(__ptr, __len) };
|
||||||
|
let __str = ::std::str::from_utf8(__slice).unwrap();
|
||||||
|
|
||||||
|
(#closure)(__str)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tokenstream.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustFunction {
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn compile(body: &str) -> Result<Self> {
|
||||||
|
Err(anyhow::anyhow!("--verify-fn only works on unix"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn compile(body: &str) -> Result<Self> {
|
||||||
|
use anyhow::bail;
|
||||||
|
use std::io;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::{ffi::CString, os::unix::prelude::OsStringExt};
|
||||||
|
|
||||||
|
let file = tempfile::tempdir()?;
|
||||||
|
|
||||||
|
let full_file = wrap_func_body(body)?;
|
||||||
|
|
||||||
|
let source_path = file.path().join("source.rs");
|
||||||
|
|
||||||
|
std::fs::write(&source_path, &full_file).context("writing source")?;
|
||||||
|
|
||||||
|
let mut rustc = Command::new("rustc");
|
||||||
|
rustc.arg(source_path);
|
||||||
|
rustc.args(["--crate-type=cdylib", "--crate-name=helper", "--emit=link"]);
|
||||||
|
rustc.current_dir(file.path());
|
||||||
|
|
||||||
|
let output = rustc.output().context("running rustc")?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8(output.stderr)?;
|
||||||
|
bail!("Failed to compile code: {stderr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let dylib_path = file.path().join("libhelper.so");
|
||||||
|
|
||||||
|
let os_str = dylib_path.into_os_string();
|
||||||
|
let vec = os_str.into_vec();
|
||||||
|
let cstr = CString::new(vec)?;
|
||||||
|
|
||||||
|
let dylib = unsafe { libc::dlopen(cstr.as_ptr(), libc::RTLD_LAZY) };
|
||||||
|
|
||||||
|
if dylib.is_null() {
|
||||||
|
bail!("failed to open dylib: {}", io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
let symbol = b"cargo_minimize_ffi_function\0";
|
||||||
|
|
||||||
|
let func = unsafe { libc::dlsym(dylib, symbol.as_ptr().cast()) };
|
||||||
|
|
||||||
|
if func.is_null() {
|
||||||
|
bail!("didn't find entrypoint symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
let func = unsafe { std::mem::transmute::<*mut _, Entrypoint>(func) };
|
||||||
|
|
||||||
|
Ok(Self { func })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, output: &str) -> bool {
|
||||||
|
let ptr = output.as_ptr();
|
||||||
|
let len = output.len();
|
||||||
|
|
||||||
|
unsafe { (self.func)(ptr, len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RustFunction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("RustFunction").finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ extern crate tracing;
|
||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
mod build;
|
mod build;
|
||||||
|
mod dylib_flag;
|
||||||
mod everybody_loops;
|
mod everybody_loops;
|
||||||
mod expand;
|
mod expand;
|
||||||
mod privatize;
|
mod privatize;
|
||||||
|
|
@ -11,9 +12,10 @@ mod processor;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use dylib_flag::RustFunction;
|
||||||
use processor::Minimizer;
|
use processor::Minimizer;
|
||||||
|
|
||||||
use crate::{processor::Processor};
|
use crate::processor::Processor;
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
#[command(version, about, name = "cargo", bin_name = "cargo")]
|
#[command(version, about, name = "cargo", bin_name = "cargo")]
|
||||||
|
|
@ -33,6 +35,8 @@ pub struct Options {
|
||||||
rustc: bool,
|
rustc: bool,
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
no_verify: bool,
|
no_verify: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
verify_fn: Option<RustFunction>,
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
env: Vec<EnvVar>,
|
env: Vec<EnvVar>,
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,40 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::{
|
use std::{fs, path::{Path, PathBuf}};
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||||
pub struct SourceFile {
|
pub(crate) struct SourceFile {
|
||||||
pub path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Changes {
|
pub(crate) struct Changes {
|
||||||
any_change: bool,
|
any_change: bool,
|
||||||
}
|
}
|
||||||
|
pub(crate) struct FileChange<'a, 'b> {
|
||||||
pub struct FileChange<'a, 'b> {
|
pub(crate) path: &'a Path,
|
||||||
pub path: &'a Path,
|
|
||||||
content: String,
|
content: String,
|
||||||
changes: &'b mut Changes,
|
changes: &'b mut Changes,
|
||||||
has_written_change: bool,
|
has_written_change: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileChange<'_, '_> {
|
impl FileChange<'_, '_> {
|
||||||
pub fn before_content(&self) -> &str {
|
pub(crate) fn before_content(&self) -> &str {
|
||||||
&self.content
|
&self.content
|
||||||
}
|
}
|
||||||
|
pub(crate) fn write(&mut self, new: &str) -> Result<()> {
|
||||||
pub fn write(&mut self, new: &str) -> Result<()> {
|
|
||||||
self.has_written_change = true;
|
self.has_written_change = true;
|
||||||
fs::write(self.path, new).with_context(|| format!("writing file {}", self.path.display()))
|
fs::write(self.path, new)
|
||||||
|
.with_context(|| format!("writing file {}", self.path.display()))
|
||||||
}
|
}
|
||||||
|
pub(crate) fn rollback(mut self) -> Result<()> {
|
||||||
pub fn rollback(mut self) -> Result<()> {
|
|
||||||
assert!(self.has_written_change);
|
assert!(self.has_written_change);
|
||||||
self.has_written_change = false;
|
self.has_written_change = false;
|
||||||
fs::write(self.path, &self.content)
|
fs::write(self.path, &self.content)
|
||||||
.with_context(|| format!("writing file {}", self.path.display()))
|
.with_context(|| format!("writing file {}", self.path.display()))
|
||||||
}
|
}
|
||||||
|
pub(crate) fn commit(mut self) {
|
||||||
pub fn commit(mut self) {
|
|
||||||
assert!(self.has_written_change);
|
assert!(self.has_written_change);
|
||||||
self.has_written_change = false;
|
self.has_written_change = false;
|
||||||
self.changes.any_change = true;
|
self.changes.any_change = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for FileChange<'_, '_> {
|
impl Drop for FileChange<'_, '_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.has_written_change {
|
if self.has_written_change {
|
||||||
|
|
@ -55,9 +45,8 @@ impl Drop for FileChange<'_, '_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceFile {
|
impl SourceFile {
|
||||||
pub fn try_change<'file, 'change>(
|
pub(crate) fn try_change<'file, 'change>(
|
||||||
&'file self,
|
&'file self,
|
||||||
changes: &'change mut Changes,
|
changes: &'change mut Changes,
|
||||||
) -> Result<FileChange<'file, 'change>> {
|
) -> Result<FileChange<'file, 'change>> {
|
||||||
|
|
@ -71,9 +60,8 @@ impl SourceFile {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Changes {
|
impl Changes {
|
||||||
pub fn had_changes(&self) -> bool {
|
pub(crate) fn had_changes(&self) -> bool {
|
||||||
self.any_change
|
self.any_change
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
mod files;
|
mod files;
|
||||||
mod reaper;
|
mod reaper;
|
||||||
|
|
||||||
use std::{borrow::Borrow, collections::HashSet, ffi::OsStr, fmt::Debug, mem, path::Path};
|
use std::{borrow::Borrow, collections::HashSet, ffi::OsStr, fmt::Debug, mem, path::Path};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::{build::Build, processor::files::Changes};
|
use crate::{build::Build, processor::files::Changes};
|
||||||
|
pub(crate) use self::files::SourceFile;
|
||||||
pub use self::files::SourceFile;
|
pub(crate) trait Processor {
|
||||||
|
|
||||||
pub trait Processor {
|
|
||||||
fn refresh_state(&mut self) -> Result<()> {
|
fn refresh_state(&mut self) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process a file. The state of the processor might get invalidated in the process as signaled with
|
/// 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`
|
/// `ProcessState::FileInvalidated`. When a file is invalidated, the minimizer will call `Processor::refersh_state`
|
||||||
/// before calling the this function on the same file again.
|
/// before calling the this function on the same file again.
|
||||||
|
|
@ -23,33 +17,27 @@ pub trait Processor {
|
||||||
file: &SourceFile,
|
file: &SourceFile,
|
||||||
checker: &mut PassController,
|
checker: &mut PassController,
|
||||||
) -> ProcessState;
|
) -> ProcessState;
|
||||||
|
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn Processor {
|
impl Debug for dyn Processor {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(self.name())
|
f.write_str(self.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum ProcessState {
|
pub(crate) enum ProcessState {
|
||||||
NoChange,
|
NoChange,
|
||||||
Changed,
|
Changed,
|
||||||
FileInvalidated,
|
FileInvalidated,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Minimizer {
|
pub(crate) struct Minimizer {
|
||||||
files: Vec<SourceFile>,
|
files: Vec<SourceFile>,
|
||||||
build: Build,
|
build: Build,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Minimizer {
|
impl Minimizer {
|
||||||
pub fn new_glob_dir(path: &Path, build: Build) -> Self {
|
pub(crate) 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
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|entry| match entry {
|
.filter_map(|entry| match entry {
|
||||||
|
|
@ -67,54 +55,41 @@ impl Minimizer {
|
||||||
println!("- {}", file.path.display());
|
println!("- {}", file.path.display());
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self { files, build }
|
Self { files, build }
|
||||||
}
|
}
|
||||||
|
pub(crate) fn run_passes<'a>(
|
||||||
pub fn run_passes<'a>(
|
|
||||||
&self,
|
&self,
|
||||||
passes: impl IntoIterator<Item = Box<dyn Processor + 'a>>,
|
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}");
|
||||||
inital_build.require_reproduction("Initial")?;
|
inital_build.require_reproduction("Initial")?;
|
||||||
|
|
||||||
for mut pass in passes {
|
for mut pass in passes {
|
||||||
self.run_pass(&mut *pass)?;
|
self.run_pass(&mut *pass)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_pass(&self, pass: &mut dyn Processor) -> Result<()> {
|
fn run_pass(&self, pass: &mut dyn Processor) -> Result<()> {
|
||||||
let mut invalidated_files = HashSet::new();
|
let mut invalidated_files = HashSet::new();
|
||||||
|
|
||||||
let mut refresh_and_try_again = false;
|
let mut refresh_and_try_again = false;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let span = info_span!("Starting round of pass", name = pass.name());
|
let span = info_span!("Starting round of pass", name = pass.name());
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
|
|
||||||
let mut changes = Changes::default();
|
let mut changes = Changes::default();
|
||||||
|
|
||||||
for file in &self.files {
|
for file in &self.files {
|
||||||
if invalidated_files.contains(file) {
|
if invalidated_files.contains(file) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.process_file(pass, file, &mut invalidated_files, &mut changes)?;
|
self.process_file(pass, file, &mut invalidated_files, &mut changes)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !changes.had_changes() {
|
if !changes.had_changes() {
|
||||||
if !refresh_and_try_again && !invalidated_files.is_empty() {
|
if !refresh_and_try_again && !invalidated_files.is_empty() {
|
||||||
// A few files have been invalidated, let's refresh and try these again.
|
|
||||||
pass.refresh_state().context("refreshing state for pass")?;
|
pass.refresh_state().context("refreshing state for pass")?;
|
||||||
invalidated_files.clear();
|
invalidated_files.clear();
|
||||||
refresh_and_try_again = true;
|
refresh_and_try_again = true;
|
||||||
println!("Refreshing files for {}", pass.name());
|
println!("Refreshing files for {}", pass.name());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Finished {}", pass.name());
|
println!("Finished {}", pass.name());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -122,7 +97,6 @@ impl Minimizer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_file<'file>(
|
fn process_file<'file>(
|
||||||
&self,
|
&self,
|
||||||
pass: &mut dyn Processor,
|
pass: &mut dyn Processor,
|
||||||
|
|
@ -131,29 +105,19 @@ impl Minimizer {
|
||||||
changes: &mut Changes,
|
changes: &mut Changes,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut checker = PassController::new();
|
let mut checker = PassController::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
dbg!(&checker);
|
dbg!(& checker);
|
||||||
|
|
||||||
let file_display = file.path.display();
|
let file_display = file.path.display();
|
||||||
|
|
||||||
let mut change = file.try_change(changes)?;
|
let mut change = file.try_change(changes)?;
|
||||||
|
|
||||||
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, file, &mut checker);
|
let has_made_change = pass.process_file(&mut krate, file, &mut checker);
|
||||||
|
|
||||||
match has_made_change {
|
match has_made_change {
|
||||||
ProcessState::Changed | ProcessState::FileInvalidated => {
|
ProcessState::Changed | ProcessState::FileInvalidated => {
|
||||||
let result = prettyplease::unparse(&krate);
|
let result = prettyplease::unparse(&krate);
|
||||||
|
|
||||||
change.write(&result)?;
|
change.write(&result)?;
|
||||||
|
|
||||||
let after = self.build.build()?;
|
let after = self.build.build()?;
|
||||||
|
|
||||||
println!("{file_display}: After {}: {after}", pass.name());
|
println!("{file_display}: After {}: {after}", pass.name());
|
||||||
|
|
||||||
if after.reproduces_issue() {
|
if after.reproduces_issue() {
|
||||||
change.commit();
|
change.commit();
|
||||||
checker.reproduces();
|
checker.reproduces();
|
||||||
|
|
@ -161,7 +125,6 @@ impl Minimizer {
|
||||||
change.rollback()?;
|
change.rollback()?;
|
||||||
checker.does_not_reproduce();
|
checker.does_not_reproduce();
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_made_change == ProcessState::FileInvalidated {
|
if has_made_change == ProcessState::FileInvalidated {
|
||||||
invalidated_files.insert(file);
|
invalidated_files.insert(file);
|
||||||
}
|
}
|
||||||
|
|
@ -171,50 +134,35 @@ impl Minimizer {
|
||||||
checker.no_change();
|
checker.no_change();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if checker.is_finished() {
|
if checker.is_finished() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
struct AstPath(Vec<String>);
|
struct AstPath(Vec<String>);
|
||||||
|
|
||||||
impl Borrow<[String]> for AstPath {
|
impl Borrow<[String]> for AstPath {
|
||||||
fn borrow(&self) -> &[String] {
|
fn borrow(&self) -> &[String] {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for AstPath {
|
impl Debug for AstPath {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "AstPath({:?})", self.0)
|
write!(f, "AstPath({:?})", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PassController {
|
pub(crate) struct PassController {
|
||||||
state: PassControllerState,
|
state: PassControllerState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum PassControllerState {
|
enum PassControllerState {
|
||||||
InitialCollection {
|
InitialCollection { candidates: Vec<AstPath> },
|
||||||
candidates: Vec<AstPath>,
|
Bisecting { current: HashSet<AstPath>, worklist: Vec<Vec<AstPath>> },
|
||||||
},
|
|
||||||
|
|
||||||
Bisecting {
|
|
||||||
current: HashSet<AstPath>,
|
|
||||||
worklist: Vec<Vec<AstPath>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
Success,
|
Success,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PassController {
|
impl PassController {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -223,44 +171,41 @@ impl PassController {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reproduces(&mut self) {
|
fn reproduces(&mut self) {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
PassControllerState::InitialCollection { .. } => {
|
PassControllerState::InitialCollection { .. } => {
|
||||||
self.state = PassControllerState::Success
|
self.state = PassControllerState::Success;
|
||||||
}
|
}
|
||||||
PassControllerState::Bisecting {
|
PassControllerState::Bisecting { current, worklist, .. } => {
|
||||||
current, worklist, ..
|
match worklist.pop() {
|
||||||
} => match worklist.pop() {
|
|
||||||
Some(next) => *current = next.into_iter().collect(),
|
Some(next) => *current = next.into_iter().collect(),
|
||||||
None => {
|
None => {
|
||||||
self.state = PassControllerState::Success;
|
self.state = PassControllerState::Success;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
PassControllerState::Success => unreachable!("Processed after success"),
|
PassControllerState::Success => unreachable!("Processed after success"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn does_not_reproduce(&mut self) {
|
fn does_not_reproduce(&mut self) {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
PassControllerState::InitialCollection { candidates } => {
|
PassControllerState::InitialCollection { candidates } => {
|
||||||
let candidates = mem::take(candidates);
|
let candidates = mem::take(candidates);
|
||||||
let half = candidates.len() / 2;
|
let half = candidates.len() / 2;
|
||||||
let (first_half, second_half) = candidates.split_at(half);
|
let (first_half, second_half) = candidates.split_at(half);
|
||||||
|
self
|
||||||
self.state = PassControllerState::Bisecting {
|
.state = PassControllerState::Bisecting {
|
||||||
current: first_half.iter().cloned().collect(),
|
current: first_half.iter().cloned().collect(),
|
||||||
worklist: vec![second_half.to_owned()],
|
worklist: vec![second_half.to_owned()],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
PassControllerState::Bisecting { current, worklist } => {
|
PassControllerState::Bisecting { current, worklist } => {
|
||||||
dbg!(¤t, &worklist);
|
dbg!(& current, & worklist);
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
PassControllerState::Success => unreachable!("Processed after success"),
|
PassControllerState::Success => unreachable!("Processed after success"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn no_change(&mut self) {
|
fn no_change(&mut self) {
|
||||||
match &self.state {
|
match &self.state {
|
||||||
PassControllerState::InitialCollection { candidates } => {
|
PassControllerState::InitialCollection { candidates } => {
|
||||||
|
|
@ -271,12 +216,13 @@ impl PassController {
|
||||||
self.state = PassControllerState::Success;
|
self.state = PassControllerState::Success;
|
||||||
}
|
}
|
||||||
PassControllerState::Bisecting { current, .. } => {
|
PassControllerState::Bisecting { current, .. } => {
|
||||||
unreachable!("No change while bisecting, current was empty somehow: {current:?}");
|
unreachable!(
|
||||||
|
"No change while bisecting, current was empty somehow: {current:?}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PassControllerState::Success => {}
|
PassControllerState::Success => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_finished(&mut self) -> bool {
|
fn is_finished(&mut self) -> bool {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
PassControllerState::InitialCollection { .. } => false,
|
PassControllerState::InitialCollection { .. } => false,
|
||||||
|
|
@ -284,8 +230,7 @@ impl PassController {
|
||||||
PassControllerState::Success => true,
|
PassControllerState::Success => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub(crate) fn can_process(&mut self, path: &[String]) -> bool {
|
||||||
pub fn can_process(&mut self, path: &[String]) -> bool {
|
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
PassControllerState::InitialCollection { candidates } => {
|
PassControllerState::InitialCollection { candidates } => {
|
||||||
candidates.push(AstPath(path.to_owned()));
|
candidates.push(AstPath(path.to_owned()));
|
||||||
|
|
@ -298,43 +243,31 @@ impl PassController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! tracking {
|
macro_rules! tracking {
|
||||||
() => {
|
() => {
|
||||||
tracking!(visit_item_fn_mut);
|
tracking!(visit_item_fn_mut); tracking!(visit_impl_item_method_mut);
|
||||||
tracking!(visit_impl_item_method_mut);
|
tracking!(visit_item_impl_mut); tracking!(visit_item_mod_mut);
|
||||||
tracking!(visit_item_impl_mut);
|
|
||||||
tracking!(visit_item_mod_mut);
|
|
||||||
};
|
};
|
||||||
(visit_item_fn_mut) => {
|
(visit_item_fn_mut) => {
|
||||||
fn visit_item_fn_mut(&mut self, func: &mut syn::ItemFn) {
|
fn visit_item_fn_mut(& mut self, func : & mut syn::ItemFn) { self.current_path
|
||||||
self.current_path.push(func.sig.ident.to_string());
|
.push(func.sig.ident.to_string()); syn::visit_mut::visit_item_fn_mut(self, func);
|
||||||
syn::visit_mut::visit_item_fn_mut(self, func);
|
self.current_path.pop(); }
|
||||||
self.current_path.pop();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
(visit_impl_item_method_mut) => {
|
(visit_impl_item_method_mut) => {
|
||||||
fn visit_impl_item_method_mut(&mut self, method: &mut syn::ImplItemMethod) {
|
fn visit_impl_item_method_mut(& mut self, method : & mut syn::ImplItemMethod) {
|
||||||
self.current_path.push(method.sig.ident.to_string());
|
self.current_path.push(method.sig.ident.to_string());
|
||||||
syn::visit_mut::visit_impl_item_method_mut(self, method);
|
syn::visit_mut::visit_impl_item_method_mut(self, method); self.current_path
|
||||||
self.current_path.pop();
|
.pop(); }
|
||||||
}
|
|
||||||
};
|
};
|
||||||
(visit_item_impl_mut) => {
|
(visit_item_impl_mut) => {
|
||||||
fn visit_item_impl_mut(&mut self, item: &mut syn::ItemImpl) {
|
fn visit_item_impl_mut(& mut self, item : & mut syn::ItemImpl) { self
|
||||||
self.current_path
|
.current_path.push(item.self_ty.clone().into_token_stream().to_string());
|
||||||
.push(item.self_ty.clone().into_token_stream().to_string());
|
syn::visit_mut::visit_item_impl_mut(self, item); self.current_path.pop(); }
|
||||||
syn::visit_mut::visit_item_impl_mut(self, item);
|
|
||||||
self.current_path.pop();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
(visit_item_mod_mut) => {
|
(visit_item_mod_mut) => {
|
||||||
fn visit_item_mod_mut(&mut self, module: &mut syn::ItemMod) {
|
fn visit_item_mod_mut(& mut self, module : & mut syn::ItemMod) { self
|
||||||
self.current_path.push(module.ident.to_string());
|
.current_path.push(module.ident.to_string());
|
||||||
syn::visit_mut::visit_item_mod_mut(self, module);
|
syn::visit_mut::visit_item_mod_mut(self, module); self.current_path.pop(); }
|
||||||
self.current_path.pop();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use tracking;
|
pub(crate) use tracking;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue