mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-17 10:53:37 +00:00
Model Python version as a PubGrub package (#745)
## Summary This PR modifies the resolver to treat the Python version as a package, which allows for better error messages (since we no longer treat incompatible packages as if they "don't exist at all"). There are a few tricky pieces here... First, we need to track both the interpreter's Python version and the _target_ Python version, because we support resolving for other versions via `--python 3.7`. Second, we allow using incompatible wheels during resolution, as long as there's a compatible source distribution. So we still need to test for `requires-python` compatibility when selecting distributions. This could use more testing, but it feels like an area where `packse` would be more productive than writing PyPI tests. Closes https://github.com/astral-sh/puffin/issues/406.
This commit is contained in:
parent
5a98add54e
commit
fd556ccd44
20 changed files with 294 additions and 80 deletions
|
|
@ -5,16 +5,15 @@ use chrono::{DateTime, Utc};
|
|||
use tracing::{instrument, warn};
|
||||
|
||||
use distribution_filename::DistFilename;
|
||||
use pep508_rs::MarkerEnvironment;
|
||||
use platform_tags::{TagPriority, Tags};
|
||||
use puffin_client::SimpleMetadata;
|
||||
use puffin_interpreter::Interpreter;
|
||||
use puffin_normalize::PackageName;
|
||||
use puffin_warnings::warn_user_once;
|
||||
use pypi_types::Yanked;
|
||||
|
||||
use crate::file::{DistFile, SdistFile, WheelFile};
|
||||
use crate::pubgrub::PubGrubVersion;
|
||||
use crate::python_requirement::PythonRequirement;
|
||||
use crate::yanks::AllowedYanks;
|
||||
|
||||
/// A map from versions to distributions.
|
||||
|
|
@ -23,13 +22,12 @@ pub struct VersionMap(BTreeMap<PubGrubVersion, PrioritizedDistribution>);
|
|||
|
||||
impl VersionMap {
|
||||
/// Initialize a [`VersionMap`] from the given metadata.
|
||||
#[instrument(skip_all, fields(package_name = %package_name))]
|
||||
#[instrument(skip_all, fields(package_name = % package_name))]
|
||||
pub(crate) fn from_metadata(
|
||||
metadata: SimpleMetadata,
|
||||
package_name: &PackageName,
|
||||
tags: &Tags,
|
||||
markers: &MarkerEnvironment,
|
||||
interpreter: &Interpreter,
|
||||
python_requirement: &PythonRequirement,
|
||||
allowed_yanks: &AllowedYanks,
|
||||
exclude_newer: Option<&DateTime<Utc>>,
|
||||
) -> Self {
|
||||
|
|
@ -39,23 +37,6 @@ impl VersionMap {
|
|||
// Collect compatible distributions.
|
||||
for (version, files) in metadata {
|
||||
for (filename, file) in files.all() {
|
||||
// Only add dists compatible with the python version. This is relevant for source
|
||||
// distributions which give no other indication of their compatibility and wheels which
|
||||
// may be tagged `py3-none-any` but have `requires-python: ">=3.9"`.
|
||||
// TODO(konstin): https://github.com/astral-sh/puffin/issues/406
|
||||
if let Some(requires_python) = file.requires_python.as_ref() {
|
||||
// The interpreter and marker version are often the same, but can differ. For
|
||||
// example, if the user is resolving against a target Python version passed in
|
||||
// via the command-line, that version will differ from the interpreter version.
|
||||
let interpreter_version = interpreter.version();
|
||||
let marker_version = &markers.python_version.version;
|
||||
if !requires_python.contains(interpreter_version)
|
||||
|| !requires_python.contains(marker_version)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Support resolving as if it were an earlier timestamp, at least as long files have
|
||||
// upload time information
|
||||
if let Some(exclude_newer) = exclude_newer {
|
||||
|
|
@ -86,8 +67,17 @@ impl VersionMap {
|
|||
|
||||
match filename {
|
||||
DistFilename::WheelFilename(filename) => {
|
||||
let priority = filename.compatibility(tags);
|
||||
|
||||
// To be compatible, the wheel must both have compatible tags _and_ have a
|
||||
// compatible Python requirement.
|
||||
let priority = filename.compatibility(tags).filter(|_| {
|
||||
file.requires_python
|
||||
.as_ref()
|
||||
.map_or(true, |requires_python| {
|
||||
python_requirement
|
||||
.versions()
|
||||
.all(|version| requires_python.contains(version))
|
||||
})
|
||||
});
|
||||
match version_map.entry(version.clone().into()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().insert_built(WheelFile(file), priority);
|
||||
|
|
@ -201,12 +191,12 @@ impl PrioritizedDistribution {
|
|||
) {
|
||||
// Prefer the highest-priority, platform-compatible wheel.
|
||||
(Some((wheel, _)), _, _) => Some(ResolvableFile::CompatibleWheel(wheel)),
|
||||
// If we have a source distribution and an incompatible wheel, return the wheel.
|
||||
// We assume that all distributions have the same metadata for a given package version.
|
||||
// If a source distribution exists, we assume we can build it, but using the wheel is
|
||||
// faster.
|
||||
// If we have a compatible source distribution and an incompatible wheel, return the
|
||||
// wheel. We assume that all distributions have the same metadata for a given package
|
||||
// version. If a compatible source distribution exists, we assume we can build it, but
|
||||
// using the wheel is faster.
|
||||
(_, Some(sdist), Some(wheel)) => Some(ResolvableFile::IncompatibleWheel(sdist, wheel)),
|
||||
// Otherwise, return the source distribution.
|
||||
// Otherwise, if we have a source distribution, return it.
|
||||
(_, Some(sdist), _) => Some(ResolvableFile::SourceDist(sdist)),
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue