mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Respect existing versions in "lockfile" (#187)
Like `pip-compile`, we now respect existing versions from the `requirements.txt` provided via `--output-file`, unless you pass a `--upgrade` flag. Closes #166.
This commit is contained in:
parent
9f894213e0
commit
6faaf4bc24
11 changed files with 347 additions and 122 deletions
|
@ -7,15 +7,16 @@ use anyhow::Result;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use pubgrub::report::Reporter;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use platform_host::Platform;
|
use platform_host::Platform;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use pubgrub::report::Reporter;
|
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::RegistryClientBuilder;
|
||||||
use puffin_dispatch::BuildDispatch;
|
use puffin_dispatch::BuildDispatch;
|
||||||
use puffin_interpreter::Virtualenv;
|
use puffin_interpreter::Virtualenv;
|
||||||
use puffin_resolver::ResolutionMode;
|
use puffin_resolver::{Manifest, ResolutionMode};
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::commands::{elapsed, ExitStatus};
|
use crate::commands::{elapsed, ExitStatus};
|
||||||
use crate::index_urls::IndexUrls;
|
use crate::index_urls::IndexUrls;
|
||||||
|
@ -25,11 +26,13 @@ use crate::requirements::RequirementsSource;
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
/// Resolve a set of requirements into a set of pinned versions.
|
/// Resolve a set of requirements into a set of pinned versions.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn pip_compile(
|
pub(crate) async fn pip_compile(
|
||||||
requirements: &[RequirementsSource],
|
requirements: &[RequirementsSource],
|
||||||
constraints: &[RequirementsSource],
|
constraints: &[RequirementsSource],
|
||||||
output_file: Option<&Path>,
|
output_file: Option<&Path>,
|
||||||
mode: ResolutionMode,
|
resolution_mode: ResolutionMode,
|
||||||
|
upgrade_mode: UpgradeMode,
|
||||||
index_urls: Option<IndexUrls>,
|
index_urls: Option<IndexUrls>,
|
||||||
cache: Option<&Path>,
|
cache: Option<&Path>,
|
||||||
mut printer: Printer,
|
mut printer: Printer,
|
||||||
|
@ -47,6 +50,19 @@ pub(crate) async fn pip_compile(
|
||||||
.map(RequirementsSource::requirements)
|
.map(RequirementsSource::requirements)
|
||||||
.flatten_ok()
|
.flatten_ok()
|
||||||
.collect::<Result<Vec<Requirement>>>()?;
|
.collect::<Result<Vec<Requirement>>>()?;
|
||||||
|
let preferences: Vec<Requirement> = output_file
|
||||||
|
.filter(|_| upgrade_mode.is_prefer_pinned())
|
||||||
|
.filter(|output_file| output_file.exists())
|
||||||
|
.map(Path::to_path_buf)
|
||||||
|
.map(RequirementsSource::from)
|
||||||
|
.as_ref()
|
||||||
|
.map(RequirementsSource::requirements)
|
||||||
|
.transpose()?
|
||||||
|
.map(Iterator::collect)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Create a manifest of the requirements.
|
||||||
|
let manifest = Manifest::new(requirements, constraints, preferences, resolution_mode);
|
||||||
|
|
||||||
// Detect the current Python interpreter.
|
// Detect the current Python interpreter.
|
||||||
let platform = Platform::current()?;
|
let platform = Platform::current()?;
|
||||||
|
@ -88,9 +104,7 @@ pub(crate) async fn pip_compile(
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolver = puffin_resolver::Resolver::new(
|
let resolver = puffin_resolver::Resolver::new(
|
||||||
requirements,
|
manifest,
|
||||||
constraints,
|
|
||||||
mode,
|
|
||||||
venv.interpreter_info().markers(),
|
venv.interpreter_info().markers(),
|
||||||
&tags,
|
&tags,
|
||||||
&client,
|
&client,
|
||||||
|
@ -155,3 +169,28 @@ pub(crate) async fn pip_compile(
|
||||||
|
|
||||||
Ok(ExitStatus::Success)
|
Ok(ExitStatus::Success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to allow package upgrades.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum UpgradeMode {
|
||||||
|
/// Allow package upgrades, ignoring the existing lockfile.
|
||||||
|
AllowUpgrades,
|
||||||
|
/// Prefer pinned versions from the existing lockfile, if possible.
|
||||||
|
PreferPinned,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpgradeMode {
|
||||||
|
fn is_prefer_pinned(self) -> bool {
|
||||||
|
self == Self::PreferPinned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for UpgradeMode {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
if value {
|
||||||
|
Self::AllowUpgrades
|
||||||
|
} else {
|
||||||
|
Self::PreferPinned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -89,6 +89,10 @@ struct PipCompileArgs {
|
||||||
/// Ignore the package index, instead relying on local archives and caches.
|
/// Ignore the package index, instead relying on local archives and caches.
|
||||||
#[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")]
|
#[clap(long, conflicts_with = "index_url", conflicts_with = "extra_index_url")]
|
||||||
no_index: bool,
|
no_index: bool,
|
||||||
|
|
||||||
|
/// Allow package upgrades, ignoring pinned versions in the existing output file.
|
||||||
|
#[clap(long)]
|
||||||
|
upgrade: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
@ -196,6 +200,7 @@ async fn main() -> ExitCode {
|
||||||
&constraints,
|
&constraints,
|
||||||
args.output_file.as_deref(),
|
args.output_file.as_deref(),
|
||||||
args.resolution.unwrap_or_default(),
|
args.resolution.unwrap_or_default(),
|
||||||
|
args.upgrade.into(),
|
||||||
index_urls,
|
index_urls,
|
||||||
cache_dir,
|
cache_dir,
|
||||||
printer,
|
printer,
|
||||||
|
|
|
@ -18,7 +18,7 @@ use puffin_installer::{
|
||||||
uninstall, Downloader, Installer, PartitionedRequirements, RemoteDistribution, Unzipper,
|
uninstall, Downloader, Installer, PartitionedRequirements, RemoteDistribution, Unzipper,
|
||||||
};
|
};
|
||||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||||
use puffin_resolver::{ResolutionMode, Resolver, WheelFinder};
|
use puffin_resolver::{Manifest, ResolutionMode, Resolver, WheelFinder};
|
||||||
use puffin_traits::BuildContext;
|
use puffin_traits::BuildContext;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
@ -70,9 +70,12 @@ impl BuildContext for BuildDispatch {
|
||||||
self.interpreter_info.simple_version(),
|
self.interpreter_info.simple_version(),
|
||||||
)?;
|
)?;
|
||||||
let resolver = Resolver::new(
|
let resolver = Resolver::new(
|
||||||
requirements.to_vec(),
|
Manifest::new(
|
||||||
Vec::default(),
|
requirements.to_vec(),
|
||||||
ResolutionMode::Highest,
|
Vec::default(),
|
||||||
|
Vec::default(),
|
||||||
|
ResolutionMode::default(),
|
||||||
|
),
|
||||||
self.interpreter_info.markers(),
|
self.interpreter_info.markers(),
|
||||||
&tags,
|
&tags,
|
||||||
&self.client,
|
&self.client,
|
||||||
|
|
|
@ -66,6 +66,19 @@ impl From<SdistFile> for DistributionFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<File> for DistributionFile {
|
||||||
|
fn from(file: File) -> Self {
|
||||||
|
if std::path::Path::new(file.filename.as_str())
|
||||||
|
.extension()
|
||||||
|
.map_or(false, |ext| ext.eq_ignore_ascii_case("whl"))
|
||||||
|
{
|
||||||
|
Self::Wheel(WheelFile::from(file))
|
||||||
|
} else {
|
||||||
|
Self::Sdist(SdistFile::from(file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DistributionFile {
|
impl DistributionFile {
|
||||||
pub(crate) fn filename(&self) -> &str {
|
pub(crate) fn filename(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub use error::ResolveError;
|
pub use error::ResolveError;
|
||||||
pub use resolution::PinnedPackage;
|
pub use resolution::PinnedPackage;
|
||||||
pub use resolver::Resolver;
|
pub use resolver::{Manifest, Resolver};
|
||||||
pub use selector::ResolutionMode;
|
pub use selector::ResolutionMode;
|
||||||
pub use source_distribution::BuiltSourceDistributionCache;
|
pub use source_distribution::BuiltSourceDistributionCache;
|
||||||
pub use wheel_finder::{Reporter, WheelFinder};
|
pub use wheel_finder::{Reporter, WheelFinder};
|
||||||
|
|
|
@ -69,6 +69,11 @@ impl Resolution {
|
||||||
self.0.into_values().map(|package| package.file)
|
self.0.into_values().map(|package| package.file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the pinned package for the given package name, if it exists.
|
||||||
|
pub fn get(&self, package_name: &PackageName) -> Option<&PinnedPackage> {
|
||||||
|
self.0.get(package_name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of pinned packages in this resolution.
|
/// Return the number of pinned packages in this resolution.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
|
|
|
@ -34,15 +34,39 @@ use crate::error::ResolveError;
|
||||||
use crate::pubgrub::package::PubGrubPackage;
|
use crate::pubgrub::package::PubGrubPackage;
|
||||||
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
|
||||||
use crate::pubgrub::{iter_requirements, version_range};
|
use crate::pubgrub::{iter_requirements, version_range};
|
||||||
use crate::resolution::{Graph, Resolution};
|
use crate::resolution::Graph;
|
||||||
use crate::selector::{CandidateSelector, ResolutionMode};
|
use crate::selector::{CandidateSelector, ResolutionMode};
|
||||||
use crate::source_distribution::{download_and_build_sdist, read_dist_info};
|
use crate::source_distribution::{download_and_build_sdist, read_dist_info};
|
||||||
use crate::BuiltSourceDistributionCache;
|
use crate::BuiltSourceDistributionCache;
|
||||||
|
|
||||||
|
/// A manifest of requirements, constraints, and preferences.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Manifest {
|
||||||
|
requirements: Vec<Requirement>,
|
||||||
|
constraints: Vec<Requirement>,
|
||||||
|
preferences: Vec<Requirement>,
|
||||||
|
mode: ResolutionMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manifest {
|
||||||
|
pub fn new(
|
||||||
|
requirements: Vec<Requirement>,
|
||||||
|
constraints: Vec<Requirement>,
|
||||||
|
preferences: Vec<Requirement>,
|
||||||
|
mode: ResolutionMode,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
requirements,
|
||||||
|
constraints,
|
||||||
|
preferences,
|
||||||
|
mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Resolver<'a, Context: BuildContext> {
|
pub struct Resolver<'a, Context: BuildContext> {
|
||||||
requirements: Vec<Requirement>,
|
requirements: Vec<Requirement>,
|
||||||
constraints: Vec<Requirement>,
|
constraints: Vec<Requirement>,
|
||||||
resolution: Option<Resolution>,
|
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
client: &'a RegistryClient,
|
client: &'a RegistryClient,
|
||||||
|
@ -54,20 +78,21 @@ pub struct Resolver<'a, Context: BuildContext> {
|
||||||
impl<'a, Context: BuildContext> Resolver<'a, Context> {
|
impl<'a, Context: BuildContext> Resolver<'a, Context> {
|
||||||
/// Initialize a new resolver.
|
/// Initialize a new resolver.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
requirements: Vec<Requirement>,
|
manifest: Manifest,
|
||||||
constraints: Vec<Requirement>,
|
|
||||||
mode: ResolutionMode,
|
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
tags: &'a Tags,
|
tags: &'a Tags,
|
||||||
client: &'a RegistryClient,
|
client: &'a RegistryClient,
|
||||||
build_context: &'a Context,
|
build_context: &'a Context,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
selector: CandidateSelector::from_mode(mode, &requirements),
|
selector: CandidateSelector::from_mode(
|
||||||
|
manifest.mode,
|
||||||
|
&manifest.requirements,
|
||||||
|
&manifest.preferences,
|
||||||
|
),
|
||||||
index: Arc::new(Index::default()),
|
index: Arc::new(Index::default()),
|
||||||
resolution: None,
|
requirements: manifest.requirements,
|
||||||
requirements,
|
constraints: manifest.constraints,
|
||||||
constraints,
|
|
||||||
markers,
|
markers,
|
||||||
tags,
|
tags,
|
||||||
client,
|
client,
|
||||||
|
@ -75,12 +100,6 @@ impl<'a, Context: BuildContext> Resolver<'a, Context> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn with_resolution(mut self, resolution: Resolution) -> Self {
|
|
||||||
self.resolution = Some(resolution);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolve a set of requirements into a set of pinned versions.
|
/// Resolve a set of requirements into a set of pinned versions.
|
||||||
pub async fn resolve(self) -> Result<Graph, ResolveError> {
|
pub async fn resolve(self) -> Result<Graph, ResolveError> {
|
||||||
// A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
|
// A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use fxhash::FxHashSet;
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use pubgrub::range::Range;
|
use pubgrub::range::Range;
|
||||||
|
|
||||||
use crate::distribution::DistributionFile;
|
use pep508_rs::{Requirement, VersionOrUrl};
|
||||||
use pep508_rs::Requirement;
|
|
||||||
|
|
||||||
use puffin_package::package_name::PackageName;
|
use puffin_package::package_name::PackageName;
|
||||||
|
|
||||||
|
use crate::distribution::DistributionFile;
|
||||||
use crate::pubgrub::version::PubGrubVersion;
|
use crate::pubgrub::version::PubGrubVersion;
|
||||||
use crate::resolver::VersionMap;
|
use crate::resolver::VersionMap;
|
||||||
|
|
||||||
|
@ -22,8 +21,10 @@ pub enum ResolutionMode {
|
||||||
LowestDirect,
|
LowestDirect,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// Like [`ResolutionMode`], but with any additional information required to select a candidate,
|
||||||
pub(crate) enum CandidateSelector {
|
/// like the set of direct dependencies.
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ResolutionStrategy {
|
||||||
/// Resolve the highest compatible version of each package.
|
/// Resolve the highest compatible version of each package.
|
||||||
Highest,
|
Highest,
|
||||||
/// Resolve the lowest compatible version of each package.
|
/// Resolve the lowest compatible version of each package.
|
||||||
|
@ -33,9 +34,8 @@ pub(crate) enum CandidateSelector {
|
||||||
LowestDirect(FxHashSet<PackageName>),
|
LowestDirect(FxHashSet<PackageName>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CandidateSelector {
|
impl ResolutionStrategy {
|
||||||
/// Return a candidate selector for the given resolution mode.
|
fn from_mode(mode: ResolutionMode, direct_dependencies: &[Requirement]) -> Self {
|
||||||
pub(crate) fn from_mode(mode: ResolutionMode, direct_dependencies: &[Requirement]) -> Self {
|
|
||||||
match mode {
|
match mode {
|
||||||
ResolutionMode::Highest => Self::Highest,
|
ResolutionMode::Highest => Self::Highest,
|
||||||
ResolutionMode::Lowest => Self::Lowest,
|
ResolutionMode::Lowest => Self::Lowest,
|
||||||
|
@ -49,26 +49,87 @@ impl CandidateSelector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of pinned packages that should be preserved during resolution, if possible.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Preferences(FxHashMap<PackageName, PubGrubVersion>);
|
||||||
|
|
||||||
|
impl Preferences {
|
||||||
|
fn get(&self, package_name: &PackageName) -> Option<&PubGrubVersion> {
|
||||||
|
self.0.get(package_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[Requirement]> for Preferences {
|
||||||
|
fn from(requirements: &[Requirement]) -> Self {
|
||||||
|
Self(
|
||||||
|
requirements
|
||||||
|
.iter()
|
||||||
|
.filter_map(|requirement| {
|
||||||
|
let Some(VersionOrUrl::VersionSpecifier(version_specifiers)) =
|
||||||
|
requirement.version_or_url.as_ref()
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let [version_specifier] = &**version_specifiers else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let package_name = PackageName::normalize(&requirement.name);
|
||||||
|
let version = PubGrubVersion::from(version_specifier.version().clone());
|
||||||
|
Some((package_name, version))
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct CandidateSelector {
|
||||||
|
strategy: ResolutionStrategy,
|
||||||
|
preferences: Preferences,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CandidateSelector {
|
||||||
|
/// Return a candidate selector for the given resolution mode.
|
||||||
|
pub(crate) fn from_mode(
|
||||||
|
mode: ResolutionMode,
|
||||||
|
direct_dependencies: &[Requirement],
|
||||||
|
resolution: &[Requirement],
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
strategy: ResolutionStrategy::from_mode(mode, direct_dependencies),
|
||||||
|
preferences: Preferences::from(resolution),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CandidateSelector {
|
impl CandidateSelector {
|
||||||
/// Select a [`Candidate`] from a set of candidate versions and files.
|
/// Select a [`Candidate`] from a set of candidate versions and files.
|
||||||
pub(crate) fn select<'a>(
|
pub(crate) fn select(
|
||||||
&self,
|
&self,
|
||||||
package_name: &'a PackageName,
|
package_name: &PackageName,
|
||||||
range: &'a Range<PubGrubVersion>,
|
range: &Range<PubGrubVersion>,
|
||||||
version_map: &'a VersionMap,
|
version_map: &VersionMap,
|
||||||
) -> Option<Candidate<'a>> {
|
) -> Option<Candidate> {
|
||||||
match self {
|
if let Some(version) = self.preferences.get(package_name) {
|
||||||
CandidateSelector::Highest => {
|
if range.contains(version) {
|
||||||
CandidateSelector::select_highest(package_name, range, version_map)
|
if let Some(file) = version_map.get(version) {
|
||||||
|
return Some(Candidate {
|
||||||
|
package_name: package_name.clone(),
|
||||||
|
version: version.clone(),
|
||||||
|
file: file.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CandidateSelector::Lowest => {
|
}
|
||||||
CandidateSelector::select_lowest(package_name, range, version_map)
|
|
||||||
}
|
match &self.strategy {
|
||||||
CandidateSelector::LowestDirect(direct_dependencies) => {
|
ResolutionStrategy::Highest => Self::select_highest(package_name, range, version_map),
|
||||||
|
ResolutionStrategy::Lowest => Self::select_lowest(package_name, range, version_map),
|
||||||
|
ResolutionStrategy::LowestDirect(direct_dependencies) => {
|
||||||
if direct_dependencies.contains(package_name) {
|
if direct_dependencies.contains(package_name) {
|
||||||
CandidateSelector::select_lowest(package_name, range, version_map)
|
Self::select_lowest(package_name, range, version_map)
|
||||||
} else {
|
} else {
|
||||||
CandidateSelector::select_highest(package_name, range, version_map)
|
Self::select_highest(package_name, range, version_map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,27 +137,27 @@ impl CandidateSelector {
|
||||||
|
|
||||||
/// Select the highest-compatible [`Candidate`] from a set of candidate versions and files,
|
/// Select the highest-compatible [`Candidate`] from a set of candidate versions and files,
|
||||||
/// preferring wheels over sdists.
|
/// preferring wheels over sdists.
|
||||||
fn select_highest<'a>(
|
fn select_highest(
|
||||||
package_name: &'a PackageName,
|
package_name: &PackageName,
|
||||||
range: &'a Range<PubGrubVersion>,
|
range: &Range<PubGrubVersion>,
|
||||||
version_map: &'a VersionMap,
|
version_map: &VersionMap,
|
||||||
) -> Option<Candidate<'a>> {
|
) -> Option<Candidate> {
|
||||||
let mut sdist = None;
|
let mut sdist = None;
|
||||||
for (version, file) in version_map.iter().rev() {
|
for (version, file) in version_map.iter().rev() {
|
||||||
if range.contains(version) {
|
if range.contains(version) {
|
||||||
match file {
|
match file {
|
||||||
DistributionFile::Wheel(_) => {
|
DistributionFile::Wheel(_) => {
|
||||||
return Some(Candidate {
|
return Some(Candidate {
|
||||||
package_name,
|
package_name: package_name.clone(),
|
||||||
version,
|
version: version.clone(),
|
||||||
file,
|
file: file.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DistributionFile::Sdist(_) => {
|
DistributionFile::Sdist(_) => {
|
||||||
sdist = Some(Candidate {
|
sdist = Some(Candidate {
|
||||||
package_name,
|
package_name: package_name.clone(),
|
||||||
version,
|
version: version.clone(),
|
||||||
file,
|
file: file.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,27 +168,27 @@ impl CandidateSelector {
|
||||||
|
|
||||||
/// Select the highest-compatible [`Candidate`] from a set of candidate versions and files,
|
/// Select the highest-compatible [`Candidate`] from a set of candidate versions and files,
|
||||||
/// preferring wheels over sdists.
|
/// preferring wheels over sdists.
|
||||||
fn select_lowest<'a>(
|
fn select_lowest(
|
||||||
package_name: &'a PackageName,
|
package_name: &PackageName,
|
||||||
range: &'a Range<PubGrubVersion>,
|
range: &Range<PubGrubVersion>,
|
||||||
version_map: &'a VersionMap,
|
version_map: &VersionMap,
|
||||||
) -> Option<Candidate<'a>> {
|
) -> Option<Candidate> {
|
||||||
let mut sdist = None;
|
let mut sdist = None;
|
||||||
for (version, file) in version_map {
|
for (version, file) in version_map {
|
||||||
if range.contains(version) {
|
if range.contains(version) {
|
||||||
match file {
|
match file {
|
||||||
DistributionFile::Wheel(_) => {
|
DistributionFile::Wheel(_) => {
|
||||||
return Some(Candidate {
|
return Some(Candidate {
|
||||||
package_name,
|
package_name: package_name.clone(),
|
||||||
version,
|
version: version.clone(),
|
||||||
file,
|
file: file.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DistributionFile::Sdist(_) => {
|
DistributionFile::Sdist(_) => {
|
||||||
sdist = Some(Candidate {
|
sdist = Some(Candidate {
|
||||||
package_name,
|
package_name: package_name.clone(),
|
||||||
version,
|
version: version.clone(),
|
||||||
file,
|
file: file.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,11 +199,11 @@ impl CandidateSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Candidate<'a> {
|
pub(crate) struct Candidate {
|
||||||
/// The name of the package.
|
/// The name of the package.
|
||||||
pub(crate) package_name: &'a PackageName,
|
pub(crate) package_name: PackageName,
|
||||||
/// The version of the package.
|
/// The version of the package.
|
||||||
pub(crate) version: &'a PubGrubVersion,
|
pub(crate) version: PubGrubVersion,
|
||||||
/// The file of the package.
|
/// The file of the package.
|
||||||
pub(crate) file: &'a DistributionFile,
|
pub(crate) file: DistributionFile,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use platform_host::{Arch, Os, Platform};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::RegistryClientBuilder;
|
||||||
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
use puffin_interpreter::{InterpreterInfo, Virtualenv};
|
||||||
use puffin_resolver::{ResolutionMode, Resolver};
|
use puffin_resolver::{Manifest, ResolutionMode, Resolver};
|
||||||
use puffin_traits::BuildContext;
|
use puffin_traits::BuildContext;
|
||||||
|
|
||||||
struct DummyContext;
|
struct DummyContext;
|
||||||
|
@ -66,15 +66,15 @@ async fn pylint() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("pylint==2.3.0").unwrap()];
|
let requirements = vec![Requirement::from_str("pylint==2.3.0").unwrap()];
|
||||||
let constraints = vec![];
|
let constraints = vec![];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -90,15 +90,15 @@ async fn black() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
let constraints = vec![];
|
let constraints = vec![];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -114,15 +114,15 @@ async fn black_colorama() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()];
|
let requirements = vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()];
|
||||||
let constraints = vec![];
|
let constraints = vec![];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -138,15 +138,15 @@ async fn black_python_310() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
let constraints = vec![];
|
let constraints = vec![];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_310,
|
|
||||||
&TAGS_310,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_310, &TAGS_310, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -164,15 +164,15 @@ async fn black_mypy_extensions() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
let constraints = vec![Requirement::from_str("mypy-extensions<1").unwrap()];
|
let constraints = vec![Requirement::from_str("mypy-extensions<1").unwrap()];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -190,15 +190,15 @@ async fn black_mypy_extensions_extra() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
let constraints = vec![Requirement::from_str("mypy-extensions[extra]<1").unwrap()];
|
let constraints = vec![Requirement::from_str("mypy-extensions[extra]<1").unwrap()];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -216,15 +216,15 @@ async fn black_flake8() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
let constraints = vec![Requirement::from_str("flake8<1").unwrap()];
|
let constraints = vec![Requirement::from_str("flake8<1").unwrap()];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::default(),
|
ResolutionMode::default(),
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -240,15 +240,15 @@ async fn black_lowest() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black>21").unwrap()];
|
let requirements = vec![Requirement::from_str("black>21").unwrap()];
|
||||||
let constraints = vec![];
|
let constraints = vec![];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::Lowest,
|
ResolutionMode::Lowest,
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
@ -264,15 +264,63 @@ async fn black_lowest_direct() -> Result<()> {
|
||||||
|
|
||||||
let requirements = vec![Requirement::from_str("black>21").unwrap()];
|
let requirements = vec![Requirement::from_str("black>21").unwrap()];
|
||||||
let constraints = vec![];
|
let constraints = vec![];
|
||||||
let resolver = Resolver::new(
|
let preferences = vec![];
|
||||||
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
|
preferences,
|
||||||
ResolutionMode::LowestDirect,
|
ResolutionMode::LowestDirect,
|
||||||
&MARKERS_311,
|
|
||||||
&TAGS_311,
|
|
||||||
&client,
|
|
||||||
&DummyContext,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn black_respect_preference() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
|
let client = RegistryClientBuilder::default().build();
|
||||||
|
|
||||||
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
|
let constraints = vec![];
|
||||||
|
let preferences = vec![Requirement::from_str("black==23.9.0").unwrap()];
|
||||||
|
let manifest = Manifest::new(
|
||||||
|
requirements,
|
||||||
|
constraints,
|
||||||
|
preferences,
|
||||||
|
ResolutionMode::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn black_ignore_preference() -> Result<()> {
|
||||||
|
colored::control::set_override(false);
|
||||||
|
|
||||||
|
let client = RegistryClientBuilder::default().build();
|
||||||
|
|
||||||
|
let requirements = vec![Requirement::from_str("black<=23.9.1").unwrap()];
|
||||||
|
let constraints = vec![];
|
||||||
|
let preferences = vec![Requirement::from_str("black==23.9.2").unwrap()];
|
||||||
|
let manifest = Manifest::new(
|
||||||
|
requirements,
|
||||||
|
constraints,
|
||||||
|
preferences,
|
||||||
|
ResolutionMode::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
insta::assert_display_snapshot!(resolution);
|
insta::assert_display_snapshot!(resolution);
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
source: crates/puffin-resolver/tests/resolver.rs
|
||||||
|
expression: resolution
|
||||||
|
---
|
||||||
|
black==23.9.1
|
||||||
|
click==8.1.7
|
||||||
|
# via black
|
||||||
|
mypy-extensions==1.0.0
|
||||||
|
# via black
|
||||||
|
packaging==23.2
|
||||||
|
# via black
|
||||||
|
pathspec==0.11.2
|
||||||
|
# via black
|
||||||
|
platformdirs==3.11.0
|
||||||
|
# via black
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
source: crates/puffin-resolver/tests/resolver.rs
|
||||||
|
expression: resolution
|
||||||
|
---
|
||||||
|
black==23.9.0
|
||||||
|
click==8.1.7
|
||||||
|
# via black
|
||||||
|
mypy-extensions==1.0.0
|
||||||
|
# via black
|
||||||
|
packaging==23.2
|
||||||
|
# via black
|
||||||
|
pathspec==0.11.2
|
||||||
|
# via black
|
||||||
|
platformdirs==3.11.0
|
||||||
|
# via black
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue