Skip to content

Commit e416238

Browse files
authored
implement package feature unification (#15684)
### What does this PR try to resolve? Implements another part of feature unification (#14774, [rfc](https://github.com/rust-lang/rfcs/blob/1c590ce05d676e72e2217845ee054758d3a6df34/text/3692-feature-unification.md)). The `workspace` option was implemented in #15157, this adds the `package` option. ### How to test and review this PR? The important change is changing `WorkspaceResolve` so it can contain multiple `ResolvedFeature`s. Along with that, it also needs to know which specs those features are resolved for. This was used in several other places: - `cargo fix --edition` (from 2018 to 2021) - I think it should be ok to disallow using `cargo fix --edition` when someone already uses this feature. - building std - it should be safe to assume std is not using this feature so I just unwrap there. I'm not sure if some attempt to later feature unification would be better. - `cargo tree` - I just use the first feature set. This is definitely not ideal, but I'm not entirely sure what's the correct solution here. Printing multiple trees? Disallowing this, forcing users to select only one package? Based on comments in #15157 I've added tests first with `selected` feature unification and then changed that after implementation. I'm not sure if that's how you expect the tests to be added first, if not, I can change the history. I've expanded the test checking that this is ignored for `cargo install` although it should work the same way even if it is not ignored (`selected` and `package` are the same thing when just one package is selected).
2 parents eba6d5b + e4a616b commit e416238

File tree

9 files changed

+1336
-151
lines changed

9 files changed

+1336
-151
lines changed

src/cargo/core/compiler/standard_lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ pub fn resolve_std<'gctx>(
9696
&features, /*all_features*/ false, /*uses_default_features*/ false,
9797
)?;
9898
let dry_run = false;
99-
let resolve = ops::resolve_ws_with_opts(
99+
let mut resolve = ops::resolve_ws_with_opts(
100100
&std_ws,
101101
target_data,
102102
&build_config.requested_kinds,
@@ -106,10 +106,15 @@ pub fn resolve_std<'gctx>(
106106
crate::core::resolver::features::ForceAllTargets::No,
107107
dry_run,
108108
)?;
109+
debug_assert_eq!(resolve.specs_and_features.len(), 1);
109110
Ok((
110111
resolve.pkg_set,
111112
resolve.targeted_resolve,
112-
resolve.resolved_features,
113+
resolve
114+
.specs_and_features
115+
.pop()
116+
.expect("resolve should have a single spec with resolved features")
117+
.resolved_features,
113118
))
114119
}
115120

src/cargo/core/workspace.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,7 @@ impl<'gctx> Workspace<'gctx> {
12871287
self.target_dir = Some(target_dir);
12881288
}
12891289

1290-
/// Returns a Vec of `(&Package, RequestedFeatures)` tuples that
1290+
/// Returns a Vec of `(&Package, CliFeatures)` tuples that
12911291
/// represent the workspace members that were requested on the command-line.
12921292
///
12931293
/// `specs` may be empty, which indicates it should return all workspace

src/cargo/ops/cargo_compile/mod.rs

Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ use crate::core::resolver::{HasDevUnits, Resolve};
5252
use crate::core::{PackageId, PackageSet, SourceId, TargetKind, Workspace};
5353
use crate::drop_println;
5454
use crate::ops;
55-
use crate::ops::resolve::WorkspaceResolve;
55+
use crate::ops::resolve::{SpecsAndResolvedFeatures, WorkspaceResolve};
5656
use crate::util::context::{GlobalContext, WarningHandling};
5757
use crate::util::interning::InternedString;
5858
use crate::util::{CargoResult, StableHasher};
@@ -284,7 +284,7 @@ pub fn create_bcx<'a, 'gctx>(
284284
mut pkg_set,
285285
workspace_resolve,
286286
targeted_resolve: resolve,
287-
resolved_features,
287+
specs_and_features,
288288
} = resolve;
289289

290290
let std_resolve_features = if let Some(crates) = &gctx.cli_unstable().build_std {
@@ -363,72 +363,91 @@ pub fn create_bcx<'a, 'gctx>(
363363
})
364364
.collect();
365365

366-
// Passing `build_config.requested_kinds` instead of
367-
// `explicit_host_kinds` here so that `generate_root_units` can do
368-
// its own special handling of `CompileKind::Host`. It will
369-
// internally replace the host kind by the `explicit_host_kind`
370-
// before setting as a unit.
371-
let generator = UnitGenerator {
372-
ws,
373-
packages: &to_builds,
374-
spec,
375-
target_data: &target_data,
376-
filter,
377-
requested_kinds: &build_config.requested_kinds,
378-
explicit_host_kind,
379-
intent: build_config.intent,
380-
resolve: &resolve,
381-
workspace_resolve: &workspace_resolve,
382-
resolved_features: &resolved_features,
383-
package_set: &pkg_set,
384-
profiles: &profiles,
385-
interner,
386-
has_dev_units,
387-
};
388-
let mut units = generator.generate_root_units()?;
366+
let mut units = Vec::new();
367+
let mut unit_graph = HashMap::new();
368+
let mut scrape_units = Vec::new();
389369

390-
if let Some(args) = target_rustc_crate_types {
391-
override_rustc_crate_types(&mut units, args, interner)?;
392-
}
370+
for SpecsAndResolvedFeatures {
371+
specs,
372+
resolved_features,
373+
} in &specs_and_features
374+
{
375+
// Passing `build_config.requested_kinds` instead of
376+
// `explicit_host_kinds` here so that `generate_root_units` can do
377+
// its own special handling of `CompileKind::Host`. It will
378+
// internally replace the host kind by the `explicit_host_kind`
379+
// before setting as a unit.
380+
let spec_names = specs.iter().map(|spec| spec.name()).collect::<Vec<_>>();
381+
let packages = to_builds
382+
.iter()
383+
.filter(|package| spec_names.contains(&package.name().as_str()))
384+
.cloned()
385+
.collect::<Vec<_>>();
386+
let generator = UnitGenerator {
387+
ws,
388+
packages: &packages,
389+
spec,
390+
target_data: &target_data,
391+
filter,
392+
requested_kinds: &build_config.requested_kinds,
393+
explicit_host_kind,
394+
intent: build_config.intent,
395+
resolve: &resolve,
396+
workspace_resolve: &workspace_resolve,
397+
resolved_features: &resolved_features,
398+
package_set: &pkg_set,
399+
profiles: &profiles,
400+
interner,
401+
has_dev_units,
402+
};
403+
let mut targeted_root_units = generator.generate_root_units()?;
393404

394-
let should_scrape = build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples;
395-
let mut scrape_units = if should_scrape {
396-
generator.generate_scrape_units(&units)?
397-
} else {
398-
Vec::new()
399-
};
405+
if let Some(args) = target_rustc_crate_types {
406+
override_rustc_crate_types(&mut targeted_root_units, args, interner)?;
407+
}
408+
409+
let should_scrape =
410+
build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples;
411+
let targeted_scrape_units = if should_scrape {
412+
generator.generate_scrape_units(&targeted_root_units)?
413+
} else {
414+
Vec::new()
415+
};
416+
417+
let std_roots = if let Some(crates) = gctx.cli_unstable().build_std.as_ref() {
418+
let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap();
419+
standard_lib::generate_std_roots(
420+
&crates,
421+
&targeted_root_units,
422+
std_resolve,
423+
std_features,
424+
&explicit_host_kinds,
425+
&pkg_set,
426+
interner,
427+
&profiles,
428+
&target_data,
429+
)?
430+
} else {
431+
Default::default()
432+
};
400433

401-
let std_roots = if let Some(crates) = gctx.cli_unstable().build_std.as_ref() {
402-
let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap();
403-
standard_lib::generate_std_roots(
404-
&crates,
405-
&units,
406-
std_resolve,
407-
std_features,
408-
&explicit_host_kinds,
434+
unit_graph.extend(build_unit_dependencies(
435+
ws,
409436
&pkg_set,
410-
interner,
411-
&profiles,
437+
&resolve,
438+
&resolved_features,
439+
std_resolve_features.as_ref(),
440+
&targeted_root_units,
441+
&targeted_scrape_units,
442+
&std_roots,
443+
build_config.intent,
412444
&target_data,
413-
)?
414-
} else {
415-
Default::default()
416-
};
417-
418-
let mut unit_graph = build_unit_dependencies(
419-
ws,
420-
&pkg_set,
421-
&resolve,
422-
&resolved_features,
423-
std_resolve_features.as_ref(),
424-
&units,
425-
&scrape_units,
426-
&std_roots,
427-
build_config.intent,
428-
&target_data,
429-
&profiles,
430-
interner,
431-
)?;
445+
&profiles,
446+
interner,
447+
)?);
448+
units.extend(targeted_root_units);
449+
scrape_units.extend(targeted_scrape_units);
450+
}
432451

