ci-cargo

CI for Rust code.
git clone https://git.philomathiclife.com/repos/ci-cargo
Log | Files | Refs | README

commit d3707a9b2b6ceccf218799136f60672fa380b545
parent 14ad6e0c95da0a362540b3ee443fb60cfc3e60c0
Author: Zack Newman <zack@philomathiclife.com>
Date:   Wed,  8 Oct 2025 16:56:01 -0600

fix bugs dealing with feature dependencies

Diffstat:
MCargo.toml | 2+-
Msrc/args.rs | 10+++-------
Msrc/cargo.rs | 8++++----
Msrc/manifest.rs | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
4 files changed, 77 insertions(+), 43 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Zack Newman <zack@philomathiclife.com>"] categories = ["command-line-utilities", "development-tools::testing", "rust-patterns"] description = "Continuous integration for Clippy, unit tests, and doc tests for all possible features." -documentation = "https://git.philomathiclife.com/ci-cargo/file/README.md.html" +documentation = "https://crates.io/crates/ci-cargo" edition = "2024" keywords = ["cargo", "ci", "rust"] license = "MIT OR Apache-2.0" diff --git a/src/args.rs b/src/args.rs @@ -745,6 +745,7 @@ impl MetaCmd { #[cfg(test)] mod tests { use super::{ArgsErr, Cmd, Ignored, MetaCmd, Opts, OsString, PathBuf}; + use core::iter; #[cfg(unix)] use std::os::unix::ffi::OsStringExt as _; #[expect( @@ -752,17 +753,12 @@ mod tests { clippy::too_many_lines, reason = "want to test for a lot of things" )] - #[expect( - clippy::iter_on_empty_collections, - clippy::iter_on_single_items, - reason = "want to test for it" - )] #[test] fn arg_parsing() { - assert_eq!(MetaCmd::from_args([].into_iter()), Err(ArgsErr::NoArgs)); + assert_eq!(MetaCmd::from_args(iter::empty()), Err(ArgsErr::NoArgs)); // We always ignore the first argument. assert_eq!( - MetaCmd::from_args([OsString::new()].into_iter()), + MetaCmd::from_args(iter::once(OsString::new())), Ok(MetaCmd::Cargo( Cmd::All(false, false, Ignored::None), Opts { diff --git a/src/cargo.rs b/src/cargo.rs @@ -347,8 +347,8 @@ const ALWAYS: &str = "always"; const NEVER: &str = "never"; /// `"--no-default-features"`. const DASH_DASH_NO_DEFAULT_FEATURES: &str = "--no-default-features"; -/// `"--features"`. -const DASH_DASH_FEATURES: &str = "--features"; +/// `"-F"`. +const DASH_F: &str = "-F"; /// `"--"`. const DASH_DASH: &str = "--"; /// `"default"`. @@ -507,7 +507,7 @@ impl Clippy { .arg(if options.color { ALWAYS } else { NEVER }) .arg(DASH_DASH_NO_DEFAULT_FEATURES); if !features.is_empty() { - _ = c.arg(DASH_DASH_FEATURES).arg(features); + _ = c.arg(DASH_F).arg(features); } if deny_warnings { _ = c.arg(DASH_DASH).arg("-Dwarnings"); @@ -565,7 +565,7 @@ impl Tests { .arg(if options.color { ALWAYS } else { NEVER }) .arg(DASH_DASH_NO_DEFAULT_FEATURES); if !features.is_empty() { - _ = c.arg(DASH_DASH_FEATURES).arg(features); + _ = c.arg(DASH_F).arg(features); } let mut doc_only = false; match kind { diff --git a/src/manifest.rs b/src/manifest.rs @@ -1,4 +1,3 @@ -extern crate alloc; use super::cargo::{Toolchain, ToolchainErr}; use alloc::borrow::Cow; use core::cmp::Ordering; @@ -1086,25 +1085,23 @@ impl Features { // so we don't need to continue. Err(()) } else if feat.1.iter().any(|feat_dep| { - let dep_name_utf8 = dep_name.as_bytes(); - let feat_dep_utf8 = feat_dep.as_bytes(); - dep_name_utf8 == feat_dep_utf8 - || feat_dep_utf8 - .split_at_checked(DEP.len()) - .is_some_and(|(pref, rem)| { - pref == DEP - && dep_name_utf8 == rem - }) + feat_dep + .as_bytes() + .split_at_checked(DEP.len()) + .is_some_and(|(pref, rem)| { + pref == DEP + && dep_name.as_bytes() + == rem + }) }) { - // The feature dependencies contain - // `"dep:<dep_name>"` or `<dep_name>`. Either way, - // we don't need to add an implied feature. + // The feature dependencies contain `"dep:<dep_name>"`, + // so we don't need to add an implied feature. Err(()) } else { // The feature name is not `<dep_name>` and all of - // the feature dependencies are not named - // `<dep_name` nor `"dep:<dep_name>"`; thus we need - // to continue our search. + // feature dependencies of all features are not named + // `"dep:<dep_name>"`; thus we need to continue our + // search. Ok(()) } }) @@ -1331,21 +1328,19 @@ impl Manifest { .add_implied_features(cargo, allow_implied_features) .map_err(ManifestErr::ImpliedFeatures) .map(|()| { - if allow_implied_features { - features.0.iter_mut().fold( - (), - |(), &mut (_, ref mut feat)| { - feat.retain(|f| { - // We retain only features. Since we didn't save any - // dependencies that contain `'/'`, it's slightly faster to just - // check that a feature dependency is not a dependency. - !is_feature_dependency_a_dependency( - f.as_bytes(), - ) - }); - }, - ); - } + features.0.iter_mut().fold( + (), + |(), &mut (_, ref mut feat)| { + feat.retain(|f| { + // We retain only features. Since we didn't save any + // dependencies that contain `'/'`, it's slightly faster to just + // check that a feature dependency is not a dependency. + !is_feature_dependency_a_dependency( + f.as_bytes(), + ) + }); + }, + ); Self { msrv, features } }) }) @@ -1946,6 +1941,49 @@ mod tests { features: Features(vec![("bar".to_owned(), Vec::new()), ("default".to_owned(), vec!["bar".to_owned()]), ("a".to_owned(), Vec::new()), ("\0".to_owned(), Vec::new()), ("fizz".to_owned(), Vec::new()), ("c".to_owned(), Vec::new())]), }) ); + // [package] + // + // [dependencies] + // foo = { "optional" = true } + // fizz = { "optional" = true } + // + // [features] + // fizz = ["dep:fizz"] + // bar = ["dep:foo"] + assert_eq!( + Manifest::from_toml( + "[package]\n[dependencies]\nfoo={\"optional\"=true}\nfizz={\"optional\"=true}\n[features]\nfizz=[\"dep:fizz\"]\nbar=[\"dep:foo\"]".to_owned(), + false, + ), + Ok(Manifest { + msrv: None, + features: Features(vec![ + ("bar".to_owned(), Vec::new()), + ("fizz".to_owned(), Vec::new()) + ]), + }) + ); + // [package] + // + // [dependencies] + // bar = { "optional" = true } + // + // [features] + // foo = ["bar"] + assert_eq!( + Manifest::from_toml( + "[package]\n[dependencies]\nbar={\"optional\"=true}\n[features]\nfoo=[\"bar\"]" + .to_owned(), + true, + ), + Ok(Manifest { + msrv: None, + features: Features(vec![ + ("foo".to_owned(), vec!["bar".to_owned()]), + ("bar".to_owned(), Vec::new()), + ]), + }) + ); } #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] #[expect(