Refactor feature handling, and improve error messages.

This commit is contained in:
Eric Huss 2021-03-19 14:36:26 -07:00
parent cc70d60a9e
commit 85854b1884
29 changed files with 761 additions and 353 deletions

View file

@ -44,9 +44,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
};
let options = OutputMetadataOptions {
features: values(args, "features"),
all_features: args.is_present("all-features"),
no_default_features: args.is_present("no-default-features"),
cli_features: args.cli_features()?,
no_deps: args.is_present("no-deps"),
filter_platforms: args._values_of("filter-platform"),
version,

View file

@ -45,9 +45,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
allow_dirty: args.is_present("allow-dirty"),
targets: args.targets(),
jobs: args.jobs()?,
features: args._values_of("features"),
all_features: args.is_present("all-features"),
no_default_features: args.is_present("no-default-features"),
cli_features: args.cli_features()?,
},
)?;
Ok(())

View file

@ -45,9 +45,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
jobs: args.jobs()?,
dry_run: args.is_present("dry-run"),
registry,
features: args._values_of("features"),
all_features: args.is_present("all-features"),
no_default_features: args.is_present("no-default-features"),
cli_features: args.cli_features()?,
},
)?;
Ok(())

View file

@ -190,9 +190,7 @@ subtree of the package given to -p.\n\
let charset = tree::Charset::from_str(args.value_of("charset").unwrap())
.map_err(|e| anyhow::anyhow!("{}", e))?;
let opts = tree::TreeOptions {
features: values(args, "features"),
all_features: args.is_present("all-features"),
no_default_features: args.is_present("no-default-features"),
cli_features: args.cli_features()?,
packages,
target,
edge_kinds,

View file

@ -3,8 +3,8 @@
use crate::core::compiler::UnitInterner;
use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit};
use crate::core::profiles::{Profiles, UnitFor};
use crate::core::resolver::features::{FeaturesFor, RequestedFeatures, ResolvedFeatures};
use crate::core::resolver::{HasDevUnits, ResolveOpts};
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::core::resolver::HasDevUnits;
use crate::core::{Dependency, PackageId, PackageSet, Resolve, SourceId, Workspace};
use crate::ops::{self, Packages};
use crate::util::errors::CargoResult;
@ -107,18 +107,14 @@ pub fn resolve_std<'cfg>(
"default".to_string(),
],
};
// dev_deps setting shouldn't really matter here.
let opts = ResolveOpts::new(
/*dev_deps*/ false,
RequestedFeatures::from_command_line(
&features, /*all_features*/ false, /*uses_default_features*/ false,
),
);
let cli_features = CliFeatures::from_command_line(
&features, /*all_features*/ false, /*uses_default_features*/ false,
)?;
let resolve = ops::resolve_ws_with_opts(
&std_ws,
target_data,
requested_targets,
&opts,
&cli_features,
&specs,
HasDevUnits::No,
crate::core::resolver::features::ForceAllTargets::No,

View file

@ -1,6 +1,7 @@
use super::dep_cache::RegistryQueryer;
use super::errors::ActivateResult;
use super::types::{ConflictMap, ConflictReason, FeaturesSet, ResolveOpts};
use super::RequestedFeatures;
use crate::core::{Dependency, PackageId, SourceId, Summary};
use crate::util::interning::InternedString;
use crate::util::Graph;
@ -160,23 +161,32 @@ impl Context {
}
}
debug!("checking if {} is already activated", summary.package_id());
if opts.features.all_features {
return Ok(false);
match &opts.features {
// This returns `false` for CliFeatures just for simplicity. It
// would take a bit of work to compare since they are not in the
// same format as DepFeatures (and that may be expensive
// performance-wise). Also, it should only occur once for a root
// package. The only drawback is that it may re-activate a root
// package again, which should only affect performance, but that
// should be rare. Cycles should still be detected since those
// will have `DepFeatures` edges.
RequestedFeatures::CliFeatures(_) => return Ok(false),
RequestedFeatures::DepFeatures {
features,
uses_default_features,
} => {
let has_default_feature = summary.features().contains_key("default");
Ok(match self.resolve_features.get(&id) {
Some(prev) => {
features.is_subset(prev)
&& (!uses_default_features
|| prev.contains("default")
|| !has_default_feature)
}
None => features.is_empty() && (!uses_default_features || !has_default_feature),
})
}
}
let has_default_feature = summary.features().contains_key("default");
Ok(match self.resolve_features.get(&id) {
Some(prev) => {
opts.features.features.is_subset(prev)
&& (!opts.features.uses_default_features
|| prev.contains("default")
|| !has_default_feature)
}
None => {
opts.features.features.is_empty()
&& (!opts.features.uses_default_features || !has_default_feature)
}
})
}
/// If the package is active returns the `ContextAge` when it was added

View file

