Skip to content

Commit 4963d88

Browse files
committed
Auto merge of #5300 - djc:namespaced-features, r=alexcrichton
Introduction of namespaced features (see #1286) I think this basically covers all of the plans from #1286, although it still needs a bunch of tests and documentation updates. Submitting this PR to get some early feedback on the direction.
2 parents 122fd5b + 0b6f420 commit 4963d88

File tree

8 files changed

+513
-47
lines changed

8 files changed

+513
-47
lines changed

src/cargo/core/features.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ features! {
177177

178178
// Overriding profiles for dependencies.
179179
[unstable] profile_overrides: bool,
180+
181+
// Separating the namespaces for features and dependencies
182+
[unstable] namespaced_features: bool,
180183
}
181184
}
182185

src/cargo/core/summary.rs

Lines changed: 181 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::BTreeMap;
1+
use std::collections::{BTreeMap, HashMap};
22
use std::mem;
33
use std::rc::Rc;
44

@@ -26,6 +26,7 @@ struct Inner {
2626
features: FeatureMap,
2727
checksum: Option<String>,
2828
links: Option<InternedString>,
29+
namespaced_features: bool,
2930
}
3031

3132
impl Summary {
@@ -34,9 +35,10 @@ impl Summary {
3435
dependencies: Vec<Dependency>,
3536
features: BTreeMap<String, Vec<String>>,
3637
links: Option<String>,
38+
namespaced_features: bool,
3739
) -> CargoResult<Summary> {
3840
for dep in dependencies.iter() {
39-
if features.get(&*dep.name()).is_some() {
41+
if !namespaced_features && features.get(&*dep.name()).is_some() {
4042
bail!(
4143
"Features and dependencies cannot have the \
4244
same name: `{}`",
@@ -50,14 +52,15 @@ impl Summary {
5052
)
5153
}
5254
}
53-
let feature_map = build_feature_map(features, &dependencies)?;
55+
let feature_map = build_feature_map(features, &dependencies, namespaced_features)?;
5456
Ok(Summary {
5557
inner: Rc::new(Inner {
5658
package_id: pkg_id,
5759
dependencies,
5860
features: feature_map,
5961
checksum: None,
6062
links: links.map(|l| InternedString::new(&l)),
63+
namespaced_features,
6164
}),
6265
})
6366
}
@@ -86,6 +89,9 @@ impl Summary {
8689
pub fn links(&self) -> Option<InternedString> {
8790
self.inner.links
8891
}
92+
pub fn namespaced_features(&self) -> bool {
93+
self.inner.namespaced_features
94+
}
8995

9096
pub fn override_id(mut self, id: PackageId) -> Summary {
9197
Rc::make_mut(&mut self.inner).package_id = id;
@@ -131,55 +137,179 @@ impl PartialEq for Summary {
131137
fn build_feature_map(
132138
features: BTreeMap<String, Vec<String>>,
133139
dependencies: &[Dependency],
140+
namespaced: bool,
134141
) -> CargoResult<FeatureMap> {
135142
use self::FeatureValue::*;
143+
let dep_map: HashMap<_, _> = dependencies
144+
.iter()
145+
.map(|d| (d.name().as_str(), d))
146+
.collect();
147+
136148
let mut map = BTreeMap::new();
137149
for (feature, list) in features.iter() {
150+
// If namespaced features is active and the key is the same as that of an
151+
// optional dependency, that dependency must be included in the values.
152+
// Thus, if a `feature` is found that has the same name as a dependency, we
153+
// (a) bail out if the dependency is non-optional, and (b) we track if the
154+
// feature requirements include the dependency `crate:feature` in the list.
155+
// This is done with the `dependency_found` variable, which can only be
156+
// false if features are namespaced and the current feature key is the same
157+
// as the name of an optional dependency. If so, it gets set to true during
158+
// iteration over the list if the dependency is found in the list.
159+
let mut dependency_found = if namespaced {
160+
match dep_map.get(feature.as_str()) {
161+
Some(ref dep_data) => {
162+
if !dep_data.is_optional() {
163+
bail!(
164+
"Feature `{}` includes the dependency of the same name, but this is \
165+
left implicit in the features included by this feature.\n\
166+
Additionally, the dependency must be marked as optional to be \
167+
included in the feature definition.\n\
168+
Consider adding `crate:{}` to this feature's requirements \
169+
and marking the dependency as `optional = true`",
170+
feature,
171+
feature
172+
)
173+
} else {
174+
false
175+
}
176+
}
177+
None => true,
178+
}
179+
} else {
180+
true
181+
};
182+
138183
let mut values = vec![];
139184
for dep in list {
140-
let val = FeatureValue::build(InternedString::new(dep), |fs| features.contains_key(fs));
141-
if let &Feature(_) = &val {
142-
values.push(val);
143-
continue;
144-
}
185+
let val = FeatureValue::build(
186+
InternedString::new(dep),
187+
|fs| features.contains_key(fs),
188+
namespaced,
189+
);
145190

146191
// Find data for the referenced dependency...
147192
let dep_data = {
148-
let dep_name = match val {
149-
Feature(_) => "",
150-
Crate(ref dep_name) | CrateFeature(ref dep_name, _) => dep_name,
151-
};
152-
dependencies.iter().find(|d| *d.name() == *dep_name)
193+
match val {
194+
Feature(ref dep_name) | Crate(ref dep_name) | CrateFeature(ref dep_name, _) => {
195+
dep_map.get(dep_name.as_str())
196+
}
197+
}
153198
};
199+
let is_optional_dep = dep_data.map_or(false, |d| d.is_optional());
200+
if let FeatureValue::Crate(ref dep_name) = val {
201+
// If we have a dependency value, check if this is the dependency named
202+
// the same as the feature that we were looking for.
203+
if !dependency_found && feature == dep_name.as_str() {
204+
dependency_found = true;
205+
}
206+
}
154207

155-
match (&val, dep_data) {
156-
(&Crate(ref dep), Some(d)) => {
157-
if !d.is_optional() {
158-
bail!(
159-
"Feature `{}` depends on `{}` which is not an \
160-
optional dependency.\nConsider adding \
161-
`optional = true` to the dependency",
162-
feature,
163-
dep
164-
)
208+
match (&val, dep_data.is_some(), is_optional_dep) {
209+
// The value is a feature. If features are namespaced, this just means
210+
// it's not prefixed with `crate:`, so we have to check whether the
211+
// feature actually exist. If the feature is not defined *and* an optional
212+
// dependency of the same name exists, the feature is defined implicitly
213+
// here by adding it to the feature map, pointing to the dependency.
214+
// If features are not namespaced, it's been validated as a feature already
215+
// while instantiating the `FeatureValue` in `FeatureValue::build()`, so
216+
// we don't have to do so here.
217+
(&Feature(feat), _, true) => {
218+
if namespaced && !features.contains_key(&*feat) {
219+
map.insert(feat.to_string(), vec![FeatureValue::Crate(feat)]);
220+
}
221+
}
222+
// If features are namespaced and the value is not defined as a feature
223+
// and there is no optional dependency of the same name, error out.
224+
// If features are not namespaced, there must be an existing feature
225+
// here (checked by `FeatureValue::build()`), so it will always be defined.
226+
(&Feature(feat), dep_exists, false) => {
227+
if namespaced && !features.contains_key(&*feat) {
228+
if dep_exists {
229+
bail!(
230+
"Feature `{}` includes `{}` which is not defined as a feature.\n\
231+
A non-optional dependency of the same name is defined; consider \
232+
adding `optional = true` to its definition",
233+
feature,
234+
feat
235+
)
236+
} else {
237+
bail!(
238+
"Feature `{}` includes `{}` which is not defined as a feature",
239+
feature,
240+
feat
241+
)
242+
}
165243
}
166244
}
167-
(&CrateFeature(ref dep_name, _), None) => bail!(
245+
// The value is a dependency. If features are namespaced, it is explicitly
246+
// tagged as such (`crate:value`). If features are not namespaced, any value
247+
// not recognized as a feature is pegged as a `Crate`. Here we handle the case
248+
// where the dependency exists but is non-optional. It branches on namespaced
249+
// just to provide the correct string for the crate dependency in the error.
250+
(&Crate(ref dep), true, false) => if namespaced {
251+
bail!(
252+
"Feature `{}` includes `crate:{}` which is not an \
253+
optional dependency.\nConsider adding \
254+
`optional = true` to the dependency",
255+
feature,
256+
dep
257+
)
258+
} else {
259+
bail!(
260+
"Feature `{}` depends on `{}` which is not an \
261+
optional dependency.\nConsider adding \
262+
`optional = true` to the dependency",
263+
feature,
264+
dep
265+
)
266+
},
267+
// If namespaced, the value was tagged as a dependency; if not namespaced,
268+
// this could be anything not defined as a feature. This handles the case
269+
// where no such dependency is actually defined; again, the branch on
270+
// namespaced here is just to provide the correct string in the error.
271+
(&Crate(ref dep), false, _) => if namespaced {
272+
bail!(
273+
"Feature `{}` includes `crate:{}` which is not a known \
274+
dependency",
275+
feature,
276+
dep
277+
)
278+
} else {
279+
bail!(
280+
"Feature `{}` includes `{}` which is neither a dependency nor \
281+
another feature",
282+
feature,
283+
dep
284+
)
285+
},
286+
(&Crate(_), true, true) => {}
287+
// If the value is a feature for one of the dependencies, bail out if no such
288+
// dependency is actually defined in the manifest.
289+
(&CrateFeature(ref dep, _), false, _) => bail!(
168290
"Feature `{}` requires a feature of `{}` which is not a \
169291
dependency",
170292
feature,
171-
dep_name
172-
),
173-
(&Crate(ref dep), None) => bail!(
174-
"Feature `{}` includes `{}` which is neither \
175-
a dependency nor another feature",
176-
feature,
177293
dep
178294
),
179-
(&CrateFeature(_, _), Some(_)) | (&Feature(_), _) => {}
295+
(&CrateFeature(_, _), true, _) => {}
180296
}
181297
values.push(val);
182298
}
299+
300+
if !dependency_found {
301+
// If we have not found the dependency of the same-named feature, we should
302+
// bail here.
303+
bail!(
304+
"Feature `{}` includes the optional dependency of the \
305+
same name, but this is left implicit in the features \
306+
included by this feature.\nConsider adding \
307+
`crate:{}` to this feature's requirements.",
308+
feature,
309+
feature
310+
)
311+
}
312+
183313
map.insert(feature.clone(), values);
184314
}
185315
Ok(map)
@@ -200,30 +330,42 @@ pub enum FeatureValue {
200330
}
201331

202332
impl FeatureValue {
203-
fn build<T>(feature: InternedString, is_feature: T) -> FeatureValue
333+
fn build<T>(feature: InternedString, is_feature: T, namespaced: bool) -> FeatureValue
204334
where
205335
T: Fn(&str) -> bool,
206336
{
207-
match feature.find('/') {
208-
Some(pos) => {
337+
match (feature.find('/'), namespaced) {
338+
(Some(pos), _) => {
209339
let (dep, dep_feat) = feature.split_at(pos);
210340
let dep_feat = &dep_feat[1..];
211341
FeatureValue::CrateFeature(InternedString::new(dep), InternedString::new(dep_feat))
212342
}
213-
None if is_feature(&feature) => FeatureValue::Feature(feature),
214-
None => FeatureValue::Crate(feature),
343+
(None, true) if feature.starts_with("crate:") => {
344+
FeatureValue::Crate(InternedString::new(&feature[6..]))
345+
}
346+
(None, true) => FeatureValue::Feature(feature),
347+
(None, false) if is_feature(&feature) => FeatureValue::Feature(feature),
348+
(None, false) => FeatureValue::Crate(feature),
215349
}
216350
}
217351

218352
pub fn new(feature: InternedString, s: &Summary) -> FeatureValue {
219-
Self::build(feature, |fs| s.features().contains_key(fs))
353+
Self::build(
354+
feature,
355+
|fs| s.features().contains_key(fs),
356+
s.namespaced_features(),
357+
)
220358
}
221359

222-
pub fn to_string(&self) -> String {
360+
pub fn to_string(&self, s: &Summary) -> String {
223361
use self::FeatureValue::*;
224362
match *self {
225363
Feature(ref f) => f.to_string(),
226-
Crate(ref c) => c.to_string(),
364+
Crate(ref c) => if s.namespaced_features() {
365+
format!("crate:{}", &c)
366+
} else {
367+
c.to_string()
368+
},
227369
CrateFeature(ref c, ref f) => [c.as_ref(), f.as_ref()].join("/"),
228370
}
229371
}

src/cargo/ops/registry.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,14 @@ fn transmit(
214214
return Ok(());
215215
}
216216

217-
let string_features = pkg.summary()
217+
let summary = pkg.summary();
218+
let string_features = summary
218219
.features()
219220
.iter()
220221
.map(|(feat, values)| {
221222
(
222223
feat.clone(),
223-
values.iter().map(|fv| fv.to_string()).collect(),
224+
values.iter().map(|fv| fv.to_string(&summary)).collect(),
224225
)
225226
})
226227
.collect::<BTreeMap<String, Vec<String>>>();

src/cargo/sources/registry/index.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ impl<'cfg> RegistryIndex<'cfg> {
153153
let deps = deps.into_iter()
154154
.map(|dep| dep.into_dep(&self.source_id))
155155
.collect::<CargoResult<Vec<_>>>()?;
156-
let summary = Summary::new(pkgid, deps, features, links)?;
156+
let summary = Summary::new(pkgid, deps, features, links, false)?;
157157
let summary = summary.set_checksum(cksum.clone());
158158
if self.hashes.contains_key(&name[..]) {
159159
self.hashes.get_mut(&name[..]).unwrap().insert(vers, cksum);

src/cargo/util/toml/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ pub struct TomlProject {
543543
autoexamples: Option<bool>,
544544
autotests: Option<bool>,
545545
autobenches: Option<bool>,
546+
#[serde(rename = "namespaced-features")]
547+
namespaced_features: Option<bool>,
546548

547549
// package metadata
548550
description: Option<String>,
@@ -837,12 +839,16 @@ impl TomlManifest {
837839

838840
let exclude = project.exclude.clone().unwrap_or_default();
839841
let include = project.include.clone().unwrap_or_default();
842+
if project.namespaced_features.is_some() {
843+
features.require(Feature::namespaced_features())?;
844+
}
840845

841846
let summary = Summary::new(
842847
pkgid,
843848
deps,
844849
me.features.clone().unwrap_or_else(BTreeMap::new),
845850
project.links.clone(),
851+
project.namespaced_features.unwrap_or(false),
846852
)?;
847853
let metadata = ManifestMetadata {
848854
description: project.description.clone(),

0 commit comments

Comments
 (0)