Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple path support #101

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub enum SweepCommand {
)]
pub struct Args {
/// Path to check
pub path: Option<PathBuf>,
pub path: Vec<PathBuf>,

/// Dry run which will not delete any files
#[arg(short, long)]
Expand Down Expand Up @@ -106,6 +106,7 @@ impl Args {
}
}

#[derive(Debug)]
pub enum Criterion {
Stamp,
File,
Expand Down Expand Up @@ -158,6 +159,5 @@ mod tests {
..Args::default()
};
assert_eq!(args, parse("cargo sweep --toolchains 1,2,3").unwrap());
assert!(parse("cargo sweep --toolchains 1 2 3").is_err());
social-anthrax marked this conversation as resolved.
Show resolved Hide resolved
}
}
72 changes: 42 additions & 30 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ fn setup_logging(verbose: bool) {
),
level = colors_level.color(record.level()),
message = message,
))
));
} else {
out.finish(format_args!("[{}] {message}", record.level()))
out.finish(format_args!("[{}] {message}", record.level()));
}
})
.level(level)
Expand All @@ -79,8 +79,7 @@ fn is_hidden(entry: &walkdir::DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
.map_or(false, |s| s.starts_with('.'))
}

/// Find all cargo project under the given root path.
Expand Down Expand Up @@ -138,30 +137,43 @@ fn main() -> anyhow::Result<()> {
setup_logging(args.verbose);

// Default to current invocation path.
let path = args
.path
.unwrap_or_else(|| env::current_dir().expect("Failed to get current directory"));
let paths = match args.path.len() {
0 => vec![env::current_dir().expect("Failed to get current directory")],
_ => args.path,
};

// FIXME: Change to write to every passed in path instead of just the first one
if let Criterion::Stamp = criterion {
debug!("Writing timestamp file in: {:?}", path);
if paths.len() > 1 {
anyhow::bail!("Using multiple paths and --stamp is currently unsupported")
}

debug!("Writing timestamp file in: {:?}", paths[0]);
return Timestamp::new()
.store(path.as_path())
.store(paths[0].as_path())
.context("Failed to write timestamp file");
}
};