433452
// TODO: In theory, Cargo should also dedupe the roots, but I'm uncertain
434453
// what heuristics to use in that case.

src/cargo/ops/fix/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,15 @@ fn check_resolver_change<'gctx>(
588588
feature_opts,
589589
)?;
590590

591-
let diffs = v2_features.compare_legacy(&ws_resolve.resolved_features);
591+
if ws_resolve.specs_and_features.len() != 1 {
592+
bail!(r#"cannot fix edition when using `feature-unification = "package"`."#);
593+
}
594+
let resolved_features = &ws_resolve
595+
.specs_and_features
596+
.first()
597+
.expect("We've already checked that there is exactly one.")
598+
.resolved_features;
599+
let diffs = v2_features.compare_legacy(resolved_features);
592600
Ok((ws_resolve, diffs))
593601
};
594602
let (_, without_dev_diffs) = resolve_differences(HasDevUnits::No)?;

src/cargo/ops/resolve.rs

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ use crate::util::CanonicalUrl;
8181
use anyhow::Context as _;
8282
use cargo_util::paths;
8383
use cargo_util_schemas::core::PartialVersion;
84+
use std::borrow::Cow;
8485
use std::collections::{HashMap, HashSet};
86+
use std::rc::Rc;
8587
use tracing::{debug, trace};
8688

