Skip to content

Commit d662f25

Browse files
committed
Auto merge of #8248 - ehuss:patch-err-help, r=alexcrichton
Provide better error messages for a bad `patch`. This attempts to provide more user-friendly error messages for some situations with a bad `patch`. This is a follow-up to #8243. I think this more or less covers all the issues from #4678. I imagine there are other corner cases, but those will need to wait for another day. The main one I can think of is when the patch location is missing required features. Today you get a "blah was not used in the crate graph." warning, with some suggestions added in #6470, but it doesn't actually check if there is a feature mismatch. Closes #4678
2 parents 9b94513 + 4f2bae9 commit d662f25

File tree

3 files changed

+686
-53
lines changed

3 files changed

+686
-53
lines changed

src/cargo/core/registry.rs

+146-27
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,35 @@ impl<'cfg> PackageRegistry<'cfg> {
222222
/// the manifest.
223223
///
224224
/// Here the `deps` will be resolved to a precise version and stored
225-
/// internally for future calls to `query` below. It's expected that `deps`
226-
/// have had `lock_to` call already, if applicable. (e.g., if a lock file was
227-
/// already present).
225+
/// internally for future calls to `query` below. `deps` should be a tuple
226+
/// where the first element is the patch definition straight from the
227+
/// manifest, and the second element is an optional variant where the
228+
/// patch has been locked. This locked patch is the patch locked to
229+
/// a specific version found in Cargo.lock. This will be `None` if
230+
/// `Cargo.lock` doesn't exist, or the patch did not match any existing
231+
/// entries in `Cargo.lock`.
228232
///
229233
/// Note that the patch list specified here *will not* be available to
230234
/// `query` until `lock_patches` is called below, which should be called
231235
/// once all patches have been added.
232-
pub fn patch(&mut self, url: &Url, deps: &[Dependency]) -> CargoResult<()> {
236+
///
237+
/// The return value is a `Vec` of patches that should *not* be locked.
238+
/// This happens when the patch is locked, but the patch has been updated
239+
/// so the locked value is no longer correct.
240+
pub fn patch(
241+
&mut self,
242+
url: &Url,
243+
deps: &[(&Dependency, Option<(Dependency, PackageId)>)],
244+
) -> CargoResult<Vec<(Dependency, PackageId)>> {
245+
// NOTE: None of this code is aware of required features. If a patch
246+
// is missing a required feature, you end up with an "unused patch"
247+
// warning, which is very hard to understand. Ideally the warning
248+
// would be tailored to indicate *why* it is unused.
233249
let canonical = CanonicalUrl::new(url)?;
234250

251+
// Return value of patches that shouldn't be locked.
252+
let mut unlock_patches = Vec::new();
253+
235254
// First up we need to actually resolve each `deps` specification to
236255
// precisely one summary. We're not using the `query` method below as it
237256
// internally uses maps we're building up as part of this method
@@ -243,7 +262,15 @@ impl<'cfg> PackageRegistry<'cfg> {
243262
// of summaries which should be the same length as `deps` above.
244263
let unlocked_summaries = deps
245264
.iter()
246-
.map(|dep| {
265+
.map(|(orig_patch, locked)| {
266+
// Remove double reference in orig_patch. Is there maybe a
267+
// magic pattern that could avoid this?
268+
let orig_patch = *orig_patch;
269+
// Use the locked patch if it exists, otherwise use the original.
270+
let dep = match locked {
271+
Some((locked_patch, _locked_id)) => locked_patch,
272+
None => orig_patch,
273+
};
247274
debug!(
248275
"registering a patch for `{}` with `{}`",
249276
url,
@@ -261,30 +288,27 @@ impl<'cfg> PackageRegistry<'cfg> {
261288
)
262289
})?;
263290

264-
let mut summaries = self
291+
let source = self
265292
.sources
266293
.get_mut(dep.source_id())
267-
.expect("loaded source not present")
268-
.query_vec(dep)?
269-
.into_iter();
270-
271-
let summary = match summaries.next() {
272-
Some(summary) => summary,
273-
None => anyhow::bail!(
274-
"patch for `{}` in `{}` did not resolve to any crates. If this is \
275-
unexpected, you may wish to consult: \
276-
https://github.com/rust-lang/cargo/issues/4678",
277-
dep.package_name(),
278-
url
279-
),
280-
};
281-
if summaries.next().is_some() {
282-
anyhow::bail!(
283-
"patch for `{}` in `{}` resolved to more than one candidate",
284-
dep.package_name(),
285-
url
286-
)
294+
.expect("loaded source not present");
295+
let summaries = source.query_vec(dep)?;
296+
let (summary, should_unlock) =
297+
summary_for_patch(orig_patch, &locked, summaries, source).chain_err(|| {
298+
format!(
299+
"patch for `{}` in `{}` failed to resolve",
300+
orig_patch.package_name(),
301+
url,
302+
)
303+
})?;
304+
debug!(
305+
"patch summary is {:?} should_unlock={:?}",
306+
summary, should_unlock
307+
);
308+
if let Some(unlock_id) = should_unlock {
309+
unlock_patches.push((orig_patch.clone(), unlock_id));
287310
}
311+
288312
if *summary.package_id().source_id().canonical_url() == canonical {
289313
anyhow::bail!(
290314
"patch for `{}` in `{}` points to the same source, but \
@@ -321,7 +345,7 @@ impl<'cfg> PackageRegistry<'cfg> {
321345
self.patches_available.insert(canonical.clone(), ids);
322346
self.patches.insert(canonical, unlocked_summaries);
323347

324-
Ok(())
348+
Ok(unlock_patches)
325349
}
326350

327351
/// Lock all patch summaries added via `patch`, making them available to
@@ -335,6 +359,7 @@ impl<'cfg> PackageRegistry<'cfg> {
335359
assert!(!self.patches_locked);
336360
for summaries in self.patches.values_mut() {
337361
for summary in summaries {
362+
debug!("locking patch {:?}", summary);
338363
*summary = lock(&self.locked, &self.patches_available, summary.clone());
339364
}
340365
}
@@ -718,3 +743,97 @@ fn lock(
718743
dep
719744
})
720745
}
746+
747+
/// This is a helper for selecting the summary, or generating a helpful error message.
748+
fn summary_for_patch(
749+
orig_patch: &Dependency,
750+
locked: &Option<(Dependency, PackageId)>,
751+
mut summaries: Vec<Summary>,
752+
source: &mut dyn Source,
753+
) -> CargoResult<(Summary, Option<PackageId>)> {
754+
if summaries.len() == 1 {
755+
return Ok((summaries.pop().unwrap(), None));
756+
}
757+
if summaries.len() > 1 {
758+
// TODO: In the future, it might be nice to add all of these
759+
// candidates so that version selection would just pick the
760+
// appropriate one. However, as this is currently structured, if we
761+
// added these all as patches, the unselected versions would end up in
762+
// the "unused patch" listing, and trigger a warning. It might take a
763+
// fair bit of restructuring to make that work cleanly, and there
764+
// isn't any demand at this time to support that.
765+
let mut vers: Vec<_> = summaries.iter().map(|summary| summary.version()).collect();
766+
vers.sort();
767+
let versions: Vec<_> = vers.into_iter().map(|v| v.to_string()).collect();
768+
anyhow::bail!(
769+
"patch for `{}` in `{}` resolved to more than one candidate\n\
770+
Found versions: {}\n\
771+
Update the patch definition to select only one package.\n\
772+
For example, add an `=` version requirement to the patch definition, \
773+
such as `version = \"={}\"`.",
774+
orig_patch.package_name(),
775+
orig_patch.source_id(),
776+
versions.join(", "),
777+
versions.last().unwrap()
778+
);
779+
}
780+
assert!(summaries.is_empty());
781+
// No summaries found, try to help the user figure out what is wrong.
782+
if let Some((_locked_patch, locked_id)) = locked {
783+
// Since the locked patch did not match anything, try the unlocked one.
784+
let orig_matches = source.query_vec(orig_patch).unwrap_or_else(|e| {
785+
log::warn!(
786+
"could not determine unlocked summaries for dep {:?}: {:?}",
787+
orig_patch,
788+
e
789+
);
790+
Vec::new()
791+
});
792+
let (summary, _) = summary_for_patch(orig_patch, &None, orig_matches, source)?;
793+
// The unlocked version found a match. This returns a value to
794+
// indicate that this entry should be unlocked.
795+
return Ok((summary, Some(*locked_id)));
796+
}
797+
// Try checking if there are *any* packages that match this by name.
798+
let name_only_dep = Dependency::new_override(orig_patch.package_name(), orig_patch.source_id());
799+
let name_summaries = source.query_vec(&name_only_dep).unwrap_or_else(|e| {
800+
log::warn!(
801+
"failed to do name-only summary query for {:?}: {:?}",
802+
name_only_dep,
803+
e
804+
);
805+
Vec::new()
806+
});
807+
let mut vers = name_summaries
808+
.iter()
809+
.map(|summary| summary.version())
810+
.collect::<Vec<_>>();
811+
let found = match vers.len() {
812+
0 => format!(""),
813+
1 => format!("version `{}`", vers[0]),
814+
_ => {
815+
vers.sort();
816+
let strs: Vec<_> = vers.into_iter().map(|v| v.to_string()).collect();
817+
format!("versions `{}`", strs.join(", "))
818+
}
819+
};
820+
if found.is_empty() {
821+
anyhow::bail!(
822+
"The patch location `{}` does not appear to contain any packages \
823+
matching the name `{}`.",
824+
orig_patch.source_id(),
825+
orig_patch.package_name()
826+
);
827+
} else {
828+
anyhow::bail!(
829+
"The patch location `{}` contains a `{}` package with {}, but the patch \
830+
definition requires `{}`.\n\
831+
Check that the version in the patch location is what you expect, \
832+
and update the patch definition to match.",
833+
orig_patch.source_id(),
834+
orig_patch.package_name(),
835+
found,
836+
orig_patch.version_req()
837+
);
838+
}
839+
}

src/cargo/ops/resolve.rs

+48-25
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::core::{PackageId, PackageIdSpec, PackageSet, Source, SourceId, Worksp
2020
use crate::ops;
2121
use crate::sources::PathSource;
2222
use crate::util::errors::{CargoResult, CargoResultExt};
23-
use crate::util::profile;
23+
use crate::util::{profile, CanonicalUrl};
2424
use log::{debug, trace};
2525
use std::collections::HashSet;
2626

@@ -224,36 +224,26 @@ pub fn resolve_with_previous<'cfg>(
224224
);
225225
}
226226

227-
let keep = |p: &PackageId| {
227+
let pre_patch_keep = |p: &PackageId| {
228228
!to_avoid_sources.contains(&p.source_id())
229229
&& match to_avoid {
230230
Some(set) => !set.contains(p),
231231
None => true,
232232
}
233233
};
234234

235-
// In the case where a previous instance of resolve is available, we
236-
// want to lock as many packages as possible to the previous version
237-
// without disturbing the graph structure.
238-
let mut try_to_use = HashSet::new();
239-
if let Some(r) = previous {
240-
trace!("previous: {:?}", r);
241-
register_previous_locks(ws, registry, r, &keep);
242-
243-
// Everything in the previous lock file we want to keep is prioritized
244-
// in dependency selection if it comes up, aka we want to have
245-
// conservative updates.
246-
try_to_use.extend(r.iter().filter(keep).inspect(|id| {
247-
debug!("attempting to prefer {}", id);
248-
}));
249-
}
250-
235+
// This is a set of PackageIds of `[patch]` entries that should not be
236+
// locked.
237+
let mut avoid_patch_ids = HashSet::new();
251238
if register_patches {
252239
for (url, patches) in ws.root_patch() {
253240
let previous = match previous {
254241
Some(r) => r,
255242
None => {
256-
registry.patch(url, patches)?;
243+
let patches: Vec<_> = patches.iter().map(|p| (p, None)).collect();
244+
let unlock_ids = registry.patch(url, &patches)?;
245+
// Since nothing is locked, this shouldn't possibly return anything.
246+
assert!(unlock_ids.is_empty());
257247
continue;
258248
}
259249
};
@@ -262,19 +252,52 @@ pub fn resolve_with_previous<'cfg>(
262252
.map(|dep| {
263253
let unused = previous.unused_patches().iter().cloned();
264254
let candidates = previous.iter().chain(unused);
265-
match candidates.filter(keep).find(|&id| dep.matches_id(id)) {
255+
match candidates
256+
.filter(pre_patch_keep)
257+
.find(|&id| dep.matches_id(id))
258+
{
266259
Some(id) => {
267-
let mut dep = dep.clone();
268-
dep.lock_to(id);
269-
dep
260+
let mut locked_dep = dep.clone();
261+
locked_dep.lock_to(id);
262+
(dep, Some((locked_dep, id)))
270263
}
271-
None => dep.clone(),
264+
None => (dep, None),
272265
}
273266
})
274267
.collect::<Vec<_>>();
275-
registry.patch(url, &patches)?;
268+
let canonical = CanonicalUrl::new(url)?;
269+
for (orig_patch, unlock_id) in registry.patch(url, &patches)? {
270+
// Avoid the locked patch ID.
271+
avoid_patch_ids.insert(unlock_id);
272+
// Also avoid the thing it is patching.
273+
avoid_patch_ids.extend(previous.iter().filter(|id| {
274+
orig_patch.matches_ignoring_source(*id)
275+
&& *id.source_id().canonical_url() == canonical
276+
}));
277+
}
276278
}
279+
}
280+
debug!("avoid_patch_ids={:?}", avoid_patch_ids);
277281

282+
let keep = |p: &PackageId| pre_patch_keep(p) && !avoid_patch_ids.contains(p);
283+
284+
// In the case where a previous instance of resolve is available, we
285+
// want to lock as many packages as possible to the previous version
286+
// without disturbing the graph structure.
287+
let mut try_to_use = HashSet::new();
288+
if let Some(r) = previous {
289+
trace!("previous: {:?}", r);
290+
register_previous_locks(ws, registry, r, &keep);
291+
292+
// Everything in the previous lock file we want to keep is prioritized
293+
// in dependency selection if it comes up, aka we want to have
294+
// conservative updates.
295+
try_to_use.extend(r.iter().filter(keep).inspect(|id| {
296+
debug!("attempting to prefer {}", id);
297+
}));
298+
}
299+
300+
if register_patches {
278301
registry.lock_patches();
279302
}
280303

0 commit comments

Comments
 (0)