1 // Copyright (C) 2024 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use std::{borrow::Borrow, collections::BTreeSet}; 16 17 use crates_index::{Dependency, Version}; 18 19 // A reference to feature. Either an explicit feature or an optional dependency. 20 #[derive(Debug, Clone, PartialEq, Eq)] 21 pub enum FeatureRef<'a> { 22 Feature(&'a str), 23 OptionalDep(&'a Dependency), 24 } 25 26 impl<'a> FeatureRef<'a> { name(&self) -> &str27 pub fn name(&self) -> &str { 28 match self { 29 FeatureRef::Feature(name) => name, 30 FeatureRef::OptionalDep(dep) => dep.name(), 31 } 32 } 33 } 34 35 // Traits that let us use FeatureRef as an element of a set. 36 impl<'a> PartialOrd for FeatureRef<'a> { partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>37 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 38 Some(self.cmp(other)) 39 } 40 } 41 impl<'a> Ord for FeatureRef<'a> { cmp(&self, other: &Self) -> std::cmp::Ordering42 fn cmp(&self, other: &Self) -> std::cmp::Ordering { 43 self.name().cmp(other.name()) 44 } 45 } 46 47 // Lets us retrieve a set element by name. 48 impl<'a> Borrow<str> for FeatureRef<'a> { borrow(&self) -> &str49 fn borrow(&self) -> &str { 50 self.name() 51 } 52 } 53 54 pub type TypedFeatures<'a> = BTreeSet<FeatureRef<'a>>; 55 56 pub trait FeaturesAndOptionalDeps { features_and_optional_deps(&self) -> TypedFeatures57 fn features_and_optional_deps(&self) -> TypedFeatures; 58 } 59 60 impl FeaturesAndOptionalDeps for Version { features_and_optional_deps(&self) -> TypedFeatures61 fn features_and_optional_deps(&self) -> TypedFeatures { 62 let explicit_deps = self 63 .features() 64 .values() 65 .flat_map(|dep| dep.iter().filter_map(|d| d.strip_prefix("dep:"))) 66 .collect::<BTreeSet<_>>(); 67 self.features() 68 .keys() 69 .map(|f| FeatureRef::Feature(f.as_str())) 70 .chain(self.dependencies().iter().filter_map(|d| { 71 if d.is_optional() && !explicit_deps.contains(d.name()) { 72 Some(FeatureRef::OptionalDep(d)) 73 } else { 74 None 75 } 76 })) 77 .collect::<TypedFeatures>() 78 } 79 } 80 81 #[cfg(test)] 82 mod tests { 83 use super::*; 84 85 use itertools::assert_equal; 86 87 #[test] test_features()88 fn test_features() { 89 let hashbrown_0_12_3: Version = 90 serde_json::from_str(include_str!("testdata/hashbrown-0.12.3")) 91 .expect("Failed to parse JSON testdata"); 92 let features = hashbrown_0_12_3.features_and_optional_deps(); 93 assert_equal( 94 features.iter().map(|f| f.name()), 95 [ 96 "ahash", 97 "ahash-compile-time-rng", 98 "alloc", 99 "bumpalo", 100 "compiler_builtins", 101 "core", 102 "default", 103 "inline-more", 104 "nightly", 105 "raw", 106 "rayon", 107 "rustc-dep-of-std", 108 "rustc-internal-api", 109 "serde", 110 ], 111 ); 112 assert_eq!( 113 features.get("ahash-compile-time-rng"), 114 Some(&FeatureRef::Feature("ahash-compile-time-rng")) 115 ); 116 assert_eq!( 117 features.get("ahash"), 118 Some(&FeatureRef::OptionalDep(&hashbrown_0_12_3.dependencies()[0])) 119 ); 120 assert_eq!( 121 features.get("alloc"), 122 Some(&FeatureRef::OptionalDep(&hashbrown_0_12_3.dependencies()[1])) 123 ); 124 assert_eq!( 125 features.get("bumpalo"), 126 Some(&FeatureRef::OptionalDep(&hashbrown_0_12_3.dependencies()[2])) 127 ); 128 129 let hashbrown_0_14_5: Version = 130 serde_json::from_str(include_str!("testdata/hashbrown-0.14.5")) 131 .expect("Failed to parse JSON testdata"); 132 let features = hashbrown_0_14_5.features_and_optional_deps(); 133 assert_equal( 134 features.iter().map(|f| f.name()), 135 [ 136 "ahash", 137 "alloc", 138 "allocator-api2", 139 "compiler_builtins", 140 "core", 141 "default", 142 "equivalent", 143 "inline-more", 144 "nightly", 145 "raw", 146 "rayon", 147 "rkyv", 148 "rustc-dep-of-std", 149 "rustc-internal-api", 150 "serde", 151 ], 152 ); 153 154 let winnow_0_5_37: Version = serde_json::from_str(include_str!("testdata/winnow-0.5.37")) 155 .expect("Failed to parse JSON testdata"); 156 let features = winnow_0_5_37.features_and_optional_deps(); 157 assert_equal( 158 features.iter().map(|f| f.name()), 159 ["alloc", "debug", "default", "simd", "std", "unstable-doc", "unstable-recover"], 160 ); 161 162 let cfg_if_1_0_0: Version = serde_json::from_str(include_str!("testdata/cfg-if-1.0.0")) 163 .expect("Failed to parse JSON testdata"); 164 let features = cfg_if_1_0_0.features_and_optional_deps(); 165 assert_equal( 166 features.iter().map(|f| f.name()), 167 ["compiler_builtins", "core", "rustc-dep-of-std"], 168 ); 169 } 170 } 171