Skip to content

Commit ec7bf4e

Browse files
committed
feat: delay file patches register to next round of resolution
Only after a few dependency resolution happen, can a `[patch]` entry with patch files know which version it is going to patch. Hence we need to "delay" the patch process until at least one resolution is done, and re-resolves the graph is any patch become applicable with the newly resolved dependency graph.
1 parent 549186b commit ec7bf4e

File tree

8 files changed

+698
-225
lines changed

8 files changed

+698
-225
lines changed

crates/cargo-util-schemas/src/core/package_id_spec.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,11 @@ mod tests {
625625
name: String::from("bar"),
626626
version: Some("1.2.0".parse().unwrap()),
627627
url: Some(Url::parse("patched+https://crates.io/foo").unwrap()),
628-
kind: Some(SourceKind::Patched(PatchInfo::new("bar".into(), "1.2.0".into(), vec!["/to/a.patch".into(), "/b.patch".into()]))),
628+
kind: Some(SourceKind::Patched(PatchInfo::Resolved {
629+
name: "bar".into(),
630+
version: "1.2.0".into(),
631+
patches: vec!["/to/a.patch".into(), "/b.patch".into()],
632+
})),
629633
},
630634
"patched+https://crates.io/foo?name=bar&version=1.2.0&patch=%2Fto%2Fa.patch&patch=%2Fb.patch#[email protected]",
631635
);

