Skip to content

Commit 932d7b8

Browse files
Filter wheels from PEP 751 files based on --no-binary et al in uv pip compile (#16956)
## Summary Like in `uv.lock`, we should omit artifacts that are filtered out by `--no-binary` or by the target platform tags. Closes #13413.
1 parent 49b70e7 commit 932d7b8

3 files changed

Lines changed: 247 additions & 153 deletions

File tree

crates/uv-resolver/src/lock/export/pylock_toml.rs

Lines changed: 164 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,16 @@ struct PylockTomlAttestationIdentity {
330330

331331
impl<'lock> PylockToml {
332332
/// Construct a [`PylockToml`] from a [`ResolverOutput`].
333+
///
334+
/// If `tags` is provided, only wheels compatible with the given tags will be included.
335+
/// If `build_options` is provided, packages marked as `--only-binary` will not include
336+
/// source distributions.
333337
pub fn from_resolution(
334338
resolution: &ResolverOutput,
335339
omit: &[PackageName],
336340
install_path: &Path,
341+
tags: Option<&Tags>,
342+
build_options: &BuildOptions,
337343
) -> Result<Self, PylockTomlErrorKind> {
338344
// The lock version is always `1.0` at time of writing.
339345
let lock_version = Version::new([1, 0]);
@@ -417,71 +423,93 @@ impl<'lock> PylockToml {
417423
});
418424
}
419425
Dist::Built(BuiltDist::Registry(dist)) => {
420-
package.wheels = Some(
421-
dist.wheels
426+
// Filter wheels based on build options (--no-binary).
427+
let no_binary = build_options.no_binary_package(dist.name());
428+
429+
if !no_binary {
430+
// Filter wheels based on tag compatibility.
431+
let wheels: Vec<_> = dist
432+
.wheels
422433
.iter()
423-
.map(|wheel| {
424-
let url = wheel
425-
.file
426-
.url
427-
.to_url()
428-
.map_err(PylockTomlErrorKind::ToUrl)?;
429-
Ok(PylockTomlWheel {
430-
// Optional "when the last component of path/ url would be the same value".
431-
name: if url
432-
.filename()
433-
.is_ok_and(|filename| filename == *wheel.file.filename)
434-
{
435-
None
436-
} else {
437-
Some(wheel.filename.clone())
438-
},
439-
upload_time: wheel
440-
.file
441-
.upload_time_utc_ms
442-
.map(Timestamp::from_millisecond)
443-
.transpose()?,
444-
url: Some(
445-
wheel
434+
.filter(|wheel| {
435+
tags.is_none_or(|tags| {
436+
wheel.filename.compatibility(tags).is_compatible()
437+
})
438+
})
439+
.collect();
440+
441+
if !wheels.is_empty() {
442+
package.wheels = Some(
443+
wheels
444+
.into_iter()
445+
.map(|wheel| {
446+
let url = wheel
446447
.file
447448
.url
448449
.to_url()
449-
.map_err(PylockTomlErrorKind::ToUrl)?,
450-
),
451-
path: None,
452-
size: wheel.file.size,
453-
hashes: Hashes::from(wheel.file.hashes.clone()),
454-
})
455-
})
456-
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
457-
);
450+
.map_err(PylockTomlErrorKind::ToUrl)?;
451+
Ok(PylockTomlWheel {
452+
// Optional "when the last component of path/ url would be the same value".
453+
name: if url.filename().is_ok_and(|filename| {
454+
filename == *wheel.file.filename
455+
}) {
456+
None
457+
} else {
458+
Some(wheel.filename.clone())
459+
},
460+
upload_time: wheel
461+
.file
462+
.upload_time_utc_ms
463+
.map(Timestamp::from_millisecond)
464+
.transpose()?,
465+
url: Some(
466+
wheel
467+
.file
468+
.url
469+
.to_url()
470+
.map_err(PylockTomlErrorKind::ToUrl)?,
471+
),
472+
path: None,
473+
size: wheel.file.size,
474+
hashes: Hashes::from(wheel.file.hashes.clone()),
475+
})
476+
})
477+
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
478+
);
479+
}
480+
}
458481

459-
if let Some(sdist) = dist.sdist.as_ref() {
460-
let url = sdist
461-
.file
462-
.url
463-
.to_url()
464-
.map_err(PylockTomlErrorKind::ToUrl)?;
465-
package.sdist = Some(PylockTomlSdist {
466-
// Optional "when the last component of path/ url would be the same value".
467-
name: if url
468-
.filename()
469-
.is_ok_and(|filename| filename == *sdist.file.filename)
470-
{
471-
None
472-
} else {
473-
Some(sdist.file.filename.clone())
474-
},
475-
upload_time: sdist
482+
// Filter sdist based on build options (--only-binary).
483+
let no_build = build_options.no_build_package(dist.name());
484+
485+
if !no_build {
486+
if let Some(sdist) = dist.sdist.as_ref() {
487+
let url = sdist
476488
.file
477-
.upload_time_utc_ms
478-
.map(Timestamp::from_millisecond)
479-
.transpose()?,
480-
url: Some(url),
481-
path: None,
482-
size: sdist.file.size,
483-
hashes: Hashes::from(sdist.file.hashes.clone()),
484-
});
489+
.url
490+
.to_url()
491+
.map_err(PylockTomlErrorKind::ToUrl)?;
492+
package.sdist = Some(PylockTomlSdist {
493+
// Optional "when the last component of path/ url would be the same value".
494+
name: if url
495+
.filename()
496+
.is_ok_and(|filename| filename == *sdist.file.filename)
497+
{
498+
None
499+
} else {
500+
Some(sdist.file.filename.clone())
501+
},
502+
upload_time: sdist
503+
.file
504+
.upload_time_utc_ms
505+
.map(Timestamp::from_millisecond)
506+
.transpose()?,
507+
url: Some(url),
508+
path: None,
509+
size: sdist.file.size,
510+
hashes: Hashes::from(sdist.file.hashes.clone()),
511+
});
512+
}
485513
}
486514
}
487515
Dist::Source(SourceDist::DirectUrl(dist)) => {
@@ -530,66 +558,88 @@ impl<'lock> PylockToml {
530558
});
531559
}
532560
Dist::Source(SourceDist::Registry(dist)) => {
533-
package.wheels = Some(
534-
dist.wheels
561+
// Filter wheels based on build options (--no-binary).
562+
let no_binary = build_options.no_binary_package(&dist.name);
563+
564+
if !no_binary {
565+
// Filter wheels based on tag compatibility.
566+
let wheels: Vec<_> = dist
567+
.wheels
535568
.iter()
536-
.map(|wheel| {
537-
let url = wheel
538-
.file
539-
.url
540-
.to_url()
541-
.map_err(PylockTomlErrorKind::ToUrl)?;
542-
Ok(PylockTomlWheel {
543-
// Optional "when the last component of path/ url would be the same value".
544-
name: if url
545-
.filename()
546-
.is_ok_and(|filename| filename == *wheel.file.filename)
547-
{
548-
None
549-
} else {
550-
Some(wheel.filename.clone())
551-
},
552-
upload_time: wheel
553-
.file
554-
.upload_time_utc_ms
555-
.map(Timestamp::from_millisecond)
556-
.transpose()?,
557-
url: Some(
558-
wheel
569+
.filter(|wheel| {
570+
tags.is_none_or(|tags| {
571+
wheel.filename.compatibility(tags).is_compatible()
572+
})
573+
})
574+
.collect();
575+
576+
if !wheels.is_empty() {
577+
package.wheels = Some(
578+
wheels
579+
.into_iter()
580+
.map(|wheel| {
581+
let url = wheel
559582
.file
560583
.url
561584
.to_url()
562-
.map_err(PylockTomlErrorKind::ToUrl)?,
563-
),
564-
path: None,
565-
size: wheel.file.size,
566-
hashes: Hashes::from(wheel.file.hashes.clone()),
567-
})
568-
})
569-
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
570-
);
585+
.map_err(PylockTomlErrorKind::ToUrl)?;
586+
Ok(PylockTomlWheel {
587+
// Optional "when the last component of path/ url would be the same value".
588+
name: if url.filename().is_ok_and(|filename| {
589+
filename == *wheel.file.filename
590+
}) {
591+
None
592+
} else {
593+
Some(wheel.filename.clone())
594+
},
595+
upload_time: wheel
596+
.file
597+
.upload_time_utc_ms
598+
.map(Timestamp::from_millisecond)
599+
.transpose()?,
600+
url: Some(
601+
wheel
602+
.file
603+
.url
604+
.to_url()
605+
.map_err(PylockTomlErrorKind::ToUrl)?,
606+
),
607+
path: None,
608+
size: wheel.file.size,
609+
hashes: Hashes::from(wheel.file.hashes.clone()),
610+
})
611+
})
612+
.collect::<Result<Vec<_>, PylockTomlErrorKind>>()?,
613+
);
614+
}
615+
}
571616

572-
let url = dist.file.url.to_url().map_err(PylockTomlErrorKind::ToUrl)?;
573-
package.sdist = Some(PylockTomlSdist {
574-
// Optional "when the last component of path/ url would be the same value".
575-
name: if url
576-
.filename()
577-
.is_ok_and(|filename| filename == *dist.file.filename)
578-
{
579-
None
580-
} else {
581-
Some(dist.file.filename.clone())
582-
},
583-
upload_time: dist
584-
.file
585-
.upload_time_utc_ms
586-
.map(Timestamp::from_millisecond)
587-
.transpose()?,
588-
url: Some(url),
589-
path: None,
590-
size: dist.file.size,
591-
hashes: Hashes::from(dist.file.hashes.clone()),
592-
});
617+
// Filter sdist based on build options (--only-binary).
618+
let no_build = build_options.no_build_package(&dist.name);
619+
620+
if !no_build {
621+
let url = dist.file.url.to_url().map_err(PylockTomlErrorKind::ToUrl)?;
622+
package.sdist = Some(PylockTomlSdist {
623+
// Optional "when the last component of path/ url would be the same value".
624+
name: if url
625+
.filename()
626+
.is_ok_and(|filename| filename == *dist.file.filename)
627+
{
628+
None
629+
} else {
630+
Some(dist.file.filename.clone())
631+
},
632+
upload_time: dist
633+
.file
634+
.upload_time_utc_ms
635+
.map(Timestamp::from_millisecond)
636+
.transpose()?,
637+
url: Some(url),
638+
path: None,
639+
size: dist.file.size,
640+
hashes: Hashes::from(dist.file.hashes.clone()),
641+
});
642+
}
593643
}
594644
}
595645

crates/uv/src/commands/pip/compile.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,13 @@ pub(crate) async fn pip_compile(
741741
};
742742

743743
// Convert the resolution to a `pylock.toml` file.
744-
let export = PylockToml::from_resolution(&resolution, &no_emit_packages, install_path)?;
744+
let export = PylockToml::from_resolution(
745+
&resolution,
746+
&no_emit_packages,
747+
install_path,
748+
tags.as_deref(),
749+
&build_options,
750+
)?;
745751
write!(writer, "{}", export.to_toml()?)?;
746752
}
747753
}

0 commit comments

Comments
 (0)