8789
/// Filter for keep using Package ID from previous lockfile.
@@ -96,9 +98,18 @@ pub struct WorkspaceResolve<'gctx> {
9698
/// This may be `None` for things like `cargo install` and `-Zavoid-dev-deps`.
9799
/// This does not include `paths` overrides.
98100
pub workspace_resolve: Option<Resolve>,
99-
/// The narrowed resolve, with the specific features enabled, and only the
100-
/// given package specs requested.
101+
/// The narrowed resolve, with the specific features enabled.
101102
pub targeted_resolve: Resolve,
103+
/// Package specs requested for compilation along with specific features enabled. This usually
104+
/// has the length of one but there may be more specs with different features when using the
105+
/// `package` feature resolver.
106+
pub specs_and_features: Vec<SpecsAndResolvedFeatures>,
107+
}
108+
109+
/// Pair of package specs requested for compilation along with enabled features.
110+
pub struct SpecsAndResolvedFeatures {
111+
/// Packages that are supposed to be built.
112+
pub specs: Vec<PackageIdSpec>,
102113
/// The features activated per package.
103114
pub resolved_features: ResolvedFeatures,
104115
}
@@ -145,10 +156,21 @@ pub fn resolve_ws_with_opts<'gctx>(
145156
force_all_targets: ForceAllTargets,
146157
dry_run: bool,
147158
) -> CargoResult<WorkspaceResolve<'gctx>> {
148-
let specs = match ws.resolve_feature_unification() {
149-
FeatureUnification::Selected => specs,
150-
FeatureUnification::Workspace => &ops::Packages::All(Vec::new()).to_package_id_specs(ws)?,
159+
let feature_unification = ws.resolve_feature_unification();
160+
let individual_specs = match feature_unification {
161+
FeatureUnification::Selected => vec![specs.to_owned()],
162+
FeatureUnification::Workspace => {
163+
vec![ops::Packages::All(Vec::new()).to_package_id_specs(ws)?]
164+
}
165+
FeatureUnification::Package => specs.iter().map(|spec| vec![spec.clone()]).collect(),
151166
};
167+
let specs: Vec<_> = individual_specs
168+
.iter()
169+
.map(|specs| specs.iter())
170+
.flatten()
171+
.cloned()
172+
.collect();
173+
let specs = &specs[..];
152174
let mut registry = ws.package_registry()?;
153175
let (resolve, resolved_with_overrides) = if ws.ignore_lock() {
154176
let add_patches = true;
@@ -229,9 +251,9 @@ pub fn resolve_ws_with_opts<'gctx>(
229251

230252
let pkg_set = get_resolved_packages(&resolved_with_overrides, registry)?;
231253

232-
let member_ids = ws
233-
.members_with_features(specs, cli_features)?
234-
.into_iter()
254+
let members_with_features = ws.members_with_features(specs, cli_features)?;
255+
let member_ids = members_with_features
256+
.iter()
235257
.map(|(p, _fts)| p.package_id())
236258
.collect::<Vec<_>>();
237259
pkg_set.download_accessible(
@@ -243,33 +265,70 @@ pub fn resolve_ws_with_opts<'gctx>(
243265
force_all_targets,
244266
)?;
245267

246-
let feature_opts = FeatureOpts::new(ws, has_dev_units, force_all_targets)?;
247-
let resolved_features = FeatureResolver::resolve(
248-
ws,
249-
target_data,
250-
&resolved_with_overrides,
251-
&pkg_set,
252-
cli_features,
253-
specs,
254-
requested_targets,
255-
feature_opts,
256-
)?;
268+
let mut specs_and_features = Vec::new();
257269

258-
pkg_set.warn_no_lib_packages_and_artifact_libs_overlapping_deps(
259-
ws,
260-
&resolved_with_overrides,
261-
&member_ids,
262-
has_dev_units,
263-
requested_targets,
264-
target_data,
265-
force_all_targets,
266-
)?;
270+
for specs in individual_specs {
271+
let feature_opts = FeatureOpts::new(ws, has_dev_units, force_all_targets)?;
272+
273+
// We want to narrow the features to the current specs so that stuff like `cargo check -p a
274+
// -p b -F a/a,b/b` works and the resolver does not contain that `a` does not have feature
275+
// `b` and vice-versa. However, resolver v1 needs to see even features of unselected
276+
// packages turned on if it was because of working directory being inside the unselected
277+
// package, because they might turn on a feature of a selected package.
278+
let narrowed_features = match feature_unification {
279+
FeatureUnification::Package => {
280+
let mut narrowed_features = cli_features.clone();
281+
let enabled_features = members_with_features
282+
.iter()
283+
.filter_map(|(package, cli_features)| {
284+
specs
285+
.iter()
286+
.any(|spec| spec.matches(package.package_id()))
287+
.then_some(cli_features.features.iter())
288+
})
289+
.flatten()
290+
.cloned()
291+
.collect();
292+
narrowed_features.features = Rc::new(enabled_features);
293+
Cow::Owned(narrowed_features)
294+
}
295+
FeatureUnification::Selected | FeatureUnification::Workspace => {
296+
Cow::Borrowed(cli_features)
297+
}
298+
};
299+
300+
let resolved_features = FeatureResolver::resolve(
301+
ws,
302+
target_data,
303+
&resolved_with_overrides,
304+
&pkg_set,
305+
&*narrowed_features,
306+
&specs,
307+
requested_targets,
308+
feature_opts,
309+
)?;
310+
311+
pkg_set.warn_no_lib_packages_and_artifact_libs_overlapping_deps(
312+
ws,
313+
&resolved_with_overrides,
314+
&member_ids,
315+
has_dev_units,
316+
requested_targets,
317+
target_data,
318+
force_all_targets,
319+
)?;
320+
321+
specs_and_features.push(SpecsAndResolvedFeatures {
322+
specs,
323+
resolved_features,
324+
});
325+
}
267326

268327
Ok(WorkspaceResolve {
269328
pkg_set,
270329
workspace_resolve: resolve,
271330
targeted_resolve: resolved_with_overrides,
272-
resolved_features,
331+
specs_and_features,
273332
})
274333
}
275334

0 commit comments

Comments
 (0)