Skip to content

Commit 9ffcf69

Browse files
committed
Implement weak dependency features.
1 parent 28af36f commit 9ffcf69

File tree

12 files changed

+800
-50
lines changed

12 files changed

+800
-50
lines changed

src/bin/cargo/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Available unstable (nightly-only) flags:
4343
-Z doctest-xcompile -- Compile and run doctests for non-host target using runner config
4444
-Z terminal-width -- Provide a terminal width to rustc for error truncation
4545
-Z namespaced-features -- Allow features with `dep:` prefix
46+
-Z weak-dep-features -- Allow `dep_name?/feature` feature syntax
4647
4748
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
4849
);

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ pub struct CliUnstable {
358358
pub rustdoc_map: bool,
359359
pub terminal_width: Option<Option<usize>>,
360360
pub namespaced_features: bool,
361+
pub weak_dep_features: bool,
361362
}
362363

363364
fn deserialize_build_std<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
@@ -464,6 +465,7 @@ impl CliUnstable {
464465
"rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?,
465466
"terminal-width" => self.terminal_width = Some(parse_usize_opt(v)?),
466467
"namespaced-features" => self.namespaced_features = parse_empty(k, v)?,
468+
"weak-dep-features" => self.weak_dep_features = parse_empty(k, v)?,
467469
_ => bail!("unknown `-Z` flag specified: {}", k),
468470
}
469471

src/cargo/core/resolver/dep_cache.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,10 @@ impl Requirements<'_> {
494494
dep_name,
495495
dep_feature,
496496
dep_prefix,
497+
// Weak features are always activated in the dependency
498+
// resolver. They will be narrowed inside the new feature
499+
// resolver.
500+
weak: _,
497501
} => self.require_dep_feature(*dep_name, *dep_feature, *dep_prefix)?,
498502
};
499503
Ok(())

src/cargo/core/resolver/features.rs

Lines changed: 136 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ impl FeatureOpts {
177177
if let ForceAllTargets::Yes = force_all_targets {
178178
opts.ignore_inactive_targets = false;
179179
}
180+
if unstable_flags.weak_dep_features {
181+
// Force this ON because it only works with the new resolver.
182+
opts.new_resolver = true;
183+
}
180184
Ok(opts)
181185
}
182186
}
@@ -311,6 +315,15 @@ pub struct FeatureResolver<'a, 'cfg> {
311315
/// This has to be separate from `FeatureOpts.decouple_host_deps` because
312316
/// `for_host` tracking is also needed for `itarget` to work properly.
313317
track_for_host: bool,
318+
/// `dep_name?/feat_name` features that will be activated if `dep_name` is
319+
/// ever activated.
320+
///
321+
/// The key is the `(package, for_host, dep_name)` of the package whose
322+
/// dependency will trigger the addition of new features. The value is the
323+
/// set of `(feature, dep_prefix)` features to activate (`dep_prefix` is a
324+
/// bool that indicates if `dep:` prefix was used).
325+
deferred_weak_dependencies:
326+
HashMap<(PackageId, bool, InternedString), HashSet<(InternedString, bool)>>,
314327
}
315328

316329
impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
@@ -353,6 +366,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
353366
activated_dependencies: HashMap::new(),
354367
processed_deps: HashSet::new(),
355368
track_for_host,
369+
deferred_weak_dependencies: HashMap::new(),
356370
};
357371
r.do_resolve(specs, requested_features)?;
358372
log::debug!("features={:#?}", r.activated_features);
@@ -399,6 +413,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
399413
for_host: bool,
400414
fvs: &[FeatureValue],
401415
) -> CargoResult<()> {
416+
log::trace!("activate_pkg {} {}", pkg_id.name(), for_host);
402417
// Add an empty entry to ensure everything is covered. This is intended for
403418
// finding bugs where the resolver missed something it should have visited.
404419
// Remove this in the future if `activated_features` uses an empty default.
@@ -446,56 +461,28 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
446461
for_host: bool,
447462
fv: &FeatureValue,
448463
) -> CargoResult<()> {
464+
log::trace!("activate_fv {} {} {}", pkg_id.name(), for_host, fv);
449465
match fv {
450466
FeatureValue::Feature(f) => {
451467
self.activate_rec(pkg_id, for_host, *f)?;
452468
}
453469
FeatureValue::Dep { dep_name } => {
454-
// Mark this dependency as activated.
455-
self.activated_dependencies
456-
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
457-
.or_default()
458-
.insert(*dep_name);
459-
// Activate the optional dep.
460-
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
461-
for (dep, dep_for_host) in deps {
462-
if dep.name_in_toml() != *dep_name {
463-
continue;
464-
}
465-
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
466-
self.activate_pkg(dep_pkg_id, dep_for_host, &fvs)?;
467-
}
468-
}
470+
self.activate_dependency(pkg_id, for_host, *dep_name)?;
469471
}
470472
FeatureValue::DepFeature {
471473
dep_name,
472474
dep_feature,
473475
dep_prefix,
476+
weak,
474477
} => {
475-
// Activate a feature within a dependency.
476-
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
477-
for (dep, dep_for_host) in deps {
478-
if dep.name_in_toml() != *dep_name {
479-
continue;
480-
}
481-
if dep.is_optional() {
482-
// Activate the dependency on self.
483-
let fv = FeatureValue::Dep {
484-
dep_name: *dep_name,
485-
};
486-
self.activate_fv(pkg_id, for_host, &fv)?;
487-
if !dep_prefix {
488-
// To retain compatibility with old behavior,
489-
// this also enables a feature of the same
490-
// name.
491-
self.activate_rec(pkg_id, for_host, *dep_name)?;
492-
}
493-
}
494-
// Activate the feature on the dependency.
495-
let fv = FeatureValue::new(*dep_feature);
496-
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
497-
}
498-
}
478+
self.activate_dep_feature(
479+
pkg_id,
480+
for_host,
481+
*dep_name,
482+
*dep_feature,
483+
*dep_prefix,
484+
*weak,
485+
)?;
499486
}
500487
}
501488
Ok(())
@@ -509,6 +496,12 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
509496
for_host: bool,
510497
feature_to_enable: InternedString,
511498
) -> CargoResult<()> {
499+
log::trace!(
500+
"activate_rec {} {} feat={}",
501+
pkg_id.name(),
502+
for_host,
503+
feature_to_enable
504+
);
512505
let enabled = self
513506
.activated_features
514507
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
@@ -539,6 +532,110 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
539532
Ok(())
540533
}
541534

535+
/// Activate a dependency (`dep:dep_name` syntax).
536+
fn activate_dependency(
537+
&mut self,
538+
pkg_id: PackageId,
539+
for_host: bool,
540+
dep_name: InternedString,
541+
) -> CargoResult<()> {
542+
// Mark this dependency as activated.
543+
let save_for_host = self.opts.decouple_host_deps && for_host;
544+
self.activated_dependencies
545+
.entry((pkg_id, save_for_host))
546+
.or_default()
547+
.insert(dep_name);
548+
// Check for any deferred features.
549+
let to_enable = self
550+
.deferred_weak_dependencies
551+
.remove(&(pkg_id, for_host, dep_name));
552+
// Activate the optional dep.
553+
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
554+
for (dep, dep_for_host) in deps {
555+
if dep.name_in_toml() != dep_name {
556+
continue;
557+
}
558+
if let Some(to_enable) = &to_enable {
559+
for (dep_feature, dep_prefix) in to_enable {
560+
log::trace!(
561+
"activate deferred {} {} -> {}/{}",
562+
pkg_id.name(),
563+
for_host,
564+
dep_name,
565+
dep_feature
566+
);
567+
if !dep_prefix {
568+
self.activate_rec(pkg_id, for_host, dep_name)?;
569+
}
570+
let fv = FeatureValue::new(*dep_feature);
571+
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
572+
}
573+
}
574+
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
575+
self.activate_pkg(dep_pkg_id, dep_for_host, &fvs)?;
576+
}
577+
}
578+
Ok(())
579+
}
580+
581+
/// Activate a feature within a dependency (`dep_name/feat_name` syntax).
582+
fn activate_dep_feature(
583+
&mut self,
584+
pkg_id: PackageId,
585+
for_host: bool,
586+
dep_name: InternedString,
587+
dep_feature: InternedString,
588+
dep_prefix: bool,
589+
weak: bool,
590+
) -> CargoResult<()> {
591+
for (dep_pkg_id, deps) in self.deps(pkg_id, for_host) {
592+
for (dep, dep_for_host) in deps {
593+
if dep.name_in_toml() != dep_name {
594+
continue;
595+
}
596+
if dep.is_optional() {
597+
let save_for_host = self.opts.decouple_host_deps && for_host;
598+
if weak
599+
&& !self
600+
.activated_dependencies
601+
.get(&(pkg_id, save_for_host))
602+
.map(|deps| deps.contains(&dep_name))
603+
.unwrap_or(false)
604+
{
605+
// This is weak, but not yet activated. Defer in case
606+
// something comes along later and enables it.
607+
log::trace!(
608+
"deferring feature {} {} -> {}/{}",
609+
pkg_id.name(),
610+
for_host,
611+
dep_name,
612+
dep_feature
613+
);
614+
self.deferred_weak_dependencies
615+
.entry((pkg_id, for_host, dep_name))
616+
.or_default()
617+
.insert((dep_feature, dep_prefix));
618+
continue;
619+
}
620+
621+
// Activate the dependency on self.
622+
let fv = FeatureValue::Dep { dep_name };
623+
self.activate_fv(pkg_id, for_host, &fv)?;
624+
if !dep_prefix {
625+
// To retain compatibility with old behavior,
626+
// this also enables a feature of the same
627+
// name.
628+
self.activate_rec(pkg_id, for_host, dep_name)?;
629+
}
630+
}
631+
// Activate the feature on the dependency.
632+
let fv = FeatureValue::new(dep_feature);
633+
self.activate_fv(dep_pkg_id, dep_for_host, &fv)?;
634+
}
635+
}
636+
Ok(())
637+
}
638+
542639
/// Returns Vec of FeatureValues from a Dependency definition.
543640
fn fvs_from_dependency(&self, dep_id: PackageId, dep: &Dependency) -> Vec<FeatureValue> {
544641
let summary = self.resolve.summary(dep_id);

src/cargo/core/summary.rs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ impl Summary {
8686

8787
/// Returns an error if this Summary is using an unstable feature that is
8888
/// not enabled.
89-
pub fn unstable_gate(&self, namespaced_features: bool) -> CargoResult<()> {
89+
pub fn unstable_gate(
90+
&self,
91+
namespaced_features: bool,
92+
weak_dep_features: bool,
93+
) -> CargoResult<()> {
9094
if !namespaced_features {
9195
if self.inner.has_namespaced_features {
9296
bail!(
@@ -101,6 +105,22 @@ impl Summary {
101105
)
102106
}
103107
}
108+
if !weak_dep_features {
109+
for (feat_name, features) in self.features() {
110+
for fv in features {
111+
if matches!(fv, FeatureValue::DepFeature{weak: true, ..}) {
112+
bail!(
113+
"optional dependency features with `?` syntax are only \
114+
allowed on the nightly channel and requires the \
115+
`-Z weak-dep-features` flag on the command line\n\
116+
Feature `{}` had feature value `{}`.",
117+
feat_name,
118+
fv
119+
);
120+
}
121+
}
122+
}
123+
}
104124
Ok(())
105125
}
106126

@@ -293,7 +313,7 @@ fn build_feature_map(
293313
);
294314
}
295315
}
296-
DepFeature { dep_name, .. } => {
316+
DepFeature { dep_name, weak, .. } => {
297317
// Validation of the feature name will be performed in the resolver.
298318
if !is_any_dep {
299319
bail!(
@@ -303,6 +323,12 @@ fn build_feature_map(
303323
dep_name
304324
);
305325
}
326+
if *weak && !is_optional_dep {
327+
bail!("feature `{}` includes `{}` with a `?`, but `{}` is not an optional dependency\n\
328+
A non-optional dependency of the same name is defined; \
329+
consider removing the `?` or changing the dependency to be optional",
330+
feature, fv, dep_name);
331+
}
306332
}
307333
}
308334
}
@@ -346,6 +372,10 @@ pub enum FeatureValue {
346372
/// If this is true, then the feature used the `dep:` prefix, which
347373
/// prevents enabling the feature named `dep_name`.
348374
dep_prefix: bool,
375+
/// If `true`, indicates the `?` syntax is used, which means this will
376+
/// not automatically enable the dependency unless the dependency is
377+
/// activated through some other means.
378+
weak: bool,
349379
},
350380
}
351381

@@ -360,10 +390,16 @@ impl FeatureValue {
360390
} else {
361391
(dep, false)
362392
};
393+
let (dep, weak) = if let Some(dep) = dep.strip_suffix('?') {
394+
(dep, true)
395+
} else {
396+
(dep, false)
397+
};
363398
FeatureValue::DepFeature {
364399
dep_name: InternedString::new(dep),
365400
dep_feature: InternedString::new(dep_feat),
366401
dep_prefix,
402+
weak,
367403
}
368404
}
369405
None => {
@@ -393,13 +429,13 @@ impl fmt::Display for FeatureValue {
393429
DepFeature {
394430
dep_name,
395431
dep_feature,
396-
dep_prefix: true,
397-
} => write!(f, "dep:{}/{}", dep_name, dep_feature),
398-
DepFeature {
399-
dep_name,
400-
dep_feature,
401-
dep_prefix: false,
402-
} => write!(f, "{}/{}", dep_name, dep_feature),
432+
dep_prefix,
433+
weak,
434+
} => {
435+
let dep_prefix = if *dep_prefix { "dep:" } else { "" };
436+
let weak = if *weak { "?" } else { "" };
437+
write!(f, "{}{}{}/{}", dep_prefix, dep_name, weak, dep_feature)
438+
}
403439
}
404440
}
405441
}

src/cargo/ops/cargo_compile.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,11 +1082,20 @@ fn validate_required_features(
10821082
target_name
10831083
);
10841084
}
1085+
FeatureValue::DepFeature { weak: true, .. } => {
1086+
anyhow::bail!(
1087+
"invalid feature `{}` in required-features of target `{}`: \
1088+
optional dependency with `?` is not allowed in required-features",
1089+
fv,
1090+
target_name
1091+
);
1092+
}
10851093
// Handling of dependent_crate/dependent_crate_feature syntax
10861094
FeatureValue::DepFeature {
10871095
dep_name,
10881096
dep_feature,
10891097
dep_prefix: false,
1098+
weak: false,
10901099
} => {
10911100
match resolve
10921101
.deps(summary.package_id())

0 commit comments

Comments
 (0)