Skip to content

Commit 4ad4b7f

Browse files
committed
add documentation
1 parent 05474fb commit 4ad4b7f

1 file changed

Lines changed: 123 additions & 34 deletions

File tree

src/lib.rs

Lines changed: 123 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#![allow(clippy::missing_errors_doc)]
22
#![warn(missing_docs)]
33

4+
//! Run cargo commands for all feature combinations across a workspace.
5+
//!
6+
//! This crate powers the `cargo-fc` and `cargo-feature-combinations` binaries.
7+
//! The main entry point for consumers is [`run`], which parses CLI arguments
8+
//! and dispatches the requested command.
9+
410
mod config;
511
mod tee;
612

@@ -23,6 +29,7 @@ static RED: LazyLock<ColorSpec> = LazyLock::new(|| color_spec(Color::Red, true))
2329
static YELLOW: LazyLock<ColorSpec> = LazyLock::new(|| color_spec(Color::Yellow, true));
2430
static GREEN: LazyLock<ColorSpec> = LazyLock::new(|| color_spec(Color::Green, true));
2531

32+
/// Summary of the outcome for running a cargo command on a single feature set.
2633
#[derive(Debug)]
2734
pub struct Summary {
2835
package_name: String,
@@ -33,31 +40,64 @@ pub struct Summary {
3340
num_errors: usize,
3441
}
3542

43+
/// High-level command requested by the user.
3644
#[derive(Debug)]
3745
pub enum Command {
38-
FeatureMatrix { pretty: bool },
46+
/// Print a JSON feature matrix to stdout.
47+
///
48+
/// The matrix is produced by combining [`Package::feature_matrix`] for all
49+
/// selected packages into a single JSON array.
50+
FeatureMatrix {
51+
/// Whether to pretty-print the JSON feature matrix.
52+
pretty: bool,
53+
},
54+
/// Print the tool version and exit.
3955
Version,
56+
/// Print help text and exit.
4057
Help,
4158
}
4259

60+
/// Command-line options recognized by this crate.
61+
///
62+
/// Instances of this type are produced by [`parse_arguments`] and consumed by
63+
/// [`run`] to drive command selection and filtering.
4364
#[derive(Debug, Default)]
4465
#[allow(clippy::struct_excessive_bools)]
4566
pub struct Options {
67+
/// Optional path to the Cargo manifest that should be inspected.
4668
pub manifest_path: Option<PathBuf>,
69+
/// Explicit list of package names to include.
4770
pub packages: HashSet<String>,
71+
/// List of package names to exclude.
4872
pub exclude_packages: HashSet<String>,
73+
/// High-level command to execute.
4974
pub command: Option<Command>,
75+
/// Whether to restrict processing to packages with a library target.
5076
pub only_packages_with_lib_target: bool,
77+
/// Whether to hide cargo output and only show the final summary.
5178
pub silent: bool,
79+
/// Whether to print more verbose information such as the full cargo command.
5280
pub verbose: bool,
81+
/// Whether to treat warnings like errors for the summary and `--fail-fast`.
5382
pub pedantic: bool,
83+
/// Whether to silence warnings from rustc and only show errors.
5484
pub errors_only: bool,
85+
/// Whether to only list packages instead of all feature combinations.
5586
pub packages_only: bool,
87+
/// Whether to stop processing after the first failing feature combination.
5688
pub fail_fast: bool,
5789
}
5890

91+
/// Helper trait to provide simple argument parsing over `Vec<String>`.
5992
pub trait ArgumentParser {
93+
/// Check whether an argument flag exists, either as a standalone flag or
94+
/// in `--flag=value` form.
6095
fn contains(&self, arg: &str) -> bool;
96+
/// Extract all occurrences of an argument and their values.
97+
///
98+
/// When `has_value` is `true`, this matches `--flag value` and
99+
/// `--flag=value` forms and returns the value part. When `has_value` is
100+
/// `false`, it matches bare flags like `--flag`.
61101
fn get_all(&self, arg: &str, has_value: bool)
62102
-> Vec<(std::ops::RangeInclusive<usize>, String)>;
63103
}
@@ -94,11 +134,12 @@ impl ArgumentParser for Vec<String> {
94134
}
95135
}
96136

