Skip to content

Commit 75f77fc

Browse files
committed
Auto merge of #5270 - djc:feature-requirements, r=Eh2406
Introduce FeatureValue type to represent features table values This is the next step towards #1286 (after #5258). The goal here is to have a central place in the code where feature strings are interpreted as (a) a feature, (b) a dependency or (c) a dependency/feature combo, and anchor that interpretation in the type system as an enum. I've spent quite a bit of effort avoiding extra string allocations, complicating the code a bit; notice in particular the use of `Cow<str>` in `FeatureValue` variants, and the slight workaround in `Context::resolve_features()` and `build_requirements()`. I hope this is all okay. cc @Eh2406
2 parents b70ab13 + 7621108 commit 75f77fc

File tree

7 files changed

+177
-82
lines changed

7 files changed

+177
-82
lines changed

src/cargo/core/interning.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use serde::{Serialize, Serializer};
2+
13
use std::fmt;
24
use std::sync::RwLock;
35
use std::collections::HashSet;
@@ -95,3 +97,12 @@ impl PartialOrd for InternedString {
9597
Some(self.cmp(other))
9698
}
9799
}
100+
101+
impl Serialize for InternedString {
102+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103+
where
104+
S: Serializer,
105+
{
106+
serializer.serialize_str(self.inner)
107+
}
108+
}

src/cargo/core/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub use self::registry::Registry;
99
pub use self::resolver::Resolve;
1010
pub use self::shell::{Shell, Verbosity};
1111
pub use self::source::{GitReference, Source, SourceId, SourceMap};
12-
pub use self::summary::Summary;
12+
pub use self::summary::{FeatureMap, FeatureValue, Summary};
1313
pub use self::workspace::{Members, Workspace, WorkspaceConfig, WorkspaceRootConfig};
1414

1515
pub mod source;

src/cargo/core/package.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::cell::{Ref, RefCell};
2-
use std::collections::{BTreeMap, HashMap};
2+
use std::collections::HashMap;
33
use std::fmt;
44
use std::hash;
55
use std::path::{Path, PathBuf};
@@ -10,7 +10,7 @@ use toml;
1010
use lazycell::LazyCell;
1111

1212
use core::{Dependency, Manifest, PackageId, SourceId, Target};
13-
use core::{SourceMap, Summary};
13+
use core::{FeatureMap, SourceMap, Summary};
1414
use core::interning::InternedString;
1515
use util::{internal, lev_distance, Config};
1616
use util::errors::{CargoResult, CargoResultExt};
@@ -39,7 +39,7 @@ struct SerializedPackage<'a> {
3939
source: &'a SourceId,
4040
dependencies: &'a [Dependency],
4141
targets: &'a [Target],
42-
features: &'a BTreeMap<String, Vec<String>>,
42+
features: &'a FeatureMap,
4343
manifest_path: &'a str,
4444
}
4545

src/cargo/core/resolver/context.rs

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::collections::{HashMap, HashSet};
22
use std::rc::Rc;
33

