blob: 09e6e93a96c18cd114dd6abf344f5bedd0a0e7b8 [file] [log] [blame]
//! Cargo flags for selecting crates in a workspace.
#[derive(Default, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "clap", derive(clap::Args))]
#[non_exhaustive]
pub struct Workspace {
#[cfg_attr(feature = "clap", arg(short, long, value_name = "SPEC"))]
/// Package to process (see `cargo help pkgid`)
pub package: Vec<String>,
#[cfg_attr(feature = "clap", arg(long))]
/// Process all packages in the workspace
pub workspace: bool,
#[cfg_attr(
feature = "clap",
arg(long, hide_short_help(true), hide_long_help(true))
)]
/// Process all packages in the workspace
pub all: bool,
#[cfg_attr(feature = "clap", arg(long, value_name = "SPEC"))]
/// Exclude packages from being processed
pub exclude: Vec<String>,
}
#[cfg(feature = "cargo_metadata")]
impl Workspace {
/// Partition workspace members into those selected and those excluded.
///
/// Notes:
/// - Requires the features `cargo_metadata`.
/// - Requires not calling `MetadataCommand::no_deps`
pub fn partition_packages<'m>(
&self,
meta: &'m cargo_metadata::Metadata,
) -> (
Vec<&'m cargo_metadata::Package>,
Vec<&'m cargo_metadata::Package>,
) {
let selection =
Packages::from_flags(self.workspace || self.all, &self.exclude, &self.package);
let workspace_members: std::collections::HashSet<_> =
meta.workspace_members.iter().collect();
let base_ids: std::collections::HashSet<_> = match selection {
Packages::Default => {
// Deviating from cargo because Metadata doesn't have default members
let resolve = meta.resolve.as_ref().expect("no-deps is unsupported");
match &resolve.root {
Some(root) => {
let mut base_ids = std::collections::HashSet::new();
base_ids.insert(root);
base_ids
}
None => workspace_members,
}
}
Packages::All => workspace_members,
Packages::OptOut(_) => workspace_members, // Deviating from cargo by only checking workspace members
Packages::Packages(patterns) => {
meta.packages
.iter()
// Deviating from cargo by not supporting patterns
// Deviating from cargo by only checking workspace members
.filter(|p| workspace_members.contains(&p.id) && patterns.contains(&p.name))
.map(|p| &p.id)
.collect()
}
};
meta.packages
.iter()
// Deviating from cargo by not supporting patterns
.partition(|p| base_ids.contains(&p.id) && !self.exclude.contains(&p.name))
}
}
// See cargo's src/cargo/ops/cargo_compile.rs
#[derive(Clone, PartialEq, Eq, Debug)]
#[cfg(feature = "cargo_metadata")]
#[allow(clippy::enum_variant_names)]
enum Packages<'p> {
Default,
All,
OptOut(&'p [String]),
Packages(&'p [String]),
}
#[cfg(feature = "cargo_metadata")]
impl<'p> Packages<'p> {
pub fn from_flags(all: bool, exclude: &'p [String], package: &'p [String]) -> Self {
match (all, exclude.len(), package.len()) {
(false, 0, 0) => Packages::Default,
(false, 0, _) => Packages::Packages(package),
(false, _, 0) => Packages::OptOut(exclude), // Deviating from cargo because we don't do error handling
(false, _, _) => Packages::Packages(package), // Deviating from cargo because we don't do error handling
(true, 0, _) => Packages::All,
(true, _, _) => Packages::OptOut(exclude),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[cfg(feature = "clap")]
fn verify_app() {
#[derive(Debug, clap::Parser)]
struct Cli {
#[command(flatten)]
workspace: Workspace,
}
use clap::CommandFactory;
Cli::command().debug_assert()
}
#[test]
#[cfg(feature = "clap")]
fn parse_multiple_occurrences() {
use clap::Parser;
#[derive(PartialEq, Eq, Debug, Parser)]
struct Args {
positional: Option<String>,
#[command(flatten)]
workspace: Workspace,
}
assert_eq!(
Args {
positional: None,
workspace: Workspace {
package: vec![],
workspace: false,
all: false,
exclude: vec![],
}
},
Args::parse_from(["test"])
);
assert_eq!(
Args {
positional: Some("baz".to_owned()),
workspace: Workspace {
package: vec!["foo".to_owned(), "bar".to_owned()],
workspace: false,
all: false,
exclude: vec![],
}
},
Args::parse_from(["test", "--package", "foo", "--package", "bar", "baz"])
);
assert_eq!(
Args {
positional: Some("baz".to_owned()),
workspace: Workspace {
package: vec![],
workspace: false,
all: false,
exclude: vec!["foo".to_owned(), "bar".to_owned()],
}
},
Args::parse_from(["test", "--exclude", "foo", "--exclude", "bar", "baz"])
);
}
#[cfg(feature = "cargo_metadata")]
#[cfg(test)]
mod partition_default {
use super::*;
#[test]
fn single_crate() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/simple/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 0);
}
#[test]
fn mixed_ws_root() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
#[test]
fn mixed_ws_leaf() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
#[test]
fn pure_ws_root() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 3);
assert_eq!(excluded.len(), 0);
}
#[test]
fn pure_ws_leaf() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
}
#[cfg(feature = "cargo_metadata")]
#[cfg(test)]
mod partition_all {
use super::*;
#[test]
fn single_crate() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/simple/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
all: true,
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 0);
}
#[test]
fn mixed_ws_root() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
all: true,
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 3);
assert_eq!(excluded.len(), 0);
}
#[test]
fn mixed_ws_leaf() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
all: true,
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 3);
assert_eq!(excluded.len(), 0);
}
#[test]
fn pure_ws_root() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
all: true,
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 3);
assert_eq!(excluded.len(), 0);
}
#[test]
fn pure_ws_leaf() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
all: true,
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 3);
assert_eq!(excluded.len(), 0);
}
}
#[cfg(feature = "cargo_metadata")]
#[cfg(test)]
mod partition_package {
use super::*;
#[test]
fn single_crate() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/simple/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
package: vec!["simple".to_owned()],
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 0);
}
#[test]
fn mixed_ws_root() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/mixed_ws/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
package: vec!["a".to_owned()],
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
#[test]
fn mixed_ws_leaf() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/mixed_ws/c/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
package: vec!["a".to_owned()],
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
#[test]
fn pure_ws_root() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/pure_ws/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
package: vec!["a".to_owned()],
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
#[test]
fn pure_ws_leaf() {
let mut metadata = cargo_metadata::MetadataCommand::new();
metadata.manifest_path("tests/fixtures/pure_ws/c/Cargo.toml");
let metadata = metadata.exec().unwrap();
let workspace = Workspace {
package: vec!["a".to_owned()],
..Default::default()
};
let (included, excluded) = workspace.partition_packages(&metadata);
assert_eq!(included.len(), 1);
assert_eq!(excluded.len(), 2);
}
}
}