mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Track supported Python range in lockfile (#4065)
## Summary This PR adds the `Requires-Python` range to the user's lockfile. This will enable us to validate it when installing. For now, we repeat the `Requires-Python` back to the user; alternatively, though, we could detect the supported Python range automatically. See: https://github.com/astral-sh/uv/issues/4052
This commit is contained in:
parent
8596525d97
commit
642cef0dad
6 changed files with 58 additions and 9 deletions
|
@ -20,7 +20,7 @@ use distribution_types::{
|
|||
GitSourceDist, IndexUrl, PathBuiltDist, PathSourceDist, RegistryBuiltDist, RegistryBuiltWheel,
|
||||
RegistrySourceDist, RemoteSource, Resolution, ResolvedDist, ToUrlError,
|
||||
};
|
||||
use pep440_rs::Version;
|
||||
use pep440_rs::{Version, VersionSpecifiers};
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree, VerbatimUrl};
|
||||
use platform_tags::{TagCompatibility, TagPriority, Tags};
|
||||
use pypi_types::{HashDigest, ParsedArchiveUrl, ParsedGitUrl};
|
||||
|
@ -36,6 +36,8 @@ use crate::{lock, ResolutionGraph};
|
|||
pub struct Lock {
|
||||
version: u32,
|
||||
distributions: Vec<Distribution>,
|
||||
/// The range of supported Python versions.
|
||||
requires_python: Option<VersionSpecifiers>,
|
||||
/// A map from distribution ID to index in `distributions`.
|
||||
///
|
||||
/// This can be used to quickly lookup the full distribution for any ID
|
||||
|
@ -87,15 +89,21 @@ impl Lock {
|
|||
}
|
||||
}
|
||||
|
||||
let lock = Self::new(locked_dists.into_values().collect())?;
|
||||
let distributions = locked_dists.into_values().collect();
|
||||
let requires_python = graph.requires_python.clone();
|
||||
let lock = Self::new(distributions, requires_python)?;
|
||||
Ok(lock)
|
||||
}
|
||||
|
||||
/// Initialize a [`Lock`] from a list of [`Distribution`] entries.
|
||||
fn new(distributions: Vec<Distribution>) -> Result<Self, LockError> {
|
||||
fn new(
|
||||
distributions: Vec<Distribution>,
|
||||
requires_python: Option<VersionSpecifiers>,
|
||||
) -> Result<Self, LockError> {
|
||||
let wire = LockWire {
|
||||
version: 1,
|
||||
distributions,
|
||||
requires_python,
|
||||
};
|
||||
Self::try_from(wire)
|
||||
}
|
||||
|
@ -196,6 +204,8 @@ struct LockWire {
|
|||
version: u32,
|
||||
#[serde(rename = "distribution")]
|
||||
distributions: Vec<Distribution>,
|
||||
#[serde(rename = "requires-python")]
|
||||
requires_python: Option<VersionSpecifiers>,
|
||||
}
|
||||
|
||||
impl From<Lock> for LockWire {
|
||||
|
@ -203,6 +213,7 @@ impl From<Lock> for LockWire {
|
|||
LockWire {
|
||||
version: lock.version,
|
||||
distributions: lock.distributions,
|
||||
requires_python: lock.requires_python,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,6 +226,10 @@ impl Lock {
|
|||
let mut doc = toml_edit::DocumentMut::new();
|
||||
doc.insert("version", value(i64::from(self.version)));
|
||||
|
||||
if let Some(ref requires_python) = self.requires_python {
|
||||
doc.insert("requires-python", value(requires_python.to_string()));
|
||||
}
|
||||
|
||||
let mut distributions = ArrayOfTables::new();
|
||||
for dist in &self.distributions {
|
||||
let mut table = Table::new();
|
||||
|
@ -344,6 +359,7 @@ impl TryFrom<LockWire> for Lock {
|
|||
Ok(Lock {
|
||||
version: wire.version,
|
||||
distributions: wire.distributions,
|
||||
requires_python: wire.requires_python,
|
||||
by_id,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -60,13 +60,14 @@ impl PythonRequirement {
|
|||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum RequiresPython {
|
||||
/// The `RequiresPython` specifier is a single version specifier, as provided via
|
||||
/// The [`RequiresPython`] specifier is a single version specifier, as provided via
|
||||
/// `--python-version` on the command line.
|
||||
///
|
||||
/// The use of a separate enum variant allows us to use a verbatim representation when reporting
|
||||
/// back to the user.
|
||||
Specifier(StringVersion),
|
||||
/// The `RequiresPython` specifier is a set of version specifiers.
|
||||
/// The [`RequiresPython`] specifier is a set of version specifiers, as extracted from the
|
||||
/// `Requires-Python` field in a `pyproject.toml` or `METADATA` file.
|
||||
Specifiers(VersionSpecifiers),
|
||||
}
|
||||
|
||||
|
@ -93,6 +94,14 @@ impl RequiresPython {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifiers`] for the [`RequiresPython`] specifier.
|
||||
pub fn as_specifiers(&self) -> Option<&VersionSpecifiers> {
|
||||
match self {
|
||||
RequiresPython::Specifier(_) => None,
|
||||
RequiresPython::Specifiers(specifiers) => Some(specifiers),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RequiresPython {
|
||||
|
|
|
@ -9,7 +9,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|||
use distribution_types::{
|
||||
Dist, DistributionMetadata, Name, ResolutionDiagnostic, VersionId, VersionOrUrlRef,
|
||||
};
|
||||
use pep440_rs::{Version, VersionSpecifier};
|
||||
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
|
||||
use pep508_rs::{MarkerEnvironment, MarkerTree};
|
||||
use pypi_types::{ParsedUrlError, Yanked};
|
||||
use uv_git::GitResolver;
|
||||
|
@ -17,10 +17,13 @@ use uv_normalize::{ExtraName, PackageName};
|
|||
|
||||
use crate::preferences::Preferences;
|
||||
use crate::pubgrub::{PubGrubDistribution, PubGrubPackageInner};
|
||||
use crate::python_requirement::RequiresPython;
|
||||
use crate::redirect::url_to_precise;
|
||||
use crate::resolution::AnnotatedDist;
|
||||
use crate::resolver::Resolution;
|
||||
use crate::{InMemoryIndex, Manifest, MetadataResponse, ResolveError, VersionsResponse};
|
||||
use crate::{
|
||||
InMemoryIndex, Manifest, MetadataResponse, PythonRequirement, ResolveError, VersionsResponse,
|
||||
};
|
||||
|
||||
/// A complete resolution graph in which every node represents a pinned package and every edge
|
||||
/// represents a dependency between two pinned packages.
|
||||
|
@ -28,6 +31,8 @@ use crate::{InMemoryIndex, Manifest, MetadataResponse, ResolveError, VersionsRes
|
|||
pub struct ResolutionGraph {
|
||||
/// The underlying graph.
|
||||
pub(crate) petgraph: Graph<AnnotatedDist, Version, Directed>,
|
||||
/// The range of supported Python versions.
|
||||
pub(crate) requires_python: Option<VersionSpecifiers>,
|
||||
/// Any diagnostics that were encountered while building the graph.
|
||||
pub(crate) diagnostics: Vec<ResolutionDiagnostic>,
|
||||
}
|
||||
|
@ -39,9 +44,10 @@ impl ResolutionGraph {
|
|||
index: &InMemoryIndex,
|
||||
preferences: &Preferences,
|
||||
git: &GitResolver,
|
||||
python: &PythonRequirement,
|
||||
resolution: Resolution,
|
||||
) -> anyhow::Result<Self, ResolveError> {
|
||||
// Collect all marker expressions from relevant pubgrub packages.
|
||||
// Collect all marker expressions from relevant PubGrub packages.
|
||||
let mut markers: FxHashMap<(&PackageName, &Version, &Option<ExtraName>), MarkerTree> =
|
||||
FxHashMap::default();
|
||||
for (package, versions) in &resolution.packages {
|
||||
|
@ -267,8 +273,17 @@ impl ResolutionGraph {
|
|||
}
|
||||
}
|
||||
|
||||
// Extract the `Requires-Python` range, if provided.
|
||||
// TODO(charlie): Infer the supported Python range from the `Requires-Python` of the
|
||||
// included packages.
|
||||
let requires_python = python
|
||||
.target()
|
||||
.and_then(RequiresPython::as_specifiers)
|
||||
.cloned();
|
||||
|
||||
Ok(Self {
|
||||
petgraph,
|
||||
requires_python,
|
||||
diagnostics,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -557,7 +557,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
for resolution in resolutions {
|
||||
combined.union(resolution);
|
||||
}
|
||||
ResolutionGraph::from_state(&self.index, &self.preferences, &self.git, combined)
|
||||
ResolutionGraph::from_state(
|
||||
&self.index,
|
||||
&self.preferences,
|
||||
&self.git,
|
||||
&self.python_requirement,
|
||||
combined,
|
||||
)
|
||||
}
|
||||
|
||||
/// Visit a [`PubGrubPackage`] prior to selection. This should be called on a [`PubGrubPackage`]
|
||||
|
|
|
@ -73,6 +73,7 @@ Ok(
|
|||
optional_dependencies: {},
|
||||
},
|
||||
],
|
||||
requires_python: None,
|
||||
by_id: {
|
||||
DistributionId {
|
||||
name: PackageName(
|
||||
|
|
|
@ -1038,6 +1038,7 @@ fn lock_requires_python() -> Result<()> {
|
|||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.7"
|
||||
|
||||
[[distribution]]
|
||||
name = "dataclasses"
|
||||
|
@ -1163,6 +1164,7 @@ fn lock_requires_python() -> Result<()> {
|
|||
assert_snapshot!(
|
||||
lock, @r###"
|
||||
version = 1
|
||||
requires-python = ">=3.7.9, <4"
|
||||
|
||||
[[distribution]]
|
||||
name = "attrs"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue