Skip to content

Commit 39d9413

Browse files
committed
Auto merge of #9290 - ehuss:non-member-features, r=alexcrichton
Refactor feature handling, and improve error messages. This changes the way feature strings are handled with an eye towards fixing some improper handling and to improve error messages. The key change is to stop treating all features as free-form strings and instead try to handle them as typed values. This helps avoid needing to deal with parsing different feature syntax (like `dep:` or `foo/bar`) or forgetting to handle it properly. Overview of refactoring changes: * `RequestedFeatures` changed to an enum to differentiate between features coming from the command-line, and those that are from a dependency. * Moved parsing of CLI features to an earlier stage (now stored in `CompileOptions`), and ensures that they are properly handled as `FeatureValue` instead of strings. * Pushed some feature validation earlier. For example, `DetailedTomlDependency` now validates things so you can see the location for the errant `Cargo.toml` (previously some validation was deep in the resolver, which provided poor errors). This is a pretty large PR, but at the core it is just changing `RequestedFeatures` and then dealing with the fallout from that. Hopefully this is an improvement overall. List of user-visible changes: * Fix handling in resolver V2 of `--features bar?/feat` and `--features dep:bar` * Better error handling for `bar/feat` and `dep:bar` in dependency declarations. * Feature keys in the `[features]` table can no longer contain slashes. * Fixed a minor issue with `cargo tree -e features --all-features -Z namespaced-features` * Fixed a panic with `cargo tree` involving `-Z weak-dep-features` I did a small amount of benchmarking, and I wasn't able to record much of a difference.
2 parents fb45eb1 + 85854b1 commit 39d9413

29 files changed

+772
-363
lines changed

src/bin/cargo/commands/metadata.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
4444
};
4545

4646
let options = OutputMetadataOptions {
47-
features: values(args, "features"),
48-
all_features: args.is_present("all-features"),
49-
no_default_features: args.is_present("no-default-features"),
47+
cli_features: args.cli_features()?,
5048
no_deps: args.is_present("no-deps"),
5149
filter_platforms: args._values_of("filter-platform"),
5250
version,

src/bin/cargo/commands/package.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
4545
allow_dirty: args.is_present("allow-dirty"),
4646
targets: args.targets(),
4747
jobs: args.jobs()?,
48-
features: args._values_of("features"),
49-
all_features: args.is_present("all-features"),
50-
no_default_features: args.is_present("no-default-features"),
48+
cli_features: args.cli_features()?,
5149
},
5250
)?;
5351
Ok(())

src/bin/cargo/commands/publish.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
4545
jobs: args.jobs()?,
4646
dry_run: args.is_present("dry-run"),
4747
registry,
48-
features: args._values_of("features"),
49-
all_features: args.is_present("all-features"),
50-
no_default_features: args.is_present("no-default-features"),
48+
cli_features: args.cli_features()?,
5149
},
5250
)?;
5351
Ok(())

src/bin/cargo/commands/tree.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,7 @@ subtree of the package given to -p.\n\
190190
let charset = tree::Charset::from_str(args.value_of("charset").unwrap())
191191
.map_err(|e| anyhow::anyhow!("{}", e))?;
192192
let opts = tree::TreeOptions {
193-
features: values(args, "features"),
194-
all_features: args.is_present("all-features"),
195-
no_default_features: args.is_present("no-default-features"),
193+
cli_features: args.cli_features()?,
196194
packages,
197195
target,
198196
edge_kinds,

src/cargo/core/compiler/standard_lib.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
use crate::core::compiler::UnitInterner;
44
use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit};
55
use crate::core::profiles::{Profiles, UnitFor};
6-
use crate::core::resolver::features::{FeaturesFor, RequestedFeatures, ResolvedFeatures};
7-
use crate::core::resolver::{HasDevUnits, ResolveOpts};
6+
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
7+
use crate::core::resolver::HasDevUnits;
88
use crate::core::{Dependency, PackageId, PackageSet, Resolve, SourceId, Workspace};
99
use crate::ops::{self, Packages};
1010
use crate::util::errors::CargoResult;
@@ -107,18 +107,14 @@ pub fn resolve_std<'cfg>(
107107
"default".to_string(),
108108
],
109109
};
110-
// dev_deps setting shouldn't really matter here.
111-
let opts = ResolveOpts::new(
112-
/*dev_deps*/ false,
113-
RequestedFeatures::from_command_line(
114-
&features, /*all_features*/ false, /*uses_default_features*/ false,
115-
),
116-
);
110+
let cli_features = CliFeatures::from_command_line(
111+
&features, /*all_features*/ false, /*uses_default_features*/ false,
112+
)?;
117113
let resolve = ops::resolve_ws_with_opts(
118114
&std_ws,
119115
target_data,
120116
requested_targets,
121-
&opts,
117+
&cli_features,
122118
&specs,
123119
HasDevUnits::No,
124120
crate::core::resolver::features::ForceAllTargets::No,

src/cargo/core/resolver/context.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::dep_cache::RegistryQueryer;
22
use super::errors::ActivateResult;
33
use super::types::{ConflictMap, ConflictReason, FeaturesSet, ResolveOpts};
4+
use super::RequestedFeatures;
45
use crate::core::{Dependency, PackageId, SourceId, Summary};
56
use crate::util::interning::InternedString;
67
use crate::util::Graph;
@@ -160,23 +161,32 @@ impl Context {
160161
}
161162
}
162163
debug!("checking if {} is already activated", summary.package_id());
163-
if opts.features.all_features {
164-
return Ok(false);
165-
}
166-
167-
let has_default_feature = summary.features().contains_key("default");
168-
Ok(match self.resolve_features.get(&id) {
169-
Some(prev) => {
170-
opts.features.features.is_subset(prev)
171-
&& (!opts.features.uses_default_features
172-
|| prev.contains("default")
173-
|| !has_default_feature)
174-
}
175-
None => {
176-
opts.features.features.is_empty()
177-
&& (!opts.features.uses_default_features || !has_default_feature)
164+
match &opts.features {
165+
// This returns `false` for CliFeatures just for simplicity. It
166+
// would take a bit of work to compare since they are not in the
167+
// same format as DepFeatures (and that may be expensive
168+
// performance-wise). Also, it should only occur once for a root
169+
// package. The only drawback is that it may re-activate a root
170+
// package again, which should only affect performance, but that
171+
// should be rare. Cycles should still be detected since those
172+
// will have `DepFeatures` edges.
173+
RequestedFeatures::CliFeatures(_) => return Ok(false),
174+
RequestedFeatures::DepFeatures {
175+
features,
176+
uses_default_features,
177+
} => {
178+
let has_default_feature = summary.features().contains_key("default");
179+
Ok(match self.resolve_features.get(&id) {
180+
Some(prev) => {
181+
features.is_subset(prev)
182+
&& (!uses_default_features
183+
|| prev.contains("default")
184+
|| !has_default_feature)
185+
}
186+
None => features.is_empty() && (!uses_default_features || !has_default_feature),
187+
})
178188
}
179-
})
189+
}
180190
}
181191

