-
Notifications
You must be signed in to change notification settings - Fork 124
feat: add cargo wdk clean command
#638
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
Open
svasista-ms
wants to merge
30
commits into
microsoft:main
Choose a base branch
from
svasista-ms:clean-command
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
b9db4c5
feat: implement clean action module with errors and tests
svasista-ms ef2d04e
feat: add clean subcommand to CLI
svasista-ms 4075932
feat: add integration tests for `cargo wdk clean` command
svasista-ms 9ce3a8e
refactor: run clean command within build command integration tests
svasista-ms 4b58929
refactor: update CleanActionError types and test names based on metho…
svasista-ms 64e7bcc
refactor(tests): run clean command again after the build command and …
svasista-ms fbf1c4b
refactor(tests): remove unnecessary Clippy lint suppression for mocka…
svasista-ms 9cbdc66
refactor: simplify project validation logic in clean action
svasista-ms 915366a
refactor: update CargoClean error
svasista-ms 3fe851f
refactor: simplify subdirectory name extraction in run method
svasista-ms 6bb2c93
refactor: add documentation for CleanAction struct
svasista-ms b7e2469
refactor: add validation for non-empty working directory in CleanAction
svasista-ms 6e1fe56
refactor: improve test function names for clarity in CleanAction tests
svasista-ms ed2a887
refactor: add validation to ensure working directory is not empty in …
svasista-ms 47285be
refactor: enhance error handling in CleanAction tests
svasista-ms 95a2721
refactor: add assertions to verify target directories do not exist af…
svasista-ms 3aee35d
refactor: improve error handling in new_fails_if_working_dir_is_empty…
svasista-ms 730a953
refactor: move logging of cleaning packages to conditional check in r…
svasista-ms 80cc4ec
refactor: move tests into CleanAction `mod.rs` file
svasista-ms abf8412
refactor: directory entry handling in CleanAction and BuildAction
svasista-ms 8a5961a
docs: add cargo-wdk preview callout to Cargo Make section in README (…
krishnakumar4a4 5d37942
refactor: fix `collapsable_if` lint, rust 1.95 `assert_matches` desta…
leon-xd 740225c
feat!: stack-based format buffer for `wdk` crate (#611)
leon-xd 2567210
fix(cargo-wdk): use `--message-format=json-render-diagnostics` in the…
svasista-ms 4cb3626
refactor(wdk-build): fix clippy lints (#655)
svasista-ms 42a67e3
fix: correct typo in debug log message
svasista-ms b23b0b7
refactor: use `DirFileTypeError` variant when `file_type()` fails ins…
svasista-ms 90a8726
fix: about string for the clean subcommand
svasista-ms 69f895e
Merge branch 'main' into clean-command
svasista-ms 3fd27e4
refactor: update variable names for clarity in build and clean actions
svasista-ms File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // Copyright (c) Microsoft Corporation | ||
| // License: MIT OR Apache-2.0 | ||
| //! This module defines error types for the clean action module. | ||
|
|
||
| use std::path::PathBuf; | ||
|
|
||
| use thiserror::Error; | ||
|
|
||
| use crate::providers::error::{CommandError, FileError}; | ||
|
|
||
| /// Errors for the clean action layer | ||
| #[derive(Error, Debug)] | ||
| pub enum CleanActionError { | ||
| #[error(transparent)] | ||
| FileIo(#[from] FileError), | ||
| #[error("No valid rust projects in the current working directory: {0}")] | ||
| NoValidRustProjectsInTheDirectory(PathBuf), | ||
| #[error("One or more projects failed to clean in the emulated workspace: {0}")] | ||
| OneOrMoreRustProjectsFailedToClean(PathBuf), | ||
| #[error("Error running cargo clean command")] | ||
| CargoClean(#[from] CommandError), | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| // Copyright (c) Microsoft Corporation | ||
| // License: MIT OR Apache-2.0 | ||
| //! This module contains the `CleanAction` struct and its associated methods | ||
| //! for cleaning build artifacts produced by the `build` command. | ||
| mod error; | ||
| #[cfg(test)] | ||
| mod tests; | ||
|
krishnakumar4a4 marked this conversation as resolved.
Outdated
|
||
|
|
||
| use std::path::{Path, PathBuf, absolute}; | ||
|
|
||
| use anyhow::Result; | ||
| use error::CleanActionError; | ||
| use mockall_double::double; | ||
|
wmmc88 marked this conversation as resolved.
|
||
| use tracing::{debug, error as err, info}; | ||
|
|
||
| #[double] | ||
| use crate::providers::{exec::CommandExec, fs::Fs}; | ||
| use crate::trace; | ||
|
|
||
| pub struct CleanAction<'a> { | ||
| working_dir: PathBuf, | ||
| verbosity_level: clap_verbosity_flag::Verbosity, | ||
|
|
||
| // Injected deps | ||
| command_exec: &'a CommandExec, | ||
| fs: &'a Fs, | ||
| } | ||
|
svasista-ms marked this conversation as resolved.
|
||
|
|
||
| impl<'a> CleanAction<'a> { | ||
| /// Creates a new instance of `CleanAction`. | ||
| /// | ||
| /// # Arguments | ||
| /// * `working_dir` - The working directory for the clean action | ||
| /// * `verbosity_level` - The verbosity level for logging | ||
| /// * `command_exec` - The command execution provider instance | ||
| /// * `fs` - The file system provider instance | ||
| /// | ||
| /// # Returns | ||
| /// * `Result<Self>` - A result containing either a new instance of | ||
| /// `CleanAction` on success, or an `anyhow::Error`. | ||
| /// | ||
| /// # Errors | ||
| /// * [`anyhow::Error`] - If `working_dir` is not a syntactically valid | ||
| /// path, e.g. it is empty | ||
| pub fn new( | ||
|
svasista-ms marked this conversation as resolved.
|
||
| working_dir: &Path, | ||
| verbosity_level: clap_verbosity_flag::Verbosity, | ||
| command_exec: &'a CommandExec, | ||
| fs: &'a Fs, | ||
| ) -> Result<Self> { | ||
| Ok(Self { | ||
| working_dir: absolute(working_dir)?, | ||
| verbosity_level, | ||
| command_exec, | ||
| fs, | ||
| }) | ||
| } | ||
|
|
||
| /// Entry point method to execute the clean action flow. | ||
| /// | ||
| /// The detection strategy is: | ||
| /// 1. If the working directory has a `Cargo.toml`, run `cargo clean` | ||
| /// directly (standalone project or workspace root). | ||
| /// 2. Otherwise, treat the directory as an emulated workspace: scan | ||
| /// immediate subdirectories for Rust projects and clean each. NOTE: This | ||
| /// follows the same logic as the build action. | ||
| /// | ||
| /// # Returns | ||
| /// `Result<(), CleanActionError>` | ||
| /// | ||
| /// # Errors | ||
| /// * `CleanActionError::FileIo` - If there is an IO error. | ||
| /// * `CleanActionError::CargoClean` - If there is an error running the | ||
| /// `cargo clean` command. | ||
| /// * `CleanActionError::NoValidRustProjectsInTheDirectory` - If no valid | ||
| /// Rust projects are found in the working directory. | ||
| /// * `CleanActionError::OneOrMoreRustProjectsFailedToClean` - If one or | ||
| /// more Rust projects fail to clean in an emulated workspace. | ||
| pub fn run(&self) -> Result<(), CleanActionError> { | ||
| debug!( | ||
| "Attempting to clean project at: {}", | ||
| self.working_dir.display() | ||
| ); | ||
|
svasista-ms marked this conversation as resolved.
|
||
|
|
||
| // Standalone driver/driver workspace support | ||
| if self.fs.exists(&self.working_dir.join("Cargo.toml")) { | ||
| debug!( | ||
| "Found Cargo.toml in {}. Running cargo clean.", | ||
| self.working_dir.display() | ||
| ); | ||
| return self.run_cargo_clean(&self.working_dir); | ||
| } | ||
|
|
||
| // Emulated workspaces support | ||
| let dirs = self.fs.read_dir_entries(&self.working_dir)?; | ||
|
krishnakumar4a4 marked this conversation as resolved.
|
||
| debug!( | ||
| "Checking for valid Rust projects in the working directory: {}", | ||
| self.working_dir.display() | ||
| ); | ||
|
|
||
| let mut is_valid_dir_with_rust_projects = false; | ||
| for dir in &dirs { | ||
| if self.fs.dir_file_type(dir)?.is_dir() | ||
| && self.fs.exists(&dir.path().join("Cargo.toml")) | ||
| { | ||
| debug!( | ||
| "Found at least one valid Rust project directory: {}, continuing with the \ | ||
| clean flow", | ||
| dir.path() | ||
| .file_name() | ||
| .expect( | ||
| "package sub directory name ended with \"..\" which is not expected", | ||
| ) | ||
| .to_string_lossy() | ||
| ); | ||
| is_valid_dir_with_rust_projects = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if !is_valid_dir_with_rust_projects { | ||
| return Err(CleanActionError::NoValidRustProjectsInTheDirectory( | ||
| self.working_dir.clone(), | ||
| )); | ||
| } | ||
|
|
||
| info!("Cleaning package(s) in {}", self.working_dir.display()); | ||
|
|
||
| let mut failed_at_least_one_project = false; | ||
| for dir in dirs { | ||
| debug!("Checking dir entry: {}", dir.path().display()); | ||
| if !self.fs.dir_file_type(&dir)?.is_dir() | ||
| || !self.fs.exists(&dir.path().join("Cargo.toml")) | ||
| { | ||
| debug!("Dir entry is not a valid Rust package"); | ||
| continue; | ||
| } | ||
|
|
||
| let working_dir_path = dir.path(); | ||
| let sub_dir = working_dir_path | ||
| .file_name() | ||
| .expect("package sub directory name ended with \"..\" which is not expected") | ||
| .to_string_lossy(); | ||
|
svasista-ms marked this conversation as resolved.
Outdated
|
||
|
|
||
| debug!("Cleaning package(s) in dir {sub_dir}"); | ||
| if let Err(e) = self.run_cargo_clean(&dir.path()) { | ||
|
svasista-ms marked this conversation as resolved.
Outdated
|
||
| failed_at_least_one_project = true; | ||
| err!( | ||
| "Error cleaning project: {sub_dir}, error: {:?}", | ||
| anyhow::Error::new(e) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| debug!("Done cleaning package(s) in {}", self.working_dir.display()); | ||
| if failed_at_least_one_project { | ||
| return Err(CleanActionError::OneOrMoreRustProjectsFailedToClean( | ||
| self.working_dir.clone(), | ||
| )); | ||
| } | ||
|
|
||
| info!( | ||
| "Clean completed successfully for package(s) in {}", | ||
| self.working_dir.display() | ||
| ); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Runs `cargo clean` in the specified directory. | ||
| fn run_cargo_clean(&self, working_dir: &Path) -> Result<(), CleanActionError> { | ||
| info!("Running cargo clean in {}", working_dir.display()); | ||
| let mut args = vec!["clean"]; | ||
|
svasista-ms marked this conversation as resolved.
svasista-ms marked this conversation as resolved.
|
||
| if let Some(flag) = trace::get_cargo_verbose_flags(self.verbosity_level) { | ||
| args.push(flag); | ||
| } | ||
| self.command_exec | ||
| .run("cargo", &args, None, Some(working_dir)) | ||
| .map_err(CleanActionError::CargoClean)?; | ||
| info!("Cleaned project at {}", working_dir.display()); | ||
| Ok(()) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| // Copyright (c) Microsoft Corporation | ||
| // License: MIT OR Apache-2.0 | ||
|
|
||
| use std::{ | ||
| os::windows::process::ExitStatusExt, | ||
| path::PathBuf, | ||
| process::{ExitStatus, Output}, | ||
| }; | ||
|
|
||
|
svasista-ms marked this conversation as resolved.
Outdated
|
||
| use mockall::predicate::eq; | ||
| use mockall_double::double; | ||
|
|
||
| use super::CleanAction; | ||
| use crate::providers::error::CommandError; | ||
| #[double] | ||
| use crate::providers::{exec::CommandExec, fs::Fs}; | ||
|
|
||
| fn create_successful_output() -> Output { | ||
| Output { | ||
| status: ExitStatus::from_raw(0), | ||
| stdout: Vec::new(), | ||
| stderr: Vec::new(), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn new_succeeds_for_valid_args() { | ||
| let cwd = PathBuf::from("C:\\tmp"); | ||
| let mock_fs = Fs::default(); | ||
| let mock_exec = CommandExec::default(); | ||
|
|
||
| let action = CleanAction::new( | ||
| &cwd, | ||
| clap_verbosity_flag::Verbosity::default(), | ||
| &mock_exec, | ||
| &mock_fs, | ||
| ); | ||
|
|
||
| assert!(action.is_ok()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn new_fails_for_empty_path() { | ||
| let cwd = PathBuf::from(""); | ||
| let mock_fs = Fs::default(); | ||
| let mock_exec = CommandExec::default(); | ||
|
|
||
| let action = CleanAction::new( | ||
| &cwd, | ||
| clap_verbosity_flag::Verbosity::default(), | ||
| &mock_exec, | ||
| &mock_fs, | ||
| ); | ||
|
|
||
| assert!(action.is_err()); | ||
| } | ||
|
svasista-ms marked this conversation as resolved.
Outdated
svasista-ms marked this conversation as resolved.
Outdated
|
||
|
|
||
| #[test] | ||
| fn run_invokes_cargo_clean_and_succeeds() { | ||
| let cwd = PathBuf::from("C:\\tmp"); | ||
| let mut mock_fs = Fs::default(); | ||
| let mut mock_exec = CommandExec::default(); | ||
|
|
||
| mock_fs | ||
| .expect_exists() | ||
| .with(eq(cwd.join("Cargo.toml"))) | ||
| .returning(|_| true); | ||
|
|
||
| mock_exec | ||
| .expect_run() | ||
| .withf(move |cmd, args, _env, working_dir| { | ||
| cmd == "cargo" | ||
| && args == ["clean"] | ||
| && *working_dir == Some(PathBuf::from("C:\\tmp").as_path()) | ||
| }) | ||
| .returning(|_, _, _, _| Ok(create_successful_output())); | ||
|
|
||
| let action = CleanAction::new( | ||
| &cwd, | ||
| clap_verbosity_flag::Verbosity::default(), | ||
| &mock_exec, | ||
| &mock_fs, | ||
| ) | ||
| .expect("CleanAction::new should succeed"); | ||
|
|
||
| assert!(action.run().is_ok()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn run_returns_error_when_cargo_clean_fails() { | ||
| let cwd = PathBuf::from("C:\\tmp"); | ||
| let mut mock_fs = Fs::default(); | ||
| let mut mock_exec = CommandExec::default(); | ||
|
|
||
| mock_fs | ||
| .expect_exists() | ||
| .with(eq(cwd.join("Cargo.toml"))) | ||
| .returning(|_| true); | ||
|
|
||
| mock_exec | ||
| .expect_run() | ||
| .withf(move |cmd, args, _env, working_dir| { | ||
| cmd == "cargo" | ||
| && args == ["clean"] | ||
| && *working_dir == Some(PathBuf::from("C:\\tmp").as_path()) | ||
| }) | ||
| .returning(|_, _, _, _| { | ||
| Err(CommandError::CommandFailed { | ||
| command: "cargo".to_string(), | ||
| args: vec!["clean".to_string()], | ||
| stdout: "error".to_string(), | ||
| }) | ||
| }); | ||
|
|
||
| let action = CleanAction::new( | ||
| &cwd, | ||
| clap_verbosity_flag::Verbosity::default(), | ||
| &mock_exec, | ||
| &mock_fs, | ||
| ) | ||
| .expect("CleanAction::new should succeed"); | ||
|
|
||
| assert!(action.run().is_err()); | ||
|
krishnakumar4a4 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| #[test] | ||
| fn run_returns_error_when_no_cargo_toml_and_no_rust_projects() { | ||
| let cwd = PathBuf::from("C:\\tmp"); | ||
| let mut mock_fs = Fs::default(); | ||
| let mock_exec = CommandExec::default(); | ||
|
|
||
| mock_fs | ||
| .expect_exists() | ||
| .with(eq(cwd.join("Cargo.toml"))) | ||
| .returning(|_| false); | ||
|
|
||
| mock_fs.expect_read_dir_entries().returning(|_| Ok(vec![])); | ||
|
|
||
| let action = CleanAction::new( | ||
| &cwd, | ||
| clap_verbosity_flag::Verbosity::default(), | ||
| &mock_exec, | ||
| &mock_fs, | ||
| ) | ||
| .expect("CleanAction::new should succeed"); | ||
|
|
||
| assert!(action.run().is_err()); | ||
|
krishnakumar4a4 marked this conversation as resolved.
Outdated
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.