crates/cargo-util-schemas/src/core/source_kind.rs

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -211,26 +211,29 @@ impl<'a> std::fmt::Display for PrettyRef<'a> {
211211

212212
/// Information to find the source package and patch files.
213213
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
214-
pub struct PatchInfo {
215-
/// Name of the package to be patched.
216-
name: String,
217-
/// Verision of the package to be patched.
218-
version: String,
219-
/// Absolute paths to patch files.
220-
///
221-
/// These are absolute to ensure Cargo can locate them in the patching phase.
222-
patches: Vec<PathBuf>,
214+
pub enum PatchInfo {
215+
/// The package to be patched is known and finalized.
216+
Resolved {
217+
/// Name and version of the package to be patched.
218+
name: String,
219+
/// Version of the package to be patched.
220+
version: String,
221+
/// Absolute paths to patch files.
222+
///
223+
/// These are absolute to ensure Cargo can locate them in the patching phase.
224+
patches: Vec<PathBuf>,
225+
},
226+
/// The package to be patched hasn't yet be resolved. Usually after a few
227+
/// times of dependecy resolution, this will become [`PatchInfo::Resolved`].
228+
Deferred {
229+
/// Absolute paths to patch files.
230+
///
231+
/// These are absolute to ensure Cargo can locate them in the patching phase.
232+
patches: Vec<PathBuf>,
233+
},
223234
}
224235

225236
impl PatchInfo {
226-
pub fn new(name: String, version: String, patches: Vec<PathBuf>) -> PatchInfo {
227-
PatchInfo {
228-
name,
229-
version,
230-
patches,
231-
}
232-
}
233-
234237
/// Collects patch information from query string.
235238
///
236239
/// * `name` --- Package name
@@ -256,24 +259,34 @@ impl PatchInfo {
256259
if patches.is_empty() {
257260
return Err(PatchInfoError("path"));
258261
}
259-
Ok(PatchInfo::new(name, version, patches))
262+
Ok(PatchInfo::Resolved {
263+
name,
264+
version,
265+
patches,
266+
})
260267
}
261268

262269
/// As a URL query string.
263270
pub fn as_query(&self) -> PatchInfoQuery<'_> {
264271
PatchInfoQuery(self)
265272
}
266273

267-
pub fn name(&self) -> &str {
268-
self.name.as_str()
269-
}
270-
271-
pub fn version(&self) -> &str {
272-
self.version.as_str()
274+
pub fn finalize(self, name: String, version: String) -> Self {
275+
match self {
276+
PatchInfo::Deferred { patches } => PatchInfo::Resolved {
277+
name,
278+
version,
279+
patches,
280+
},
281+
_ => panic!("patch info has already finalized: {self:?}"),
282+
}
273283
}
274284

275285
pub fn patches(&self) -> &[PathBuf] {
276-
self.patches.as_slice()
286+
match self {
287+
PatchInfo::Resolved { patches, .. } => patches.as_slice(),
288+
PatchInfo::Deferred { patches } => patches.as_slice(),
289+
}
277290
}
278291
}
279292

@@ -282,22 +295,38 @@ pub struct PatchInfoQuery<'a>(&'a PatchInfo);
282295

283296
impl<'a> std::fmt::Display for PatchInfoQuery<'a> {
284297
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285-
write!(f, "name=")?;
286-
for value in url::form_urlencoded::byte_serialize(self.0.name.as_bytes()) {
287-
write!(f, "{value}")?;
288-
}
289-
write!(f, "&version=")?;
290-
for value in url::form_urlencoded::byte_serialize(self.0.version.as_bytes()) {
291-
write!(f, "{value}")?;
298+
match self.0 {
299+
PatchInfo::Resolved {
300+
name,
301+
version,
302+
patches,
303+
} => {
304+
f.write_str("name=")?;
305+
for value in url::form_urlencoded::byte_serialize(name.as_bytes()) {
306+
write!(f, "{value}")?;
307+
}
308+
f.write_str("&version=")?;
309+
for value in url::form_urlencoded::byte_serialize(version.as_bytes()) {
310+
write!(f, "{value}")?;
311+
}
312+
if !patches.is_empty() {
313+
f.write_str("&")?;
314+
}
315+
}
316+
_ => {}
292317
}
293-
for path in &self.0.patches {
294-
write!(f, "&patch=")?;
318+
319+
let mut patches = self.0.patches().iter().peekable();
320+
while let Some(path) = patches.next() {
321+
f.write_str("patch=")?;
295322
let path = path.to_str().expect("utf8 patch").replace("\\", "/");
296323
for value in url::form_urlencoded::byte_serialize(path.as_bytes()) {
297324
write!(f, "{value}")?;
298325
}
326+
if patches.peek().is_some() {
327+
f.write_str("&")?
328+
}
299329
}
300-
301330
Ok(())
302331
}
303332
}

src/cargo/core/registry.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,13 @@ impl<'gctx> PackageRegistry<'gctx> {
516516
&self.patches
517517
}
518518

519+
/// Removes all residual state of patches.
520+
pub fn clear_patch(&mut self) {
521+
self.patches = Default::default();
522+
self.patches_locked = false;
523+
self.patches_available = Default::default();
524+
}
525+
519526
/// Loads the [`Source`] for a given [`SourceId`] to this registry, making
520527
/// them available to resolution.
521528
fn load(&mut self, source_id: SourceId, kind: Kind) -> CargoResult<()> {

src/cargo/core/source_id.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,11 @@ impl SourceId {
396396
matches!(self.inner.kind, SourceKind::Git(_))
397397
}
398398

399+
/// Returns `true` if this source is patched by patch files.
400+
pub fn is_patched(self) -> bool {
401+
matches!(self.inner.kind, SourceKind::Patched(_))
402+
}
403+
399404
/// Creates an implementation of `Source` corresponding to this ID.
400405
///
401406
/// * `yanked_whitelist` --- Packages allowed to be used, even if they are yanked.
@@ -451,6 +456,14 @@ impl SourceId {
451456
}
452457
}
453458

459+
/// Gets the patch information if this is a patched source, otherwise `None`.
460+
pub fn patch_info(self) -> Option<&'static PatchInfo> {
461+
match &self.inner.kind {
462+
SourceKind::Patched(i) => Some(i),
463+
_ => None,
464+
}
465+
}
466+
454467
/// Check if the precise data field has bean set
455468
pub fn has_precise(self) -> bool {
456469
self.inner.precise.is_some()
@@ -689,9 +702,10 @@ impl fmt::Display for SourceId {
689702
SourceKind::Patched(ref patch_info) => {
690703
let n = patch_info.patches().len();
691704
let plural = if n == 1 { "" } else { "s" };
692-
let name = patch_info.name();
693-
let version = patch_info.version();
694-
write!(f, "{name}@{version} with {n} patch file{plural}")
705+
if let PatchInfo::Resolved { name, version, .. } = &patch_info {
706+
write!(f, "{name}@{version} ")?;
707+
}
708+
write!(f, "with {n} patch file{plural}")
695709
}
696710
}
697711
}
@@ -889,11 +903,15 @@ mod tests {
889903
assert_eq!(gen_hash(source_id), 17459999773908528552);
890904
assert_eq!(crate::util::hex::short_hash(&source_id), "6568fe2c2fab5bfe");
891905

892-
let patch_info = PatchInfo::new("foo".into(), "1.0.0".into(), vec![path.into()]);
906+
let patch_info = PatchInfo::Resolved {
907+
name: "foo".into(),
908+
version: "1.0.0".into(),
909+
patches: vec![path.into()],
910+
};
893911
let registry_source_id = SourceId::for_registry(&url).unwrap();
894912
let source_id = SourceId::for_patches(registry_source_id, patch_info).unwrap();
895-
assert_eq!(gen_hash(source_id), 10476212805277277232);
896-
assert_eq!(crate::util::hex::short_hash(&source_id), "45f3b913ab447282");
913+
assert_eq!(gen_hash(source_id), 15459318675065232737);
914+
assert_eq!(crate::util::hex::short_hash(&source_id), "87ca345b36470e4d");
897915
}
898916

899917
#[test]

0 commit comments

Comments
 (0)