From 5199e09a115d56b523622488860b15745bc6b527 Mon Sep 17 00:00:00 2001 From: ITSMESB <131141975+ITSMERNB@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:04:15 +0200 Subject: [PATCH] refactor(cargo-wdk): share project directory iteration Signed-off-by: ITSMESB <131141975+ITSMERNB@users.noreply.github.com> --- crates/cargo-wdk/src/actions/build/mod.rs | 34 +----- .../src/actions/cargo_project_iterator.rs | 113 ++++++++++++++++++ crates/cargo-wdk/src/actions/clean/mod.rs | 33 ++--- crates/cargo-wdk/src/actions/mod.rs | 1 + 4 files changed, 127 insertions(+), 54 deletions(-) create mode 100644 crates/cargo-wdk/src/actions/cargo_project_iterator.rs diff --git a/crates/cargo-wdk/src/actions/build/mod.rs b/crates/cargo-wdk/src/actions/build/mod.rs index 709cd38d0..a9dc2a512 100644 --- a/crates/cargo-wdk/src/actions/build/mod.rs +++ b/crates/cargo-wdk/src/actions/build/mod.rs @@ -33,6 +33,7 @@ use wdk_build::{ metadata::{TryFromCargoMetadataError, Wdk}, }; +use crate::actions::cargo_project_iterator::CargoProjectIterator; #[double] use crate::providers::{exec::CommandExec, fs::Fs, metadata::Metadata, wdk_build::WdkBuild}; @@ -201,32 +202,12 @@ impl<'a> BuildAction<'a> { } // Emulated workspaces support - let dirs = self.fs.read_dir_entries(&self.working_dir)?; debug!( "Checking for valid Rust projects in the working directory: {}", self.working_dir.display() ); - - let mut is_valid_dir_with_rust_projects = false; - for entry in &dirs { - if entry.is_dir && self.fs.exists(&entry.path.join("Cargo.toml")) { - debug!( - "Found at least one valid Rust project directory: {}, continuing with the \ - build flow", - entry - .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 { + let mut cargo_projects = CargoProjectIterator::new(self.fs, &self.working_dir)?.peekable(); + if cargo_projects.peek().is_none() { return Err(BuildActionError::NoValidRustProjectsInTheDirectory( self.working_dir.clone(), )); @@ -235,14 +216,7 @@ impl<'a> BuildAction<'a> { info!("Building packages in {}", self.working_dir.display()); let mut failed_atleast_one_project = false; - for entry in dirs { - debug!("Checking dir entry: {}", entry.path.display()); - if !entry.is_dir || !self.fs.exists(&entry.path.join("Cargo.toml")) { - debug!("Dir entry is not a valid Rust package"); - continue; - } - - let cargo_package_path = entry.path; + for cargo_package_path in cargo_projects { let package_dir_name = cargo_package_path .file_name() .expect("package sub directory name ended with \"..\" which is not expected") diff --git a/crates/cargo-wdk/src/actions/cargo_project_iterator.rs b/crates/cargo-wdk/src/actions/cargo_project_iterator.rs new file mode 100644 index 000000000..b1e2eec0f --- /dev/null +++ b/crates/cargo-wdk/src/actions/cargo_project_iterator.rs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +use std::path::{Path, PathBuf}; + +use mockall_double::double; + +#[double] +use crate::providers::fs::Fs; +use crate::providers::{error::FileError, fs::DirEntryInfo}; + +/// Iterates over immediate subdirectories that contain a `Cargo.toml`. +pub(super) struct CargoProjectIterator<'a> { + fs: &'a Fs, + entries: std::vec::IntoIter, +} + +impl<'a> CargoProjectIterator<'a> { + /// Reads the immediate entries under `working_dir` and prepares to yield + /// only Rust project directories. + pub(super) fn new(fs: &'a Fs, working_dir: &Path) -> Result { + Ok(Self { + fs, + entries: fs.read_dir_entries(working_dir)?.into_iter(), + }) + } +} + +impl Iterator for CargoProjectIterator<'_> { + type Item = PathBuf; + + fn next(&mut self) -> Option { + self.entries.find_map(|entry| { + (entry.is_dir && self.fs.exists(&entry.path.join("Cargo.toml"))).then_some(entry.path) + }) + } +} + +#[cfg(test)] +mod tests { + use std::path::{Path, PathBuf}; + + use mockall::predicate::eq; + use mockall_double::double; + + use super::CargoProjectIterator; + use crate::providers::fs::DirEntryInfo; + #[double] + use crate::providers::fs::Fs; + + fn mock_entries(fs: &mut Fs, root: &Path, entries: &[(&str, bool)]) { + let root = root.to_owned(); + let entries = entries + .iter() + .map(|(name, is_dir)| DirEntryInfo { + path: root.join(name), + is_dir: *is_dir, + }) + .collect::>(); + fs.expect_read_dir_entries() + .with(eq(root)) + .times(1) + .return_once(move |_| Ok(entries)); + } + + #[test] + fn yields_only_directories_with_cargo_toml() { + let root = PathBuf::from("C:\\tmp"); + let docs = root.join("docs"); + let package = root.join("package"); + let mut fs = Fs::default(); + + mock_entries( + &mut fs, + &root, + &[("README.md", false), ("docs", true), ("package", true)], + ); + fs.expect_exists() + .with(eq(docs.join("Cargo.toml"))) + .times(1) + .return_const(false); + fs.expect_exists() + .with(eq(package.join("Cargo.toml"))) + .times(1) + .return_const(true); + + let projects = CargoProjectIterator::new(&fs, &root) + .expect("directory enumeration should succeed") + .collect::>(); + + assert_eq!(projects, vec![package]); + } + + #[test] + fn empty_iterator_when_no_rust_projects_exist() { + let root = PathBuf::from("C:\\tmp"); + let docs = root.join("docs"); + let mut fs = Fs::default(); + + mock_entries(&mut fs, &root, &[("docs", true)]); + fs.expect_exists() + .with(eq(docs.join("Cargo.toml"))) + .times(1) + .return_const(false); + + assert!( + CargoProjectIterator::new(&fs, &root) + .expect("directory enumeration should succeed") + .next() + .is_none() + ); + } +} diff --git a/crates/cargo-wdk/src/actions/clean/mod.rs b/crates/cargo-wdk/src/actions/clean/mod.rs index 904acf1ff..6a427c578 100644 --- a/crates/cargo-wdk/src/actions/clean/mod.rs +++ b/crates/cargo-wdk/src/actions/clean/mod.rs @@ -13,7 +13,7 @@ use tracing::{debug, error as err, info}; #[double] use crate::providers::{exec::CommandExec, fs::Fs}; -use crate::trace; +use crate::{actions::cargo_project_iterator::CargoProjectIterator, trace}; /// Action that removes build artifacts produced by the `build` command for a /// driver project or emulated workspace. @@ -96,34 +96,25 @@ impl<'a> CleanAction<'a> { } // Emulated workspaces support - let dirs = self.fs.read_dir_entries(&self.working_dir)?; debug!( "Checking for valid Rust projects in the working directory: {}", self.working_dir.display() ); + let mut cargo_projects = CargoProjectIterator::new(self.fs, &self.working_dir)?.peekable(); + if cargo_projects.peek().is_none() { + return Err(CleanActionError::NoValidRustProjectsInTheDirectory( + self.working_dir.clone(), + )); + } - let mut found_at_least_one_project = false; + info!("Cleaning package(s) in {}", self.working_dir.display()); let mut failed_at_least_one_project = false; - for entry in dirs { - debug!("Checking dir entry: {}", entry.path.display()); - if !entry.is_dir || !self.fs.exists(&entry.path.join("Cargo.toml")) { - debug!("Dir entry is not a valid Rust package"); - continue; - } - - let cargo_package_path = entry.path; + for cargo_package_path in cargo_projects { let package_dir_name = cargo_package_path .file_name() .map(|s| s.to_string_lossy().into_owned()) .unwrap_or_default(); - // Emit the log only once for the entire emulated workspace, the - // first time a valid Rust project is discovered during - // the scan. - if !found_at_least_one_project { - info!("Cleaning package(s) in {}", self.working_dir.display()); - } - found_at_least_one_project = true; debug!("Cleaning package(s) in dir {package_dir_name}"); if let Err(e) = self.run_cargo_clean(&cargo_package_path) { failed_at_least_one_project = true; @@ -134,12 +125,6 @@ impl<'a> CleanAction<'a> { } } - if !found_at_least_one_project { - return Err(CleanActionError::NoValidRustProjectsInTheDirectory( - self.working_dir.clone(), - )); - } - debug!("Done cleaning package(s) in {}", self.working_dir.display()); if failed_at_least_one_project { return Err(CleanActionError::OneOrMoreRustProjectsFailedToClean( diff --git a/crates/cargo-wdk/src/actions/mod.rs b/crates/cargo-wdk/src/actions/mod.rs index bc7c7788d..c20069bb1 100644 --- a/crates/cargo-wdk/src/actions/mod.rs +++ b/crates/cargo-wdk/src/actions/mod.rs @@ -8,5 +8,6 @@ //! * `build` - Build action module //! * `clean` - Clean action module pub mod build; +mod cargo_project_iterator; pub mod clean; pub mod new;