let paths = if args.recursive {
find_cargo_projects(&path, args.hidden)
let processed_paths = if args.recursive {
paths
.iter()
.flat_map(|path| find_cargo_projects(path, args.hidden))
.collect::<Vec<_>>()
} else {
let metadata = metadata(&path).context(format!(
"Failed to gather metadata for {:?}",
path.display()
))?;
let out = Path::new(&metadata.target_directory).to_path_buf();
if out.exists() {
vec![out]
} else {
anyhow::bail!("Failed to clean {:?} as it does not exist.", out);
let mut return_paths = Vec::with_capacity(paths.len());
for path in &paths {
let metadata = metadata(path).context(format!(
"Failed to gather metadata for {:?}",
path.display()
))?;
let out = Path::new(&metadata.target_directory).to_path_buf();
if out.exists() {
return_paths.push(out);
} else {
anyhow::bail!("Failed to clean {:?} as it does not exist.", out)
};
}
return_paths
};

let toolchains = match &criterion {
Expand All @@ -170,13 +182,13 @@ fn main() -> anyhow::Result<()> {
_ => None,
};
if let Some(toolchains) = toolchains {
for project_path in &paths {
for project_path in &processed_paths {
match remove_not_built_with(project_path, &toolchains, dry_run) {
Ok(cleaned_amount) if dry_run => {
info!(
"Would clean: {} from {project_path:?}",
format_bytes(cleaned_amount)
)
);
}
Ok(cleaned_amount) => info!(
"Cleaned {} from {project_path:?}",
Expand All @@ -189,13 +201,13 @@ fn main() -> anyhow::Result<()> {
};
}
} else if let Criterion::MaxSize(size) = criterion {
for project_path in &paths {
for project_path in &processed_paths {
match remove_older_until_fits(project_path, size, dry_run) {
Ok(cleaned_amount) if dry_run => {
info!(
"Would clean: {} from {project_path:?}",
format_bytes(cleaned_amount)
)
);
}
Ok(cleaned_amount) => info!(
"Cleaned {} from {project_path:?}",
Expand All @@ -206,22 +218,22 @@ fn main() -> anyhow::Result<()> {
}
} else {
let keep_duration = if let Criterion::File = criterion {
let ts =
Timestamp::load(path.as_path(), dry_run).expect("Failed to load timestamp file");
let ts = Timestamp::load(paths[0].as_path(), dry_run)
.expect("Failed to load timestamp file");
Duration::from(ts)
} else if let Criterion::Time(days_to_keep) = criterion {
Duration::from_secs(days_to_keep * 24 * 3600)
} else {
unreachable!();
unreachable!("unknown criteria {:?}", criterion);
};

for project_path in &paths {
for project_path in &processed_paths {
match remove_older_than(project_path, &keep_duration, dry_run) {
Ok(cleaned_amount) if dry_run => {
info!(
"Would clean: {} from {project_path:?}",
format_bytes(cleaned_amount)
)
);
}
Ok(cleaned_amount) => info!(
"Cleaned {} from {project_path:?}",
Expand Down
120 changes: 120 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{
borrow::BorrowMut,
env::temp_dir,
fmt::Debug,
fs,
path::{Path, PathBuf},
};

Expand Down Expand Up @@ -457,3 +458,122 @@ fn recursive_multiple_root_workspaces() -> TestResult {

Ok(())
}

/// This test follows the logic of the recursive multiple root test, however, instead of recursing it passes each workspace individually.
#[test]
fn multiple_paths() -> TestResult {
let project_root_path = tempdir()?;

let crate_dir = test_dir().join("sample-project");
let options = CopyOptions::default();

let project_names = ["sample-project-1", "sample-project-2"];

// Copy the sample project folder twice
// and then `cargo build` and run the sweep tests inside that directory.
for project_name in &project_names {
fs_extra::dir::copy(&crate_dir, project_root_path.path(), &options)?;
fs::rename(
project_root_path
.path()
.join(crate_dir.file_name().unwrap()),
dbg!(project_root_path.path().join(project_name)),
)?;
}

let old_size = get_size(project_root_path.path())?;

// Build crates
for path in &project_names {
run(cargo(project_root_path.path().join(path))
// If someone has built & run these tests with CARGO_TARGET_DIR,
// we need to override that.
.env_remove("CARGO_TARGET_DIR")
.arg("build"));
}

let final_build_size = get_size(project_root_path.path())?;
// Calculate the size of each individual crate
let final_built_crates_size =
project_names.map(|path| get_size(project_root_path.path().join(path)).unwrap());

assert!(final_build_size > old_size);

// Measure the size of the crates before cargo-sweep is invoked.
// Run a dry-run of cargo-sweep ("clean") in the target directory of all the crates
let mut args = vec!["--time", "0", "--dry-run"];
args.append(&mut project_names.to_vec());

let expected_cleaned = clean_and_parse(&args, |cmd| {
// If someone has built & run these tests with CARGO_TARGET_DIR,
// we need to override that.
cmd.env_remove("CARGO_TARGET_DIR")
.current_dir(project_root_path.path())
})?;

assert!(expected_cleaned > 0);
let size_after_dry_run_clean = get_size(project_root_path.path())?;
// Make sure that nothing was actually cleaned
assert_eq!(final_build_size, size_after_dry_run_clean);

// Run a proper cargo-sweep ("clean") in the target directories
let mut args = vec!["--time", "0"];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only want to test the functionality for multiple paths, setting both the multiple paths and recursive could cause an error in either one so I just wanted to minimise the test case.

args.append(&mut project_names.to_vec());

let actual_cleaned = clean_and_parse(&args, |cmd| {
// If someone has built & run these tests with CARGO_TARGET_DIR,
// we need to override that.
cmd.env_remove("CARGO_TARGET_DIR")
.current_dir(project_root_path.path())
})?;

assert_sweeped_size(project_root_path.path(), actual_cleaned, final_build_size)?;
assert_eq!(actual_cleaned, expected_cleaned);

// Assert that each crate was cleaned
let cleaned_crates_size =
project_names.map(|path| get_size(project_root_path.path().join(path)).unwrap());

final_built_crates_size
.iter()
.zip(cleaned_crates_size.iter())
.for_each(|(a, b)| assert!(a > b));

Ok(())
}

#[test]
fn multiple_paths_and_stamp_errors() -> TestResult {
let project_root_path = tempdir()?;

let crate_dir = test_dir().join("sample-project");
let options = CopyOptions::default();

let project_names = ["sample-project-1", "sample-project-2"];

// Copy the sample project folder twice
// and then `cargo build` and run the sweep tests inside that directory.
for project_name in &project_names {
fs_extra::dir::copy(&crate_dir, project_root_path.path(), &options)?;
fs::rename(
project_root_path
.path()
.join(crate_dir.file_name().unwrap()),
dbg!(project_root_path.path().join(project_name)),
)?;
}

let mut args = vec!["--stamp"];
args.append(&mut project_names.to_vec());

sweep(&args)
.env_remove("CARGO_TARGET_DIR")
.current_dir(project_root_path.path())
.assert()
.failure()
.stderr(contains(
"Using multiple paths and --stamp is currently unsupported",
));

Ok(())
}
4 changes: 2 additions & 2 deletions tests/usage.trycmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
$ cargo-sweep sweep -h
A tool for cleaning unused build files created by Cargo

Usage: cargo-sweep[EXE] sweep [OPTIONS] <--stamp|--file|--time <DAYS>|--installed|--toolchains <TOOLCHAINS>|--maxsize <MAXSIZE_MB>> [PATH]
Usage: cargo-sweep[EXE] sweep [OPTIONS] <--stamp|--file|--time <DAYS>|--installed|--toolchains <TOOLCHAINS>|--maxsize <MAXSIZE_MB>> [PATH]...

Arguments:
[PATH] Path to check
[PATH]... Path to check

Options:
-d, --dry-run Dry run which will not delete any files
Expand Down