This commit is contained in:
nora 2022-10-06 21:31:02 +02:00
commit a2bc92d651
No known key found for this signature in database
10 changed files with 1548 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target
expanded.rs

1310
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[workspace]
exclude = ["test-cases/*"]
[package]
name = "cargo-minimize"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.65"
cargo = "0.65.0"
prettyplease = "0.1.19"
proc-macro2 = "1.0.46"
syn = { version = "1.0.101", features = ["full"] }

28
src/build.rs Normal file
View file

@ -0,0 +1,28 @@
use anyhow::{Context, Result};
use std::path::PathBuf;
pub struct Build {
path: PathBuf,
}
impl Build {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into() }
}
pub fn build(&self) -> Result<BuildResult> {
let mut cmd = std::process::Command::new("cargo");
cmd.current_dir(&self.path).arg("build");
let output = cmd.output().context("spawning cargo")?;
Ok(BuildResult {
success: output.status.success(),
})
}
}
pub struct BuildResult {
pub success: bool,
}

108
src/expand.rs Normal file
View file

@ -0,0 +1,108 @@
use anyhow::{bail, Context, Result};
use cargo::{
core::{
compiler::{BuildContext, Unit, UnitInterner},
manifest::TargetSourcePath,
Workspace,
},
ops::{self, CompileOptions},
util::{command_prelude::CompileMode, Config},
};
use std::{ops::Not, path::Path, process::Command};
fn cargo_expand(cargo_dir: &TargetSourcePath) -> Result<syn::File> {
let cargo_dir = cargo_dir
.path()
.context("target path is not a path")?
.parent()
.context("target path has no parent")?;
let mut cmd = Command::new("cargo");
cmd.current_dir(cargo_dir).arg("expand");
let output = cmd.output().context(format!(
"spawning cargo with target path {}",
cargo_dir.display()
))?;
if output.status.success().not() {
bail!(String::from_utf8(output.stderr).context("stderr utf8")?);
}
let src = String::from_utf8(output.stdout).context("stdout utf8")?;
let root = syn::parse_str(&src).context("parsing crate")?;
Ok(root)
}
struct DepExpander<'ws, 'cfg> {
bcx: BuildContext<'ws, 'cfg>,
}
impl<'ws, 'cfg> DepExpander<'ws, 'cfg> {
fn source(unit: &Unit) -> Result<&Path> {
unit.target
.src_path()
.path()
.context("unit source path not found")
}
fn expand(&self) -> Result<syn::File> {
let unit = self.bcx.roots.get(0).context("root unit not found")?;
self.expand_recursively(unit)
.context(format!("expanding {} crate", unit.target.crate_name()))
}
fn expand_recursively(&self, unit: &Unit) -> Result<syn::File> {
let mut ast = cargo_expand(unit.target.src_path()).context("expanding unit")?;
let deps = self
.bcx
.unit_graph
.get(unit)
.context("dependencies not found for crate")?;
for dep in deps {
let crate_name = dep.unit.target.crate_name();
let file = self
.expand_recursively(&dep.unit)
.context(format!("expanding {crate_name} crate"))?;
let name = proc_macro2::Ident::new(&crate_name, proc_macro2::Span::call_site());
let module = syn::ItemMod {
attrs: file.attrs,
vis: syn::Visibility::Inherited,
mod_token: Default::default(),
ident: name,
content: Some((Default::default(), file.items)),
semi: None,
};
ast.items.push(syn::Item::Mod(module));
}
Ok(ast)
}
}
/// Expands the crate in `cargo_dir` into a single file without dependencies
pub fn expand(cargo_dir: &Path) -> Result<syn::File> {
let cargo_dir = cargo_dir.canonicalize().context("could not find path")?;
let manifest_path = cargo_dir.join("Cargo.toml");
let cfg = Config::default().context("create cargo config")?;
let ws = Workspace::new(&manifest_path, &cfg).context("getting workspace")?;
let interner = UnitInterner::new();
let options = CompileOptions::new(&cfg, CompileMode::Build).context("create options")?;
let bcx = ops::create_bcx(&ws, &options, &interner).context("resolve dep graph")?;
let expander = DepExpander { bcx };
let root = expander.expand()?;
Ok(root)
}

30
src/lib.rs Normal file
View file

@ -0,0 +1,30 @@
#![allow(dead_code)]
use std::path::Path;
mod build;
mod expand;
use anyhow::{Context, Result};
pub fn minimize(cargo_dir: &Path) -> Result<()> {
let file = expand::expand(cargo_dir).context("during expansion")?;
let file = prettyplease::unparse(&file);
println!("// EXPANDED-START\n\n{file}\n\n// EXPANDED-END");
std::fs::write("expanded.rs", file)?;
println!("wow, expanded");
Ok(())
/*
let build = Build::new(cargo_dir);
if build.build()?.success {
bail!("build must initially fail!");
}
*/
}

13
src/main.rs Normal file
View file

@ -0,0 +1,13 @@
use std::path::Path;
use anyhow::{Context, Result};
fn main() -> Result<()> {
let dir = std::env::args().nth(1).context("expected an argument")?;
cargo_minimize::minimize(&Path::new(&dir))?;
println!("Exit");
Ok(())
}

25
test-cases/uwu/Cargo.lock generated Normal file
View file

@ -0,0 +1,25 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "is-even"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83d558b5cf9dbf1e6c224d9cea60821ad82e09092ade7aed6d83aef48ed382d0"
dependencies = [
"is-odd",
]
[[package]]
name = "is-odd"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5741cc4ac9f6105f6bb067d09bb5685dc255e5bdec6f3bf6d86f4bda6187d17e"
[[package]]
name = "uwu"
version = "0.1.0"
dependencies = [
"is-even",
]

11
test-cases/uwu/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[workspace]
[package]
name = "uwu"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
is-even = "1.0.0"

View file

@ -0,0 +1,5 @@
use is_even::IsEven;
fn main() {
println!("{}", 4.is_even());
}