4-
use core::{Dependency, PackageId, SourceId, Summary};
4+
use core::{Dependency, FeatureValue, PackageId, SourceId, Summary};
55
use core::interning::InternedString;
66
use util::Graph;
77
use util::CargoResult;
@@ -170,7 +170,24 @@ impl Context {
170170
let deps = s.dependencies();
171171
let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
172172

173-
let mut reqs = build_requirements(s, method)?;
173+
// Requested features stored in the Method are stored as string references, but we want to
174+
// transform them into FeatureValues here. In order to pass the borrow checker with
175+
// storage of the FeatureValues that outlives the Requirements object, we do the
176+
// transformation here, and pass the FeatureValues to build_requirements().
177+
let values = if let Method::Required {
178+
all_features: false,
179+
features: requested,
180+
..
181+
} = *method
182+
{
183+
requested
184+
.iter()
185+
.map(|f| FeatureValue::new(f, s))
186+
.collect::<Vec<FeatureValue>>()
187+
} else {
188+
vec![]
189+
};
190+
let mut reqs = build_requirements(s, method, &values)?;
174191
let mut ret = Vec::new();
175192

176193
// Next, collect all actually enabled dependencies and their features.
@@ -269,8 +286,12 @@ impl Context {
269286
fn build_requirements<'a, 'b: 'a>(
270287
s: &'a Summary,
271288
method: &'b Method,
289+
requested: &'a [FeatureValue],
272290
) -> CargoResult<Requirements<'a>> {
273291
let mut reqs = Requirements::new(s);
292+
for fv in requested.iter() {
293+
reqs.require_value(fv)?;
294+
}
274295
match *method {
275296
Method::Everything
276297
| Method::Required {
@@ -283,12 +304,7 @@ fn build_requirements<'a, 'b: 'a>(
283304
reqs.require_dependency(dep.name().as_str());
284305
}
285306
}
286-
Method::Required {
287-
features: requested_features,
288-
..
289-
} => for feat in requested_features.iter() {
290-
reqs.add_feature(feat)?;
291-
},
307+
_ => {} // Explicitly requested features are handled through `requested`
292308
}
293309
match *method {
294310
Method::Everything
@@ -359,50 +375,34 @@ impl<'r> Requirements<'r> {
359375
}
360376

361377
fn require_feature(&mut self, feat: &'r str) -> CargoResult<()> {
362-
if self.seen(feat) {
378+
if feat.is_empty() || self.seen(feat) {
363379
return Ok(());
364380
}
365-
for f in self.summary
381+
for fv in self.summary
366382
.features()
367383
.get(feat)
368384
.expect("must be a valid feature")
369385
{
370-
if f == feat {
371-
bail!(
386+
match *fv {
387+
FeatureValue::Feature(ref dep_feat) if **dep_feat == *feat => bail!(
372388
"Cyclic feature dependency: feature `{}` depends on itself",
373389
feat
374-
);
390+
),
391+
_ => {}
375392
}
376-
self.add_feature(f)?;
393+
self.require_value(&fv)?;
377394
}
378395
Ok(())
379396
}
380397

381-
fn add_feature(&mut self, feat: &'r str) -> CargoResult<()> {
382-
if feat.is_empty() {
383-
return Ok(());
384-
}
385-
386-
// If this feature is of the form `foo/bar`, then we just lookup package
387-
// `foo` and enable its feature `bar`. Otherwise this feature is of the
388-
// form `foo` and we need to recurse to enable the feature `foo` for our
389-
// own package, which may end up enabling more features or just enabling
390-
// a dependency.
391-
let mut parts = feat.splitn(2, '/');
392-
let feat_or_package = parts.next().unwrap();
393-
match parts.next() {
394-
Some(feat) => {
395-
self.require_crate_feature(feat_or_package, feat);
396-
}
397-
None => {
398-
if self.summary.features().contains_key(feat_or_package) {
399-
self.require_feature(feat_or_package)?;
400-
} else {
401-
self.require_dependency(feat_or_package);
402-
}
398+
fn require_value(&mut self, fv: &'r FeatureValue) -> CargoResult<()> {
399+
match *fv {
400+
FeatureValue::Feature(ref feat) => self.require_feature(feat),
401+
FeatureValue::Crate(ref dep) => Ok(self.require_dependency(dep)),
402+
FeatureValue::CrateFeature(ref dep, ref dep_feat) => {
403+
Ok(self.require_crate_feature(dep, dep_feat))
403404
}
404405
}
405-
Ok(())
406406
}
407407
}
408408

src/cargo/core/summary.rs

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct Summary {
2121
struct Inner {
2222
package_id: PackageId,
2323
dependencies: Vec<Dependency>,
24-
features: BTreeMap<String, Vec<String>>,
24+
features: FeatureMap,
2525
checksum: Option<String>,
2626
links: Option<InternedString>,
2727
}
@@ -48,47 +48,12 @@ impl Summary {
4848
)
4949
}
5050
}
51-
for (feature, list) in features.iter() {
52-
for dep in list.iter() {
53-
let mut parts = dep.splitn(2, '/');
54-
let dep = parts.next().unwrap();
55-
let is_reexport = parts.next().is_some();
56-
if !is_reexport && features.get(dep).is_some() {
57-
continue;
58-
}
59-
match dependencies.iter().find(|d| &*d.name() == dep) {
60-
Some(d) => {
61-
if d.is_optional() || is_reexport {
62-
continue;
63-
}
64-
bail!(
65-
"Feature `{}` depends on `{}` which is not an \
66-
optional dependency.\nConsider adding \
67-
`optional = true` to the dependency",
68-
feature,
69-
dep
70-
)
71-
}
72-
None if is_reexport => bail!(
73-
"Feature `{}` requires a feature of `{}` which is not a \
74-
dependency",
75-
feature,
76-
dep
77-
),
78-
None => bail!(
79-
"Feature `{}` includes `{}` which is neither \
80-
a dependency nor another feature",
81-
feature,
82-
dep
83-
),
84-
}
85-
}
86-
}
51+
let feature_map = build_feature_map(features, &dependencies)?;
8752
Ok(Summary {
8853
inner: Rc::new(Inner {
8954
package_id: pkg_id,
9055
dependencies,
91-
features,
56+
features: feature_map,
9257
checksum: None,
9358
links: links.map(|l| InternedString::new(&l)),
9459
}),
@@ -110,7 +75,7 @@ impl Summary {
11075
pub fn dependencies(&self) -> &[Dependency] {
11176
&self.inner.dependencies
11277
}
113-
pub fn features(&self) -> &BTreeMap<String, Vec<String>> {
78+
pub fn features(&self) -> &FeatureMap {
11479
&self.inner.features
11580
}
11681
pub fn checksum(&self) -> Option<&str> {
@@ -158,3 +123,108 @@ impl PartialEq for Summary {
158123
self.inner.package_id == other.inner.package_id
159124
}
160125
}
126+
127+
// Checks features for errors, bailing out a CargoResult:Err if invalid,
128+
// and creates FeatureValues for each feature.
129+
fn build_feature_map(
130+
features: BTreeMap<String, Vec<String>>,
131+
dependencies: &Vec<Dependency>,
132+
) -> CargoResult<FeatureMap> {
133+
use self::FeatureValue::*;
134+
let mut map = BTreeMap::new();
135+
for (feature, list) in features.iter() {
136+
let mut values = vec![];
137+
for dep in list {
138+
let val = FeatureValue::build(dep, |fs| (&features).get(fs).is_some());
139+
if let &Feature(_) = &val {
140+
values.push(val);
141+
continue;
142+
}
143+
144+
// Find data for the referenced dependency...
145+
let dep_data = {
146+
let dep_name = match &val {
147+
&Feature(_) => "",
148+
&Crate(ref dep_name) | &CrateFeature(ref dep_name, _) => dep_name,
149+
};
150+
dependencies.iter().find(|d| *d.name() == *dep_name)
151+
};
152+
153+
match (&val, dep_data) {
154+
(&Crate(ref dep), Some(d)) => {
155+
if !d.is_optional() {
156+
bail!(
157+
"Feature `{}` depends on `{}` which is not an \
158+
optional dependency.\nConsider adding \
159+
`optional = true` to the dependency",
160+
feature,
161+
dep
162+
)
163+
}
164+
}
165+
(&CrateFeature(ref dep_name, _), None) => bail!(
166+
"Feature `{}` requires a feature of `{}` which is not a \
167+
dependency",
168+
feature,
169+
dep_name
170+
),
171+
(&Crate(ref dep), None) => bail!(
172+
"Feature `{}` includes `{}` which is neither \
173+
a dependency nor another feature",
174+
feature,
175+
dep
176+
),
177+
(&CrateFeature(_, _), Some(_)) | (&Feature(_), _) => {}
178+
}
179+
values.push(val);
180+
}
181+
map.insert(feature.clone(), values);
182+
}
183+
Ok(map)
184+
}
185+
186+
/// FeatureValue represents the types of dependencies a feature can have:
187+
///
188+
/// * Another feature
189+
/// * An optional dependency
190+
/// * A feature in a depedency
191+
///
192+
/// The selection between these 3 things happens as part of the construction of the FeatureValue.
193+
#[derive(Clone, Debug, Serialize)]
194+
pub enum FeatureValue {
195+
Feature(InternedString),
196+
Crate(InternedString),
197+
CrateFeature(InternedString, InternedString),
198+
}
199+
200+
impl FeatureValue {
201+
fn build<T>(feature: &str, is_feature: T) -> FeatureValue
202+
where
203+
T: Fn(&str) -> bool,
204+
{
205+
match feature.find('/') {
206+
Some(pos) => {
207+
let (dep, dep_feat) = feature.split_at(pos);
208+
let dep_feat = &dep_feat[1..];
209+
FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
210+
}
211+
None if is_feature(&feature) => FeatureValue::Feature(InternedString::new(feature)),
212+
None => FeatureValue::Crate(InternedString::new(feature)),
213+
}
214+
}
215+
216+
pub fn new(feature: &str, s: &Summary) -> FeatureValue {
217+
Self::build(feature, |fs| s.features().get(fs).is_some())
218+
}
219+
220+
pub fn to_string(&self) -> String {
221+
use self::FeatureValue::*;
222+
match *self {
223+
Feature(ref f) => f.to_string(),
224+
Crate(ref c) => c.to_string(),
225+
CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
226+
}
227+
}
228+
}
229+
230+
pub type FeatureMap = BTreeMap<String, Vec<FeatureValue>>;

src/cargo/ops/registry.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::{cmp, env};
2+
use std::collections::BTreeMap;
23
use std::fs::{self, File};
34
use std::iter::repeat;
45
use std::time::Duration;
@@ -213,12 +214,23 @@ fn transmit(
213214
return Ok(());
214215
}
215216

217+
let string_features = pkg.summary()
218+
.features()
219+
.iter()
220+
.map(|(feat, values)| {
221+
(
222+
feat.clone(),
223+
values.iter().map(|fv| fv.to_string()).collect(),
224+
)
225+
})
226+
.collect::<BTreeMap<String, Vec<String>>>();
227+
216228
let publish = registry.publish(
217229
&NewCrate {
218230
name: pkg.name().to_string(),
219231
vers: pkg.version().to_string(),
220232
deps,
221-
features: pkg.summary().features().clone(),
233+
features: string_features,
222234
authors: authors.clone(),
223235
description: description.clone(),
224236
homepage: homepage.clone(),

tests/testsuite/metadata.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ optional_feat = []
192192
],
193193
"features": {
194194
"default": [
195-
"default_feat"
195+
{
196+
"Feature": "default_feat"
197+
}
196198
],
197199
"default_feat": [],
198200
"optional_feat": []

0 commit comments

Comments
 (0)