Skip to content

Add a flag for keeping target directory on clean #11888

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

Closed
wants to merge 2 commits into from
Closed
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
5 changes: 5 additions & 0 deletions src/bin/cargo/commands/clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ pub fn cli() -> Command {
.arg_target_dir()
.arg_release("Whether or not to clean release artifacts")
.arg_profile("Clean artifacts of the specified profile")
.arg(flag(
"keep-directory",
"Whether to keep the target directory when cleaning everything",
))
.arg_doc("Whether or not to clean just the documentation directory")
.after_help("Run `cargo help clean` for more detailed information.\n")
}
Expand All @@ -31,6 +35,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult {
requested_profile: args.get_profile_name(config, "dev", ProfileChecking::Custom)?,
profile_specified: args.contains_id("profile") || args.flag("release"),
doc: args.flag("doc"),
keep_directory: args.flag("keep-directory"),
};
ops::clean(&ws, &opts)?;
Ok(())
Expand Down
79 changes: 67 additions & 12 deletions src/cargo/ops/cargo_clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub struct CleanOptions<'a> {
pub requested_profile: InternedString,
/// Whether to just clean the doc directory
pub doc: bool,
/// Whether to keep the target directory
pub keep_directory: bool,
}

/// Cleans the package's build artifacts.
Expand Down Expand Up @@ -52,8 +54,15 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
//
// Note that we don't bother grabbing a lock here as we're just going to
// blow it all away anyway.
//
// If the user wants to keep the target directory, make sure we aren't going
// to keep the profile directory.
if opts.spec.is_empty() {
return clean_entire_folder(&target_dir.into_path_unlocked(), config);
return clean_folder(
&target_dir.into_path_unlocked(),
config,
opts.keep_directory && !opts.profile_specified,
);
}

// Clean specific packages.
Expand Down Expand Up @@ -283,17 +292,29 @@ fn rm_rf_glob(
Ok(())
}

fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -> CargoResult<()> {
if fs::symlink_metadata(path).is_err() {
return Ok(());
}
struct SkipLast<I: Iterator>(std::iter::Peekable<I>);

config
.shell()
.verbose(|shell| shell.status("Removing", path.display()))?;
progress.display_now()?;
impl<I: Iterator> Iterator for SkipLast<I> {
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
let value = self.0.next()?;

for entry in walkdir::WalkDir::new(path).contents_first(true) {
if self.0.peek().is_none() {
None
} else {
Some(value)
}
}
}

/// Remove files that appear in the iterator. This is abstracted because when we want to keep the root directory
/// we should skip the last element of [`walkdir::WalkDir`] iterator which is naively implemented via [`SkipLast`].
fn rm_rf_via<I: IntoIterator<Item = Result<walkdir::DirEntry, walkdir::Error>>>(
iterator: I,
progress: &mut dyn CleaningProgressBar,
) -> CargoResult<()> {
for entry in iterator {
let entry = entry?;
progress.on_clean()?;
if entry.file_type().is_dir() {
Expand All @@ -306,10 +327,44 @@ fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -
Ok(())
}

fn clean_entire_folder(path: &Path, config: &Config) -> CargoResult<()> {
fn rm_rf_keep(
path: &Path,
config: &Config,
progress: &mut dyn CleaningProgressBar,
keep_folder: bool,
) -> CargoResult<()> {
if fs::symlink_metadata(path).is_err() {
return Ok(());
}

config
.shell()
.verbose(|shell| shell.status("Removing", path.display()))?;
progress.display_now()?;

let walk_dir = walkdir::WalkDir::new(path).contents_first(true).into_iter();

// If we keep the root folder, we need to skip the last element which is the root directory
// given by the walkdir recursive iterator.
if keep_folder {
rm_rf_via(SkipLast(walk_dir.peekable()), progress)
} else {
rm_rf_via(walk_dir, progress)
}
}

fn rm_rf(path: &Path, config: &Config, progress: &mut dyn CleaningProgressBar) -> CargoResult<()> {
rm_rf_keep(path, config, progress, false)
}

fn clean_folder(path: &Path, config: &Config, keep_folder: bool) -> CargoResult<()> {
let num_paths = walkdir::WalkDir::new(path).into_iter().count();
let mut progress = CleaningFolderBar::new(config, num_paths);
rm_rf(path, config, &mut progress)
rm_rf_keep(path, config, &mut progress, keep_folder)
}

fn clean_entire_folder(path: &Path, config: &Config) -> CargoResult<()> {
clean_folder(path, config, false)
}

trait CleaningProgressBar {
Expand Down
4 changes: 4 additions & 0 deletions src/doc/man/cargo-clean.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ the target directory.
Remove all artifacts in the `release` directory.
{{/option}}

{{#option "`--keep-directory`" }}
Keep the target directory when removing everything.
{{/option}}

{{#option "`--profile` _name_" }}
Remove all artifacts in the directory with the given profile name.
{{/option}}
Expand Down
3 changes: 3 additions & 0 deletions src/doc/man/generated_txt/cargo-clean.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ OPTIONS
--release
Remove all artifacts in the release directory.

--keep-directory
Keep the target directory when removing everything.

--profile name
Remove all artifacts in the directory with the given profile name.

Expand Down
4 changes: 4 additions & 0 deletions src/doc/src/commands/cargo-clean.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ the target directory.</dd>
<dd class="option-desc">Remove all artifacts in the <code>release</code> directory.</dd>


<dt class="option-term" id="option-cargo-clean---keep-directory"><a class="option-anchor" href="#option-cargo-clean---keep-directory"></a><code>--keep-directory</code></dt>
<dd class="option-desc">Keep the target directory when removing everything.</dd>


<dt class="option-term" id="option-cargo-clean---profile"><a class="option-anchor" href="#option-cargo-clean---profile"></a><code>--profile</code> <em>name</em></dt>
<dd class="option-desc">Remove all artifacts in the directory with the given profile name.</dd>

Expand Down
5 changes: 5 additions & 0 deletions src/etc/man/cargo-clean.1
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ the target directory.
Remove all artifacts in the \fBrelease\fR directory.
.RE
.sp
\fB\-\-keep\-directory\fR
.RS 4
Keep the target directory when removing everything.
.RE
.sp
\fB\-\-profile\fR \fIname\fR
.RS 4
Remove all artifacts in the directory with the given profile name.
Expand Down