137+
/// Abstraction over a Cargo workspace used by this crate.
97138
pub trait Workspace {
98-
/// Returns the workspace configuration section for feature combinations.
139+
/// Return the workspace configuration section for feature combinations.
99140
fn workspace_config(&self) -> eyre::Result<WorkspaceConfig>;
100141

101-
/// Returns the packages relevant for feature combinations.
142+
/// Return the packages that should be considered for feature combinations.
102143
fn packages_for_fc(&self) -> eyre::Result<Vec<&cargo_metadata::Package>>;
103144
}
104145

@@ -188,8 +229,9 @@ impl Workspace for cargo_metadata::Metadata {
188229
}
189230
}
190231

232+
/// Extension trait for [`cargo_metadata::Package`] used by this crate.
191233
pub trait Package {
192-
/// Parses the config for this package if present.
234+
/// Parse the configuration for this package if present.
193235
///
194236
/// If the Cargo.toml manifest contains a configuration section,
195237
/// the latter is parsed.
@@ -198,10 +240,14 @@ pub trait Package {
198240
/// # Errors
199241
///
200242
/// If the configuration in the manifest can not be parsed,
201-
/// an Error is returned.
243+
/// an error is returned.
202244
///
203245
fn config(&self) -> eyre::Result<Config>;
246+
/// Compute all feature combinations for this package based on the
247+
/// provided [`Config`].
204248
fn feature_combinations<'a>(&'a self, config: &'a Config) -> Vec<Vec<&'a String>>;
249+
/// Convert [`Package::feature_combinations`] into a list of comma-separated
250+
/// feature strings suitable for passing to `cargo --features`.
205251
fn feature_matrix(&self, config: &Config) -> Vec<String>;
206252
}
207253

@@ -371,6 +417,16 @@ fn generate_isolated_base_powerset<'a>(
371417
.collect()
372418
}
373419

