Skip to content

Commit 008c369

Browse files
committed
Auto merge of #5353 - matklad:features, r=alexcrichton
New semantics for `--features` flag Historically, feature-related flags like `--all-features`, `--no-default-features` and `--features` operated on the *current* package. That is, `cargo --package foo --feature feat` would activate `feat` for the package at the current working directory, and not for the `foo` package. `-Z package-features` flag implements the more obvious semantics for this combination of flags. This changes behavior, and that is why we want to start with an unstable opt-in. The changes are: * `--feature` flag affects the selected package. It is an error to specify `--feature` with more than a single `-p`, or with `-p` outside workspace (the latter could work in theory, but would be hard to implement). * `--all-features` and `--no-default-features` affect all selected packages, and not the one at cwd. * The package in `cwd` is not implicitly enabled when doing feature selection. That is, `cargo build -Z package-features -p foo` could select *less* features for various packages than `cargo build -p foo`. r? @alexcrichton
2 parents 8afcf45 + 8d0b31b commit 008c369

File tree

3 files changed

+158
-41
lines changed

3 files changed

+158
-41
lines changed

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ pub struct CliUnstable {
292292
pub no_index_update: bool,
293293
pub avoid_dev_deps: bool,
294294
pub minimal_versions: bool,
295+
pub package_features: bool,
295296
}
296297

297298
impl CliUnstable {
@@ -325,6 +326,7 @@ impl CliUnstable {
325326
"no-index-update" => self.no_index_update = true,
326327
"avoid-dev-deps" => self.avoid_dev_deps = true,
327328
"minimal-versions" => self.minimal_versions = true,
329+
"package-features" => self.package_features = true,
328330
_ => bail!("unknown `-Z` flag specified: {}", k),
329331
}
330332

src/cargo/ops/resolve.rs

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -213,53 +213,86 @@ pub fn resolve_with_previous<'a, 'cfg>(
213213
registry.lock_patches();
214214
}
215215

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

260-
let summary = registry.lock(member.summary().clone());
261-
summaries.push((summary, method_to_resolve));
262-
}
263296

264297
let root_replace = ws.root_replace();
265298

tests/testsuite/features.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::io::prelude::*;
33

44
use cargotest::support::paths::CargoPathExt;
55
use cargotest::support::{execs, project};
6+
use cargotest::ChannelChanger;
67
use hamcrest::assert_that;
8+
use cargotest::support::registry::Package;
79

810
#[test]
911
fn invalid1() {
@@ -1629,3 +1631,83 @@ fn many_cli_features_comma_and_space_delimited() {
16291631
)),
16301632
);
16311633
}
1634+
1635+
#[test]
1636+
fn combining_features_and_package() {
1637+
Package::new("dep", "1.0.0").publish();
1638+
1639+
let p = project("ws")
1640+
.file(
1641+
"Cargo.toml",
1642+
r#"
1643+
[project]
1644+
name = "ws"
1645+
version = "0.0.1"
1646+
authors = []
1647+
1648+
[workspace]
1649+
members = ["foo"]
1650+
1651+
[dependencies]
1652+
dep = "1"
1653+
"#,
1654+
)
1655+
.file("src/lib.rs", "")
1656+
.file(
1657+
"foo/Cargo.toml",
1658+
r#"
1659+
[package]
1660+
name = "foo"
1661+
version = "0.0.1"
1662+
authors = []
1663+
[features]
1664+
main = []
1665+
"#,
1666+
)
1667+
.file("foo/src/main.rs", r#"
1668+
#[cfg(feature = "main")]
1669+
fn main() {}
1670+
"#)
1671+
.build();
1672+
1673+
assert_that(
1674+
p.cargo("build -Z package-features --all --features main")
1675+
.masquerade_as_nightly_cargo(),
1676+
execs().with_status(101).with_stderr_contains("\
1677+
[ERROR] cannot specify features for more than one package"
1678+
),
1679+
);
1680+
1681+
assert_that(
1682+
p.cargo("build -Z package-features --package dep --features main")
1683+
.masquerade_as_nightly_cargo(),
1684+
execs().with_status(101).with_stderr_contains("\
1685+
[ERROR] cannot specify features for packages outside of workspace"
1686+
),
1687+
);
1688+
assert_that(
1689+
p.cargo("build -Z package-features --package dep --all-features")
1690+
.masquerade_as_nightly_cargo(),
1691+
execs().with_status(101).with_stderr_contains("\
1692+
[ERROR] cannot specify features for packages outside of workspace"
1693+
),
1694+
);
1695+
assert_that(
1696+
p.cargo("build -Z package-features --package dep --no-default-features")
1697+
.masquerade_as_nightly_cargo(),
1698+
execs().with_status(101).with_stderr_contains("\
1699+
[ERROR] cannot specify features for packages outside of workspace"
1700+
),
1701+
);
1702+
1703+
assert_that(
1704+
p.cargo("build -Z package-features --all --all-features")
1705+
.masquerade_as_nightly_cargo(),
1706+
execs().with_status(0),
1707+
);
1708+
assert_that(
1709+
p.cargo("run -Z package-features --package foo --features main")
1710+
.masquerade_as_nightly_cargo(),
1711+
execs().with_status(0),
1712+
);
1713+
}

0 commit comments

Comments
 (0)