Skip to content
Open
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
34 changes: 4 additions & 30 deletions crates/cargo-wdk/src/actions/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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(),
));
Expand All @@ -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")
Expand Down
113 changes: 113 additions & 0 deletions crates/cargo-wdk/src/actions/cargo_project_iterator.rs
Original file line number Diff line number Diff line change
@@ -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<DirEntryInfo>,
}

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<Self, FileError> {
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::Item> {
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::<Vec<_>>();
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::<Vec<_>>();

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()
);
}
}
33 changes: 9 additions & 24 deletions crates/cargo-wdk/src/actions/clean/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand All @@ -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(
Expand Down
1 change: 1 addition & 0 deletions crates/cargo-wdk/src/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;