Skip to content

Commit 35ff555

Browse files
committed
just give up and make it an error
1 parent a473716 commit 35ff555

File tree

8 files changed

+76
-119
lines changed

8 files changed

+76
-119
lines changed

src/cargo/core/resolver/context.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use log::debug;
99

1010
use crate::core::interning::InternedString;
1111
use crate::core::{Dependency, FeatureValue, PackageId, SourceId, Summary};
12-
use crate::util::config::Config;
1312
use crate::util::CargoResult;
1413
use crate::util::Graph;
1514

@@ -158,7 +157,7 @@ impl Context {
158157
// First, figure out our set of dependencies based on the requested set
159158
// of features. This also calculates what features we're going to enable
160159
// for our own dependencies.
161-
let deps = self.resolve_features(parent, candidate, method, None)?;
160+
let deps = self.resolve_features(parent, candidate, method)?;
162161

163162
// Next, transform all dependencies into a list of possible candidates
164163
// which can satisfy that dependency.
@@ -218,7 +217,6 @@ impl Context {
218217
parent: Option<&Summary>,
219218
s: &'b Summary,
220219
method: &'b Method<'_>,
221-
config: Option<&Config>,
222220
) -> ActivateResult<Vec<(Dependency, Vec<InternedString>)>> {
223221
let dev_deps = match *method {
224222
Method::Everything => true,
@@ -246,23 +244,26 @@ impl Context {
246244
// name.
247245
let base = reqs.deps.get(&dep.name_in_toml()).unwrap_or(&default_dep);
248246
used_features.insert(dep.name_in_toml());
249-
if let Some(config) = config {
250-
let mut shell = config.shell();
251-
let always_required = !dep.is_optional()
252-
&& !s
253-
.dependencies()
254-
.iter()
255-
.any(|d| d.is_optional() && d.name_in_toml() == dep.name_in_toml());
256-
if always_required && base.0 {
257-
shell.warn(&format!(
247+
let always_required = !dep.is_optional()
248+
&& !s
249+
.dependencies()
250+
.iter()
251+
.any(|d| d.is_optional() && d.name_in_toml() == dep.name_in_toml());
252+
if always_required && base.0 {
253+
return Err(match parent {
254+
None => failure::format_err!(
258255
"Package `{}` does not have feature `{}`. It has a required dependency \
259-
with that name, but only optional dependencies can be used as features. \
260-
This is currently a warning to ease the transition, but it will become an \
261-
error in the future.",
256+
with that name, but only optional dependencies can be used as features.",
262257
s.package_id(),
263258
dep.name_in_toml()
264-
))?
265-
}
259+
)
260+
.into(),
261+
Some(p) => (
262+
p.package_id(),
263+
ConflictReason::RequiredDependencyAsFeatures(dep.name_in_toml()),
264+
)
265+
.into(),
266+
});
266267
}
267268
let mut base = base.1.clone();
268269
base.extend(dep.features().iter());

src/cargo/core/resolver/errors.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ pub(super) fn activation_error(
127127
msg.push_str(&describe_path(&cx.parents.path_to_bottom(p)));
128128
}
129129

130-
let (features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors
130+
let (features_errors, mut other_errors): (Vec<_>, Vec<_>) = other_errors
131131
.drain(..)
132132
.partition(|&(_, r)| r.is_missing_features());
133133

@@ -146,6 +146,29 @@ pub(super) fn activation_error(
146146
// p == parent so the full path is redundant.
147147
}
148148

149+
let (required_dependency_as_features_errors, other_errors): (Vec<_>, Vec<_>) = other_errors
150+
.drain(..)
151+
.partition(|&(_, r)| r.is_required_dependency_as_features());
152+
153+
for &(p, r) in required_dependency_as_features_errors.iter() {
154+
if let ConflictReason::RequiredDependencyAsFeatures(ref features) = *r {
155+
msg.push_str("\n\nthe package `");
156+
msg.push_str(&*p.name());
157+
msg.push_str("` depends on `");
158+
msg.push_str(&*dep.package_name());
159+
msg.push_str("`, with features: `");
160+
msg.push_str(features);
161+
msg.push_str("` but `");
162+
msg.push_str(&*dep.package_name());
163+
msg.push_str("` does not have these features.\n");
164+
msg.push_str(
165+
" It has a required dependency with that name, \
166+
but only optional dependencies can be used as features.\n",
167+
);
168+
}
169+
// p == parent so the full path is redundant.
170+
}
171+
149172
if !other_errors.is_empty() {
150173
msg.push_str(
151174
"\n\nall possible versions conflict with \

src/cargo/core/resolver/mod.rs

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ pub fn resolve(
124124
registry: &mut dyn Registry,
125125
try_to_use: &HashSet<PackageId>,
126126
config: Option<&Config>,
127-
print_warnings: bool,
128127
check_public_visible_dependencies: bool,
129128
) -> CargoResult<Resolve> {
130129
let cx = Context::new(check_public_visible_dependencies);
@@ -157,11 +156,6 @@ pub fn resolve(
157156
check_duplicate_pkgs_in_lockfile(&resolve)?;
158157
trace!("resolved: {:?}", resolve);
159158

160-
// If we have a shell, emit warnings about required deps used as feature.
161-
if print_warnings && config.is_some() {
162-
emit_warnings(&cx, &resolve, summaries, config)
163-
}
164-
165159
Ok(resolve)
166160
}
167161

@@ -1087,65 +1081,3 @@ fn check_duplicate_pkgs_in_lockfile(resolve: &Resolve) -> CargoResult<()> {
10871081
}
10881082
Ok(())
10891083
}
1090-
1091-
/// Re-run all used `resolve_features` so it can print warnings.
1092-
/// Within the resolution loop we do not pass a `config` to `resolve_features`
1093-
/// this tells it not to print warnings. Hear we do pass `config` triggering it to print them.
1094-
/// This is done as the resolution is NP-Hard so the loop can potentially call `resolve features`
1095-
/// an exponential number of times, but this pass is linear in the number of dependencies.
1096-
fn emit_warnings(
1097-
cx: &Context,
1098-
resolve: &Resolve,
1099-
summaries: &[(Summary, Method<'_>)],
1100-
config: Option<&Config>,
1101-
) {
1102-
let mut new_cx = cx.clone();
1103-
new_cx.resolve_features = im_rc::HashMap::new();
1104-
let mut features_from_dep = HashMap::new();
1105-
for (summary, method) in summaries {
1106-
let replacement = &cx.activations[&resolve
1107-
.replacements()
1108-
.get(&summary.package_id())
1109-
.unwrap_or(&summary.package_id())
1110-
.as_activations_key()]
1111-
.0;
1112-
for (dep, features) in new_cx
1113-
.resolve_features(None, replacement, &method, config)
1114-
.expect("can not resolve_features for a required summery")
1115-
{
1116-
features_from_dep.insert((replacement.package_id(), dep), features);
1117-
}
1118-
}
1119-
// crates enable features for their dependencies.
1120-
// So by iterating reverse topologically we process all of the parents before each child.
1121-
// Then we know all the needed features for each crate.
1122-
let topological_sort = resolve.sort();
1123-
for id in topological_sort
1124-
.iter()
1125-
.rev()
1126-
.filter(|&id| !resolve.replacements().contains_key(id))
1127-
{
1128-
let summary = &cx.activations[&id.as_activations_key()].0;
1129-
1130-
for (parent, deps) in cx.parents.edges(&summary.package_id()) {
1131-
for dep in deps.iter() {
1132-
let features = features_from_dep
1133-
.remove(&(*parent, dep.clone()))
1134-
.expect("fulfilled a dep that was not needed");
1135-
let method = Method::Required {
1136-
dev_deps: false,
1137-
features: &features,
1138-
all_features: false,
1139-
uses_default_features: dep.uses_default_features(),
1140-
};
1141-
for (dep, features) in new_cx
1142-
.resolve_features(None, summary, &method, config)
1143-
.expect("can not resolve_features for a used dep")
1144-
{
1145-
features_from_dep.insert((summary.package_id(), dep), features);
1146-
}
1147-
}
1148-
}
1149-
}
1150-
assert_eq!(cx.resolve_features, new_cx.resolve_features);
1151-
}

src/cargo/core/resolver/types.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,12 @@ pub enum ConflictReason {
426426
/// candidate we're activating didn't actually have the feature `foo`.
427427
MissingFeatures(String),
428428

429+
/// A dependency listed features that ended up being a required dependency.
430+
/// For example we tried to activate feature `foo` but the
431+
/// candidate we're activating didn't actually have the feature `foo`
432+
/// it had a dependency `foo` instead.
433+
RequiredDependencyAsFeatures(InternedString),
434+
429435
// TODO: needs more info for `activation_error`
430436
// TODO: needs more info for `find_candidate`
431437
/// pub dep error
@@ -446,6 +452,13 @@ impl ConflictReason {
446452
}
447453
false
448454
}
455+
456+
pub fn is_required_dependency_as_features(&self) -> bool {
457+
if let ConflictReason::RequiredDependencyAsFeatures(_) = *self {
458+
return true;
459+
}
460+
false
461+
}
449462
}
450463

451464
/// A list of packages that have gotten in the way of resolving a dependency.

src/cargo/ops/cargo_generate_lockfile.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,8 @@ pub struct UpdateOptions<'a> {
2121

2222
pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
2323
let mut registry = PackageRegistry::new(ws.config())?;
24-
let resolve = ops::resolve_with_previous(
25-
&mut registry,
26-
ws,
27-
Method::Everything,
28-
None,
29-
None,
30-
&[],
31-
true,
32-
true,
33-
)?;
24+
let resolve =
25+
ops::resolve_with_previous(&mut registry, ws, Method::Everything, None, None, &[], true)?;
3426
ops::write_pkg_lockfile(ws, &resolve)?;
3527
Ok(())
3628
}
@@ -92,7 +84,6 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
9284
Some(&to_avoid),
9385
&[],
9486
true,
95-
true,
9687
)?;
9788

9889
// Summarize what is changing for the user.

src/cargo/ops/resolve.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ version. This may also occur with an optional dependency that is not enabled.";
2323
/// lock file.
2424
pub fn resolve_ws<'a>(ws: &Workspace<'a>) -> CargoResult<(PackageSet<'a>, Resolve)> {
2525
let mut registry = PackageRegistry::new(ws.config())?;
26-
let resolve = resolve_with_registry(ws, &mut registry, true)?;
26+
let resolve = resolve_with_registry(ws, &mut registry)?;
2727
let packages = get_resolved_packages(&resolve, registry)?;
2828
Ok((packages, resolve))
2929
}
@@ -64,7 +64,7 @@ pub fn resolve_ws_with_method<'a>(
6464
} else if ws.require_optional_deps() {
6565
// First, resolve the root_package's *listed* dependencies, as well as
6666
// downloading and updating all remotes and such.
67-
let resolve = resolve_with_registry(ws, &mut registry, false)?;
67+
let resolve = resolve_with_registry(ws, &mut registry)?;
6868
add_patches = false;
6969

7070
// Second, resolve with precisely what we're doing. Filter out
@@ -98,7 +98,6 @@ pub fn resolve_ws_with_method<'a>(
9898
None,
9999
specs,
100100
add_patches,
101-
true,
102101
)?;
103102

104103
let packages = get_resolved_packages(&resolved_with_overrides, registry)?;
@@ -109,7 +108,6 @@ pub fn resolve_ws_with_method<'a>(
109108
fn resolve_with_registry<'cfg>(
110109
ws: &Workspace<'cfg>,
111110
registry: &mut PackageRegistry<'cfg>,
112-
warn: bool,
113111
) -> CargoResult<Resolve> {
114112
let prev = ops::load_pkg_lockfile(ws)?;
115113
let resolve = resolve_with_previous(
@@ -120,7 +118,6 @@ fn resolve_with_registry<'cfg>(
120118
None,
121119
&[],
122120
true,
123-
warn,
124121
)?;
125122

126123
if !ws.is_ephemeral() {
@@ -146,7 +143,6 @@ pub fn resolve_with_previous<'cfg>(
146143
to_avoid: Option<&HashSet<PackageId>>,
147144
specs: &[PackageIdSpec],
148145
register_patches: bool,
149-
warn: bool,
150146
) -> CargoResult<Resolve> {
151147
// Here we place an artificial limitation that all non-registry sources
152148
// cannot be locked at more than one revision. This means that if a Git
@@ -334,7 +330,6 @@ pub fn resolve_with_previous<'cfg>(
334330
registry,
335331
&try_to_use,
336332
Some(ws.config()),
337-
warn,
338333
false, // TODO: use "public and private dependencies" feature flag
339334
)?;
340335
resolved.register_used_patches(registry.patches());

tests/testsuite/features.rs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,12 @@ fn invalid9() {
291291
.file("bar/src/lib.rs", "")
292292
.build();
293293

294-
p.cargo("build --features bar").with_stderr("\
295-
warning: Package `foo v0.0.1 ([..])` does not have feature `bar`. It has a required dependency with \
296-
that name, but only optional dependencies can be used as features. [..]
297-
Compiling bar v0.0.1 ([..])
298-
Compiling foo v0.0.1 ([..])
299-
Finished dev [unoptimized + debuginfo] target(s) in [..]s
300-
").run();
294+
p.cargo("build --features bar")
295+
.with_stderr(
296+
"\
297+
error: Package `foo v0.0.1 ([..])` does not have feature `bar`. It has a required dependency with that name, but only optional dependencies can be used as features.
298+
",
299+
).with_status(101).run();
301300
}
302301

303302
#[test]
@@ -335,13 +334,17 @@ fn invalid10() {
335334
.build();
336335

337336
p.cargo("build").with_stderr("\
338-
warning: Package `bar v0.0.1 ([..])` does not have feature `baz`. It has a required dependency with \
339-
that name, but only optional dependencies can be used as features. [..]
340-
Compiling baz v0.0.1 ([..])
341-
Compiling bar v0.0.1 ([..])
342-
Compiling foo v0.0.1 ([..])
343-
Finished dev [unoptimized + debuginfo] target(s) in [..]s
344-
").run();
337+
error: failed to select a version for `bar`.
338+
... required by package `foo v0.0.1 ([..])`
339+
versions that meet the requirements `*` are: 0.0.1
340+
341+
the package `foo` depends on `bar`, with features: `baz` but `bar` does not have these features.
342+
It has a required dependency with that name, but only optional dependencies can be used as features.
343+
344+
345+
failed to select a version for `bar` which could resolve this conflict
346+
").with_status(101)
347+
.run();
345348
}
346349

347350
#[test]

tests/testsuite/support/resolver.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ pub fn resolve_with_config_raw(
115115
&HashSet::new(),
116116
config,
117117
true,
118-
true,
119118
);
120119

121120
// The largest test in our suite takes less then 30 sec.

0 commit comments

Comments
 (0)