420+
/// Print a JSON feature matrix for the given packages to stdout.
421+
///
422+
/// The matrix is a JSON array of objects produced from each package's
423+
/// configuration and the feature combinations returned by
424+
/// [`Package::feature_matrix`].
425+
///
426+
/// # Errors
427+
///
428+
/// Returns an error if any configuration can not be parsed or serialization
429+
/// of the JSON matrix fails.
374430
pub fn print_feature_matrix(
375431
packages: &[&cargo_metadata::Package],
376432
pretty: bool,
@@ -414,6 +470,7 @@ pub fn print_feature_matrix(
414470
Ok(())
415471
}
416472

473+
/// Build a [`ColorSpec`] with the given foreground color and bold setting.
417474
#[must_use]
418475
pub fn color_spec(color: Color, bold: bool) -> ColorSpec {
419476
let mut spec = ColorSpec::new();
@@ -422,6 +479,10 @@ pub fn color_spec(color: Color, bold: bool) -> ColorSpec {
422479
spec
423480
}
424481

482+
/// Extract per-crate warning counts from cargo output.
483+
///
484+
/// The iterator yields the number of warnings for each compiled crate that
485+
/// matches the summary line produced by cargo.
425486
pub fn warning_counts(output: &str) -> impl Iterator<Item = usize> + '_ {
426487
static WARNING_REGEX: LazyLock<Regex> =
427488
LazyLock::new(|| Regex::new(r"warning: .* generated (\d+) warnings?").unwrap());
@@ -431,6 +492,10 @@ pub fn warning_counts(output: &str) -> impl Iterator<Item = usize> + '_ {
431492
.map(|m| m.as_str().parse::<usize>().unwrap_or(0))
432493
}
433494

495+
/// Extract per-crate error counts from cargo output.
496+
///
497+
/// The iterator yields the number of errors for each compiled crate that
498+
/// matches the summary line produced by cargo.
434499
pub fn error_counts(output: &str) -> impl Iterator<Item = usize> + '_ {
435500
static ERROR_REGEX: LazyLock<Regex> = LazyLock::new(|| {
436501
Regex::new(r"error: could not compile `.*` due to\s*(\d*)\s*previous errors?").unwrap()
@@ -441,6 +506,10 @@ pub fn error_counts(output: &str) -> impl Iterator<Item = usize> + '_ {
441506
.map(|m| m.as_str().parse::<usize>().unwrap_or(1))
442507
}
443508

509+
/// Print an aggregated summary for all executed feature combinations.
510+
///
511+
/// This function is used by [`run_cargo_command`] after all packages and
512+
/// feature sets have been processed.
444513
pub fn print_summary(
445514
summary: Vec<Summary>,
446515
mut stdout: termcolor::StandardStream,
@@ -553,6 +622,15 @@ fn print_package_cmd(
553622
}
554623
}
555624

625+
/// Run a cargo command for all requested packages and feature combinations.
626+
///
627+
/// This function drives the main execution loop by spawning cargo for each
628+
/// feature set and collecting a [`Summary`] for every run.
629+
///
630+
/// # Errors
631+
///
632+
/// Returns an error if a cargo process can not be spawned or if IO operations
633+
/// fail while reading cargo's output.
556634
pub fn run_cargo_command(
557635
packages: &[&cargo_metadata::Package],
558636
mut cargo_args: Vec<&str>,
@@ -763,6 +841,7 @@ enum CargoSubcommand {
763841
Other,
764842
}
765843

844+
/// Determine the cargo subcommand implied by the argument list.
766845
fn cargo_subcommand(args: &[impl AsRef<str>]) -> CargoSubcommand {
767846
let args: HashSet<&str> = args.iter().map(AsRef::as_ref).collect();
768847
if args.contains("build") || args.contains("b") {
@@ -780,6 +859,15 @@ fn cargo_subcommand(args: &[impl AsRef<str>]) -> CargoSubcommand {
780859
}
781860
}
782861

862+
/// Parse command-line arguments for the `cargo-*` binary.
863+
///
864+
/// The returned [`Options`] drives workspace discovery and filtering, while
865+
/// the remaining `Vec<String>` contains the raw cargo arguments.
866+
///
867+
/// # Errors
868+
///
869+
/// Returns an error if the manifest path passed via `--manifest-path` does
870+
/// not exist or can not be canonicalized.
783871
pub fn parse_arguments(bin_name: &str) -> eyre::Result<(Options, Vec<String>)> {
784872
let mut args: Vec<String> = std::env::args_os()
785873
// Skip executable name
@@ -894,6 +982,14 @@ pub fn parse_arguments(bin_name: &str) -> eyre::Result<(Options, Vec<String>)> {
894982
Ok((options, args))
895983
}
896984

985+
/// Run the cargo subcommand for all relevant feature combinations.
986+
///
987+
/// This is the main entry point used by the binaries in this crate.
988+
///
989+
/// # Errors
990+
///
991+
/// Returns an error if argument parsing fails or `cargo metadata` can not be
992+
/// executed successfully.
897993
pub fn run(bin_name: &str) -> eyre::Result<()> {
898994
color_eyre::install()?;
899995

@@ -989,7 +1085,7 @@ mod test {
9891085
init();
9901086
let package = package_with_features(&["foo-c", "foo-a", "foo-b"])?;
9911087
let config = Config::default();
992-
let expected_combinations = vec![
1088+
let want = vec![
9931089
vec![],
9941090
vec!["foo-a"],
9951091
vec!["foo-a", "foo-b"],
@@ -999,10 +1095,9 @@ mod test {
9991095
vec!["foo-b", "foo-c"],
10001096
vec!["foo-c"],
10011097
];
1098+
let have = package.feature_combinations(&config);
10021099

1003-
let actual_combinations = package.feature_combinations(&config);
1004-
1005-
sim_assert_eq!(expected_combinations, actual_combinations);
1100+
sim_assert_eq!(have: have, want: want);
10061101
Ok(())
10071102
}
10081103

@@ -1018,7 +1113,7 @@ mod test {
10181113
],
10191114
..Default::default()
10201115
};
1021-
let expected_combinations = vec![
1116+
let want = vec![
10221117
vec![],
10231118
vec!["bar-a"],
10241119
vec!["bar-a", "bar-b"],
@@ -1027,10 +1122,9 @@ mod test {
10271122
vec!["foo-a", "foo-b"],
10281123
vec!["foo-b"],
10291124
];
1125+
let have = package.feature_combinations(&config);
10301126

1031-
let actual_combinations = package.feature_combinations(&config);
1032-
1033-
sim_assert_eq!(expected_combinations, actual_combinations);
1127+
sim_assert_eq!(have: have, want: want);
10341128
Ok(())
10351129
}
10361130

@@ -1046,17 +1140,16 @@ mod test {
10461140
],
10471141
..Default::default()
10481142
};
1049-
let expected_combinations = vec![
1143+
let want = vec![
10501144
vec![],
10511145
vec!["bar-a"],
10521146
vec!["bar-a", "bar-b"],
10531147
vec!["bar-b"],
10541148
vec!["foo-a"],
10551149
];
1150+
let have = package.feature_combinations(&config);
10561151

1057-
let actual_combinations = package.feature_combinations(&config);
1058-
1059-
sim_assert_eq!(expected_combinations, actual_combinations);
1152+
sim_assert_eq!(have: have, want: want);
10601153
Ok(())
10611154
}
10621155

@@ -1073,17 +1166,16 @@ mod test {
10731166
exclude_features: HashSet::from(["bar-a".to_string()]),
10741167
..Default::default()
10751168
};
1076-
let expected_combinations = vec![
1169+
let want = vec![
10771170
vec![],
10781171
vec!["bar-b"],
10791172
vec!["foo-a"],
10801173
vec!["foo-a", "foo-b"],
10811174
vec!["foo-b"],
10821175
];
1176+
let have = package.feature_combinations(&config);
10831177

1084-
let actual_combinations = package.feature_combinations(&config);
1085-
1086-
sim_assert_eq!(expected_combinations, actual_combinations);
1178+
sim_assert_eq!(have: have, want: want);
10871179
Ok(())
10881180
}
10891181

@@ -1100,11 +1192,10 @@ mod test {
11001192
exclude_features: HashSet::from(["bar-a".to_string()]),
11011193
..Default::default()
11021194
};
1103-
let expected_combinations = vec![vec![], vec!["bar-b"], vec!["foo-a"]];
1104-
1105-
let actual_combinations = package.feature_combinations(&config);
1195+
let want = vec![vec![], vec!["bar-b"], vec!["foo-a"]];
1196+
let have = package.feature_combinations(&config);
11061197

1107-
sim_assert_eq!(expected_combinations, actual_combinations);
1198+
sim_assert_eq!(have: have, want: want);
11081199
Ok(())
11091200
}
11101201

@@ -1126,12 +1217,10 @@ mod test {
11261217
])],
11271218
..Default::default()
11281219
};
1129-
let expected_combinations =
1130-
vec![vec![], vec!["bar-a", "car-a"], vec!["bar-b"], vec!["foo-a"]];
1131-
1132-
let actual_combinations = package.feature_combinations(&config);
1220+
let want = vec![vec![], vec!["bar-a", "car-a"], vec!["bar-b"], vec!["foo-a"]];
1221+
let have = package.feature_combinations(&config);
11331222

1134-
sim_assert_eq!(expected_combinations, actual_combinations);
1223+
sim_assert_eq!(have: have, want: want);
11351224
Ok(())
11361225
}
11371226

@@ -1145,8 +1234,8 @@ mod test {
11451234
.workspace_members(vec![package.id.clone()])
11461235
.build()?;
11471236

1148-
let packages = metadata.packages_for_fc()?;
1149-
sim_assert_eq!(packages, vec![&package]);
1237+
let have = metadata.packages_for_fc()?;
1238+
sim_assert_eq!(have: have, want: vec![&package]);
11501239
Ok(())
11511240
}
11521241

@@ -1165,8 +1254,8 @@ mod test {
11651254
}))
11661255
.build()?;
11671256

1168-
let packages = metadata.packages_for_fc()?;
1169-
assert!(packages.is_empty(), "expected no packages after exclusion");
1257+
let have = metadata.packages_for_fc()?;
1258+
assert!(have.is_empty(), "expected no packages after exclusion");
11701259
Ok(())
11711260
}
11721261

0 commit comments

Comments
 (0)