Skip to content

Commit afa3ace

Browse files
committed
Make patches automatically update if updated.
1 parent b78cb37 commit afa3ace

File tree

3 files changed

+487
-104
lines changed

3 files changed

+487
-104
lines changed

src/cargo/core/registry.rs

Lines changed: 95 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
22

33
use anyhow::bail;
44
use log::{debug, trace};
5-
use semver::{Version, VersionReq};
5+
use semver::VersionReq;
66
use url::Url;
77

88
use crate::core::PackageSet;
@@ -233,13 +233,24 @@ impl<'cfg> PackageRegistry<'cfg> {
233233
/// Note that the patch list specified here *will not* be available to
234234
/// `query` until `lock_patches` is called below, which should be called
235235
/// once all patches have been added.
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.
236240
pub fn patch(
237241
&mut self,
238242
url: &Url,
239-
deps: &[(&Dependency, Option<Dependency>)],
240-
) -> CargoResult<()> {
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.
241249
let canonical = CanonicalUrl::new(url)?;
242250

251+
// Return value of patches that shouldn't be locked.
252+
let mut unlock_patches = Vec::new();
253+
243254
// First up we need to actually resolve each `deps` specification to
244255
// precisely one summary. We're not using the `query` method below as it
245256
// internally uses maps we're building up as part of this method
@@ -251,8 +262,15 @@ impl<'cfg> PackageRegistry<'cfg> {
251262
// of summaries which should be the same length as `deps` above.
252263
let unlocked_summaries = deps
253264
.iter()
254-
.map(|(orig_dep, locked_dep)| {
255-
let dep = locked_dep.as_ref().unwrap_or(orig_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+
};
256274
debug!(
257275
"registering a patch for `{}` with `{}`",
258276
url,
@@ -275,7 +293,21 @@ impl<'cfg> PackageRegistry<'cfg> {
275293
.get_mut(dep.source_id())
276294
.expect("loaded source not present");
277295
let summaries = source.query_vec(dep)?;
278-
let summary = summary_for_patch(orig_dep, locked_dep, url, summaries, source)?;
296+
let (summary, should_unlock) =
297+
summary_for_patch(orig_patch, &locked, summaries, source).chain_err(|| {
298+
format!(
299+
"patch for `{}` in `{}` did not resolve to any crates",
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));
310+
}
279311

280312
if *summary.package_id().source_id().canonical_url() == canonical {
281313
anyhow::bail!(
@@ -313,7 +345,7 @@ impl<'cfg> PackageRegistry<'cfg> {
313345
self.patches_available.insert(canonical.clone(), ids);
314346
self.patches.insert(canonical, unlocked_summaries);
315347

316-
Ok(())
348+
Ok(unlock_patches)
317349
}
318350

319351
/// Lock all patch summaries added via `patch`, making them available to
@@ -327,6 +359,7 @@ impl<'cfg> PackageRegistry<'cfg> {
327359
assert!(!self.patches_locked);
328360
for summaries in self.patches.values_mut() {
329361
for summary in summaries {
362+
debug!("locking patch {:?}", summary);
330363
*summary = lock(&self.locked, &self.patches_available, summary.clone());
331364
}
332365
}
@@ -711,60 +744,68 @@ fn lock(
711744
})
712745
}
713746

714-
/// This is a helper for generating a user-friendly error message for a bad patch.
747+
/// This is a helper for selecting the summary, or generating a helpful error message.
715748
fn summary_for_patch(
716749
orig_patch: &Dependency,
717-
locked_patch: &Option<Dependency>,
718-
url: &Url,
750+
locked: &Option<(Dependency, PackageId)>,
719751
mut summaries: Vec<Summary>,
720752
source: &mut dyn Source,
721-
) -> CargoResult<Summary> {
753+
) -> CargoResult<(Summary, Option<PackageId>)> {
722754
if summaries.len() == 1 {
723-
return Ok(summaries.pop().unwrap());
755+
return Ok((summaries.pop().unwrap(), None));
724756
}
725-
// Helpers to create a comma-separated string of versions.
726-
let versions = |versions: &mut [&Version]| -> String {
727-
versions.sort();
728-
let versions: Vec<_> = versions.into_iter().map(|v| v.to_string()).collect();
729-
versions.join(", ")
730-
};
731-
let summary_versions = |summaries: &[Summary]| -> String {
732-
let mut vers: Vec<_> = summaries.iter().map(|summary| summary.version()).collect();
733-
versions(&mut vers)
757+
let best_summary = |summaries: &mut Vec<Summary>| -> Summary {
758+
// TODO: This could maybe honor -Zminimal-versions?
759+
summaries.sort_by(|a, b| a.version().cmp(b.version()));
760+
summaries.pop().unwrap()
734761
};
735762
if summaries.len() > 1 {
736-
anyhow::bail!(
737-
"patch for `{}` in `{}` resolved to more than one candidate\n\
738-
Found versions: {}\n\
739-
Update the patch definition to select only one package, \
740-
or remove the extras from the patch location.",
741-
orig_patch.package_name(),
742-
url,
743-
summary_versions(&summaries)
744-
);
763+
let summary = best_summary(&mut summaries);
764+
if let Some((_dep, lock_id)) = locked {
765+
// I can't think of a scenario where this might happen (locked by
766+
// definition should only match at most one summary). Maybe if the
767+
// source is broken?
768+
return Ok((summary, Some(*lock_id)));
769+
} else {
770+
return Ok((summary, None));
771+
}
745772
}
773+
assert!(summaries.is_empty());
746774
// No summaries found, try to help the user figure out what is wrong.
747-
let extra = if let Some(locked_patch) = locked_patch {
748-
let found = match source.query_vec(orig_patch) {
749-
Ok(unlocked_summaries) => format!(" (found {})", summary_versions(&unlocked_summaries)),
775+
if let Some((locked_patch, locked_id)) = locked {
776+
// Since the locked patch did not match anything, try the unlocked one.
777+
let mut orig_matches = match source.query_vec(orig_patch) {
778+
Ok(summaries) => summaries,
750779
Err(e) => {
751780
log::warn!(
752781
"could not determine unlocked summaries for dep {:?}: {:?}",
753782
orig_patch,
754783
e
755784
);
756-
"".to_string()
785+
Vec::new()
757786
}
758787
};
759-
format!(
760-
"The patch is locked to {} in Cargo.lock, \
761-
but the version in the patch location does not match{}.\n\
762-
Make sure the patch points to the correct version.\n\
763-
If it does, run `cargo update -p {}` to update Cargo.lock.",
764-
locked_patch.version_req(),
765-
found,
766-
locked_patch.package_name(),
767-
)
788+
if orig_matches.is_empty() {
789+
// This should be relatively unusual. For example, a patch of
790+
// {version="0.1.2", ...} and the patch location no longer contains a
791+
// version that matches "0.1.2". It is unusual to explicitly write a
792+
// version in the patch.
793+
anyhow::bail!(
794+
"The patch is locked to {} in Cargo.lock, but the version in the \
795+
patch location does not match any packages in the patch location.\n\
796+
Make sure the patch points to the correct version.",
797+
locked_patch.version_req(),
798+
);
799+
}
800+
let summary = best_summary(&mut orig_matches);
801+
debug!(
802+
"locked patch no longer matches, but unlocked version should work. \
803+
locked={:?} unlocked={:?} summary={:?}",
804+
locked, orig_patch, summary
805+
);
806+
// The unlocked version found a match. This returns a value to
807+
// indicate that this entry should be unlocked.
808+
return Ok((summary, Some(*locked_id)));
768809
} else {
769810
// Try checking if there are *any* packages that match this by name.
770811
let name_only_dep =
@@ -777,8 +818,12 @@ fn summary_for_patch(
777818
.collect::<Vec<_>>();
778819
match vers.len() {
779820
0 => format!(""),
780-
1 => format!("version `{}`", versions(&mut vers)),
781-
_ => format!("versions `{}`", versions(&mut vers)),
821+
1 => format!("version `{}`", vers[0]),
822+
_ => {
823+
vers.sort();
824+
let strs: Vec<_> = vers.into_iter().map(|v| v.to_string()).collect();
825+
format!("versions `{}`", strs.join(", "))
826+
}
782827
}
783828
}
784829
Err(e) => {
@@ -791,27 +836,21 @@ fn summary_for_patch(
791836
}
792837
};
793838
if found.is_empty() {
794-
format!(
839+
anyhow::bail!(
795840
"The patch location does not appear to contain any packages \
796841
matching the name `{}`.",
797842
orig_patch.package_name()
798-
)
843+
);
799844
} else {
800-
format!(
845+
anyhow::bail!(
801846
"The patch location contains a `{}` package with {}, but the patch \
802847
definition requires `{}`.\n\
803848
Check that the version in the patch location is what you expect, \
804849
and update the patch definition to match.",
805850
orig_patch.package_name(),
806851
found,
807852
orig_patch.version_req()
808-
)
853+
);
809854
}
810-
};
811-
anyhow::bail!(
812-
"patch for `{}` in `{}` did not resolve to any crates.\n{}",
813-
orig_patch.package_name(),
814-
url,
815-
extra
816-
);
855+
}
817856
}

src/cargo/ops/resolve.rs

Lines changed: 44 additions & 22 deletions
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,37 +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 => {
256243
let patches: Vec<_> = patches.iter().map(|p| (p, None)).collect();
257-
registry.patch(url, &patches)?;
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());
258247
continue;
259248
}
260249
};
@@ -263,19 +252,52 @@ pub fn resolve_with_previous<'cfg>(
263252
.map(|dep| {
264253
let unused = previous.unused_patches().iter().cloned();
265254
let candidates = previous.iter().chain(unused);
266-
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+
{
267259
Some(id) => {
268260
let mut locked_dep = dep.clone();
269261
locked_dep.lock_to(id);
270-
(dep, Some(locked_dep))
262+
(dep, Some((locked_dep, id)))
271263
}
272264
None => (dep, None),
273265
}
274266
})
275267
.collect::<Vec<_>>();
276-
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+
}
277278
}
279+
}
280+
debug!("avoid_patch_ids={:?}", avoid_patch_ids);
278281

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 {
279301
registry.lock_patches();
280302
}
281303

0 commit comments

Comments
 (0)