Skip to content

Commit 122fd5b

Browse files
committed
Auto merge of #5430 - matklad:bring-old-features-back, r=alexcrichton
Revert "Enable new behavior of `--feature`" This reverts commit 038eec5. As discussed at #5364, the new behavior unfortunately causes real-life breakage, so we have to revert it. This is kinda sad, this is a part of the larger issue with feature selection, which, at the moment, has a behavior which I would classify (loosely speaking) as unsound: * `cargo build -p foo` and `cargo build -p foo -p bar` might produce different artifacts for `foo` ([repro](https://github.com/matklad/workspace-vs-feaures)) * `cargo build -p foo` might produce different artifacts, depending on cwd ([repro](https://github.com/matklad/features-cwd)) The new feature behavior specifically addressed the second point. It is unclear what we could do with this... One option, instead of flatly erroring out, as the revreted commit does, is to print a warning, but change the set of activated features. It will still be a breaking change, but it at least has a chance of working by accident. r? @alexcrichton
2 parents 07d022d + d369f97 commit 122fd5b

File tree

6 files changed

+144
-50
lines changed

6 files changed

+144
-50
lines changed

src/cargo/core/features.rs

+2
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ pub struct CliUnstable {
299299
pub no_index_update: bool,
300300
pub avoid_dev_deps: bool,
301301
pub minimal_versions: bool,
302+
pub package_features: bool,
302303
}
303304

304305
impl CliUnstable {
@@ -332,6 +333,7 @@ impl CliUnstable {
332333
"no-index-update" => self.no_index_update = true,
333334
"avoid-dev-deps" => self.avoid_dev_deps = true,
334335
"minimal-versions" => self.minimal_versions = true,
336+
"package-features" => self.package_features = true,
335337
_ => bail!("unknown `-Z` flag specified: {}", k),
336338
}
337339

src/cargo/core/resolver/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ impl<'a> RegistryQueryer<'a> {
155155
}
156156
}
157157

158-
#[derive(Clone, Copy, Eq, PartialEq)]
158+
#[derive(Clone, Copy)]
159159
pub enum Method<'a> {
160160
Everything, // equivalent to Required { dev_deps: true, all_features: true, .. }
161161
Required {

src/cargo/core/workspace.rs

-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ pub struct WorkspaceRootConfig {
118118

119119
/// An iterator over the member packages of a workspace, returned by
120120
/// `Workspace::members`
121-
#[derive(Clone)]
122121
pub struct Members<'a, 'cfg: 'a> {
123122
ws: &'a Workspace<'cfg>,
124123
iter: slice::Iter<'a, PathBuf>,

src/cargo/ops/resolve.rs

+74-31
Original file line numberDiff line numberDiff line change
@@ -217,43 +217,86 @@ pub fn resolve_with_previous<'a, 'cfg>(
217217
registry.add_sources(&[member.package_id().source_id().clone()])?;
218218
}
219219

220-
let method = match method {
221-
Method::Everything => Method::Everything,
222-
Method::Required {
223-
features,
224-
all_features,
225-
uses_default_features,
226-
..
227-
} => {
228-
if specs.len() > 1 && !features.is_empty() {
229-
bail!("cannot specify features for more than one package");
230-
}
231-
let members_requested = ws.members()
232-
.filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
233-
.count();
234-
if members_requested == 0 {
220+
let mut summaries = Vec::new();
221+
if ws.config().cli_unstable().package_features {
222+
let mut members = Vec::new();
223+
match method {
224+
Method::Everything => members.extend(ws.members()),
225+
Method::Required {
226+
features,
227+
all_features,
228+
uses_default_features,
229+
..
230+
} => {
231+
if specs.len() > 1 && !features.is_empty() {
232+
bail!("cannot specify features for more than one package");
233+
}
234+
members.extend(
235+
ws.members()
236+
.filter(|m| specs.iter().any(|spec| spec.matches(m.package_id()))),
237+
);
235238
// Edge case: running `cargo build -p foo`, where `foo` is not a member
236-
// of current workspace. Resolve whole workspace to get `foo` into the
237-
// resolution graph.
238-
if !(features.is_empty() && !all_features && uses_default_features) {
239-
bail!("cannot specify features for packages outside of workspace");
239+
// of current workspace. Add all packages from workspace to get `foo`
240+
// into the resolution graph.
241+
if members.is_empty() {
242+
if !(features.is_empty() && !all_features && uses_default_features) {
243+
bail!("cannot specify features for packages outside of workspace");
244+
}
245+
members.extend(ws.members());
240246
}
241-
Method::Everything
242-
} else {
243-
method
244247
}
245248
}
246-
};
249+
for member in members {
250+
let summary = registry.lock(member.summary().clone());
251+
summaries.push((summary, method))
252+
}
253+
} else {
254+
for member in ws.members() {
255+
let method_to_resolve = match method {
256+
// When everything for a workspace we want to be sure to resolve all
257+
// members in the workspace, so propagate the `Method::Everything`.
258+
Method::Everything => Method::Everything,
259+
260+
// If we're not resolving everything though then we're constructing the
261+
// exact crate graph we're going to build. Here we don't necessarily
262+
// want to keep around all workspace crates as they may not all be
263+
// built/tested.
264+
//
265+
// Additionally, the `method` specified represents command line
266+
// flags, which really only matters for the current package
267+
// (determined by the cwd). If other packages are specified (via
268+
// `-p`) then the command line flags like features don't apply to
269+
// them.
270+
//
271+
// As a result, if this `member` is the current member of the
272+
// workspace, then we use `method` specified. Otherwise we use a
273+
// base method with no features specified but using default features
274+
// for any other packages specified with `-p`.
275+
Method::Required { dev_deps, .. } => {
276+
let base = Method::Required {
277+
dev_deps,
278+
features: &[],
279+
all_features: false,
280+
uses_default_features: true,
281+
};
282+
let member_id = member.package_id();
283+
match ws.current_opt() {
284+
Some(current) if member_id == current.package_id() => method,
285+
_ => {
286+
if specs.iter().any(|spec| spec.matches(member_id)) {
287+
base
288+
} else {
289+
continue;
290+
}
291+
}
292+
}
293+
}
294+
};
247295

248-
let summaries = ws.members()
249-
.filter(|m| {
250-
method == Method::Everything || specs.iter().any(|spec| spec.matches(m.package_id()))
251-
})
252-
.map(|member| {
253296
let summary = registry.lock(member.summary().clone());
254-
(summary, method)
255-
})
256-
.collect::<Vec<_>>();
297+
summaries.push((summary, method_to_resolve));
298+
}
299+
};
257300

258301
let root_replace = ws.root_replace();
259302

tests/testsuite/features.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -1674,42 +1674,46 @@ fn combining_features_and_package() {
16741674
.build();
16751675

16761676
assert_that(
1677-
p.cargo("build --all --features main")
1677+
p.cargo("build -Z package-features --all --features main")
16781678
.masquerade_as_nightly_cargo(),
1679-
execs()
1680-
.with_status(101)
1681-
.with_stderr_contains("[ERROR] cannot specify features for more than one package"),
1679+
execs().with_status(101).with_stderr_contains(
1680+
"\
1681+
[ERROR] cannot specify features for more than one package",
1682+
),
16821683
);
16831684

16841685
assert_that(
1685-
p.cargo("build --package dep --features main")
1686+
p.cargo("build -Z package-features --package dep --features main")
16861687
.masquerade_as_nightly_cargo(),
16871688
execs().with_status(101).with_stderr_contains(
1688-
"[ERROR] cannot specify features for packages outside of workspace",
1689+
"\
1690+
[ERROR] cannot specify features for packages outside of workspace",
16891691
),
16901692
);
16911693
assert_that(
1692-
p.cargo("build --package dep --all-features")
1694+
p.cargo("build -Z package-features --package dep --all-features")
16931695
.masquerade_as_nightly_cargo(),
16941696
execs().with_status(101).with_stderr_contains(
1695-
"[ERROR] cannot specify features for packages outside of workspace",
1697+
"\
1698+
[ERROR] cannot specify features for packages outside of workspace",
16961699
),
16971700
);
16981701
assert_that(
1699-
p.cargo("build --package dep --no-default-features")
1702+
p.cargo("build -Z package-features --package dep --no-default-features")
17001703
.masquerade_as_nightly_cargo(),
17011704
execs().with_status(101).with_stderr_contains(
1702-
"[ERROR] cannot specify features for packages outside of workspace",
1705+
"\
1706+
[ERROR] cannot specify features for packages outside of workspace",
17031707
),
17041708
);
17051709

17061710
assert_that(
1707-
p.cargo("build --all --all-features")
1711+
p.cargo("build -Z package-features --all --all-features")
17081712
.masquerade_as_nightly_cargo(),
17091713
execs().with_status(0),
17101714
);
17111715
assert_that(
1712-
p.cargo("run --package foo --features main")
1716+
p.cargo("run -Z package-features --package foo --features main")
17131717
.masquerade_as_nightly_cargo(),
17141718
execs().with_status(0),
17151719
);

tests/testsuite/test.rs

+51-5
Original file line numberDiff line numberDiff line change
@@ -2863,7 +2863,13 @@ fn selective_test_optional_dep() {
28632863
let p = p.build();
28642864

28652865
assert_that(
2866-
p.cargo("test -v --no-run --package a"),
2866+
p.cargo("test")
2867+
.arg("-v")
2868+
.arg("--no-run")
2869+
.arg("--features")
2870+
.arg("a")
2871+
.arg("-p")
2872+
.arg("a"),
28672873
execs().with_status(0).with_stderr(
28682874
"\
28692875
[COMPILING] a v0.0.1 ([..])
@@ -3050,8 +3056,6 @@ fn pass_correct_cfgs_flags_to_rustdoc() {
30503056
version = "0.1.0"
30513057
authors = []
30523058
3053-
[workspace]
3054-
30553059
[features]
30563060
default = ["feature_a/default"]
30573061
nightly = ["feature_a/nightly"]
@@ -3129,7 +3133,10 @@ fn pass_correct_cfgs_flags_to_rustdoc() {
31293133
let p = p.build();
31303134

31313135
assert_that(
3132-
p.cargo("test --package feature_a --verbose"),
3136+
p.cargo("test")
3137+
.arg("--package")
3138+
.arg("feature_a")
3139+
.arg("--verbose"),
31333140
execs().with_status(0).with_stderr_contains(
31343141
"\
31353142
[DOCTEST] feature_a
@@ -3138,7 +3145,7 @@ fn pass_correct_cfgs_flags_to_rustdoc() {
31383145
);
31393146

31403147
assert_that(
3141-
p.cargo("test --verbose"),
3148+
p.cargo("test").arg("--verbose"),
31423149
execs().with_status(0).with_stderr_contains(
31433150
"\
31443151
[DOCTEST] foo
@@ -3188,6 +3195,45 @@ fn test_release_ignore_panic() {
31883195
assert_that(p.cargo("bench").arg("-v"), execs().with_status(0));
31893196
}
31903197

3198+
#[test]
3199+
fn test_many_with_features() {
3200+
let p = project("foo")
3201+
.file(
3202+
"Cargo.toml",
3203+
r#"
3204+
[package]
3205+
name = "foo"
3206+
version = "0.0.1"
3207+
authors = []
3208+
3209+
[dependencies]
3210+
a = { path = "a" }
3211+
3212+
[features]
3213+
foo = []
3214+
3215+
[workspace]
3216+
"#,
3217+
)
3218+
.file("src/lib.rs", "")
3219+
.file(
3220+
"a/Cargo.toml",
3221+
r#"
3222+
[package]
3223+
name = "a"
3224+
version = "0.0.1"
3225+
authors = []
3226+
"#,
3227+
)
3228+
.file("a/src/lib.rs", "")
3229+
.build();
3230+
3231+
assert_that(
3232+
p.cargo("test -v -p a -p foo --features foo"),
3233+
execs().with_status(0),
3234+
);
3235+
}
3236+
31913237
#[test]
31923238
fn test_all_workspace() {
31933239
let p = project("foo")

0 commit comments

Comments
 (0)