mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Fix handling of pre-releases in preferences (#14498)
Some checks are pending
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
Some checks are pending
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
Closes https://github.com/astral-sh/uv/issues/14485 I tested this using the reproduction in the issue. It'd be nice to add test coverage though.
This commit is contained in:
parent
e31f556205
commit
7e48292fac
6 changed files with 53 additions and 20 deletions
|
@ -13,10 +13,9 @@ use uv_normalize::PackageName;
|
|||
use uv_pep440::Version;
|
||||
use uv_types::InstalledPackagesProvider;
|
||||
|
||||
use crate::preferences::{Entry, Preferences};
|
||||
use crate::preferences::{Entry, PreferenceSource, Preferences};
|
||||
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
|
||||
use crate::resolution_mode::ResolutionStrategy;
|
||||
use crate::universal_marker::UniversalMarker;
|
||||
use crate::version_map::{VersionMap, VersionMapDistHandle};
|
||||
use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
|
||||
|
||||
|
@ -188,7 +187,7 @@ impl CandidateSelector {
|
|||
if index.is_some_and(|index| !entry.index().matches(index)) {
|
||||
return None;
|
||||
}
|
||||
Either::Left(std::iter::once((entry.marker(), entry.pin().version())))
|
||||
Either::Left(std::iter::once((entry.pin().version(), entry.source())))
|
||||
}
|
||||
[..] => {
|
||||
type Entries<'a> = SmallVec<[&'a Entry; 3]>;
|
||||
|
@ -219,7 +218,7 @@ impl CandidateSelector {
|
|||
Either::Right(
|
||||
preferences
|
||||
.into_iter()
|
||||
.map(|entry| (entry.marker(), entry.pin().version())),
|
||||
.map(|entry| (entry.pin().version(), entry.source())),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
@ -238,7 +237,7 @@ impl CandidateSelector {
|
|||
/// Return the first preference that satisfies the current range and is allowed.
|
||||
fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>(
|
||||
&'a self,
|
||||
preferences: impl Iterator<Item = (&'a UniversalMarker, &'a Version)>,
|
||||
preferences: impl Iterator<Item = (&'a Version, PreferenceSource)>,
|
||||
package_name: &'a PackageName,
|
||||
range: &Range<Version>,
|
||||
version_maps: &'a [VersionMap],
|
||||
|
@ -246,7 +245,7 @@ impl CandidateSelector {
|
|||
reinstall: bool,
|
||||
env: &ResolverEnvironment,
|
||||
) -> Option<Candidate<'a>> {
|
||||
for (marker, version) in preferences {
|
||||
for (version, source) in preferences {
|
||||
// Respect the version range for this requirement.
|
||||
if !range.contains(version) {
|
||||
continue;
|
||||
|
@ -290,9 +289,14 @@ impl CandidateSelector {
|
|||
let allow = match self.prerelease_strategy.allows(package_name, env) {
|
||||
AllowPrerelease::Yes => true,
|
||||
AllowPrerelease::No => false,
|
||||
// If the pre-release is "global" (i.e., provided via a lockfile, rather than
|
||||
// a fork), accept it unless pre-releases are completely banned.
|
||||
AllowPrerelease::IfNecessary => marker.is_true(),
|
||||
// If the pre-release was provided via an existing file, rather than from the
|
||||
// current solve, accept it unless pre-releases are completely banned.
|
||||
AllowPrerelease::IfNecessary => match source {
|
||||
PreferenceSource::Resolver => false,
|
||||
PreferenceSource::Lock
|
||||
| PreferenceSource::Environment
|
||||
| PreferenceSource::RequirementsTxt => true,
|
||||
},
|
||||
};
|
||||
if !allow {
|
||||
continue;
|
||||
|
|
|
@ -34,6 +34,8 @@ pub struct Preference {
|
|||
/// is part of, otherwise `None`.
|
||||
fork_markers: Vec<UniversalMarker>,
|
||||
hashes: HashDigests,
|
||||
/// The source of the preference.
|
||||
source: PreferenceSource,
|
||||
}
|
||||
|
||||
impl Preference {
|
||||
|
@ -73,6 +75,7 @@ impl Preference {
|
|||
.map(String::as_str)
|
||||
.map(HashDigest::from_str)
|
||||
.collect::<Result<_, _>>()?,
|
||||
source: PreferenceSource::RequirementsTxt,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -91,6 +94,7 @@ impl Preference {
|
|||
index: PreferenceIndex::from(package.index(install_path)?),
|
||||
fork_markers: package.fork_markers().to_vec(),
|
||||
hashes: HashDigests::empty(),
|
||||
source: PreferenceSource::Lock,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -112,6 +116,7 @@ impl Preference {
|
|||
// `pylock.toml` doesn't have fork annotations.
|
||||
fork_markers: vec![],
|
||||
hashes: HashDigests::empty(),
|
||||
source: PreferenceSource::Lock,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -127,6 +132,7 @@ impl Preference {
|
|||
index: PreferenceIndex::Any,
|
||||
fork_markers: vec![],
|
||||
hashes: HashDigests::empty(),
|
||||
source: PreferenceSource::Environment,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -171,11 +177,24 @@ impl From<Option<IndexUrl>> for PreferenceIndex {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum PreferenceSource {
|
||||
/// The preference is from an installed package in the environment.
|
||||
Environment,
|
||||
/// The preference is from a `uv.ock` file.
|
||||
Lock,
|
||||
/// The preference is from a `requirements.txt` file.
|
||||
RequirementsTxt,
|
||||
/// The preference is from the current solve.
|
||||
Resolver,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Entry {
|
||||
marker: UniversalMarker,
|
||||
index: PreferenceIndex,
|
||||
pin: Pin,
|
||||
source: PreferenceSource,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
|
@ -193,6 +212,11 @@ impl Entry {
|
|||
pub(crate) fn pin(&self) -> &Pin {
|
||||
&self.pin
|
||||
}
|
||||
|
||||
/// Return the source of the entry.
|
||||
pub(crate) fn source(&self) -> PreferenceSource {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of pinned packages that should be preserved during resolution, if possible.
|
||||
|
@ -245,6 +269,7 @@ impl Preferences {
|
|||
version: preference.version,
|
||||
hashes: preference.hashes,
|
||||
},
|
||||
source: preference.source,
|
||||
});
|
||||
} else {
|
||||
for fork_marker in preference.fork_markers {
|
||||
|
@ -255,6 +280,7 @@ impl Preferences {
|
|||
version: preference.version.clone(),
|
||||
hashes: preference.hashes.clone(),
|
||||
},
|
||||
source: preference.source,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -270,11 +296,13 @@ impl Preferences {
|
|||
index: Option<IndexUrl>,
|
||||
markers: UniversalMarker,
|
||||
pin: impl Into<Pin>,
|
||||
source: PreferenceSource,
|
||||
) {
|
||||
self.0.entry(package_name).or_default().push(Entry {
|
||||
marker: markers,
|
||||
index: PreferenceIndex::from(index),
|
||||
pin: pin.into(),
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ use crate::fork_strategy::ForkStrategy;
|
|||
use crate::fork_urls::ForkUrls;
|
||||
use crate::manifest::Manifest;
|
||||
use crate::pins::FilePins;
|
||||
use crate::preferences::Preferences;
|
||||
use crate::preferences::{PreferenceSource, Preferences};
|
||||
use crate::pubgrub::{
|
||||
PubGrubDependency, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
|
||||
PubGrubPython,
|
||||
|
@ -447,6 +447,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.try_universal_markers()
|
||||
.unwrap_or(UniversalMarker::TRUE),
|
||||
version.clone(),
|
||||
PreferenceSource::Resolver,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -933,7 +933,7 @@ impl ValidatedLock {
|
|||
lock.prerelease_mode().cyan(),
|
||||
options.prerelease_mode.cyan()
|
||||
);
|
||||
return Ok(Self::Unusable(lock));
|
||||
return Ok(Self::Preferable(lock));
|
||||
}
|
||||
if lock.fork_strategy() != options.fork_strategy {
|
||||
let _ = writeln!(
|
||||
|
|
|
@ -1730,7 +1730,7 @@ pub(crate) async fn resolve_names(
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum PreferenceSource<'lock> {
|
||||
pub(crate) enum PreferenceLocation<'lock> {
|
||||
/// The preferences should be extracted from a lockfile.
|
||||
Lock {
|
||||
lock: &'lock Lock,
|
||||
|
@ -1745,7 +1745,7 @@ pub(crate) struct EnvironmentSpecification<'lock> {
|
|||
/// The requirements to include in the environment.
|
||||
requirements: RequirementsSpecification,
|
||||
/// The preferences to respect when resolving.
|
||||
preferences: Option<PreferenceSource<'lock>>,
|
||||
preferences: Option<PreferenceLocation<'lock>>,
|
||||
}
|
||||
|
||||
impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
|
||||
|
@ -1758,9 +1758,9 @@ impl From<RequirementsSpecification> for EnvironmentSpecification<'_> {
|
|||
}
|
||||
|
||||
impl<'lock> EnvironmentSpecification<'lock> {
|
||||
/// Set the [`PreferenceSource`] for the specification.
|
||||
/// Set the [`PreferenceLocation`] for the specification.
|
||||
#[must_use]
|
||||
pub(crate) fn with_preferences(self, preferences: PreferenceSource<'lock>) -> Self {
|
||||
pub(crate) fn with_preferences(self, preferences: PreferenceLocation<'lock>) -> Self {
|
||||
Self {
|
||||
preferences: Some(preferences),
|
||||
..self
|
||||
|
@ -1869,7 +1869,7 @@ pub(crate) async fn resolve_environment(
|
|||
|
||||
// If an existing lockfile exists, build up a set of preferences.
|
||||
let preferences = match spec.preferences {
|
||||
Some(PreferenceSource::Lock { lock, install_path }) => {
|
||||
Some(PreferenceLocation::Lock { lock, install_path }) => {
|
||||
let LockedRequirements { preferences, git } =
|
||||
read_lock_requirements(lock, install_path, &upgrade)?;
|
||||
|
||||
|
@ -1881,7 +1881,7 @@ pub(crate) async fn resolve_environment(
|
|||
|
||||
preferences
|
||||
}
|
||||
Some(PreferenceSource::Entries(entries)) => entries,
|
||||
Some(PreferenceLocation::Entries(entries)) => entries,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ use crate::commands::project::install_target::InstallTarget;
|
|||
use crate::commands::project::lock::LockMode;
|
||||
use crate::commands::project::lock_target::LockTarget;
|
||||
use crate::commands::project::{
|
||||
EnvironmentSpecification, PreferenceSource, ProjectEnvironment, ProjectError,
|
||||
EnvironmentSpecification, PreferenceLocation, ProjectEnvironment, ProjectError,
|
||||
ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
|
||||
default_dependency_groups, script_specification, update_environment,
|
||||
validate_project_requires_python,
|
||||
|
@ -958,10 +958,10 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
let spec = EnvironmentSpecification::from(spec).with_preferences(
|
||||
if let Some((lock, install_path)) = base_lock.as_ref() {
|
||||
// If we have a lockfile, use the locked versions as preferences.
|
||||
PreferenceSource::Lock { lock, install_path }
|
||||
PreferenceLocation::Lock { lock, install_path }
|
||||
} else {
|
||||
// Otherwise, extract preferences from the base environment.
|
||||
PreferenceSource::Entries(
|
||||
PreferenceLocation::Entries(
|
||||
base_site_packages
|
||||
.iter()
|
||||
.filter_map(Preference::from_installed)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue