Skip to content

Commit a713da1

Browse files
authored
Auto merge of #3136 - alexcrichton:warn-bad-override, r=brson
Warn about path overrides that won't work Cargo has a long-standing [bug] in path overrides where they will cause spurious rebuilds of crates in the crate graph. This can be very difficult to diagnose and be incredibly frustrating as well. Unfortunately, though, it's behavior that fundamentally can't be fixed in Cargo. The crux of the problem happens when a `path` replacement changes something about the list of dependencies of the crate that it's replacing. This alteration to the list of dependencies *cannot be tracked by Cargo* as the lock file was previously emitted. In the best case this ends up causing random recompiles. In the worst case it cause infinite registry updates that always result in recompiles. A solution to this intention, changing the dependency graph of an overridden dependency, was [implemented] with the `[replace]` feature in Cargo awhile back. With that in mind, this commit implements a *warning* whenever a bad dependency replacement is detected. The message here is pretty wordy, but it's intended to convey that you should switch to using `[replace]` for a more robust impelmentation, and it can also give security to anyone using `path` overrides that if they get past this warning everything should work as intended. [bug]: #2041 [implemented]: http://doc.crates.io/specifying-dependencies.html#overriding-dependencies Closes #2041
2 parents 80d20e9 + fc0e642 commit a713da1

File tree

3 files changed

+156
-22
lines changed

3 files changed

+156
-22
lines changed

src/cargo/core/registry.rs

+85-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::{HashSet, HashMap};
1+
use std::collections::HashMap;
22

33
use core::{Source, SourceId, SourceMap, Summary, Dependency, PackageId, Package};
44
use core::PackageSet;
@@ -188,17 +188,16 @@ impl<'cfg> PackageRegistry<'cfg> {
188188
}
189189

190190
fn query_overrides(&mut self, dep: &Dependency)
191-
-> CargoResult<Vec<Summary>> {
192-
let mut seen = HashSet::new();
193-
let mut ret = Vec::new();
191+
-> CargoResult<Option<Summary>> {
194192
for s in self.overrides.iter() {
195193
let src = self.sources.get_mut(s).unwrap();
196194
let dep = Dependency::new_override(dep.name(), s);
197-
ret.extend(try!(src.query(&dep)).into_iter().filter(|s| {
198-
seen.insert(s.name().to_string())
199-
}));
195+
let mut results = try!(src.query(&dep));
196+
if results.len() > 0 {
197+
return Ok(Some(results.remove(0)))
198+
}
200199
}
201-
Ok(ret)
200+
Ok(None)
202201
}
203202

204203
/// This function is used to transform a summary to another locked summary
@@ -271,25 +270,89 @@ impl<'cfg> PackageRegistry<'cfg> {
271270
}
272271
})
273272
}
273+
274+
fn warn_bad_override(&self,
275+
override_summary: &Summary,
276+
real_summary: &Summary) -> CargoResult<()> {
277+
let real = real_summary.package_id();
278+
let map = try!(self.locked.get(real.source_id()).chain_error(|| {
279+
human(format!("failed to find lock source of {}", real))
280+
}));
281+
let list = try!(map.get(real.name()).chain_error(|| {
282+
human(format!("failed to find lock name of {}", real))
283+
}));
284+
let &(_, ref real_deps) = try!(list.iter().find(|&&(ref id, _)| {
285+
real == id
286+
}).chain_error(|| {
287+
human(format!("failed to find lock version of {}", real))
288+
}));
289+
let mut real_deps = real_deps.clone();
290+
291+
let boilerplate = "\
292+
This is currently allowed but is known to produce buggy behavior with spurious
293+
recompiles and changes to the crate graph. Path overrides unfortunately were
294+
never intended to support this feature, so for now this message is just a
295+
warning. In the future, however, this message will become a hard error.
296+
297+
To change the dependency graph via an override it's recommended to use the
298+
`[replace]` feature of Cargo instead of the path override feature. This is
299+
documented online at the url below for more information.
300+
301+
http://doc.crates.io/specifying-dependencies.html#overriding-dependencies
302+
";
303+
304+
for dep in override_summary.dependencies() {
305+
if let Some(i) = real_deps.iter().position(|id| dep.matches_id(id)) {
306+
real_deps.remove(i);
307+
continue
308+
}
309+
let msg = format!("\
310+
path override for crate `{}` has altered the original list of\n\
311+
dependencies; the dependency on `{}` was either added or\n\
312+
modified to not match the previously resolved version\n\n\
313+
{}", override_summary.package_id().name(), dep.name(), boilerplate);
314+
try!(self.source_config.config().shell().warn(&msg));
315+
return Ok(())
316+
}
317+
318+
for id in real_deps {
319+
let msg = format!("\
320+
path override for crate `{}` has altered the original list of
321+
dependencies; the dependency on `{}` was removed\n\n
322+
{}", override_summary.package_id().name(), id.name(), boilerplate);
323+
try!(self.source_config.config().shell().warn(&msg));
324+
return Ok(())
325+
}
326+
327+
Ok(())
328+
}
274329
}
275330

276331
impl<'cfg> Registry for PackageRegistry<'cfg> {
277332
fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
278-
let overrides = try!(self.query_overrides(&dep));
279-
280-
let ret = if overrides.is_empty() {
281-
// Ensure the requested source_id is loaded
282-
try!(self.ensure_loaded(dep.source_id(), Kind::Normal).chain_error(|| {
283-
human(format!("failed to load source for a dependency \
284-
on `{}`", dep.name()))
285-
}));
286-
287-
match self.sources.get_mut(dep.source_id()) {
288-
Some(src) => try!(src.query(&dep)),
289-
None => Vec::new(),
333+
// Ensure the requested source_id is loaded
334+
try!(self.ensure_loaded(dep.source_id(), Kind::Normal).chain_error(|| {
335+
human(format!("failed to load source for a dependency \
336+
on `{}`", dep.name()))
337+
}));
338+
339+
let override_summary = try!(self.query_overrides(&dep));
340+
let real_summaries = match self.sources.get_mut(dep.source_id()) {
341+
Some(src) => Some(try!(src.query(&dep))),
342+
None => None,
343+
};
344+
345+
let ret = match (override_summary, real_summaries) {
346+
(Some(candidate), Some(summaries)) => {
347+
if summaries.len() != 1 {
348+
bail!("found an override with a non-locked list");
349+
}
350+
try!(self.warn_bad_override(&candidate, &summaries[0]));
351+
vec![candidate]
290352
}
291-
} else {
292-
overrides
353+
(Some(_), None) => bail!("override found but no real ones"),
354+
(None, Some(summaries)) => summaries,
355+
(None, None) => Vec::new(),
293356
};
294357

295358
// post-process all returned summaries to ensure that we lock all

src/cargo/sources/config.rs

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ impl<'cfg> SourceConfigMap<'cfg> {
6262
Ok(base)
6363
}
6464

65+
pub fn config(&self) -> &'cfg Config {
66+
self.config
67+
}
68+
6569
pub fn load(&self, id: &SourceId) -> CargoResult<Box<Source + 'cfg>> {
6670
debug!("loading: {}", id);
6771
let mut name = match self.id2name.get(id) {

tests/overrides.rs

+67
Original file line numberDiff line numberDiff line change
@@ -708,3 +708,70 @@ fn no_override_self() {
708708
assert_that(p.cargo_process("build").arg("--verbose"),
709709
execs().with_status(0));
710710
}
711+
712+
#[test]
713+
fn broken_path_override_warns() {
714+
Package::new("foo", "0.1.0").publish();
715+
Package::new("foo", "0.2.0").publish();
716+
717+
let p = project("local")
718+
.file("Cargo.toml", r#"
719+
[package]
720+
name = "local"
721+
version = "0.0.1"
722+
authors = []
723+
724+
[dependencies]
725+
a = { path = "a1" }
726+
"#)
727+
.file("src/lib.rs", "")
728+
.file("a1/Cargo.toml", r#"
729+
[package]
730+
name = "a"
731+
version = "0.0.1"
732+
authors = []
733+
734+
[dependencies]
735+
foo = "0.1"
736+
"#)
737+
.file("a1/src/lib.rs", "")
738+
.file("a2/Cargo.toml", r#"
739+
[package]
740+
name = "a"
741+
version = "0.0.1"
742+
authors = []
743+
744+
[dependencies]
745+
foo = "0.2"
746+
"#)
747+
.file("a2/src/lib.rs", "")
748+
.file(".cargo/config", r#"
749+
paths = ["a2"]
750+
"#);
751+
752+
assert_that(p.cargo_process("build"),
753+
execs().with_status(0)
754+
.with_stderr("\
755+
[UPDATING] [..]
756+
warning: path override for crate `a` has altered the original list of
757+
dependencies; the dependency on `foo` was either added or
758+
modified to not match the previously resolved version
759+
760+
This is currently allowed but is known to produce buggy behavior with spurious
761+
recompiles and changes to the crate graph. Path overrides unfortunately were
762+
never intended to support this feature, so for now this message is just a
763+
warning. In the future, however, this message will become a hard error.
764+
765+
To change the dependency graph via an override it's recommended to use the
766+
`[replace]` feature of Cargo instead of the path override feature. This is
767+
documented online at the url below for more information.
768+
769+
http://doc.crates.io/specifying-dependencies.html#overriding-dependencies
770+
771+
[DOWNLOADING] [..]
772+
[COMPILING] [..]
773+
[COMPILING] [..]
774+
[COMPILING] [..]
775+
[FINISHED] [..]
776+
"));
777+
}

0 commit comments

Comments
 (0)