ci

CI for all possible combinations of features in Cargo.toml.
git clone https://git.philomathiclife.com/repos/ci
Log | Files | Refs | README

manifest.rs (11545B)


      1 use core::fmt::{self, Formatter};
      2 use serde::de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, SeqAccess, Visitor};
      3 use std::collections::{HashMap, HashSet};
      4 use toml::de::Error as TomlErr;
      5 /// Features that are enabled by another feature.
      6 struct ImpliedFeatures(HashSet<String>);
      7 impl ImpliedFeatures {
      8     /// Returns `true` iff any feature in the `ImpliedFeatures` graph is `feature`.
      9     fn contains(&self, feature: &str, features: &Features) -> bool {
     10         /// Recursively iterates the features in `left` and checks if one is `feature`.
     11         fn recurse<'a: 'e, 'b, 'c: 'e, 'd, 'e>(
     12             left: &'a ImpliedFeatures,
     13             feature: &'b str,
     14             features: &'c Features,
     15             cycle_detection: &'d mut HashSet<&'e str>,
     16         ) -> Result<(), ()> {
     17             left.0.iter().try_fold((), |(), feat| {
     18                 if feat == feature || !cycle_detection.insert(feat) {
     19                     Err(())
     20                 } else {
     21                     features
     22                         .0
     23                         .get(feat)
     24                         // This will be `None` iff `feat` is a dependency-related feature.
     25                         .map_or(Ok(()), |f| recurse(f, feature, features, cycle_detection))
     26                 }
     27             })
     28         }
     29         let mut recursion_detection = HashSet::new();
     30         recurse(self, feature, features, &mut recursion_detection).is_err()
     31     }
     32 }
     33 impl<'de> Deserialize<'de> for ImpliedFeatures {
     34     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     35     where
     36         D: Deserializer<'de>,
     37     {
     38         /// `Visitor` for `ImpliedFeatures`.
     39         struct ImpliedFeaturesVisitor;
     40         impl<'d> Visitor<'d> for ImpliedFeaturesVisitor {
     41             type Value = ImpliedFeatures;
     42             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
     43                 formatter.write_str("ImpliedFeatures")
     44             }
     45             fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
     46             where
     47                 A: SeqAccess<'d>,
     48             {
     49                 let mut feats = HashSet::with_capacity(seq.size_hint().unwrap_or_default());
     50                 while let Some(val) = seq.next_element::<String>()? {
     51                     feats.insert(val);
     52                 }
     53                 Ok(ImpliedFeatures(feats))
     54             }
     55         }
     56         deserializer.deserialize_seq(ImpliedFeaturesVisitor)
     57     }
     58 }
     59 /// Features with the features that are enabled.
     60 struct Features(HashMap<String, ImpliedFeatures>);
     61 impl Features {
     62     #[expect(
     63         clippy::arithmetic_side_effects,
     64         clippy::as_conversions,
     65         clippy::cast_possible_truncation,
     66         clippy::indexing_slicing,
     67         clippy::unreachable,
     68         reason = "comments in code explain their correctness"
     69     )]
     70     /// Returns all possible combinations of features.
     71     fn powerset(self) -> Vec<String> {
     72         // In the event `self.0.len() as u32` causes truncation, a `panic` will occur later due to lack of memory.
     73         let mut init = Vec::with_capacity(2usize.pow(self.0.len() as u32));
     74         init.push(Vec::new());
     75         let mut power = self.0.keys().fold(init, |mut power, feature| {
     76             let features = power.clone().into_iter().map(|mut features| {
     77                 features.push(feature.clone());
     78                 features
     79             });
     80             power.extend(features);
     81             power
     82         });
     83         power.retain(|features| {
     84             features
     85                 .iter()
     86                 .enumerate()
     87                 .try_fold((), |(), (idx, feature)| {
     88                     // `idx < features.len()`, so overflow is not possible and indexing is fine.
     89                     features[idx + 1..].iter().try_fold((), |(), feat| {
     90                         if feature == feat
     91                             || self
     92                                 .0
     93                                 .get(feature)
     94                                 .unwrap_or_else(|| {
     95                                     unreachable!("there is a bug in Features::powerset")
     96                                 })
     97                                 .contains(feat, &self)
     98                             || self
     99                                 .0
    100                                 .get(feat)
    101                                 .unwrap_or_else(|| {
    102                                     unreachable!("there is a bug in Features::powerset")
    103                                 })
    104                                 .contains(feature, &self)
    105                         {
    106                             Err(())
    107                         } else {
    108                             Ok(())
    109                         }
    110                     })
    111                 })
    112                 .is_ok()
    113         });
    114         let len = power.len();
    115         power
    116             .into_iter()
    117             .fold(Vec::with_capacity(len), |mut feats, features| {
    118                 let mut feat_combo = features.into_iter().fold(String::new(), |mut f, feature| {
    119                     f.push_str(feature.as_str());
    120                     f.push(',');
    121                     f
    122                 });
    123                 // Remove the trailing comma.
    124                 // Note a trailing comma exist iff `feat_combo` is not empty. When empty, this does nothing.
    125                 feat_combo.pop();
    126                 feats.push(feat_combo);
    127                 feats
    128             })
    129     }
    130 }
    131 impl<'de> Deserialize<'de> for Features {
    132     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    133     where
    134         D: Deserializer<'de>,
    135     {
    136         /// `Visitor` for `Features`.
    137         struct FeaturesVisitor;
    138         impl<'d> Visitor<'d> for FeaturesVisitor {
    139             type Value = Features;
    140             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    141                 formatter.write_str("Features")
    142             }
    143             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    144             where
    145                 A: MapAccess<'d>,
    146             {
    147                 /// Feature names.
    148                 enum Field {
    149                     /// `default` feature.
    150                     Default,
    151                     /// Feature with a different name.
    152                     Other(String),
    153                 }
    154                 impl<'d> Deserialize<'d> for Field {
    155                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    156                     where
    157                         D: Deserializer<'d>,
    158                     {
    159                         /// `Visitor` for `Field`.
    160                         struct FieldVisitor;
    161                         impl<'e> Visitor<'e> for FieldVisitor {
    162                             type Value = Field;
    163                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    164                                 formatter.write_str("unique feature names")
    165                             }
    166                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    167                             where
    168                                 E: Error,
    169                             {
    170                                 Ok(match v {
    171                                     "default" => Field::Default,
    172                                     _ => Field::Other(v.to_owned()),
    173                                 })
    174                             }
    175                         }
    176                         deserializer.deserialize_identifier(FieldVisitor)
    177                     }
    178                 }
    179                 let mut dflt = false;
    180                 let mut features = HashMap::new();
    181                 while let Some(key) = map.next_key()? {
    182                     match key {
    183                         Field::Default => {
    184                             if dflt {
    185                                 return Err(Error::duplicate_field("default"));
    186                             }
    187                             dflt = map.next_value::<ImpliedFeatures>().map(|_| true)?;
    188                         }
    189                         Field::Other(val) => {
    190                             map.next_value().and_then(|implied| {
    191                                 if features.insert(val, implied).is_none() {
    192                                     Ok(())
    193                                 } else {
    194                                     Err(Error::custom("duplicate features"))
    195                                 }
    196                             })?;
    197                         }
    198                     }
    199                 }
    200                 Ok(Features(features))
    201             }
    202         }
    203         deserializer.deserialize_map(FeaturesVisitor)
    204     }
    205 }
    206 /// `Cargo.toml`.
    207 struct Manifest(Features);
    208 impl<'de> Deserialize<'de> for Manifest {
    209     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    210     where
    211         D: Deserializer<'de>,
    212     {
    213         /// `Visitor` for `Manifest`.
    214         struct ManifestVisitor;
    215         impl<'d> Visitor<'d> for ManifestVisitor {
    216             type Value = Manifest;
    217             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    218                 formatter.write_str("Manifest")
    219             }
    220             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
    221             where
    222                 A: MapAccess<'d>,
    223             {
    224                 /// `Cargo.toml` field.
    225                 enum Field {
    226                     /// `features`.
    227                     Features,
    228                     /// All other fields.
    229                     Other,
    230                 }
    231                 impl<'e> Deserialize<'e> for Field {
    232                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    233                     where
    234                         D: Deserializer<'e>,
    235                     {
    236                         /// `Visitor` for `Field`.
    237                         struct FieldVisitor;
    238                         impl<'f> Visitor<'f> for FieldVisitor {
    239                             type Value = Field;
    240                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
    241                                 write!(formatter, "'{FEATURES}'")
    242                             }
    243                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    244                             where
    245                                 E: Error,
    246                             {
    247                                 Ok(match v {
    248                                     FEATURES => Field::Features,
    249                                     _ => Field::Other,
    250                                 })
    251                             }
    252                         }
    253                         deserializer.deserialize_identifier(FieldVisitor)
    254                     }
    255                 }
    256                 let mut features = None;
    257                 while let Some(key) = map.next_key()? {
    258                     match key {
    259                         Field::Features => {
    260                             if features.is_some() {
    261                                 return Err(Error::duplicate_field(FEATURES));
    262                             }
    263                             features = Some(map.next_value()?);
    264                         }
    265                         Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?,
    266                     }
    267                 }
    268                 Ok(Manifest(
    269                     features.unwrap_or_else(|| Features(HashMap::new())),
    270                 ))
    271             }
    272         }
    273         /// `features`.
    274         const FEATURES: &str = "features";
    275         deserializer.deserialize_map(ManifestVisitor)
    276     }
    277 }
    278 /// Returns possible combinations of features.
    279 pub fn from_toml(val: &str) -> Result<Vec<String>, TomlErr> {
    280     toml::from_str::<Manifest>(val).map(|man| man.0.powerset())
    281 }