Skip to content

Commit d5556ae

Browse files
committed
Auto merge of #8818 - ehuss:weak-dep-features, r=alexcrichton
Implement weak dependency features. This adds the feature syntax `dep_name?/feat_name` with a `?` to only enable `feat_name` if the optional dependency `dep_name` is enabled through some other means. See `unstable.md` for documentation. This only works with the new feature resolver. I don't think I understand the dependency resolver well enough to implement it there. It would require teaching it to defer activating a feature, but due to the backtracking nature, I don't really know how to accomplish that. I don't think it matters, the main drawback is that the dependency resolver will be slightly more constrained, but in practice I doubt it will ever matter. Closes #3494 **Question** * An alternate syntax I considered was `dep_name?feat_name` (without the slash), what do people think? For some reason the `?/` seems kinda awkward to me.
2 parents 862df61 + 9ffcf69 commit d5556ae

File tree

12 files changed

+809
-59
lines changed

12 files changed

+809
-59
lines changed

src/bin/cargo/cli.rs

+1
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

+2
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

+4
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

+145-48
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);
@@ -378,7 +392,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
378392
for (member, requested_features) in &member_features {
379393
let fvs = self.fvs_from_requested(member.package_id(), requested_features);
380394
let for_host = self.track_for_host && self.is_proc_macro(member.package_id());
381-
self.activate_pkg(member.package_id(), &fvs, for_host)?;
395+
self.activate_pkg(member.package_id(), for_host, &fvs)?;
382396
if for_host {
383397
// Also activate without for_host. This is needed if the
384398
// proc-macro includes other targets (like binaries or tests),
@@ -387,7 +401,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
387401
// `--workspace`), this forces feature unification with normal
388402
// dependencies. This is part of the bigger problem where
389403
// features depend on which packages are built.
390-
self.activate_pkg(member.package_id(), &fvs, false)?;
404+
self.activate_pkg(member.package_id(), false, &fvs)?;
391405
}
392406
}
393407
Ok(())
@@ -396,17 +410,18 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
396410
fn activate_pkg(
397411
&mut self,
398412
pkg_id: PackageId,
399-
fvs: &[FeatureValue],
400413
for_host: bool,
414+
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.
405420
self.activated_features
406421
.entry((pkg_id, self.opts.decouple_host_deps && for_host))
407422
.or_insert_with(BTreeSet::new);
408423
for fv in fvs {
409-
self.activate_fv(pkg_id, fv, for_host)?;
424+
self.activate_fv(pkg_id, for_host, fv)?;
410425
}
411426
if !self.processed_deps.insert((pkg_id, for_host)) {
412427
// Already processed dependencies. There's no need to process them
@@ -433,7 +448,7 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
433448
}
434449
// Recurse into the dependency.
435450
let fvs = self.fvs_from_dependency(dep_pkg_id, dep);
436-
self.activate_pkg(dep_pkg_id, &fvs, dep_for_host)?;
451+
self.activate_pkg(dep_pkg_id, dep_for_host, &fvs)?;
437452
}
438453
}
439454
Ok(())
@@ -443,59 +458,31 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
443458
fn activate_fv(
444459
&mut self,
445460
pkg_id: PackageId,
446-
fv: &FeatureValue,
447461
for_host: bool,
462+
fv: &FeatureValue,
448463
) -> CargoResult<()> {
464+
log::trace!("activate_fv {} {} {}", pkg_id.name(), for_host, fv);
449465
match fv {
450466
FeatureValue::Feature(f) => {
451-
self.activate_rec(pkg_id, *f, for_host)?;
467+
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, &fvs, dep_for_host)?;
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, &fv, for_host)?;
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, *dep_name, for_host)?;
492-
}
493-
}
494-
// Activate the feature on the dependency.
495-
let fv = FeatureValue::new(*dep_feature);
496-
self.activate_fv(dep_pkg_id, &fv, dep_for_host)?;
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(())
@@ -506,9 +493,15 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
506493
fn activate_rec(
507494
&mut self,
508495
pkg_id: PackageId,
509-
feature_to_enable: InternedString,
510496
for_host: bool,
497+
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))
@@ -534,7 +527,111 @@ impl<'a, 'cfg> FeatureResolver<'a, 'cfg> {
534527
}
535528
};
536529
for fv in fvs {
537-
self.activate_fv(pkg_id, fv, for_host)?;
530+
self.activate_fv(pkg_id, for_host, fv)?;
531+
}
532+
Ok(())
533+
}
534+
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+
}
538635
}
539636
Ok(())
540637
}

0 commit comments

Comments
 (0)