Add support for pre-release versions (#216)

We now accept a pre-release if (1) all versions are pre-releases, or (2)
there was a pre-release marker in the dependency specifiers for a direct
dependency.

The code is written such that we can support a variety of pre-release
strategies.

Closes https://github.com/astral-sh/puffin/issues/191.
This commit is contained in:
Charlie Marsh 2023-10-29 11:31:55 -07:00 committed by GitHub
parent 6cd4650c1f
commit 7e7e9f8a0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 695 additions and 295 deletions

View file

@ -364,6 +364,11 @@ impl VersionSpecifier {
pub fn version(&self) -> &Version {
&self.version
}
/// Whether the version marker includes a prerelease.
pub fn any_prerelease(&self) -> bool {
self.version.any_prerelease()
}
}
impl VersionSpecifier {

View file

@ -16,7 +16,7 @@ use platform_tags::Tags;
use puffin_client::RegistryClientBuilder;
use puffin_dispatch::BuildDispatch;
use puffin_interpreter::Virtualenv;
use puffin_resolver::{ResolutionManifest, ResolutionMode};
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode};
use crate::commands::{elapsed, ExitStatus};
use crate::index_urls::IndexUrls;
@ -56,7 +56,13 @@ pub(crate) async fn pip_compile(
.unwrap_or_default();
// Create a manifest of the requirements.
let manifest = ResolutionManifest::new(requirements, constraints, preferences, resolution_mode);
let manifest = Manifest::new(
requirements,
constraints,
preferences,
resolution_mode,
PreReleaseMode::default(),
);
// Detect the current Python interpreter.
let platform = Platform::current()?;

View file

@ -19,7 +19,7 @@ use puffin_installer::{
Downloader, Installer, PartitionedRequirements, RemoteDistribution, Unzipper,
};
use puffin_interpreter::{InterpreterInfo, Virtualenv};
use puffin_resolver::{ResolutionManifest, ResolutionMode, Resolver, WheelFinder};
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, Resolver, WheelFinder};
use puffin_traits::BuildContext;
/// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`]
@ -71,11 +71,12 @@ impl BuildContext for BuildDispatch {
self.interpreter_info.simple_version(),
)?;
let resolver = Resolver::new(
ResolutionManifest::new(
Manifest::new(
requirements.to_vec(),
Vec::default(),
Vec::default(),
ResolutionMode::default(),
PreReleaseMode::default(),
),
self.interpreter_info.markers(),
&tags,

View file

@ -0,0 +1,260 @@
use fxhash::FxHashMap;
use pubgrub::range::Range;
use pep508_rs::{Requirement, VersionOrUrl};
use puffin_package::package_name::PackageName;
use crate::distribution::DistributionFile;
use crate::prerelease_mode::PreReleaseStrategy;
use crate::pubgrub::version::PubGrubVersion;
use crate::resolution_mode::ResolutionStrategy;
use crate::resolver::VersionMap;
use crate::Manifest;
#[derive(Debug)]
pub(crate) struct CandidateSelector {
resolution_strategy: ResolutionStrategy,
prerelease_strategy: PreReleaseStrategy,
preferences: Preferences,
}
impl From<&Manifest> for CandidateSelector {
/// Return a [`CandidateSelector`] for the given [`Manifest`].
fn from(manifest: &Manifest) -> Self {
Self {
resolution_strategy: ResolutionStrategy::from_mode(
manifest.resolution_mode,
manifest.requirements.as_slice(),
),
prerelease_strategy: PreReleaseStrategy::from_mode(
manifest.prerelease_mode,
manifest.requirements.as_slice(),
),
preferences: Preferences::from(manifest.preferences.as_slice()),
}
}
}
/// 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, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum AllowPreRelease {
Yes,
No,
IfNecessary,
}
impl CandidateSelector {
/// Select a [`Candidate`] from a set of candidate versions and files.
pub(crate) fn select(
&self,
package_name: &PackageName,
range: &Range<PubGrubVersion>,
version_map: &VersionMap,
) -> Option<Candidate> {
// If the package has a preference (e.g., an existing version from an existing lockfile),
// and the preference satisfies the current range, use that.
if let Some(version) = self.preferences.get(package_name) {
if range.contains(version) {
if let Some(file) = version_map.get(version) {
return Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
}
// Determine the appropriate prerelease strategy for the current package.
let allow_prerelease = match &self.prerelease_strategy {
PreReleaseStrategy::DisallowAll => AllowPreRelease::No,
PreReleaseStrategy::AllowAll => AllowPreRelease::Yes,
PreReleaseStrategy::IfNecessary => AllowPreRelease::IfNecessary,
PreReleaseStrategy::Explicit(packages) => {
if packages.contains(package_name) {
AllowPreRelease::Yes
} else {
AllowPreRelease::No
}
}
PreReleaseStrategy::IfNecessaryOrExplicit(packages) => {
if packages.contains(package_name) {
AllowPreRelease::Yes
} else {
AllowPreRelease::IfNecessary
}
}
};
match &self.resolution_strategy {
ResolutionStrategy::Highest => Self::select_candidate(
version_map.iter().rev(),
package_name,
range,
allow_prerelease,
),
ResolutionStrategy::Lowest => {
Self::select_candidate(version_map.iter(), package_name, range, allow_prerelease)
}
ResolutionStrategy::LowestDirect(direct_dependencies) => {
if direct_dependencies.contains(package_name) {
Self::select_candidate(
version_map.iter(),
package_name,
range,
allow_prerelease,
)
} else {
Self::select_candidate(
version_map.iter().rev(),
package_name,
range,
allow_prerelease,
)
}
}
}
}
/// Select the first-matching [`Candidate`] from a set of candidate versions and files,
/// preferring wheels over sdists.
fn select_candidate<'a>(
versions: impl Iterator<Item = (&'a PubGrubVersion, &'a DistributionFile)>,
package_name: &PackageName,
range: &Range<PubGrubVersion>,
allow_prerelease: AllowPreRelease,
) -> Option<Candidate> {
// We prefer a stable wheel, followed by a prerelease wheel, followed by a stable sdist,
// followed by a prerelease sdist.
let mut sdist = None;
let mut prerelease_sdist = None;
let mut prerelease_wheel = None;
for (version, file) in versions {
if range.contains(version) {
match file {
DistributionFile::Wheel(_) => {
if version.any_prerelease() {
match allow_prerelease {
AllowPreRelease::Yes => {
// If prereleases are allowed, treat them equivalently
// to stable wheels.
return Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
AllowPreRelease::IfNecessary => {
// If prereleases are allowed as a fallback, store the
// first-matching prerelease wheel.
if prerelease_wheel.is_none() {
prerelease_wheel = Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
AllowPreRelease::No => {
continue;
}
}
} else {
// Always return the first-matching stable wheel.
return Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
DistributionFile::Sdist(_) => {
if version.any_prerelease() {
match allow_prerelease {
AllowPreRelease::Yes => {
// If prereleases are allowed, treat them equivalently to
// stable sdists.
if sdist.is_none() {
sdist = Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
AllowPreRelease::IfNecessary => {
// If prereleases are allowed as a fallback, store the
// first-matching prerelease sdist.
if prerelease_sdist.is_none() {
prerelease_sdist = Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
AllowPreRelease::No => {
continue;
}
}
} else {
// Store the first-matching stable sdist.
if sdist.is_none() {
sdist = Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
}
}
}
}
sdist.or(prerelease_wheel).or(prerelease_sdist)
}
}
#[derive(Debug, Clone)]
pub(crate) struct Candidate {
/// The name of the package.
pub(crate) package_name: PackageName,
/// The version of the package.
pub(crate) version: PubGrubVersion,
/// The file of the package.
pub(crate) file: DistributionFile,
}

View file

@ -1,15 +1,20 @@
pub use error::ResolveError;
pub use manifest::Manifest;
pub use prerelease_mode::PreReleaseMode;
pub use resolution::{Graph, PinnedPackage};
pub use resolver::{ResolutionManifest, Resolver};
pub use selector::ResolutionMode;
pub use resolution_mode::ResolutionMode;
pub use resolver::Resolver;
pub use source_distribution::BuiltSourceDistributionCache;
pub use wheel_finder::{Reporter, WheelFinder};
mod candidate_selector;
mod distribution;
mod error;
mod manifest;
mod prerelease_mode;
mod pubgrub;
mod resolution;
mod resolution_mode;
mod resolver;
mod selector;
mod source_distribution;
mod wheel_finder;

View file

@ -0,0 +1,32 @@
use pep508_rs::Requirement;
use crate::prerelease_mode::PreReleaseMode;
use crate::resolution_mode::ResolutionMode;
/// A manifest of requirements, constraints, and preferences.
#[derive(Debug)]
pub struct Manifest {
pub(crate) requirements: Vec<Requirement>,
pub(crate) constraints: Vec<Requirement>,
pub(crate) preferences: Vec<Requirement>,
pub(crate) resolution_mode: ResolutionMode,
pub(crate) prerelease_mode: PreReleaseMode,
}
impl Manifest {
pub fn new(
requirements: Vec<Requirement>,
constraints: Vec<Requirement>,
preferences: Vec<Requirement>,
resolution_mode: ResolutionMode,
prerelease_mode: PreReleaseMode,
) -> Self {
Self {
requirements,
constraints,
preferences,
resolution_mode,
prerelease_mode,
}
}
}

View file

@ -0,0 +1,98 @@
use fxhash::FxHashSet;
use pep508_rs::{Requirement, VersionOrUrl};
use puffin_package::package_name::PackageName;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum PreReleaseMode {
/// Disallow all pre-release versions.
DisallowAll,
/// Allow all pre-release versions.
AllowAll,
/// Allow pre-release versions if all versions of a package are pre-release.
IfNecessary,
/// Allow pre-release versions for first-party packages with explicit pre-release markers in
/// their version requirements.
Explicit,
/// Allow pre-release versions if all versions of a package are pre-release, or if the package
/// has an explicit pre-release marker in its version requirements.
#[default]
IfNecessaryOrExplicit,
}
/// Like [`PreReleaseMode`], but with any additional information required to select a candidate,
/// like the set of direct dependencies.
#[derive(Debug)]
pub(crate) enum PreReleaseStrategy {
/// Disallow all pre-release versions.
DisallowAll,
/// Allow all pre-release versions.
AllowAll,
/// Allow pre-release versions if all versions of a package are pre-release.
IfNecessary,
/// Allow pre-release versions for first-party packages with explicit pre-release markers in
/// their version requirements.
Explicit(FxHashSet<PackageName>),
/// Allow pre-release versions if all versions of a package are pre-release, or if the package
/// has an explicit pre-release marker in its version requirements.
IfNecessaryOrExplicit(FxHashSet<PackageName>),
}
impl PreReleaseStrategy {
pub(crate) fn from_mode(mode: PreReleaseMode, direct_dependencies: &[Requirement]) -> Self {
match mode {
PreReleaseMode::DisallowAll => Self::DisallowAll,
PreReleaseMode::AllowAll => Self::AllowAll,
PreReleaseMode::IfNecessary => Self::IfNecessary,
PreReleaseMode::Explicit => Self::Explicit(
direct_dependencies
.iter()
.filter(|requirement| {
let Some(version_or_url) = &requirement.version_or_url else {
return false;
};
let version_specifiers = match version_or_url {
VersionOrUrl::VersionSpecifier(version_specifiers) => {
version_specifiers
}
VersionOrUrl::Url(_) => return false,
};
version_specifiers
.iter()
.any(pep440_rs::VersionSpecifier::any_prerelease)
})
.map(|requirement| PackageName::normalize(&requirement.name))
.collect(),
),
PreReleaseMode::IfNecessaryOrExplicit => Self::IfNecessaryOrExplicit(
direct_dependencies
.iter()
.filter(|requirement| {
let Some(version_or_url) = &requirement.version_or_url else {
return false;
};
let version_specifiers = match version_or_url {
VersionOrUrl::VersionSpecifier(version_specifiers) => {
version_specifiers
}
VersionOrUrl::Url(_) => return false,
};
version_specifiers
.iter()
.any(pep440_rs::VersionSpecifier::any_prerelease)
})
.map(|requirement| PackageName::normalize(&requirement.name))
.collect(),
),
}
}
}

View file

@ -20,6 +20,10 @@ impl PubGrubVersion {
}
next
}
pub fn any_prerelease(&self) -> bool {
self.0.any_prerelease()
}
}
impl std::fmt::Display for PubGrubVersion {

View file

@ -0,0 +1,45 @@
use fxhash::FxHashSet;
use pep508_rs::Requirement;
use puffin_package::package_name::PackageName;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum ResolutionMode {
/// Resolve the highest compatible version of each package.
#[default]
Highest,
/// Resolve the lowest compatible version of each package.
Lowest,
/// Resolve the lowest compatible version of any direct dependencies, and the highest
/// compatible version of any transitive dependencies.
LowestDirect,
}
/// Like [`ResolutionMode`], but with any additional information required to select a candidate,
/// like the set of direct dependencies.
#[derive(Debug)]
pub(crate) enum ResolutionStrategy {
/// Resolve the highest compatible version of each package.
Highest,
/// Resolve the lowest compatible version of each package.
Lowest,
/// Resolve the lowest compatible version of any direct dependencies, and the highest
/// compatible version of any transitive dependencies.
LowestDirect(FxHashSet<PackageName>),
}
impl ResolutionStrategy {
pub(crate) fn from_mode(mode: ResolutionMode, direct_dependencies: &[Requirement]) -> Self {
match mode {
ResolutionMode::Highest => Self::Highest,
ResolutionMode::Lowest => Self::Lowest,
ResolutionMode::LowestDirect => Self::LowestDirect(
direct_dependencies
.iter()
.map(|requirement| PackageName::normalize(&requirement.name))
.collect(),
),
}
}
}

View file

@ -29,41 +29,17 @@ use puffin_package::metadata::Metadata21;
use puffin_package::package_name::PackageName;
use puffin_traits::BuildContext;
use crate::candidate_selector::CandidateSelector;
use crate::distribution::{DistributionFile, SdistFile, WheelFile};
use crate::error::ResolveError;
use crate::manifest::Manifest;
use crate::pubgrub::package::PubGrubPackage;
use crate::pubgrub::version::{PubGrubVersion, MIN_VERSION};
use crate::pubgrub::{iter_requirements, version_range};
use crate::resolution::Graph;
use crate::selector::{CandidateSelector, ResolutionMode};
use crate::source_distribution::{download_and_build_sdist, read_dist_info};
use crate::BuiltSourceDistributionCache;
/// A manifest of requirements, constraints, and preferences.
#[derive(Debug)]
pub struct ResolutionManifest {
requirements: Vec<Requirement>,
constraints: Vec<Requirement>,
preferences: Vec<Requirement>,
mode: ResolutionMode,
}
impl ResolutionManifest {
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 + Sync> {
requirements: Vec<Requirement>,
constraints: Vec<Requirement>,
@ -78,18 +54,14 @@ pub struct Resolver<'a, Context: BuildContext + Sync> {
impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> {
/// Initialize a new resolver.
pub fn new(
manifest: ResolutionManifest,
manifest: Manifest,
markers: &'a MarkerEnvironment,
tags: &'a Tags,
client: &'a RegistryClient,
build_context: &'a Context,
) -> Self {
Self {
selector: CandidateSelector::from_mode(
manifest.mode,
&manifest.requirements,
&manifest.preferences,
),
selector: CandidateSelector::from(&manifest),
index: Arc::new(Index::default()),
requirements: manifest.requirements,
constraints: manifest.constraints,

View file

@ -1,212 +0,0 @@
use fxhash::{FxHashMap, FxHashSet};
use pubgrub::range::Range;
use pep508_rs::{Requirement, VersionOrUrl};
use puffin_package::package_name::PackageName;
use crate::distribution::DistributionFile;
use crate::pubgrub::version::PubGrubVersion;
use crate::resolver::VersionMap;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum ResolutionMode {
/// Resolve the highest compatible version of each package.
#[default]
Highest,
/// Resolve the lowest compatible version of each package.
Lowest,
/// Resolve the lowest compatible version of any direct dependencies, and the highest
/// compatible version of any transitive dependencies.
LowestDirect,
}
/// Like [`ResolutionMode`], but with any additional information required to select a candidate,
/// like the set of direct dependencies.
#[derive(Debug)]
enum ResolutionStrategy {
/// Resolve the highest compatible version of each package.
Highest,
/// Resolve the lowest compatible version of each package.
Lowest,
/// Resolve the lowest compatible version of any direct dependencies, and the highest
/// compatible version of any transitive dependencies.
LowestDirect(FxHashSet<PackageName>),
}
impl ResolutionStrategy {
fn from_mode(mode: ResolutionMode, direct_dependencies: &[Requirement]) -> Self {
match mode {
ResolutionMode::Highest => Self::Highest,
ResolutionMode::Lowest => Self::Lowest,
ResolutionMode::LowestDirect => Self::LowestDirect(
direct_dependencies
.iter()
.map(|requirement| PackageName::normalize(&requirement.name))
.collect(),
),
}
}
}
/// 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 {
/// Select a [`Candidate`] from a set of candidate versions and files.
pub(crate) fn select(
&self,
package_name: &PackageName,
range: &Range<PubGrubVersion>,
version_map: &VersionMap,
) -> Option<Candidate> {
if let Some(version) = self.preferences.get(package_name) {
if range.contains(version) {
if let Some(file) = version_map.get(version) {
return Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
}
match &self.strategy {
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) {
Self::select_lowest(package_name, range, version_map)
} else {
Self::select_highest(package_name, range, version_map)
}
}
}
}
/// Select the highest-compatible [`Candidate`] from a set of candidate versions and files,
/// preferring wheels over sdists.
fn select_highest(
package_name: &PackageName,
range: &Range<PubGrubVersion>,
version_map: &VersionMap,
) -> Option<Candidate> {
let mut sdist = None;
for (version, file) in version_map.iter().rev() {
if range.contains(version) {
match file {
DistributionFile::Wheel(_) => {
return Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
DistributionFile::Sdist(_) if sdist.is_none() => {
sdist = Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
DistributionFile::Sdist(_) => {
// We already selected a more recent source distribution
}
}
}
}
sdist
}
/// Select the highest-compatible [`Candidate`] from a set of candidate versions and files,
/// preferring wheels over sdists.
fn select_lowest(
package_name: &PackageName,
range: &Range<PubGrubVersion>,
version_map: &VersionMap,
) -> Option<Candidate> {
let mut sdist = None;
for (version, file) in version_map {
if range.contains(version) {
match file {
DistributionFile::Wheel(_) => {
return Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
DistributionFile::Sdist(_) => {
sdist = Some(Candidate {
package_name: package_name.clone(),
version: version.clone(),
file: file.clone(),
});
}
}
}
}
sdist
}
}
#[derive(Debug, Clone)]
pub(crate) struct Candidate {
/// The name of the package.
pub(crate) package_name: PackageName,
/// The version of the package.
pub(crate) version: PubGrubVersion,
/// The file of the package.
pub(crate) file: DistributionFile,
}

View file

@ -16,7 +16,7 @@ use platform_host::{Arch, Os, Platform};
use platform_tags::Tags;
use puffin_client::RegistryClientBuilder;
use puffin_interpreter::{InterpreterInfo, Virtualenv};
use puffin_resolver::{ResolutionManifest, ResolutionMode, Resolver};
use puffin_resolver::{Manifest, PreReleaseMode, ResolutionMode, Resolver};
use puffin_traits::BuildContext;
struct DummyContext;
@ -58,38 +58,18 @@ impl BuildContext for DummyContext {
}
}
#[tokio::test]
async fn pylint() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
vec![Requirement::from_str("pylint==2.3.0").unwrap()],
vec![],
vec![],
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() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -106,11 +86,12 @@ async fn black_colorama() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black[colorama]<=23.9.1").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -127,11 +108,12 @@ async fn black_python_310() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_310, &TAGS_310, &client, &DummyContext);
@ -150,11 +132,12 @@ async fn black_mypy_extensions() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![Requirement::from_str("mypy-extensions<1").unwrap()],
vec![],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -173,11 +156,12 @@ async fn black_mypy_extensions_extra() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![Requirement::from_str("mypy-extensions[extra]<1").unwrap()],
vec![],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -196,11 +180,12 @@ async fn black_flake8() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![Requirement::from_str("flake8<1").unwrap()],
vec![],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -217,11 +202,12 @@ async fn black_lowest() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black>21").unwrap()],
vec![],
vec![],
ResolutionMode::Lowest,
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -238,11 +224,12 @@ async fn black_lowest_direct() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black>21").unwrap()],
vec![],
vec![],
ResolutionMode::LowestDirect,
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -259,11 +246,12 @@ async fn black_respect_preference() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![Requirement::from_str("black==23.9.0").unwrap()],
ResolutionMode::default(),
PreReleaseMode::default(),
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
@ -280,11 +268,150 @@ async fn black_ignore_preference() -> Result<()> {
let client = RegistryClientBuilder::default().build();
let manifest = ResolutionManifest::new(
let manifest = Manifest::new(
vec![Requirement::from_str("black<=23.9.1").unwrap()],
vec![],
vec![Requirement::from_str("black==23.9.2").unwrap()],
ResolutionMode::default(),
PreReleaseMode::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_disallow_prerelease() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
vec![Requirement::from_str("black<=20.0").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::DisallowAll,
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);
let err = resolver.resolve().await.unwrap_err();
insta::assert_display_snapshot!(err);
Ok(())
}
#[tokio::test]
async fn black_allow_prerelease_if_necessary() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
vec![Requirement::from_str("black<=20.0").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::IfNecessary,
);
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 pylint_disallow_prerelease() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
vec![Requirement::from_str("pylint==2.3.0").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::DisallowAll,
);
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 pylint_allow_prerelease() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
vec![Requirement::from_str("pylint==2.3.0").unwrap()],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::AllowAll,
);
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 pylint_allow_explicit_prerelease_without_marker() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
vec![
Requirement::from_str("pylint==2.3.0").unwrap(),
Requirement::from_str("isort>=5.0.0").unwrap(),
],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::Explicit,
);
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 pylint_allow_explicit_prerelease_with_marker() -> Result<()> {
colored::control::set_override(false);
let client = RegistryClientBuilder::default().build();
let manifest = Manifest::new(
vec![
Requirement::from_str("pylint==2.3.0").unwrap(),
Requirement::from_str("isort>=5.0.0b").unwrap(),
],
vec![],
vec![],
ResolutionMode::default(),
PreReleaseMode::Explicit,
);
let resolver = Resolver::new(manifest, &MARKERS_311, &TAGS_311, &client, &DummyContext);

View file

@ -0,0 +1,20 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
appdirs==1.4.4
# via black
attrs==23.1.0
# via black
black==19.10b0
click==8.1.7
# via black
pathspec==0.11.2
# via black
regex==2023.10.3
# via black
toml==0.10.2
# via black
typed-ast==1.5.5
# via black

View file

@ -0,0 +1,5 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: err
---
No solution

View file

@ -2,17 +2,15 @@
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
appdirs==1.4.0
# via black
black==21.4b0
click==7.1.2
black==22.1.0
click==8.0.0
# via black
mypy-extensions==0.4.3
# via black
pathspec==0.7.0
pathspec==0.9.0
# via black
regex==2022.9.11
platformdirs==2.0.0
# via black
toml==0.10.1
tomli==1.1.0
# via black

View file

@ -2,17 +2,15 @@
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
appdirs==1.4.4
# via black
black==21.4b0
black==22.1.0
click==8.1.7
# via black
mypy-extensions==1.0.0
# via black
pathspec==0.11.2
# via black
regex==2023.10.3
platformdirs==3.11.0
# via black
toml==0.10.2
tomli==2.0.1
# via black

View file

@ -0,0 +1,12 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
astroid==3.0.1
# via pylint
isort==5.12.0
# via pylint
mccabe==0.7.0
# via pylint
pylint==2.3.0

View file

@ -0,0 +1,12 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
astroid==3.0.1
# via pylint
isort==6.0.0b2
# via pylint
mccabe==0.7.0
# via pylint
pylint==2.3.0

View file

@ -0,0 +1,12 @@
---
source: crates/puffin-resolver/tests/resolver.rs
expression: resolution
---
astroid==3.0.1
# via pylint
isort==5.12.0
# via pylint
mccabe==0.7.0
# via pylint
pylint==2.3.0