182192
/// If the package is active returns the `ContextAge` when it was added

src/cargo/core/resolver/dep_cache.rs

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
use crate::core::resolver::context::Context;
1313
use crate::core::resolver::errors::describe_path;
1414
use crate::core::resolver::types::{ConflictReason, DepInfo, FeaturesSet};
15-
use crate::core::resolver::{ActivateError, ActivateResult, ResolveOpts};
15+
use crate::core::resolver::{
16+
ActivateError, ActivateResult, CliFeatures, RequestedFeatures, ResolveOpts,
17+
};
1618
use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, Summary};
1719
use crate::util::errors::{CargoResult, CargoResultExt};
1820
use crate::util::interning::InternedString;
@@ -281,15 +283,6 @@ pub fn resolve_features<'b>(
281283
.unwrap_or(&default_dep)
282284
.clone();
283285
base.extend(dep.features().iter());
284-
for feature in base.iter() {
285-
if feature.contains('/') {
286-
return Err(anyhow::format_err!(
287-
"feature names may not contain slashes: `{}`",
288-
feature
289-
)
290-
.into());
291-
}
292-
}
293286
ret.push((dep.clone(), Rc::new(base)));
294287
}
295288

@@ -317,30 +310,46 @@ fn build_requirements<'a, 'b: 'a>(
317310
) -> ActivateResult<Requirements<'a>> {
318311
let mut reqs = Requirements::new(s);
319312

320-
if opts.features.all_features {
321-
for key in s.features().keys() {
322-
if let Err(e) = reqs.require_feature(*key) {
313+
let handle_default = |uses_default_features, reqs: &mut Requirements<'_>| {
314+
if uses_default_features && s.features().contains_key("default") {
315+
if let Err(e) = reqs.require_feature(InternedString::new("default")) {
323316
return Err(e.into_activate_error(parent, s));
324317
}
325318
}
326-
} else {
327-
for &f in opts.features.features.iter() {
328-
let fv = FeatureValue::new(f);
329-
if fv.has_dep_prefix() {
330-
return Err(ActivateError::Fatal(anyhow::format_err!(
331-
"feature value `{}` is not allowed to use explicit `dep:` syntax",
332-
fv
333-
)));
334-
}
335-
if let Err(e) = reqs.require_value(&fv) {
336-
return Err(e.into_activate_error(parent, s));
319+
Ok(())
320+
};
321+
322+
match &opts.features {
323+
RequestedFeatures::CliFeatures(CliFeatures {
324+
features,
325+
all_features,
326+
uses_default_features,
327+
}) => {
328+
if *all_features {
329+
for key in s.features().keys() {
330+
if let Err(e) = reqs.require_feature(*key) {
331+
return Err(e.into_activate_error(parent, s));
332+
}
333+
}
334+
} else {
335+
for fv in features.iter() {
336+
if let Err(e) = reqs.require_value(&fv) {
337+
return Err(e.into_activate_error(parent, s));
338+
}
339+
}
340+
handle_default(*uses_default_features, &mut reqs)?;
337341
}
338342
}
339-
}
340-
341-
if opts.features.uses_default_features && s.features().contains_key("default") {
342-
if let Err(e) = reqs.require_feature(InternedString::new("default")) {
343-
return Err(e.into_activate_error(parent, s));
343+
RequestedFeatures::DepFeatures {
344+
features,
345+
uses_default_features,
346+
} => {
347+
for feature in features.iter() {
348+
if let Err(e) = reqs.require_feature(*feature) {
349+
return Err(e.into_activate_error(parent, s));
350+
}
351+
}
352+
handle_default(*uses_default_features, &mut reqs)?;
344353
}
345354
}
346355

0 commit comments

Comments
 (0)