@ -12,7 +12,9 @@
use crate::core::resolver::context::Context;
use crate::core::resolver::errors::describe_path;
use crate::core::resolver::types::{ConflictReason, DepInfo, FeaturesSet};
use crate::core::resolver::{ActivateError, ActivateResult, ResolveOpts};
use crate::core::resolver::{
ActivateError, ActivateResult, CliFeatures, RequestedFeatures, ResolveOpts,
};
use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry, Summary};
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::interning::InternedString;
@ -281,15 +283,6 @@ pub fn resolve_features<'b>(
.unwrap_or(&default_dep)
.clone();
base.extend(dep.features().iter());
for feature in base.iter() {
if feature.contains('/') {
return Err(anyhow::format_err!(
"feature names may not contain slashes: `{}`",
feature
)
.into());
}
}
ret.push((dep.clone(), Rc::new(base)));
}
@ -317,30 +310,46 @@ fn build_requirements<'a, 'b: 'a>(
) -> ActivateResult<Requirements<'a>> {
let mut reqs = Requirements::new(s);
if opts.features.all_features {
for key in s.features().keys() {
if let Err(e) = reqs.require_feature(*key) {
let handle_default = |uses_default_features, reqs: &mut Requirements<'_>| {
if uses_default_features && s.features().contains_key("default") {
if let Err(e) = reqs.require_feature(InternedString::new("default")) {
return Err(e.into_activate_error(parent, s));
}
}
} else {
for &f in opts.features.features.iter() {
let fv = FeatureValue::new(f);
if fv.has_dep_prefix() {
return Err(ActivateError::Fatal(anyhow::format_err!(
"feature value `{}` is not allowed to use explicit `dep:` syntax",
fv
)));
}
if let Err(e) = reqs.require_value(&fv) {
return Err(e.into_activate_error(parent, s));
}
}
}
Ok(())
};
if opts.features.uses_default_features && s.features().contains_key("default") {
if let Err(e) = reqs.require_feature(InternedString::new("default")) {
return Err(e.into_activate_error(parent, s));
match &opts.features {
RequestedFeatures::CliFeatures(CliFeatures {
features,
all_features,
uses_default_features,
}) => {
if *all_features {
for key in s.features().keys() {
if let Err(e) = reqs.require_feature(*key) {
return Err(e.into_activate_error(parent, s));
}
}
} else {
for fv in features.iter() {
if let Err(e) = reqs.require_value(&fv) {
return Err(e.into_activate_error(parent, s));
}
}
handle_default(*uses_default_features, &mut reqs)?;
}
}
RequestedFeatures::DepFeatures {
features,
uses_default_features,
} => {
for feature in features.iter() {
if let Err(e) = reqs.require_feature(*feature) {
return Err(e.into_activate_error(parent, s));
}
}
handle_default(*uses_default_features, &mut reqs)?;
}
}

View file

@ -42,6 +42,7 @@ use crate::core::resolver::{Resolve, ResolveBehavior};
use crate::core::{FeatureValue, PackageId, PackageIdSpec, PackageSet, Workspace};
use crate::util::interning::InternedString;
use crate::util::CargoResult;
use anyhow::bail;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::rc::Rc;
@ -144,7 +145,7 @@ impl FeatureOpts {
}
"compare" => opts.compare = true,
"ws" => unimplemented!(),
s => anyhow::bail!("-Zfeatures flag `{}` is not supported", s),
s => bail!("-Zfeatures flag `{}` is not supported", s),
}
}
Ok(())
@ -197,44 +198,93 @@ impl FeatureOpts {
}
/// Features flags requested for a package.
///
/// This should be cheap and fast to clone, it is used in the resolver for
/// various caches.
///
/// This is split into enum variants because the resolver needs to handle
/// features coming from different places (command-line and dependency
/// declarations), but those different places have different constraints on
/// which syntax is allowed. This helps ensure that every place dealing with
/// features is properly handling those syntax restrictions.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct RequestedFeatures {
pub features: FeaturesSet,
pub enum RequestedFeatures {
/// Features requested on the command-line with flags.
CliFeatures(CliFeatures),
/// Features specified in a dependency declaration.
DepFeatures {
/// The `features` dependency field.
features: FeaturesSet,
/// The `default-features` dependency field.
uses_default_features: bool,
},
}
/// Features specified on the command-line.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct CliFeatures {
/// Features from the `--features` flag.
pub features: Rc<BTreeSet<FeatureValue>>,
/// The `--all-features` flag.
pub all_features: bool,
/// Inverse of `--no-default-features` flag.
pub uses_default_features: bool,
}
impl RequestedFeatures {
/// Creates a new RequestedFeatures from the given command-line flags.
impl CliFeatures {
/// Creates a new CliFeatures from the given command-line flags.
pub fn from_command_line(
features: &[String],
all_features: bool,
uses_default_features: bool,
) -> RequestedFeatures {
RequestedFeatures {
features: Rc::new(RequestedFeatures::split_features(features)),
) -> CargoResult<CliFeatures> {
let features = Rc::new(CliFeatures::split_features(features));
// Some early validation to ensure correct syntax.
for feature in features.iter() {
match feature {
// Maybe call validate_feature_name here once it is an error?
FeatureValue::Feature(_) => {}
FeatureValue::Dep { .. }
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => {
bail!(
"feature `{}` is not allowed to use explicit `dep:` syntax",
feature
);
}
FeatureValue::DepFeature { dep_feature, .. } => {
if dep_feature.contains('/') {
bail!("multiple slashes in feature `{}` is not allowed", feature);
}
}
}
}
Ok(CliFeatures {
features,
all_features,
uses_default_features,
}
})
}
/// Creates a new RequestedFeatures with the given `all_features` setting.
pub fn new_all(all_features: bool) -> RequestedFeatures {
RequestedFeatures {
/// Creates a new CliFeatures with the given `all_features` setting.
pub fn new_all(all_features: bool) -> CliFeatures {
CliFeatures {
features: Rc::new(BTreeSet::new()),
all_features,
uses_default_features: true,
}
}
fn split_features(features: &[String]) -> BTreeSet<InternedString> {
fn split_features(features: &[String]) -> BTreeSet<FeatureValue> {
features
.iter()
.flat_map(|s| s.split_whitespace())
.flat_map(|s| s.split(','))
.filter(|s| !s.is_empty())
.map(InternedString::new)
.collect::<BTreeSet<InternedString>>()
.map(FeatureValue::new)
.collect()
}
}
@ -296,7 +346,7 @@ impl ResolvedFeatures {
if let Some(fs) = self.activated_features.get(&(pkg_id, is_build)) {
Ok(fs.iter().cloned().collect())
} else {
anyhow::bail!("features did not find {:?} {:?}", pkg_id, is_build)
bail!("features did not find {:?} {:?}", pkg_id, is_build)
}
}
}
@ -405,7 +455,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
target_data: &RustcTargetData,
resolve: &Resolve,
package_set: &'a PackageSet<'cfg>,
requested_features: &RequestedFeatures,
cli_features: &CliFeatures,
specs: &[PackageIdSpec],
requested_targets: &[CompileKind],
opts: FeatureOpts,
@ -437,7 +487,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
track_for_host,
deferred_weak_dependencies: HashMap::new(),
};
r.do_resolve(specs, requested_features)?;
r.do_resolve(specs, cli_features)?;
log::debug!("features={:#?}", r.activated_features);
if r.opts.compare {
r.compare();
@ -455,11 +505,11 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
fn do_resolve(
&mut self,
specs: &[PackageIdSpec],
requested_features: &RequestedFeatures,
cli_features: &CliFeatures,
) -> CargoResult<()> {
let member_features = self.ws.members_with_features(specs, requested_features)?;
for (member, requested_features) in &member_features {
let fvs = self.fvs_from_requested(member.package_id(), requested_features);
let member_features = self.ws.members_with_features(specs, cli_features)?;
for (member, cli_features) in &member_features {
let fvs = self.fvs_from_requested(member.package_id(), cli_features);
let for_host = self.track_for_host && self.is_proc_macro(member.package_id());
self.activate_pkg(member.package_id(), for_host, &fvs)?;
if for_host {
@ -725,24 +775,19 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
fn fvs_from_requested(
&self,
pkg_id: PackageId,
requested_features: &RequestedFeatures,
cli_features: &CliFeatures,
) -> Vec<FeatureValue> {
let summary = self.resolve.summary(pkg_id);
let feature_map = summary.features();
if requested_features.all_features {
if cli_features.all_features {
feature_map
.keys()
.map(|k| FeatureValue::Feature(*k))
.collect()
} else {
let mut result: Vec<FeatureValue> = requested_features
.features
.as_ref()
.iter()
.map(|f| FeatureValue::new(*f))
.collect();
let mut result: Vec<FeatureValue> = cli_features.features.iter().cloned().collect();
let default = InternedString::new("default");
if requested_features.uses_default_features && feature_map.contains_key(&default) {
if cli_features.uses_default_features && feature_map.contains_key(&default) {
result.push(FeatureValue::Feature(default));
}
result

View file

@ -69,7 +69,7 @@ use self::types::{FeaturesSet, RcVecIter, RemainingDeps, ResolverProgress};
pub use self::encode::Metadata;
pub use self::encode::{EncodableDependency, EncodablePackageId, EncodableResolve};
pub use self::errors::{ActivateError, ActivateResult, ResolveError};
pub use self::features::{ForceAllTargets, HasDevUnits};
pub use self::features::{CliFeatures, ForceAllTargets, HasDevUnits};
pub use self::resolve::{Resolve, ResolveVersion};
pub use self::types::{ResolveBehavior, ResolveOpts};
@ -192,7 +192,7 @@ fn activate_deps_loop(
// Activate all the initial summaries to kick off some work.
for &(ref summary, ref opts) in summaries {
debug!("initial activation: {}", summary.package_id());
let res = activate(&mut cx, registry, None, summary.clone(), opts.clone());
let res = activate(&mut cx, registry, None, summary.clone(), opts);
match res {
Ok(Some((frame, _))) => remaining_deps.push(frame),
Ok(None) => (),
@ -378,9 +378,8 @@ fn activate_deps_loop(
let pid = candidate.package_id();
let opts = ResolveOpts {
dev_deps: false,
features: RequestedFeatures {
features: RequestedFeatures::DepFeatures {
features: Rc::clone(&features),
all_features: false,
uses_default_features: dep.uses_default_features(),
},
};
@ -391,7 +390,7 @@ fn activate_deps_loop(
dep.package_name(),
candidate.version()
);
let res = activate(&mut cx, registry, Some((&parent, &dep)), candidate, opts);
let res = activate(&mut cx, registry, Some((&parent, &dep)), candidate, &opts);
let successfully_activated = match res {
// Success! We've now activated our `candidate` in our context
@ -603,7 +602,7 @@ fn activate(
registry: &mut RegistryQueryer<'_>,
parent: Option<(&Summary, &Dependency)>,
candidate: Summary,
opts: ResolveOpts,
opts: &ResolveOpts,
) -> ActivateResult<Option<(DepsFrame, Duration)>> {
let candidate_pid = candidate.package_id();
cx.age += 1;

View file

@ -1,4 +1,4 @@
use super::features::RequestedFeatures;
use super::features::{CliFeatures, RequestedFeatures};
use crate::core::{Dependency, PackageId, Summary};
use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
@ -133,6 +133,7 @@ pub struct ResolveOpts {
/// Whether or not dev-dependencies should be included.
///
/// This may be set to `false` by things like `cargo install` or `-Z avoid-dev-deps`.
/// It also gets set to `false` when activating dependencies in the resolver.
pub dev_deps: bool,
/// Set of features requested on the command-line.
pub features: RequestedFeatures,
@ -143,7 +144,7 @@ impl ResolveOpts {
pub fn everything() -> ResolveOpts {
ResolveOpts {
dev_deps: true,
features: RequestedFeatures::new_all(true),
features: RequestedFeatures::CliFeatures(CliFeatures::new_all(true)),
}
}

View file

@ -250,6 +250,12 @@ fn build_feature_map(
feature
);
}
if feature.contains('/') {
bail!(
"feature named `{}` is not allowed to contain slashes",
feature
);
}
validate_feature_name(config, pkg_id, feature)?;
for fv in fvs {
// Find data for the referenced dependency...
@ -316,7 +322,20 @@ fn build_feature_map(
);
}
}
DepFeature { dep_name, weak, .. } => {
DepFeature {
dep_name,
dep_feature,
weak,
..
} => {
// Early check for some unlikely syntax.
if dep_feature.contains('/') {
bail!(
"multiple slashes in feature `{}` (included by feature `{}`) are not allowed",
fv,
feature
);
}
// Validation of the feature name will be performed in the resolver.
if !is_any_dep {
bail!(
@ -362,7 +381,7 @@ fn build_feature_map(
}
/// FeatureValue represents the types of dependencies a feature can have.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum FeatureValue {
/// A feature enabling another feature.
Feature(InternedString),

View file

@ -12,9 +12,9 @@ use url::Url;
use crate::core::features::Features;
use crate::core::registry::PackageRegistry;
use crate::core::resolver::features::RequestedFeatures;
use crate::core::resolver::features::CliFeatures;
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, Edition, PackageId, PackageIdSpec};
use crate::core::{Dependency, Edition, FeatureValue, PackageId, PackageIdSpec};
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
use crate::ops;
use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
@ -1047,10 +1047,10 @@ impl<'cfg> Workspace<'cfg> {
pub fn members_with_features(
&self,
specs: &[PackageIdSpec],
requested_features: &RequestedFeatures,
) -> CargoResult<Vec<(&Package, RequestedFeatures)>> {
cli_features: &CliFeatures,
) -> CargoResult<Vec<(&Package, CliFeatures)>> {
assert!(
!specs.is_empty() || requested_features.all_features,
!specs.is_empty() || cli_features.all_features,
"no specs requires all_features"
);
if specs.is_empty() {
@ -1058,13 +1058,13 @@ impl<'cfg> Workspace<'cfg> {
// all features enabled.
return Ok(self
.members()
.map(|m| (m, RequestedFeatures::new_all(true)))
.map(|m| (m, CliFeatures::new_all(true)))
.collect());
}
if self.allows_new_cli_feature_behavior() {
self.members_with_features_new(specs, requested_features)
self.members_with_features_new(specs, cli_features)
} else {
Ok(self.members_with_features_old(specs, requested_features))
Ok(self.members_with_features_old(specs, cli_features))
}
}
@ -1073,17 +1073,17 @@ impl<'cfg> Workspace<'cfg> {
fn members_with_features_new(
&self,
specs: &[PackageIdSpec],
requested_features: &RequestedFeatures,
) -> CargoResult<Vec<(&Package, RequestedFeatures)>> {
cli_features: &CliFeatures,
) -> CargoResult<Vec<(&Package, CliFeatures)>> {
// Keep track of which features matched *any* member, to produce an error
// if any of them did not match anywhere.
let mut found: BTreeSet<InternedString> = BTreeSet::new();
let mut found: BTreeSet<FeatureValue> = BTreeSet::new();
// Returns the requested features for the given member.
// This filters out any named features that the member does not have.
let mut matching_features = |member: &Package| -> RequestedFeatures {
if requested_features.features.is_empty() || requested_features.all_features {
return requested_features.clone();
let mut matching_features = |member: &Package| -> CliFeatures {
if cli_features.features.is_empty() || cli_features.all_features {
return cli_features.clone();
}
// Only include features this member defines.
let summary = member.summary();
@ -1099,40 +1099,54 @@ impl<'cfg> Workspace<'cfg> {
.any(|dep| dep.is_optional() && dep.name_in_toml() == feature)
};
for feature in requested_features.features.iter() {
let mut split = feature.splitn(2, '/');
let split = (split.next().unwrap(), split.next());
if let (pkg, Some(pkg_feature)) = split {
let pkg = InternedString::new(pkg);
let pkg_feature = InternedString::new(pkg_feature);
if summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == pkg)
{
// pkg/feat for a dependency.
// Will rely on the dependency resolver to validate `feat`.
features.insert(*feature);
found.insert(*feature);
} else if pkg == member.name() && contains(pkg_feature) {
// member/feat where "feat" is a feature in member.
features.insert(pkg_feature);
found.insert(*feature);
for feature in cli_features.features.iter() {
match feature {
FeatureValue::Feature(f) => {
if contains(*f) {
// feature exists in this member.
features.insert(feature.clone());
found.insert(feature.clone());
}
}
// This should be enforced by CliFeatures.
FeatureValue::Dep { .. }
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => panic!("unexpected dep: syntax {}", feature),
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix: _,
weak: _,
} => {
if summary
.dependencies()
.iter()
.any(|dep| dep.name_in_toml() == *dep_name)
{
// pkg/feat for a dependency.
// Will rely on the dependency resolver to validate `dep_feature`.
features.insert(feature.clone());
found.insert(feature.clone());
} else if *dep_name == member.name() && contains(*dep_feature) {
// member/feat where "feat" is a feature in member.
//
// `weak` can be ignored here, because the member
// either is or isn't being built.
features.insert(FeatureValue::Feature(*dep_feature));
found.insert(feature.clone());
}
}
} else if contains(*feature) {
// feature exists in this member.
features.insert(*feature);
found.insert(*feature);
}
}
RequestedFeatures {
CliFeatures {
features: Rc::new(features),
all_features: false,
uses_default_features: requested_features.uses_default_features,
uses_default_features: cli_features.uses_default_features,
}
};
let members: Vec<(&Package, RequestedFeatures)> = self
let members: Vec<(&Package, CliFeatures)> = self
.members()
.filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
.map(|m| (m, matching_features(m)))
@ -1140,9 +1154,9 @@ impl<'cfg> Workspace<'cfg> {
if members.is_empty() {
// `cargo build -p foo`, where `foo` is not a member.
// Do not allow any command-line flags (defaults only).
if !(requested_features.features.is_empty()
&& !requested_features.all_features
&& requested_features.uses_default_features)
if !(cli_features.features.is_empty()
&& !cli_features.all_features
&& cli_features.uses_default_features)
{
bail!("cannot specify features for packages outside of workspace");
}
@ -1150,15 +1164,16 @@ impl<'cfg> Workspace<'cfg> {
// is in the resolve graph.
return Ok(self
.members()
.map(|m| (m, RequestedFeatures::new_all(false)))
.map(|m| (m, CliFeatures::new_all(false)))
.collect());
}
if *requested_features.features != found {
let missing: Vec<_> = requested_features
if *cli_features.features != found {
let mut missing: Vec<_> = cli_features
.features
.difference(&found)
.copied()
.map(|fv| fv.to_string())
.collect();
missing.sort();
// TODO: typo suggestions would be good here.
bail!(
"none of the selected packages contains these features: {}",
@ -1173,28 +1188,46 @@ impl<'cfg> Workspace<'cfg> {
fn members_with_features_old(
&self,
specs: &[PackageIdSpec],
requested_features: &RequestedFeatures,
) -> Vec<(&Package, RequestedFeatures)> {
cli_features: &CliFeatures,
) -> Vec<(&Package, CliFeatures)> {
// Split off any features with the syntax `member-name/feature-name` into a map
// so that those features can be applied directly to those workspace-members.
let mut member_specific_features: HashMap<&str, BTreeSet<InternedString>> = HashMap::new();
let mut member_specific_features: HashMap<InternedString, BTreeSet<FeatureValue>> =
HashMap::new();
// Features for the member in the current directory.
let mut cwd_features = BTreeSet::new();
for feature in requested_features.features.iter() {
if let Some(index) = feature.find('/') {
let name = &feature[..index];
let is_member = self.members().any(|member| member.name() == name);
if is_member && specs.iter().any(|spec| spec.name() == name) {
member_specific_features
.entry(name)
.or_default()
.insert(InternedString::new(&feature[index + 1..]));
} else {
cwd_features.insert(*feature);
for feature in cli_features.features.iter() {
match feature {
FeatureValue::Feature(_) => {
cwd_features.insert(feature.clone());
}
} else {
cwd_features.insert(*feature);
};
// This should be enforced by CliFeatures.
FeatureValue::Dep { .. }
| FeatureValue::DepFeature {
dep_prefix: true, ..
} => panic!("unexpected dep: syntax {}", feature),
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix: _,
weak: _,
} => {
// I think weak can be ignored here.
// * With `--features member?/feat -p member`, the ? doesn't
// really mean anything (either the member is built or it isn't).
// * With `--features nonmember?/feat`, cwd_features will
// handle processing it correctly.
let is_member = self.members().any(|member| member.name() == *dep_name);
if is_member && specs.iter().any(|spec| spec.name() == *dep_name) {
member_specific_features
.entry(*dep_name)
.or_default()
.insert(FeatureValue::Feature(*dep_feature));
} else {
cwd_features.insert(feature.clone());
}
}
}
}
let ms = self.members().filter_map(|member| {
@ -1203,10 +1236,10 @@ impl<'cfg> Workspace<'cfg> {
// The features passed on the command-line only apply to
// the "current" package (determined by the cwd).
Some(current) if member_id == current.package_id() => {
let feats = RequestedFeatures {
let feats = CliFeatures {
features: Rc::new(cwd_features.clone()),
all_features: requested_features.all_features,
uses_default_features: requested_features.uses_default_features,
all_features: cli_features.all_features,
uses_default_features: cli_features.uses_default_features,
};
Some((member, feats))
}
@ -1222,14 +1255,14 @@ impl<'cfg> Workspace<'cfg> {
// "current" package. As an extension, this allows
// member-name/feature-name to set member-specific
// features, which should be backwards-compatible.
let feats = RequestedFeatures {
let feats = CliFeatures {
features: Rc::new(
member_specific_features
.remove(member.name().as_str())
.unwrap_or_default(),
),
uses_default_features: true,
all_features: requested_features.all_features,
all_features: cli_features.all_features,
};
Some((member, feats))
} else {

View file

@ -33,8 +33,8 @@ use crate::core::compiler::{BuildConfig, BuildContext, Compilation, Context};
use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, RustcTargetData, Unit};
use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner};
use crate::core::profiles::{Profiles, UnitFor};
use crate::core::resolver::features::{self, FeaturesFor, RequestedFeatures};
use crate::core::resolver::{HasDevUnits, Resolve, ResolveOpts};
use crate::core::resolver::features::{self, CliFeatures, FeaturesFor};
use crate::core::resolver::{HasDevUnits, Resolve};
use crate::core::{FeatureValue, Package, PackageSet, Shell, Summary, Target};
use crate::core::{PackageId, PackageIdSpec, SourceId, TargetKind, Workspace};
use crate::drop_println;
@ -59,12 +59,8 @@ use anyhow::Context as _;
pub struct CompileOptions {
/// Configuration information for a rustc build
pub build_config: BuildConfig,
/// Extra features to build for the root package
pub features: Vec<String>,
/// Flag whether all available features should be built for the root package
pub all_features: bool,
/// Flag if the default feature should be built for the root package
pub no_default_features: bool,
/// Feature flags requested by the user.
pub cli_features: CliFeatures,
/// A set of packages to build.
pub spec: Packages,
/// Filter to apply to the root package to select which targets will be
@ -89,9 +85,7 @@ impl<'a> CompileOptions {
pub fn new(config: &Config, mode: CompileMode) -> CargoResult<CompileOptions> {
Ok(CompileOptions {
build_config: BuildConfig::new(config, None, &[], mode)?,
features: Vec::new(),
all_features: false,
no_default_features: false,
cli_features: CliFeatures::new_all(false),
spec: ops::Packages::Packages(Vec::new()),
filter: CompileFilter::Default {
required_features_filterable: false,
@ -334,9 +328,7 @@ pub fn create_bcx<'a, 'cfg>(
let CompileOptions {
ref build_config,
ref spec,
ref features,
all_features,
no_default_features,
ref cli_features,
ref filter,
ref target_rustdoc_args,
ref target_rustc_args,
@ -372,11 +364,6 @@ pub fn create_bcx<'a, 'cfg>(
let target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?;
let specs = spec.to_package_id_specs(ws)?;
let dev_deps = ws.require_optional_deps() || filter.need_dev_deps(build_config.mode);
let opts = ResolveOpts::new(
dev_deps,
RequestedFeatures::from_command_line(features, all_features, !no_default_features),
);
let has_dev_units = if filter.need_dev_deps(build_config.mode) {
HasDevUnits::Yes
} else {
@ -386,7 +373,7 @@ pub fn create_bcx<'a, 'cfg>(
ws,
&target_data,
&build_config.requested_kinds,
&opts,
cli_features,
&specs,
has_dev_units,
crate::core::resolver::features::ForceAllTargets::No,

View file

@ -1,5 +1,5 @@
use crate::core::compiler::RustcTargetData;
use crate::core::resolver::{features::RequestedFeatures, HasDevUnits, ResolveOpts};
use crate::core::resolver::HasDevUnits;
use crate::core::{Shell, Workspace};
use crate::ops;
use crate::util::CargoResult;
@ -19,20 +19,12 @@ pub struct DocOptions {
/// Main method for `cargo doc`.
pub fn doc(ws: &Workspace<'_>, options: &DocOptions) -> CargoResult<()> {
let specs = options.compile_opts.spec.to_package_id_specs(ws)?;
let opts = ResolveOpts::new(
/*dev_deps*/ true,
RequestedFeatures::from_command_line(
&options.compile_opts.features,
options.compile_opts.all_features,
!options.compile_opts.no_default_features,
),
);
let target_data = RustcTargetData::new(ws, &options.compile_opts.build_config.requested_kinds)?;
let ws_resolve = ops::resolve_ws_with_opts(
ws,
&target_data,
&options.compile_opts.build_config.requested_kinds,
&opts,
&options.compile_opts.cli_features,
&specs,
HasDevUnits::No,
crate::core::resolver::features::ForceAllTargets::No,

View file

@ -4,7 +4,7 @@ use log::debug;
use termcolor::Color::{self, Cyan, Green, Red};
use crate::core::registry::PackageRegistry;
use crate::core::resolver::ResolveOpts;
use crate::core::resolver::features::{CliFeatures, HasDevUnits};
use crate::core::{PackageId, PackageIdSpec};
use crate::core::{Resolve, SourceId, Workspace};
use crate::ops;
@ -25,7 +25,8 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
let mut resolve = ops::resolve_with_previous(
&mut registry,
ws,
&ResolveOpts::everything(),
&CliFeatures::new_all(true),
HasDevUnits::Yes,
None,
None,
&[],
@ -61,7 +62,8 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
ops::resolve_with_previous(
&mut registry,
ws,
&ResolveOpts::everything(),
&CliFeatures::new_all(true),
HasDevUnits::Yes,
None,
None,
&[],
@ -115,7 +117,8 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
let mut resolve = ops::resolve_with_previous(
&mut registry,
ws,
&ResolveOpts::everything(),
&CliFeatures::new_all(true),
HasDevUnits::Yes,
Some(&previous_resolve),
Some(&to_avoid),
&[],

View file

@ -1,7 +1,7 @@
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind;
use crate::core::package::SerializedPackage;
use crate::core::resolver::{features::RequestedFeatures, HasDevUnits, Resolve, ResolveOpts};
use crate::core::resolver::{features::CliFeatures, HasDevUnits, Resolve};
use crate::core::{Dependency, Package, PackageId, Workspace};
use crate::ops::{self, Packages};
use crate::util::interning::InternedString;
@ -14,9 +14,7 @@ use std::path::PathBuf;
const VERSION: u32 = 1;
pub struct OutputMetadataOptions {
pub features: Vec<String>,
pub no_default_features: bool,
pub all_features: bool,
pub cli_features: CliFeatures,
pub no_deps: bool,
pub version: u32,
pub filter_platforms: Vec<String>,
@ -115,12 +113,6 @@ fn build_resolve_graph(
let target_data = RustcTargetData::new(ws, &requested_kinds)?;
// Resolve entire workspace.
let specs = Packages::All.to_package_id_specs(ws)?;
let requested_features = RequestedFeatures::from_command_line(
&metadata_opts.features,
metadata_opts.all_features,
!metadata_opts.no_default_features,
);
let resolve_opts = ResolveOpts::new(/*dev_deps*/ true, requested_features);
let force_all = if metadata_opts.filter_platforms.is_empty() {
crate::core::resolver::features::ForceAllTargets::Yes
} else {
@ -133,7 +125,7 @@ fn build_resolve_graph(
ws,
&target_data,
&requested_kinds,
&resolve_opts,
&metadata_opts.cli_features,
&specs,
HasDevUnits::Yes,
force_all,

View file

@ -12,6 +12,7 @@ use log::debug;
use tar::{Archive, Builder, EntryType, Header, HeaderMode};
use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor};
use crate::core::resolver::CliFeatures;
use crate::core::{Feature, Shell, Verbosity, Workspace};
use crate::core::{Package, PackageId, PackageSet, Resolve, Source, SourceId};
use crate::sources::PathSource;
@ -29,9 +30,7 @@ pub struct PackageOpts<'cfg> {
pub verify: bool,
pub jobs: Option<u32>,
pub targets: Vec<String>,
pub features: Vec<String>,
pub all_features: bool,
pub no_default_features: bool,
pub cli_features: CliFeatures,
}
const VCS_INFO_FILE: &str = ".cargo_vcs_info.json";
@ -690,9 +689,7 @@ fn run_verify(ws: &Workspace<'_>, tar: &FileLock, opts: &PackageOpts<'_>) -> Car
&ws,
&ops::CompileOptions {
build_config: BuildConfig::new(config, opts.jobs, &opts.targets, CompileMode::Build)?,
features: opts.features.clone(),
no_default_features: opts.no_default_features,
all_features: opts.all_features,
cli_features: opts.cli_features.clone(),
spec: ops::Packages::Packages(Vec::new()),
filter: ops::CompileFilter::Default {
required_features_filterable: true,

View file

@ -3,12 +3,13 @@ use std::env;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use anyhow::{bail, format_err};
use serde::{Deserialize, Serialize};
use crate::core::compiler::Freshness;
use crate::core::{Dependency, Package, PackageId, Source, SourceId};
use crate::core::{Dependency, FeatureValue, Package, PackageId, Source, SourceId};
use crate::ops::{self, CompileFilter, CompileOptions};
use crate::sources::PathSource;
use crate::util::errors::{CargoResult, CargoResultExt};
@ -422,9 +423,9 @@ impl CrateListingV2 {
if let Some(info) = self.installs.get_mut(&pkg.package_id()) {
info.bins.append(&mut bins.clone());
info.version_req = version_req;
info.features = feature_set(&opts.features);
info.all_features = opts.all_features;
info.no_default_features = opts.no_default_features;
info.features = feature_set(&opts.cli_features.features);
info.all_features = opts.cli_features.all_features;
info.no_default_features = !opts.cli_features.uses_default_features;
info.profile = opts.build_config.requested_profile.to_string();
info.target = Some(target.to_string());
info.rustc = Some(rustc.to_string());
@ -434,9 +435,9 @@ impl CrateListingV2 {
InstallInfo {
version_req,
bins: bins.clone(),
features: feature_set(&opts.features),
all_features: opts.all_features,
no_default_features: opts.no_default_features,
features: feature_set(&opts.cli_features.features),
all_features: opts.cli_features.all_features,
no_default_features: !opts.cli_features.uses_default_features,
profile: opts.build_config.requested_profile.to_string(),
target: Some(target.to_string()),
rustc: Some(rustc.to_string()),
@ -489,9 +490,9 @@ impl InstallInfo {
///
/// This does not do Package/Source/Version checking.
fn is_up_to_date(&self, opts: &CompileOptions, target: &str, exes: &BTreeSet<String>) -> bool {
self.features == feature_set(&opts.features)
&& self.all_features == opts.all_features
&& self.no_default_features == opts.no_default_features
self.features == feature_set(&opts.cli_features.features)
&& self.all_features == opts.cli_features.all_features
&& self.no_default_features == !opts.cli_features.uses_default_features
&& self.profile.as_str() == opts.build_config.requested_profile.as_str()
&& (self.target.is_none() || self.target.as_deref() == Some(target))
&& &self.bins == exes
@ -641,9 +642,9 @@ where
}
}
/// Helper to convert features Vec to a BTreeSet.
fn feature_set(features: &[String]) -> BTreeSet<String> {
features.iter().cloned().collect()
/// Helper to convert features to a BTreeSet.
fn feature_set(features: &Rc<BTreeSet<FeatureValue>>) -> BTreeSet<String> {
features.iter().map(|s| s.to_string()).collect()
}
/// Helper to get the executable names from a filter.

View file

@ -51,8 +51,8 @@ use rustfix::diagnostics::Diagnostic;
use rustfix::{self, CodeFix};
use crate::core::compiler::RustcTargetData;
use crate::core::resolver::features::{FeatureOpts, FeatureResolver, RequestedFeatures};
use crate::core::resolver::{HasDevUnits, ResolveBehavior, ResolveOpts};
use crate::core::resolver::features::{FeatureOpts, FeatureResolver};
use crate::core::resolver::{HasDevUnits, ResolveBehavior};
use crate::core::{Edition, MaybePackage, Workspace};
use crate::ops::{self, CompileOptions};
use crate::util::diagnostic_server::{Message, RustfixDiagnosticServer};
@ -227,14 +227,6 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<(
// 2018 without `resolver` set must be V1
assert_eq!(ws.resolve_behavior(), ResolveBehavior::V1);
let specs = opts.compile_opts.spec.to_package_id_specs(ws)?;
let resolve_opts = ResolveOpts::new(
/*dev_deps*/ true,
RequestedFeatures::from_command_line(
&opts.compile_opts.features,
opts.compile_opts.all_features,
!opts.compile_opts.no_default_features,
),
);
let target_data = RustcTargetData::new(ws, &opts.compile_opts.build_config.requested_kinds)?;
// HasDevUnits::No because that may uncover more differences.
// This is not the same as what `cargo fix` is doing, since it is doing
@ -243,7 +235,7 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<(
ws,
&target_data,
&opts.compile_opts.build_config.requested_kinds,
&resolve_opts,
&opts.compile_opts.cli_features,
&specs,
HasDevUnits::No,
crate::core::resolver::features::ForceAllTargets::No,
@ -255,7 +247,7 @@ fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<(
&target_data,
&ws_resolve.targeted_resolve,
&ws_resolve.pkg_set,
&resolve_opts.features,
&opts.compile_opts.cli_features,
&specs,
&opts.compile_opts.build_config.requested_kinds,
feature_opts,

View file

@ -15,6 +15,7 @@ use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use crate::core::dependency::DepKind;
use crate::core::manifest::ManifestMetadata;
use crate::core::resolver::CliFeatures;
use crate::core::source::Source;
use crate::core::{Package, SourceId, Workspace};
use crate::ops;
@ -51,9 +52,7 @@ pub struct PublishOpts<'cfg> {
pub targets: Vec<String>,
pub dry_run: bool,
pub registry: Option<String>,
pub features: Vec<String>,
pub all_features: bool,
pub no_default_features: bool,
pub cli_features: CliFeatures,
}
pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
@ -111,9 +110,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
allow_dirty: opts.allow_dirty,
targets: opts.targets.clone(),
jobs: opts.jobs,
features: opts.features.clone(),
all_features: opts.all_features,
no_default_features: opts.no_default_features,
cli_features: opts.cli_features.clone(),
},
)?
.unwrap();

View file

@ -13,7 +13,7 @@
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::registry::PackageRegistry;
use crate::core::resolver::features::{
FeatureOpts, FeatureResolver, ForceAllTargets, ResolvedFeatures,
CliFeatures, FeatureOpts, FeatureResolver, ForceAllTargets, RequestedFeatures, ResolvedFeatures,
};
use crate::core::resolver::{self, HasDevUnits, Resolve, ResolveOpts, ResolveVersion};
use crate::core::summary::Summary;
@ -80,7 +80,7 @@ pub fn resolve_ws_with_opts<'cfg>(
ws: &Workspace<'cfg>,
target_data: &RustcTargetData,
requested_targets: &[CompileKind],
opts: &ResolveOpts,
cli_features: &CliFeatures,
specs: &[PackageIdSpec],
has_dev_units: HasDevUnits,
force_all_targets: ForceAllTargets,
@ -122,7 +122,8 @@ pub fn resolve_ws_with_opts<'cfg>(
let resolved_with_overrides = resolve_with_previous(
&mut registry,
ws,
opts,
cli_features,
has_dev_units,
resolve.as_ref(),
None,
specs,
@ -132,7 +133,7 @@ pub fn resolve_ws_with_opts<'cfg>(
let pkg_set = get_resolved_packages(&resolved_with_overrides, registry)?;
let member_ids = ws
.members_with_features(specs, &opts.features)?
.members_with_features(specs, cli_features)?
.into_iter()
.map(|(p, _fts)| p.package_id())
.collect::<Vec<_>>();
@ -151,7 +152,7 @@ pub fn resolve_ws_with_opts<'cfg>(
target_data,
&resolved_with_overrides,
&pkg_set,
&opts.features,
cli_features,
specs,
requested_targets,
feature_opts,
@ -173,7 +174,8 @@ fn resolve_with_registry<'cfg>(
let mut resolve = resolve_with_previous(
registry,
ws,
&ResolveOpts::everything(),
&CliFeatures::new_all(true),
HasDevUnits::Yes,
prev.as_ref(),
None,
&[],
@ -204,7 +206,8 @@ fn resolve_with_registry<'cfg>(
pub fn resolve_with_previous<'cfg>(
registry: &mut PackageRegistry<'cfg>,
ws: &Workspace<'cfg>,
opts: &ResolveOpts,
cli_features: &CliFeatures,
has_dev_units: HasDevUnits,
previous: Option<&Resolve>,
to_avoid: Option<&HashSet<PackageId>>,
specs: &[PackageIdSpec],
@ -316,16 +319,17 @@ pub fn resolve_with_previous<'cfg>(
registry.add_sources(Some(member.package_id().source_id()))?;
}
let dev_deps = ws.require_optional_deps() || has_dev_units == HasDevUnits::Yes;
let summaries: Vec<(Summary, ResolveOpts)> = ws
.members_with_features(specs, &opts.features)?
.members_with_features(specs, cli_features)?
.into_iter()
.map(|(member, features)| {
let summary = registry.lock(member.summary().clone());
(
summary,
ResolveOpts {
dev_deps: opts.dev_deps,
features,
dev_deps,
features: RequestedFeatures::CliFeatures(features),
},
)
})

View file

@ -3,7 +3,7 @@
use super::TreeOptions;
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind;
use crate::core::resolver::features::{FeaturesFor, RequestedFeatures, ResolvedFeatures};
use crate::core::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures};
use crate::core::resolver::Resolve;
use crate::core::{FeatureMap, FeatureValue, Package, PackageId, PackageIdSpec, Workspace};
use crate::util::interning::InternedString;
@ -248,16 +248,16 @@ pub fn build<'a>(
resolve: &Resolve,
resolved_features: &ResolvedFeatures,
specs: &[PackageIdSpec],
requested_features: &RequestedFeatures,
cli_features: &CliFeatures,
target_data: &RustcTargetData,
requested_kinds: &[CompileKind],
package_map: HashMap<PackageId, &'a Package>,
opts: &TreeOptions,
) -> CargoResult<Graph<'a>> {
let mut graph = Graph::new(package_map);
let mut members_with_features = ws.members_with_features(specs, requested_features)?;
let mut members_with_features = ws.members_with_features(specs, cli_features)?;
members_with_features.sort_unstable_by_key(|e| e.0.package_id());
for (member, requested_features) in members_with_features {
for (member, cli_features) in members_with_features {
let member_id = member.package_id();
let features_for = FeaturesFor::from_for_host(member.proc_macro());
for kind in requested_kinds {
@ -273,7 +273,7 @@ pub fn build<'a>(
);
if opts.graph_features {
let fmap = resolve.summary(member_id).features();
add_cli_features(&mut graph, member_index, &requested_features, fmap);
add_cli_features(&mut graph, member_index, &cli_features, fmap);
}
}
}
@ -392,7 +392,7 @@ fn add_pkg(
EdgeKind::Dep(dep.kind()),
);
}
for feature in dep.features() {
for feature in dep.features().iter() {
add_feature(
graph,
*feature,
@ -459,48 +459,66 @@ fn add_feature(
fn add_cli_features(
graph: &mut Graph<'_>,
package_index: usize,
requested_features: &RequestedFeatures,
cli_features: &CliFeatures,
feature_map: &FeatureMap,
) {
// NOTE: Recursive enabling of features will be handled by
// add_internal_features.
// Create a list of feature names requested on the command-line.
let mut to_add: Vec<InternedString> = Vec::new();
if requested_features.all_features {
to_add.extend(feature_map.keys().copied());
// Add optional deps.
for (dep_name, deps) in &graph.dep_name_map[&package_index] {
if deps.iter().any(|(_idx, is_optional)| *is_optional) {
to_add.push(*dep_name);
}
}
// Create a set of feature names requested on the command-line.
let mut to_add: HashSet<FeatureValue> = HashSet::new();
if cli_features.all_features {
to_add.extend(feature_map.keys().map(|feat| FeatureValue::Feature(*feat)));
} else {
if requested_features.uses_default_features {
to_add.push(InternedString::new("default"));
if cli_features.uses_default_features {
to_add.insert(FeatureValue::Feature(InternedString::new("default")));
}
to_add.extend(requested_features.features.iter().copied());
to_add.extend(cli_features.features.iter().cloned());
};
// Add each feature as a node, and mark as "from command-line" in graph.cli_features.
for name in to_add {
if name.contains('/') {
let mut parts = name.splitn(2, '/');
let dep_name = InternedString::new(parts.next().unwrap());
let feat_name = InternedString::new(parts.next().unwrap());
for (dep_index, is_optional) in graph.dep_name_map[&package_index][&dep_name].clone() {
if is_optional {
// Activate the optional dep on self.
let index =
add_feature(graph, dep_name, None, package_index, EdgeKind::Feature);
graph.cli_features.insert(index);
}
let index = add_feature(graph, feat_name, None, dep_index, EdgeKind::Feature);
for fv in to_add {
match fv {
FeatureValue::Feature(feature) => {
let index = add_feature(graph, feature, None, package_index, EdgeKind::Feature);
graph.cli_features.insert(index);
}
} else {
let index = add_feature(graph, name, None, package_index, EdgeKind::Feature);
graph.cli_features.insert(index);
// This is enforced by CliFeatures.
FeatureValue::Dep { .. } => panic!("unexpected cli dep feature {}", fv),
FeatureValue::DepFeature {
dep_name,
dep_feature,
dep_prefix: _,
weak,
} => {
let dep_connections = match graph.dep_name_map[&package_index].get(&dep_name) {
// Clone to deal with immutable borrow of `graph`. :(
Some(dep_connections) => dep_connections.clone(),
None => {
// --features bar?/feat where `bar` is not activated should be ignored.
// If this wasn't weak, then this is a bug.
if weak {
continue;
}
panic!(
"missing dep graph connection for CLI feature `{}` for member {:?}\n\
Please file a bug report at https://github.com/rust-lang/cargo/issues",
fv,
graph.nodes.get(package_index)
);
}
};
for (dep_index, is_optional) in dep_connections {
if is_optional {
// Activate the optional dep on self.
let index =
add_feature(graph, dep_name, None, package_index, EdgeKind::Feature);
graph.cli_features.insert(index);
}
let index = add_feature(graph, dep_feature, None, dep_index, EdgeKind::Feature);
graph.cli_features.insert(index);
}
}
}
}
}
@ -570,6 +588,10 @@ fn add_feature_rec(
package_index,
);
}
// Dependencies are already shown in the graph as dep edges. I'm
// uncertain whether or not this might be confusing in some cases
// (like feature `"somefeat" = ["dep:somedep"]`), so maybe in the
// future consider explicitly showing this?
FeatureValue::Dep { .. } => {}
FeatureValue::DepFeature {
dep_name,

View file

@ -3,9 +3,7 @@
use self::format::Pattern;
use crate::core::compiler::{CompileKind, RustcTargetData};
use crate::core::dependency::DepKind;
use crate::core::resolver::{
features::RequestedFeatures, ForceAllTargets, HasDevUnits, ResolveOpts,
};
use crate::core::resolver::{features::CliFeatures, ForceAllTargets, HasDevUnits};
use crate::core::{Package, PackageId, PackageIdSpec, Workspace};
use crate::ops::{self, Packages};
use crate::util::{CargoResult, Config};
@ -21,9 +19,7 @@ mod graph;
pub use {graph::EdgeKind, graph::Node};
pub struct TreeOptions {
pub features: Vec<String>,
pub no_default_features: bool,
pub all_features: bool,
pub cli_features: CliFeatures,
/// The packages to display the tree for.
pub packages: Packages,
/// The platform to filter for.
@ -138,12 +134,6 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
let requested_kinds = CompileKind::from_requested_targets(ws.config(), &requested_targets)?;
let target_data = RustcTargetData::new(ws, &requested_kinds)?;
let specs = opts.packages.to_package_id_specs(ws)?;
let requested_features = RequestedFeatures::from_command_line(
&opts.features,
opts.all_features,
!opts.no_default_features,
);
let resolve_opts = ResolveOpts::new(/*dev_deps*/ true, requested_features);
let has_dev = if opts
.edge_kinds
.contains(&EdgeKind::Dep(DepKind::Development))
@ -161,7 +151,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
ws,
&target_data,
&requested_kinds,
&resolve_opts,
&opts.cli_features,
&specs,
has_dev,
force_all,
@ -178,7 +168,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
&ws_resolve.targeted_resolve,
&ws_resolve.resolved_features,
&specs,
&resolve_opts.features,
&opts.cli_features,
&target_data,
&requested_kinds,
package_map,

View file

@ -1,4 +1,5 @@
use crate::core::compiler::{BuildConfig, MessageFormat};
use crate::core::resolver::CliFeatures;
use crate::core::{Edition, Workspace};
use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl};
use crate::sources::CRATES_IO_REGISTRY;
@ -495,9 +496,7 @@ pub trait ArgMatchesExt {
let opts = CompileOptions {
build_config,
features: self._values_of("features"),
all_features: self._is_present("all-features"),
no_default_features: self._is_present("no-default-features"),
cli_features: self.cli_features()?,
spec,
filter: CompileFilter::from_raw_arguments(
self._is_present("lib"),
@ -539,6 +538,14 @@ pub trait ArgMatchesExt {
Ok(opts)
}
fn cli_features(&self) -> CargoResult<CliFeatures> {
CliFeatures::from_command_line(
&self._values_of("features"),
self._is_present("all-features"),
!self._is_present("no-default-features"),
)
}
fn compile_options_for_single_package(
&self,
config: &Config,

View file

@ -1773,6 +1773,35 @@ impl<P: ResolveToPath> DetailedTomlDependency<P> {
}
}
// Early detection of potentially misused feature syntax
// instead of generating a "feature not found" error.
if let Some(features) = &self.features {
for feature in features {
if feature.contains('/') {
bail!(
"feature `{}` in dependency `{}` is not allowed to contain slashes\n\
If you want to enable features of a transitive dependency, \
the direct dependency needs to re-export those features from \
the `[features]` table.",
feature,
name_in_toml
);
}
if feature.starts_with("dep:") {
bail!(
"feature `{}` in dependency `{}` is not allowed to use explicit \
`dep:` syntax\n\
If you want to enable an optional dependency, specify the name \
of the optional dependency without the `dep:` prefix, or specify \
a feature from the dependency's `[features]` table that enables \
the optional dependency.",
feature,
name_in_toml
);
}
}
}
let new_source_id = match (
self.git.as_ref(),
self.path.as_ref(),

View file

@ -269,7 +269,15 @@ fn invalid8() {
p.cargo("build --features foo")
.with_status(101)
.with_stderr("[ERROR] feature names may not contain slashes: `foo/bar`")
.with_stderr(
"\
error: failed to parse manifest at `[CWD]/Cargo.toml`
Caused by:
feature `foo/bar` in dependency `bar` is not allowed to contain slashes
If you want to enable features [..]
",
)
.run();
}
@ -409,7 +417,14 @@ fn no_transitive_dep_feature_requirement() {
.build();
p.cargo("build")
.with_status(101)
.with_stderr("[ERROR] feature names may not contain slashes: `bar/qux`")
.with_stderr(
"\
error: failed to parse manifest at `[CWD]/Cargo.toml`
Caused by:
multiple slashes in feature `derived/bar/qux` (included by feature `default`) are not allowed
",
)
.run();
}
@ -1192,7 +1207,7 @@ fn dep_feature_in_cmd_line() {
// Hierarchical feature specification should still be disallowed
p.cargo("build --features derived/bar/some-feat")
.with_status(101)
.with_stderr("[ERROR] feature names may not contain slashes: `bar/some-feat`")
.with_stderr("[ERROR] multiple slashes in feature `derived/bar/some-feat` is not allowed")
.run();
}
@ -1906,7 +1921,7 @@ fn nonexistent_required_features() {
}
#[cargo_test]
fn invalid_feature_names() {
fn invalid_feature_names_warning() {
// Warnings for more restricted feature syntax.
let p = project()
.file(
@ -1929,7 +1944,6 @@ fn invalid_feature_names() {
"+foo" = []
"-foo" = []
".foo" = []
"foo/bar" = []
"foo:bar" = []
"foo?" = []
"?foo" = []
@ -1961,9 +1975,6 @@ For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues
[WARNING] invalid character `¼` in feature `a¼` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
This was previously accepted but is being phased out; it will become a hard error in a future release.
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
[WARNING] invalid character `/` in feature `foo/bar` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
This was previously accepted but is being phased out; it will become a hard error in a future release.
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
[WARNING] invalid character `:` in feature `foo:bar` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
This was previously accepted but is being phased out; it will become a hard error in a future release.
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
@ -1994,9 +2005,6 @@ For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues
[WARNING] invalid character `¼` in feature `a¼` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
This was previously accepted but is being phased out; it will become a hard error in a future release.
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
[WARNING] invalid character `/` in feature `foo/bar` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
This was previously accepted but is being phased out; it will become a hard error in a future release.
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
[WARNING] invalid character `:` in feature `foo:bar` in package foo v0.1.0 ([ROOT]/foo), characters must be Unicode XID characters, `+`, or `.` (numbers, `+`, `-`, `_`, `.`, or most letters)
This was previously accepted but is being phased out; it will become a hard error in a future release.
For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues/8813>, and please leave a comment if this will be a problem for your project.
@ -2017,3 +2025,34 @@ For more information, see issue #8813 <https://github.com/rust-lang/cargo/issues
")
.run();
}
#[cargo_test]
fn invalid_feature_names_error() {
// Errors for more restricted feature syntax.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[features]
"foo/bar" = []
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.with_status(101)
.with_stderr(
"\
error: failed to parse manifest at `[CWD]/Cargo.toml`
Caused by:
feature named `foo/bar` is not allowed to contain slashes
",
)
.run();
}

View file

@ -1,5 +1,6 @@
//! Tests for namespaced features.
use super::features2::switch_to_resolver_2;
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{project, publish};
@ -633,8 +634,11 @@ fn crate_syntax_in_dep() {
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] feature value `dep:baz` is not allowed to use explicit `dep:` syntax
error: failed to parse manifest at `[CWD]/Cargo.toml`
Caused by:
feature `dep:baz` in dependency `bar` is not allowed to use explicit `dep:` syntax
If you want to enable [..]
",
)
.run();
@ -664,8 +668,18 @@ fn crate_syntax_cli() {
.with_status(101)
.with_stderr(
"\
[UPDATING] [..]
[ERROR] feature value `dep:bar` is not allowed to use explicit `dep:` syntax
[ERROR] feature `dep:bar` is not allowed to use explicit `dep:` syntax
",
)
.run();
switch_to_resolver_2(&p);
p.cargo("check -Z namespaced-features --features dep:bar")
.masquerade_as_nightly_cargo()
.with_status(101)
.with_stderr(
"\
[ERROR] feature `dep:bar` is not allowed to use explicit `dep:` syntax
",
)
.run();
@ -998,6 +1012,57 @@ bar v1.0.0
.run();
}
#[cargo_test]
fn tree_no_implicit() {
// tree without an implicit feature
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = { version = "1.0", optional=true }
[features]
a = ["dep:bar"]
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("tree -e features -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
p.cargo("tree -e features --all-features -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
foo v0.1.0 ([ROOT]/foo)
bar feature \"default\"
bar v1.0.0
",
)
.run();
p.cargo("tree -e features -i bar --all-features -Z namespaced-features")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
bar v1.0.0
bar feature \"default\"
foo v0.1.0 ([ROOT]/foo)
foo feature \"a\" (command-line)
",
)
.run();
}
#[cargo_test]
fn publish_no_implicit() {
// Does not include implicit features or dep: syntax on publish.

View file

@ -1,8 +1,9 @@
//! Tests for feature selection on the command-line.
use super::features2::switch_to_resolver_2;
use cargo_test_support::registry::Package;
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{basic_manifest, project};
use std::fmt::Write;
#[cargo_test]
fn virtual_no_default_features() {
@ -460,32 +461,162 @@ fn resolver1_member_features() {
}
#[cargo_test]
fn resolver1_non_member_optional_feature() {
// --features x/y for an optional dependency `x` with the v1 resolver.
fn non_member_feature() {
// --features for a non-member
Package::new("jazz", "1.0.0").publish();
Package::new("bar", "1.0.0")
.feature("feat1", &[])
.file(
"src/lib.rs",
r#"
#[cfg(not(feature = "feat1"))]
compile_error!("feat1 should be activated");
"#,
)
.add_dep(Dependency::new("jazz", "1.0").optional(true))
.publish();
let p = project()
.file(
"Cargo.toml",
let make_toml = |resolver, optional| {
let mut s = String::new();
write!(
s,
r#"
[package]
name = "foo"
version = "0.1.0"
resolver = "{}"
[dependencies]
bar = { version="1.0", optional=true }
"#,
resolver
)
.unwrap();
if optional {
s.push_str(r#"bar = { version = "1.0", optional = true } "#);
} else {
s.push_str(r#"bar = "1.0""#)
}
s.push('\n');
s
};
let p = project()
.file("Cargo.toml", &make_toml("1", false))
.file("src/lib.rs", "")
.build();
p.cargo("fetch").run();
///////////////////////// V1 non-optional
eprintln!("V1 non-optional");
p.cargo("check -p bar")
.with_stderr(
"\
[CHECKING] bar v1.0.0
[FINISHED] [..]
",
)
.run();
// TODO: This should not be allowed (future warning?)
p.cargo("check --features bar/jazz")
.with_stderr(
"\
[DOWNLOADING] crates ...
[DOWNLOADED] jazz v1.0.0 [..]
[CHECKING] jazz v1.0.0
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
// TODO: This should not be allowed (future warning?)
p.cargo("check -p bar --features bar/jazz -v")
.with_stderr(
"\
[FRESH] jazz v1.0.0
[FRESH] bar v1.0.0
[FINISHED] [..]
",
)
.run();
p.cargo("check -p bar --features bar/feat1").run();
///////////////////////// V1 optional
eprintln!("V1 optional");
p.change_file("Cargo.toml", &make_toml("1", true));
// This error isn't great, but is probably unlikely to be common in
// practice, so I'm not going to put much effort into improving it.
p.cargo("check -p bar")
.with_status(101)
.with_stderr(
"\
error: package ID specification `bar` did not match any packages
<tab>Did you mean `foo`?
",
)
.run();
p.cargo("check -p bar --features bar -v")
.with_stderr(
"\
[FRESH] bar v1.0.0
[FINISHED] [..]
",
)
.run();
// TODO: This should not be allowed (future warning?)
p.cargo("check -p bar --features bar/jazz -v")
.with_stderr(
"\
[FRESH] jazz v1.0.0
[FRESH] bar v1.0.0
[FINISHED] [..]
",
)
.run();
///////////////////////// V2 non-optional
eprintln!("V2 non-optional");
p.change_file("Cargo.toml", &make_toml("2", false));
// TODO: This should not be allowed (future warning?)
p.cargo("check --features bar/jazz -v")
.with_stderr(
"\
[FRESH] jazz v1.0.0
[FRESH] bar v1.0.0
[FRESH] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
p.cargo("check -p bar -v")
.with_stderr(
"\
[FRESH] bar v1.0.0
[FINISHED] [..]
",
)
.run();
p.cargo("check -p bar --features bar/jazz")
.with_status(101)
.with_stderr("error: cannot specify features for packages outside of workspace")
.run();
///////////////////////// V2 optional
eprintln!("V2 optional");
p.change_file("Cargo.toml", &make_toml("2", true));
p.cargo("check -p bar")
.with_status(101)
.with_stderr(
"\
error: package ID specification `bar` did not match any packages
<tab>Did you mean `foo`?
",
)
.run();
// New --features behavior does not look at cwd.
p.cargo("check -p bar --features bar")
.with_status(101)
.with_stderr("error: cannot specify features for packages outside of workspace")
.run();
p.cargo("check -p bar --features bar/jazz")
.with_status(101)
.with_stderr("error: cannot specify features for packages outside of workspace")
.run();
p.cargo("check -p bar --features foo/bar")
.with_status(101)
.with_stderr("error: cannot specify features for packages outside of workspace")
.run();
}

View file

@ -1,5 +1,7 @@
//! Tests for weak-dep-features.
use super::features2::switch_to_resolver_2;
use cargo_test_support::paths::CargoPathExt;
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{project, publish};
use std::fmt::Write;
@ -272,6 +274,7 @@ fn optional_cli_syntax() {
.file("src/lib.rs", "")
.build();
// Does not build bar.
p.cargo("check --features bar?/feat -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
@ -285,6 +288,33 @@ fn optional_cli_syntax() {
)
.run();
// Builds bar.
p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[CHECKING] bar v1.0.0
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
eprintln!("check V2 resolver");
switch_to_resolver_2(&p);
p.build_dir().rm_rf();
// Does not build bar.
p.cargo("check --features bar?/feat -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
"\
[CHECKING] foo v0.1.0 [..]
[FINISHED] [..]
",
)
.run();
// Builds bar.
p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
.masquerade_as_nightly_cargo()
.with_stderr(
@ -561,6 +591,32 @@ bar v1.0.0
foo feature \"f1\" (command-line)
bar feature \"feat\"
foo feature \"f1\" (command-line)
",
)
.run();
p.cargo("tree -Z weak-dep-features -e features --features bar?/feat")
.masquerade_as_nightly_cargo()
.with_stdout("foo v0.1.0 ([ROOT]/foo)")
.run();
// This is a little strange in that it produces no output.
// Maybe `cargo tree` should print a note about why?
p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat")
.masquerade_as_nightly_cargo()
.with_stdout("")
.run();
p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat,bar")
.masquerade_as_nightly_cargo()
.with_stdout(
"\
bar v1.0.0
bar feature \"default\"
foo v0.1.0 ([ROOT]/foo)
foo feature \"bar\" (command-line)
foo feature \"default\" (command-line)
bar feature \"feat\" (command-line)
",
)
.run();