mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-19 03:28:42 +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
|
|
@ -207,7 +207,15 @@ pub(crate) async fn pip_compile(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolver = Resolver::new(manifest, options, &markers, tags, &client, &build_dispatch)
|
let resolver = Resolver::new(
|
||||||
|
manifest,
|
||||||
|
options,
|
||||||
|
&markers,
|
||||||
|
&interpreter,
|
||||||
|
tags,
|
||||||
|
&client,
|
||||||
|
&build_dispatch,
|
||||||
|
)
|
||||||
.with_reporter(ResolverReporter::from(printer));
|
.with_reporter(ResolverReporter::from(printer));
|
||||||
let resolution = match resolver.resolve().await {
|
let resolution = match resolver.resolve().await {
|
||||||
Err(puffin_resolver::ResolveError::NoSolution(err)) => {
|
Err(puffin_resolver::ResolveError::NoSolution(err)) => {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use puffin_dispatch::BuildDispatch;
|
||||||
use puffin_installer::{
|
use puffin_installer::{
|
||||||
BuiltEditable, Downloader, InstallPlan, Reinstall, ResolvedEditable, SitePackages,
|
BuiltEditable, Downloader, InstallPlan, Reinstall, ResolvedEditable, SitePackages,
|
||||||
};
|
};
|
||||||
use puffin_interpreter::Virtualenv;
|
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use puffin_resolver::{
|
use puffin_resolver::{
|
||||||
Manifest, PreReleaseMode, ResolutionGraph, ResolutionMode, ResolutionOptions, Resolver,
|
Manifest, PreReleaseMode, ResolutionGraph, ResolutionMode, ResolutionOptions, Resolver,
|
||||||
|
|
@ -173,6 +173,7 @@ pub(crate) async fn pip_install(
|
||||||
&editables,
|
&editables,
|
||||||
&site_packages,
|
&site_packages,
|
||||||
reinstall,
|
reinstall,
|
||||||
|
&interpreter,
|
||||||
tags,
|
tags,
|
||||||
markers,
|
markers,
|
||||||
&client,
|
&client,
|
||||||
|
|
@ -320,6 +321,7 @@ async fn resolve(
|
||||||
editables: &[BuiltEditable],
|
editables: &[BuiltEditable],
|
||||||
site_packages: &SitePackages<'_>,
|
site_packages: &SitePackages<'_>,
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
|
interpreter: &Interpreter,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
markers: &MarkerEnvironment,
|
markers: &MarkerEnvironment,
|
||||||
client: &RegistryClient,
|
client: &RegistryClient,
|
||||||
|
|
@ -361,7 +363,15 @@ async fn resolve(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolver = Resolver::new(manifest, options, markers, tags, client, build_dispatch)
|
let resolver = Resolver::new(
|
||||||
|
manifest,
|
||||||
|
options,
|
||||||
|
markers,
|
||||||
|
interpreter,
|
||||||
|
tags,
|
||||||
|
client,
|
||||||
|
build_dispatch,
|
||||||
|
)
|
||||||
.with_reporter(ResolverReporter::from(printer));
|
.with_reporter(ResolverReporter::from(printer));
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -669,8 +669,9 @@ fn compile_python_37() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because there is no version of black available matching ==23.10.1 and
|
╰─▶ Because there is no version of Python available matching >=3.8 and
|
||||||
root depends on black==23.10.1, version solving failed.
|
black==23.10.1 depends on Python>=3.8, black==23.10.1 is forbidden.
|
||||||
|
And because root depends on black==23.10.1, version solving failed.
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1552,8 +1553,8 @@ fn conflicting_transitive_url_dependency() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because there is no version of werkzeug available matching >=3.0.0 and
|
╰─▶ Because flask==3.0.0 depends on werkzeug>=3.0.0 and there is no version
|
||||||
flask==3.0.0 depends on werkzeug>=3.0.0, flask==3.0.0 is forbidden.
|
of werkzeug available matching >=3.0.0, flask==3.0.0 is forbidden.
|
||||||
And because root depends on flask==3.0.0, version solving failed.
|
And because root depends on flask==3.0.0, version solving failed.
|
||||||
"###);
|
"###);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -74,11 +74,11 @@ fn no_solution() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
× No solution found when resolving dependencies:
|
× No solution found when resolving dependencies:
|
||||||
╰─▶ Because flask==3.0.0 depends on werkzeug>=3.0.0 and there is no
|
╰─▶ Because there is no version of flask available matching >3.0.0 and
|
||||||
version of flask available matching >3.0.0, flask>=3.0.0 depends on
|
flask==3.0.0 depends on werkzeug>=3.0.0, flask>=3.0.0 depends on
|
||||||
werkzeug>=3.0.0.
|
werkzeug>=3.0.0.
|
||||||
And because root depends on werkzeug<1.0.0 and root depends on
|
And because root depends on flask>=3.0.0 and root depends on
|
||||||
flask>=3.0.0, version solving failed.
|
werkzeug<1.0.0, version solving failed.
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
||||||
Manifest::simple(args.requirements.clone()),
|
Manifest::simple(args.requirements.clone()),
|
||||||
ResolutionOptions::default(),
|
ResolutionOptions::default(),
|
||||||
venv.interpreter().markers(),
|
venv.interpreter().markers(),
|
||||||
|
venv.interpreter(),
|
||||||
tags,
|
tags,
|
||||||
&client,
|
&client,
|
||||||
&build_dispatch,
|
&build_dispatch,
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
||||||
self.no_build
|
self.no_build
|
||||||
}
|
}
|
||||||
|
|
||||||
//#[instrument(skip(self, requirements), fields(requirements = requirements.iter().map(ToString::to_string).join(", ")))]
|
|
||||||
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
|
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
|
||||||
let markers = self.interpreter.markers();
|
let markers = self.interpreter.markers();
|
||||||
let tags = self.interpreter.tags()?;
|
let tags = self.interpreter.tags()?;
|
||||||
|
|
@ -89,6 +88,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
||||||
Manifest::simple(requirements.to_vec()),
|
Manifest::simple(requirements.to_vec()),
|
||||||
self.options,
|
self.options,
|
||||||
markers,
|
markers,
|
||||||
|
self.interpreter,
|
||||||
tags,
|
tags,
|
||||||
self.client,
|
self.client,
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use pubgrub::range::Range;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use distribution_types::{Dist, DistributionMetadata, IndexUrl, Name};
|
use distribution_types::{Dist, DistributionMetadata, IndexUrl, Name};
|
||||||
|
use pep440_rs::VersionSpecifiers;
|
||||||
use pep508_rs::{Requirement, VersionOrUrl};
|
use pep508_rs::{Requirement, VersionOrUrl};
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use pypi_types::BaseUrl;
|
use pypi_types::BaseUrl;
|
||||||
|
|
@ -9,6 +10,7 @@ use pypi_types::BaseUrl;
|
||||||
use crate::file::DistFile;
|
use crate::file::DistFile;
|
||||||
use crate::prerelease_mode::PreReleaseStrategy;
|
use crate::prerelease_mode::PreReleaseStrategy;
|
||||||
use crate::pubgrub::PubGrubVersion;
|
use crate::pubgrub::PubGrubVersion;
|
||||||
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::resolution_mode::ResolutionStrategy;
|
use crate::resolution_mode::ResolutionStrategy;
|
||||||
use crate::version_map::{ResolvableFile, VersionMap};
|
use crate::version_map::{ResolvableFile, VersionMap};
|
||||||
use crate::{Manifest, ResolutionOptions};
|
use crate::{Manifest, ResolutionOptions};
|
||||||
|
|
@ -254,6 +256,30 @@ impl<'a> Candidate<'a> {
|
||||||
self.file.install()
|
self.file.install()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If the candidate doesn't the given requirement, return the version specifiers.
|
||||||
|
pub(crate) fn validate(&self, requirement: &PythonRequirement) -> Option<&VersionSpecifiers> {
|
||||||
|
// Validate against the _installed_ file. It's fine if the _resolved_ file is incompatible,
|
||||||
|
// since it could be an incompatible wheel. (If the resolved file is an incompatible source
|
||||||
|
// distribution, then the resolved and installed file will be the same anyway.)
|
||||||
|
let requires_python = self.install().requires_python.as_ref()?;
|
||||||
|
|
||||||
|
// If the candidate doesn't support the target Python version, return the failing version
|
||||||
|
// specifiers.
|
||||||
|
if !requires_python.contains(requirement.target()) {
|
||||||
|
return Some(requires_python);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the candidate is a source distribution, and doesn't support the installed Python
|
||||||
|
// version, return the failing version specifiers, since we won't be able to build it.
|
||||||
|
if self.install().is_sdist() {
|
||||||
|
if !requires_python.contains(requirement.installed()) {
|
||||||
|
return Some(requires_python);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the [`Dist`] to use when resolving the candidate.
|
/// Return the [`Dist`] to use when resolving the candidate.
|
||||||
pub(crate) fn into_distribution(self, index: IndexUrl, base: BaseUrl) -> Dist {
|
pub(crate) fn into_distribution(self, index: IndexUrl, base: BaseUrl) -> Dist {
|
||||||
Dist::from_registry(
|
Dist::from_registry(
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ use puffin_traits::OnceMap;
|
||||||
use pypi_types::BaseUrl;
|
use pypi_types::BaseUrl;
|
||||||
|
|
||||||
use crate::candidate_selector::CandidateSelector;
|
use crate::candidate_selector::CandidateSelector;
|
||||||
use crate::pubgrub::{PubGrubHints, PubGrubPackage, PubGrubReportFormatter, PubGrubVersion};
|
use crate::pubgrub::{
|
||||||
|
PubGrubHints, PubGrubPackage, PubGrubPython, PubGrubReportFormatter, PubGrubVersion,
|
||||||
|
};
|
||||||
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::version_map::VersionMap;
|
use crate::version_map::VersionMap;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -159,11 +162,26 @@ impl NoSolutionError {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn with_available_versions(
|
pub(crate) fn with_available_versions(
|
||||||
mut self,
|
mut self,
|
||||||
|
python_requirement: &PythonRequirement,
|
||||||
package_versions: &OnceMap<PackageName, (IndexUrl, BaseUrl, VersionMap)>,
|
package_versions: &OnceMap<PackageName, (IndexUrl, BaseUrl, VersionMap)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut available_versions = FxHashMap::default();
|
let mut available_versions = FxHashMap::default();
|
||||||
for package in self.derivation_tree.packages() {
|
for package in self.derivation_tree.packages() {
|
||||||
if let PubGrubPackage::Package(name, ..) = package {
|
match package {
|
||||||
|
PubGrubPackage::Root(_) => {}
|
||||||
|
PubGrubPackage::Python(PubGrubPython::Installed) => {
|
||||||
|
available_versions.insert(
|
||||||
|
package.clone(),
|
||||||
|
vec![PubGrubVersion::from(python_requirement.installed().clone())],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PubGrubPackage::Python(PubGrubPython::Target) => {
|
||||||
|
available_versions.insert(
|
||||||
|
package.clone(),
|
||||||
|
vec![PubGrubVersion::from(python_requirement.target().clone())],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PubGrubPackage::Package(name, ..) => {
|
||||||
if let Some(entry) = package_versions.get(name) {
|
if let Some(entry) = package_versions.get(name) {
|
||||||
let (_, _, version_map) = entry.value();
|
let (_, _, version_map) = entry.value();
|
||||||
available_versions.insert(
|
available_versions.insert(
|
||||||
|
|
@ -176,6 +194,7 @@ impl NoSolutionError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.available_versions = available_versions;
|
self.available_versions = available_versions;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,13 @@ impl DistFile {
|
||||||
Self::Sdist(sdist) => sdist.filename.as_str(),
|
Self::Sdist(sdist) => sdist.filename.as_str(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_sdist(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Wheel(_) => false,
|
||||||
|
Self::Sdist(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DistFile> for File {
|
impl From<DistFile> for File {
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,6 @@ impl<'a> DistFinder<'a> {
|
||||||
// This is relevant for source dists which give no other indication of their
|
// This is relevant for source dists which give no other indication of their
|
||||||
// compatibility and wheels which may be tagged `py3-none-any` but
|
// compatibility and wheels which may be tagged `py3-none-any` but
|
||||||
// have `requires-python: ">=3.9"`
|
// have `requires-python: ">=3.9"`
|
||||||
// TODO(konstin): https://github.com/astral-sh/puffin/issues/406
|
|
||||||
if !file
|
if !file
|
||||||
.requires_python
|
.requires_python
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -170,7 +169,6 @@ impl<'a> DistFinder<'a> {
|
||||||
// This is relevant for source dists which give no other indication of their
|
// This is relevant for source dists which give no other indication of their
|
||||||
// compatibility and wheels which may be tagged `py3-none-any` but
|
// compatibility and wheels which may be tagged `py3-none-any` but
|
||||||
// have `requires-python: ">=3.9"`
|
// have `requires-python: ">=3.9"`
|
||||||
// TODO(konstin): https://github.com/astral-sh/puffin/issues/406
|
|
||||||
if !file
|
if !file
|
||||||
.requires_python
|
.requires_python
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ mod overrides;
|
||||||
mod pins;
|
mod pins;
|
||||||
mod prerelease_mode;
|
mod prerelease_mode;
|
||||||
mod pubgrub;
|
mod pubgrub;
|
||||||
|
mod python_requirement;
|
||||||
mod resolution;
|
mod resolution;
|
||||||
mod resolution_mode;
|
mod resolution_mode;
|
||||||
mod resolution_options;
|
mod resolution_options;
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,9 @@ fn merge_package(
|
||||||
// Either package is `root`.
|
// Either package is `root`.
|
||||||
(PubGrubPackage::Root(_), _) | (_, PubGrubPackage::Root(_)) => Ok(None),
|
(PubGrubPackage::Root(_), _) | (_, PubGrubPackage::Root(_)) => Ok(None),
|
||||||
|
|
||||||
|
// Either package is the Python installation.
|
||||||
|
(PubGrubPackage::Python(_), _) | (_, PubGrubPackage::Python(_)) => Ok(None),
|
||||||
|
|
||||||
// Left package has a URL. Propagate the URL.
|
// Left package has a URL. Propagate the URL.
|
||||||
(PubGrubPackage::Package(name, extra, Some(url)), PubGrubPackage::Package(.., None)) => {
|
(PubGrubPackage::Package(name, extra, Some(url)), PubGrubPackage::Package(.., None)) => {
|
||||||
Ok(Some(PubGrubPackage::Package(
|
Ok(Some(PubGrubPackage::Package(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies;
|
pub(crate) use crate::pubgrub::dependencies::PubGrubDependencies;
|
||||||
pub(crate) use crate::pubgrub::distribution::PubGrubDistribution;
|
pub(crate) use crate::pubgrub::distribution::PubGrubDistribution;
|
||||||
pub(crate) use crate::pubgrub::package::PubGrubPackage;
|
pub(crate) use crate::pubgrub::package::{PubGrubPackage, PubGrubPython};
|
||||||
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority};
|
pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority};
|
||||||
pub(crate) use crate::pubgrub::report::{PubGrubHints, PubGrubReportFormatter};
|
pub(crate) use crate::pubgrub::report::{PubGrubHints, PubGrubReportFormatter};
|
||||||
|
pub(crate) use crate::pubgrub::specifier::PubGrubSpecifier;
|
||||||
pub(crate) use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
pub(crate) use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
||||||
|
|
||||||
mod dependencies;
|
mod dependencies;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ use puffin_normalize::{ExtraName, PackageName};
|
||||||
#[derive(Debug, Clone, Eq, Derivative)]
|
#[derive(Debug, Clone, Eq, Derivative)]
|
||||||
#[derivative(PartialEq, Hash)]
|
#[derivative(PartialEq, Hash)]
|
||||||
pub enum PubGrubPackage {
|
pub enum PubGrubPackage {
|
||||||
|
/// The root package, which is used to start the resolution process.
|
||||||
Root(Option<PackageName>),
|
Root(Option<PackageName>),
|
||||||
|
/// A Python version.
|
||||||
|
Python(PubGrubPython),
|
||||||
|
/// A Python package.
|
||||||
Package(
|
Package(
|
||||||
PackageName,
|
PackageName,
|
||||||
Option<ExtraName>,
|
Option<ExtraName>,
|
||||||
|
|
@ -70,6 +74,14 @@ pub enum PubGrubPackage {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum PubGrubPython {
|
||||||
|
/// The Python version installed in the current environment.
|
||||||
|
Installed,
|
||||||
|
/// The Python version for which dependencies are being resolved.
|
||||||
|
Target,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for PubGrubPackage {
|
impl std::fmt::Display for PubGrubPackage {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -80,6 +92,7 @@ impl std::fmt::Display for PubGrubPackage {
|
||||||
write!(f, "root")
|
write!(f, "root")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PubGrubPackage::Python(_) => write!(f, "Python"),
|
||||||
PubGrubPackage::Package(name, None, ..) => write!(f, "{name}"),
|
PubGrubPackage::Package(name, None, ..) => write!(f, "{name}"),
|
||||||
PubGrubPackage::Package(name, Some(extra), ..) => {
|
PubGrubPackage::Package(name, Some(extra), ..) => {
|
||||||
write!(f, "{name}[{extra}]")
|
write!(f, "{name}[{extra}]")
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ impl PubGrubPriorities {
|
||||||
pub(crate) fn get(&self, package: &PubGrubPackage) -> Option<PubGrubPriority> {
|
pub(crate) fn get(&self, package: &PubGrubPackage) -> Option<PubGrubPriority> {
|
||||||
match package {
|
match package {
|
||||||
PubGrubPackage::Root(_) => Some(Reverse(0)),
|
PubGrubPackage::Root(_) => Some(Reverse(0)),
|
||||||
|
PubGrubPackage::Python(_) => Some(Reverse(0)),
|
||||||
PubGrubPackage::Package(name, _, _) => self
|
PubGrubPackage::Package(name, _, _) => self
|
||||||
.0
|
.0
|
||||||
.get(name)
|
.get(name)
|
||||||
|
|
|
||||||
37
crates/puffin-resolver/src/python_requirement.rs
Normal file
37
crates/puffin-resolver/src/python_requirement.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use pep440_rs::Version;
|
||||||
|
use pep508_rs::MarkerEnvironment;
|
||||||
|
use puffin_interpreter::Interpreter;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PythonRequirement<'a> {
|
||||||
|
/// The installed version of Python.
|
||||||
|
installed: &'a Version,
|
||||||
|
/// The target version of Python; that is, the version of Python for which we are resolving
|
||||||
|
/// dependencies. This is typically the same as the installed version, but may be different
|
||||||
|
/// when specifying an alternate Python version for the resolution.
|
||||||
|
target: &'a Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PythonRequirement<'a> {
|
||||||
|
pub fn new(interpreter: &'a Interpreter, markers: &'a MarkerEnvironment) -> Self {
|
||||||
|
Self {
|
||||||
|
installed: interpreter.version(),
|
||||||
|
target: &markers.python_version.version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the installed version of Python.
|
||||||
|
pub(crate) fn installed(&self) -> &'a Version {
|
||||||
|
self.installed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the target version of Python.
|
||||||
|
pub(crate) fn target(&self) -> &'a Version {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the versions of Python to consider when resolving dependencies.
|
||||||
|
pub(crate) fn versions(&self) -> impl Iterator<Item = &'a Version> {
|
||||||
|
std::iter::once(self.installed).chain(std::iter::once(self.target))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::{PreReleaseMode, ResolutionMode};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
use crate::{PreReleaseMode, ResolutionMode};
|
||||||
|
|
||||||
/// Options for resolving a manifest.
|
/// Options for resolving a manifest.
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
pub struct ResolutionOptions {
|
pub struct ResolutionOptions {
|
||||||
// TODO(konstin): These should be pub(crate) again
|
|
||||||
pub resolution_mode: ResolutionMode,
|
pub resolution_mode: ResolutionMode,
|
||||||
pub prerelease_mode: PreReleaseMode,
|
pub prerelease_mode: PreReleaseMode,
|
||||||
pub exclude_newer: Option<DateTime<Utc>>,
|
pub exclude_newer: Option<DateTime<Utc>>,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use anyhow::Result;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use futures::channel::mpsc::UnboundedReceiver;
|
use futures::channel::mpsc::UnboundedReceiver;
|
||||||
use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
|
use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
|
||||||
|
use itertools::Itertools;
|
||||||
use pubgrub::error::PubGrubError;
|
use pubgrub::error::PubGrubError;
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
use pubgrub::solver::{Incompatibility, State};
|
use pubgrub::solver::{Incompatibility, State};
|
||||||
|
|
@ -21,10 +22,12 @@ use distribution_types::{
|
||||||
BuiltDist, Dist, DistributionMetadata, IndexUrl, LocalEditable, Name, PackageId, SourceDist,
|
BuiltDist, Dist, DistributionMetadata, IndexUrl, LocalEditable, Name, PackageId, SourceDist,
|
||||||
VersionOrUrl,
|
VersionOrUrl,
|
||||||
};
|
};
|
||||||
|
use pep440_rs::VersionSpecifiers;
|
||||||
use pep508_rs::{MarkerEnvironment, Requirement};
|
use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use puffin_client::RegistryClient;
|
use puffin_client::RegistryClient;
|
||||||
use puffin_distribution::{DistributionDatabase, DistributionDatabaseError};
|
use puffin_distribution::{DistributionDatabase, DistributionDatabaseError};
|
||||||
|
use puffin_interpreter::Interpreter;
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use puffin_traits::{BuildContext, OnceMap};
|
use puffin_traits::{BuildContext, OnceMap};
|
||||||
use pypi_types::{BaseUrl, Metadata21};
|
use pypi_types::{BaseUrl, Metadata21};
|
||||||
|
|
@ -35,9 +38,10 @@ use crate::manifest::Manifest;
|
||||||
use crate::overrides::Overrides;
|
use crate::overrides::Overrides;
|
||||||
use crate::pins::FilePins;
|
use crate::pins::FilePins;
|
||||||
use crate::pubgrub::{
|
use crate::pubgrub::{
|
||||||
PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPriorities, PubGrubVersion,
|
PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPriorities, PubGrubPython,
|
||||||
MIN_VERSION,
|
PubGrubSpecifier, PubGrubVersion, MIN_VERSION,
|
||||||
};
|
};
|
||||||
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::resolution::ResolutionGraph;
|
use crate::resolution::ResolutionGraph;
|
||||||
use crate::version_map::VersionMap;
|
use crate::version_map::VersionMap;
|
||||||
use crate::yanks::AllowedYanks;
|
use crate::yanks::AllowedYanks;
|
||||||
|
|
@ -73,9 +77,8 @@ pub trait ResolverProvider: Send + Sync {
|
||||||
pub struct DefaultResolverProvider<'a, Context: BuildContext + Send + Sync> {
|
pub struct DefaultResolverProvider<'a, Context: BuildContext + Send + Sync> {
|
||||||
client: &'a RegistryClient,
|
client: &'a RegistryClient,
|
||||||
fetcher: DistributionDatabase<'a, Context>,
|
fetcher: DistributionDatabase<'a, Context>,
|
||||||
build_context: &'a Context,
|
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
markers: &'a MarkerEnvironment,
|
python_requirement: PythonRequirement<'a>,
|
||||||
exclude_newer: Option<DateTime<Utc>>,
|
exclude_newer: Option<DateTime<Utc>>,
|
||||||
allowed_yanks: AllowedYanks,
|
allowed_yanks: AllowedYanks,
|
||||||
}
|
}
|
||||||
|
|
@ -84,18 +87,16 @@ impl<'a, Context: BuildContext + Send + Sync> DefaultResolverProvider<'a, Contex
|
||||||
pub fn new(
|
pub fn new(
|
||||||
client: &'a RegistryClient,
|
client: &'a RegistryClient,
|
||||||
fetcher: DistributionDatabase<'a, Context>,
|
fetcher: DistributionDatabase<'a, Context>,
|
||||||
build_context: &'a Context,
|
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
markers: &'a MarkerEnvironment,
|
python_requirement: PythonRequirement<'a>,
|
||||||
exclude_newer: Option<DateTime<Utc>>,
|
exclude_newer: Option<DateTime<Utc>>,
|
||||||
allowed_yanks: AllowedYanks,
|
allowed_yanks: AllowedYanks,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
fetcher,
|
fetcher,
|
||||||
build_context,
|
|
||||||
tags,
|
tags,
|
||||||
markers,
|
python_requirement,
|
||||||
exclude_newer,
|
exclude_newer,
|
||||||
allowed_yanks,
|
allowed_yanks,
|
||||||
}
|
}
|
||||||
|
|
@ -119,8 +120,7 @@ impl<'a, Context: BuildContext + Send + Sync> ResolverProvider
|
||||||
metadata,
|
metadata,
|
||||||
package_name,
|
package_name,
|
||||||
self.tags,
|
self.tags,
|
||||||
self.markers,
|
&self.python_requirement,
|
||||||
self.build_context.interpreter(),
|
|
||||||
&self.allowed_yanks,
|
&self.allowed_yanks,
|
||||||
self.exclude_newer.as_ref(),
|
self.exclude_newer.as_ref(),
|
||||||
),
|
),
|
||||||
|
|
@ -152,6 +152,7 @@ pub struct Resolver<'a, Provider: ResolverProvider> {
|
||||||
overrides: Overrides,
|
overrides: Overrides,
|
||||||
allowed_urls: AllowedUrls,
|
allowed_urls: AllowedUrls,
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
|
python_requirement: PythonRequirement<'a>,
|
||||||
selector: CandidateSelector,
|
selector: CandidateSelector,
|
||||||
index: Arc<Index>,
|
index: Arc<Index>,
|
||||||
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
||||||
|
|
@ -165,6 +166,7 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, DefaultResolverProvid
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
options: ResolutionOptions,
|
options: ResolutionOptions,
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
|
interpreter: &'a Interpreter,
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
client: &'a RegistryClient,
|
client: &'a RegistryClient,
|
||||||
build_context: &'a Context,
|
build_context: &'a Context,
|
||||||
|
|
@ -172,9 +174,8 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, DefaultResolverProvid
|
||||||
let provider = DefaultResolverProvider::new(
|
let provider = DefaultResolverProvider::new(
|
||||||
client,
|
client,
|
||||||
DistributionDatabase::new(build_context.cache(), tags, client, build_context),
|
DistributionDatabase::new(build_context.cache(), tags, client, build_context),
|
||||||
build_context,
|
|
||||||
tags,
|
tags,
|
||||||
markers,
|
PythonRequirement::new(interpreter, markers),
|
||||||
options.exclude_newer,
|
options.exclude_newer,
|
||||||
manifest
|
manifest
|
||||||
.requirements
|
.requirements
|
||||||
|
|
@ -182,7 +183,13 @@ impl<'a, Context: BuildContext + Send + Sync> Resolver<'a, DefaultResolverProvid
|
||||||
.chain(manifest.constraints.iter())
|
.chain(manifest.constraints.iter())
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
Self::new_custom_io(manifest, options, markers, provider)
|
Self::new_custom_io(
|
||||||
|
manifest,
|
||||||
|
options,
|
||||||
|
markers,
|
||||||
|
PythonRequirement::new(interpreter, markers),
|
||||||
|
provider,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,6 +199,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
options: ResolutionOptions,
|
options: ResolutionOptions,
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
|
python_requirement: PythonRequirement<'a>,
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let selector = CandidateSelector::for_resolution(&manifest, options);
|
let selector = CandidateSelector::for_resolution(&manifest, options);
|
||||||
|
|
@ -245,6 +253,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
constraints: manifest.constraints,
|
constraints: manifest.constraints,
|
||||||
overrides: Overrides::from_requirements(manifest.overrides),
|
overrides: Overrides::from_requirements(manifest.overrides),
|
||||||
markers,
|
markers,
|
||||||
|
python_requirement,
|
||||||
editables,
|
editables,
|
||||||
reporter: None,
|
reporter: None,
|
||||||
provider,
|
provider,
|
||||||
|
|
@ -287,7 +296,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
resolution.map_err(|err| {
|
resolution.map_err(|err| {
|
||||||
// Add version information to improve unsat error messages
|
// Add version information to improve unsat error messages
|
||||||
if let ResolveError::NoSolution(err) = err {
|
if let ResolveError::NoSolution(err) = err {
|
||||||
ResolveError::NoSolution(err.with_available_versions(&self.index.packages).with_selector(self.selector.clone()))
|
ResolveError::NoSolution(err.with_available_versions(&self.python_requirement, &self.index.packages).with_selector(self.selector.clone()))
|
||||||
} else {
|
} else {
|
||||||
err
|
err
|
||||||
}
|
}
|
||||||
|
|
@ -440,6 +449,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
) -> Result<(), ResolveError> {
|
) -> Result<(), ResolveError> {
|
||||||
match package {
|
match package {
|
||||||
PubGrubPackage::Root(_) => {}
|
PubGrubPackage::Root(_) => {}
|
||||||
|
PubGrubPackage::Python(_) => {}
|
||||||
PubGrubPackage::Package(package_name, _extra, None) => {
|
PubGrubPackage::Package(package_name, _extra, None) => {
|
||||||
// Emit a request to fetch the metadata for this package.
|
// Emit a request to fetch the metadata for this package.
|
||||||
if index.packages.register(package_name) {
|
if index.packages.register(package_name) {
|
||||||
|
|
@ -488,6 +498,24 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
return match package {
|
return match package {
|
||||||
PubGrubPackage::Root(_) => Ok(Some(MIN_VERSION.clone())),
|
PubGrubPackage::Root(_) => Ok(Some(MIN_VERSION.clone())),
|
||||||
|
|
||||||
|
PubGrubPackage::Python(PubGrubPython::Installed) => {
|
||||||
|
let version = PubGrubVersion::from(self.python_requirement.installed().clone());
|
||||||
|
if range.contains(&version) {
|
||||||
|
Ok(Some(version))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PubGrubPackage::Python(PubGrubPython::Target) => {
|
||||||
|
let version = PubGrubVersion::from(self.python_requirement.target().clone());
|
||||||
|
if range.contains(&version) {
|
||||||
|
Ok(Some(version))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PubGrubPackage::Package(package_name, extra, Some(url)) => {
|
PubGrubPackage::Package(package_name, extra, Some(url)) => {
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
debug!(
|
debug!(
|
||||||
|
|
@ -548,6 +576,14 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If the version is incompatible, short-circuit.
|
||||||
|
if let Some(requires_python) = candidate.validate(&self.python_requirement) {
|
||||||
|
self.index
|
||||||
|
.incompatibilities
|
||||||
|
.done(candidate.package_id(), requires_python.clone());
|
||||||
|
return Ok(Some(candidate.version().clone()));
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
debug!(
|
debug!(
|
||||||
"Selecting: {}[{}]=={} ({})",
|
"Selecting: {}[{}]=={} ({})",
|
||||||
|
|
@ -636,13 +672,37 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
Ok(Dependencies::Known(constraints.into()))
|
Ok(Dependencies::Known(constraints.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PubGrubPackage::Python(_) => Ok(Dependencies::Known(DependencyConstraints::default())),
|
||||||
|
|
||||||
PubGrubPackage::Package(package_name, extra, url) => {
|
PubGrubPackage::Package(package_name, extra, url) => {
|
||||||
// Wait for the metadata to be available.
|
// Wait for the metadata to be available.
|
||||||
let dist = match url {
|
let dist = match url {
|
||||||
Some(url) => PubGrubDistribution::from_url(package_name, url),
|
Some(url) => PubGrubDistribution::from_url(package_name, url),
|
||||||
None => PubGrubDistribution::from_registry(package_name, version),
|
None => PubGrubDistribution::from_registry(package_name, version),
|
||||||
};
|
};
|
||||||
let entry = self.index.distributions.wait(&dist.package_id()).await;
|
let package_id = dist.package_id();
|
||||||
|
|
||||||
|
// If the package is known to be incompatible, return the Python version as an
|
||||||
|
// incompatibility, and skip fetching the metadata.
|
||||||
|
if let Some(entry) = self.index.incompatibilities.get(&package_id) {
|
||||||
|
let requires_python = entry.value();
|
||||||
|
let version = requires_python
|
||||||
|
.iter()
|
||||||
|
.map(PubGrubSpecifier::try_from)
|
||||||
|
.fold_ok(Range::full(), |range, specifier| {
|
||||||
|
range.intersection(&specifier.into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut constraints = DependencyConstraints::default();
|
||||||
|
constraints.insert(
|
||||||
|
PubGrubPackage::Python(PubGrubPython::Installed),
|
||||||
|
version.clone(),
|
||||||
|
);
|
||||||
|
constraints.insert(PubGrubPackage::Python(PubGrubPython::Target), version);
|
||||||
|
return Ok(Dependencies::Known(constraints));
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = self.index.distributions.wait(&package_id).await;
|
||||||
let metadata = entry.value();
|
let metadata = entry.value();
|
||||||
|
|
||||||
let mut constraints = PubGrubDependencies::from_requirements(
|
let mut constraints = PubGrubDependencies::from_requirements(
|
||||||
|
|
@ -661,6 +721,23 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
Self::visit_package(package, priorities, index, request_sink)?;
|
Self::visit_package(package, priorities, index, request_sink)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a package has a `requires_python` field, add a constraint on the target
|
||||||
|
// Python version.
|
||||||
|
if let Some(requires_python) = metadata.requires_python.as_ref() {
|
||||||
|
let version = requires_python
|
||||||
|
.iter()
|
||||||
|
.map(PubGrubSpecifier::try_from)
|
||||||
|
.fold_ok(Range::full(), |range, specifier| {
|
||||||
|
range.intersection(&specifier.into())
|
||||||
|
})?;
|
||||||
|
constraints.insert(
|
||||||
|
PubGrubPackage::Python(PubGrubPython::Installed),
|
||||||
|
version.clone(),
|
||||||
|
);
|
||||||
|
constraints.insert(PubGrubPackage::Python(PubGrubPython::Target), version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a package has an extra, insert a constraint on the base package.
|
||||||
if extra.is_some() {
|
if extra.is_some() {
|
||||||
constraints.insert(
|
constraints.insert(
|
||||||
PubGrubPackage::Package(package_name.clone(), None, None),
|
PubGrubPackage::Package(package_name.clone(), None, None),
|
||||||
|
|
@ -766,6 +843,14 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If the version is incompatible, short-circuit.
|
||||||
|
if let Some(requires_python) = candidate.validate(&self.python_requirement) {
|
||||||
|
self.index
|
||||||
|
.incompatibilities
|
||||||
|
.done(candidate.package_id(), requires_python.clone());
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
// Emit a request to fetch the metadata for this version.
|
// Emit a request to fetch the metadata for this version.
|
||||||
if self.index.distributions.register(&candidate.package_id()) {
|
if self.index.distributions.register(&candidate.package_id()) {
|
||||||
let dist = candidate.into_distribution(index.clone(), base.clone());
|
let dist = candidate.into_distribution(index.clone(), base.clone());
|
||||||
|
|
@ -802,6 +887,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
if let Some(reporter) = self.reporter.as_ref() {
|
if let Some(reporter) = self.reporter.as_ref() {
|
||||||
match package {
|
match package {
|
||||||
PubGrubPackage::Root(_) => {}
|
PubGrubPackage::Root(_) => {}
|
||||||
|
PubGrubPackage::Python(_) => {}
|
||||||
PubGrubPackage::Package(package_name, _extra, Some(url)) => {
|
PubGrubPackage::Package(package_name, _extra, Some(url)) => {
|
||||||
reporter.on_progress(package_name, VersionOrUrl::Url(url));
|
reporter.on_progress(package_name, VersionOrUrl::Url(url));
|
||||||
}
|
}
|
||||||
|
|
@ -892,9 +978,12 @@ pub(crate) struct Index {
|
||||||
/// came from.
|
/// came from.
|
||||||
pub(crate) packages: OnceMap<PackageName, (IndexUrl, BaseUrl, VersionMap)>,
|
pub(crate) packages: OnceMap<PackageName, (IndexUrl, BaseUrl, VersionMap)>,
|
||||||
|
|
||||||
/// A map from distribution SHA to metadata for that distribution.
|
/// A map from package ID to metadata for that distribution.
|
||||||
pub(crate) distributions: OnceMap<PackageId, Metadata21>,
|
pub(crate) distributions: OnceMap<PackageId, Metadata21>,
|
||||||
|
|
||||||
|
/// A map from package ID to required Python version.
|
||||||
|
pub(crate) incompatibilities: OnceMap<PackageId, VersionSpecifiers>,
|
||||||
|
|
||||||
/// A map from source URL to precise URL.
|
/// A map from source URL to precise URL.
|
||||||
pub(crate) redirects: OnceMap<Url, Url>,
|
pub(crate) redirects: OnceMap<Url, Url>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,15 @@ use chrono::{DateTime, Utc};
|
||||||
use tracing::{instrument, warn};
|
use tracing::{instrument, warn};
|
||||||
|
|
||||||
use distribution_filename::DistFilename;
|
use distribution_filename::DistFilename;
|
||||||
use pep508_rs::MarkerEnvironment;
|
|
||||||
use platform_tags::{TagPriority, Tags};
|
use platform_tags::{TagPriority, Tags};
|
||||||
use puffin_client::SimpleMetadata;
|
use puffin_client::SimpleMetadata;
|
||||||
use puffin_interpreter::Interpreter;
|
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use puffin_warnings::warn_user_once;
|
use puffin_warnings::warn_user_once;
|
||||||
use pypi_types::Yanked;
|
use pypi_types::Yanked;
|
||||||
|
|
||||||
use crate::file::{DistFile, SdistFile, WheelFile};
|
use crate::file::{DistFile, SdistFile, WheelFile};
|
||||||
use crate::pubgrub::PubGrubVersion;
|
use crate::pubgrub::PubGrubVersion;
|
||||||
|
use crate::python_requirement::PythonRequirement;
|
||||||
use crate::yanks::AllowedYanks;
|
use crate::yanks::AllowedYanks;
|
||||||
|
|
||||||
/// A map from versions to distributions.
|
/// A map from versions to distributions.
|
||||||
|
|
@ -28,8 +27,7 @@ impl VersionMap {
|
||||||
metadata: SimpleMetadata,
|
metadata: SimpleMetadata,
|
||||||
package_name: &PackageName,
|
package_name: &PackageName,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
markers: &MarkerEnvironment,
|
python_requirement: &PythonRequirement,
|
||||||
interpreter: &Interpreter,
|
|
||||||
allowed_yanks: &AllowedYanks,
|
allowed_yanks: &AllowedYanks,
|
||||||
exclude_newer: Option<&DateTime<Utc>>,
|
exclude_newer: Option<&DateTime<Utc>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -39,23 +37,6 @@ impl VersionMap {
|
||||||
// Collect compatible distributions.
|
// Collect compatible distributions.
|
||||||
for (version, files) in metadata {
|
for (version, files) in metadata {
|
||||||
for (filename, file) in files.all() {
|
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
|
// Support resolving as if it were an earlier timestamp, at least as long files have
|
||||||
// upload time information
|
// upload time information
|
||||||
if let Some(exclude_newer) = exclude_newer {
|
if let Some(exclude_newer) = exclude_newer {
|
||||||
|
|
@ -86,8 +67,17 @@ impl VersionMap {
|
||||||
|
|
||||||
match filename {
|
match filename {
|
||||||
DistFilename::WheelFilename(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()) {
|
match version_map.entry(version.clone().into()) {
|
||||||
Entry::Occupied(mut entry) => {
|
Entry::Occupied(mut entry) => {
|
||||||
entry.get_mut().insert_built(WheelFile(file), priority);
|
entry.get_mut().insert_built(WheelFile(file), priority);
|
||||||
|
|
@ -201,12 +191,12 @@ impl PrioritizedDistribution {
|
||||||
) {
|
) {
|
||||||
// Prefer the highest-priority, platform-compatible wheel.
|
// Prefer the highest-priority, platform-compatible wheel.
|
||||||
(Some((wheel, _)), _, _) => Some(ResolvableFile::CompatibleWheel(wheel)),
|
(Some((wheel, _)), _, _) => Some(ResolvableFile::CompatibleWheel(wheel)),
|
||||||
// If we have a source distribution and an incompatible wheel, return the wheel.
|
// If we have a compatible source distribution and an incompatible wheel, return the
|
||||||
// We assume that all distributions have the same metadata for a given package version.
|
// wheel. We assume that all distributions have the same metadata for a given package
|
||||||
// If a source distribution exists, we assume we can build it, but using the wheel is
|
// version. If a compatible source distribution exists, we assume we can build it, but
|
||||||
// faster.
|
// using the wheel is faster.
|
||||||
(_, Some(sdist), Some(wheel)) => Some(ResolvableFile::IncompatibleWheel(sdist, wheel)),
|
(_, 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)),
|
(_, Some(sdist), _) => Some(ResolvableFile::SourceDist(sdist)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,17 +91,26 @@ async fn resolve(
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
) -> Result<ResolutionGraph> {
|
) -> Result<ResolutionGraph> {
|
||||||
let client = RegistryClientBuilder::new(Cache::temp()?).build();
|
let client = RegistryClientBuilder::new(Cache::temp()?).build();
|
||||||
let build_context = DummyContext {
|
let interpreter = Interpreter::artificial(
|
||||||
cache: Cache::temp()?,
|
|
||||||
interpreter: Interpreter::artificial(
|
|
||||||
Platform::current()?,
|
Platform::current()?,
|
||||||
markers.clone(),
|
markers.clone(),
|
||||||
PathBuf::from("/dev/null"),
|
PathBuf::from("/dev/null"),
|
||||||
PathBuf::from("/dev/null"),
|
PathBuf::from("/dev/null"),
|
||||||
PathBuf::from("/dev/null"),
|
PathBuf::from("/dev/null"),
|
||||||
),
|
);
|
||||||
|
let build_context = DummyContext {
|
||||||
|
cache: Cache::temp()?,
|
||||||
|
interpreter: interpreter.clone(),
|
||||||
};
|
};
|
||||||
let resolver = Resolver::new(manifest, options, markers, tags, &client, &build_context);
|
let resolver = Resolver::new(
|
||||||
|
manifest,
|
||||||
|
options,
|
||||||
|
markers,
|
||||||
|
&interpreter,
|
||||||
|
tags,
|
||||||
|
&client,
|
||||||
|
&build_context,
|
||||||
|
);
|
||||||
Ok(resolver.resolve().await?)
|
Ok(resolver.resolve().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue