mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
uv-resolver: make MarkerEnvironment optional
This commit touches a lot of code, but the conceptual change here is pretty simple: make it so we can run the resolver without providing a `MarkerEnvironment`. This also indicates that the resolver should run in universal mode. That is, the effect of a missing marker environment is that all marker expressions that reference the marker environment are evaluated to `true`. That is, they are ignored. (The only markers we evaluate in that context are extras, which are the only markers that aren't dependent on the environment.) One interesting change here is that a `Resolver` no longer needs an `Interpreter`. Previously, it had only been using it to construct a `PythonRequirement`, by filling in the installed version from the `Interpreter` state. But we now construct a `PythonRequirement` explicitly since its `target` Python version should no longer be tied to the `MarkerEnvironment`. (Currently, the marker environment is mutated such that its `python_full_version` is derived from multiple sources, including the CLI, which I found a touch confusing.) The change in behavior can now be observed through the `--unstable-uv-lock-file` flag. First, without it: ``` $ cat requirements.in anyio>=4.3.0 ; sys_platform == "linux" anyio<4 ; sys_platform == "darwin" $ cargo run -qp uv -- pip compile -p3.10 requirements.in anyio==4.3.0 exceptiongroup==1.2.1 # via anyio idna==3.7 # via anyio sniffio==1.3.1 # via anyio typing-extensions==4.11.0 # via anyio ``` And now with it: ``` $ cargo run -qp uv -- pip compile -p3.10 requirements.in --unstable-uv-lock-file x No solution found when resolving dependencies: `-> Because you require anyio>=4.3.0 and anyio<4, we can conclude that the requirements are unsatisfiable. ``` This is expected at this point because the marker expressions are being explicitly ignored, *and* there is no forking done yet to account for the conflict.
This commit is contained in:
parent
21f5999b57
commit
8b0fad3560
26 changed files with 153 additions and 95 deletions
|
@ -50,7 +50,9 @@ mod resolver {
|
|||
use uv_client::RegistryClient;
|
||||
use uv_configuration::{BuildKind, NoBinary, NoBuild, SetupPyStrategy};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Manifest, Options, ResolutionGraph, Resolver};
|
||||
use uv_resolver::{
|
||||
FlatIndex, InMemoryIndex, Manifest, Options, PythonRequirement, ResolutionGraph, Resolver,
|
||||
};
|
||||
use uv_types::{
|
||||
BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceBuildTrait,
|
||||
};
|
||||
|
@ -93,12 +95,13 @@ mod resolver {
|
|||
let build_context = Context::new(cache, interpreter.clone());
|
||||
let hashes = HashStrategy::None;
|
||||
let installed_packages = EmptyInstalledPackages;
|
||||
let python_requirement = PythonRequirement::from_marker_environment(&interpreter, &MARKERS);
|
||||
|
||||
let resolver = Resolver::new(
|
||||
manifest,
|
||||
Options::default(),
|
||||
&MARKERS,
|
||||
&interpreter,
|
||||
&python_requirement,
|
||||
Some(&MARKERS),
|
||||
&TAGS,
|
||||
client,
|
||||
&flat_index,
|
||||
|
|
|
@ -33,24 +33,11 @@ pub struct Requirement {
|
|||
|
||||
impl Requirement {
|
||||
/// Returns whether the markers apply for the given environment.
|
||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||
if let Some(marker) = &self.marker {
|
||||
marker.evaluate(env, extras)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the markers apply only for the given extras.
|
||||
///
|
||||
/// When `env` is `None`, this specifically evaluates all marker
|
||||
/// expressions based on the environment to `true`. That is, this provides
|
||||
/// environment independent marker evaluation.
|
||||
pub fn evaluate_optional_environment(
|
||||
&self,
|
||||
env: Option<&MarkerEnvironment>,
|
||||
extras: &[ExtraName],
|
||||
) -> bool {
|
||||
pub fn evaluate_markers(&self, env: Option<&MarkerEnvironment>, extras: &[ExtraName]) -> bool {
|
||||
if let Some(marker) = &self.marker {
|
||||
marker.evaluate_optional_environment(env, extras)
|
||||
} else {
|
||||
|
|
|
@ -45,10 +45,15 @@ impl Display for UnresolvedRequirement {
|
|||
|
||||
impl UnresolvedRequirement {
|
||||
/// Returns whether the markers apply for the given environment.
|
||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||
///
|
||||
/// When the environment is not given, this treats all marker expressions
|
||||
/// that reference the environment as true. In other words, it does
|
||||
/// environment independent expression evaluation. (Which in turn devolves
|
||||
/// to "only evaluate marker expressions that reference an extra name.")
|
||||
pub fn evaluate_markers(&self, env: Option<&MarkerEnvironment>, extras: &[ExtraName]) -> bool {
|
||||
match self {
|
||||
Self::Named(requirement) => requirement.evaluate_markers(env, extras),
|
||||
Self::Unnamed(requirement) => requirement.evaluate_markers(env, extras),
|
||||
Self::Unnamed(requirement) => requirement.evaluate_optional_environment(env, extras),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,8 +37,17 @@ pub struct UnnamedRequirement {
|
|||
impl UnnamedRequirement {
|
||||
/// Returns whether the markers apply for the given environment
|
||||
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
||||
self.evaluate_optional_environment(Some(env), extras)
|
||||
}
|
||||
|
||||
/// Returns whether the markers apply for the given environment
|
||||
pub fn evaluate_optional_environment(
|
||||
&self,
|
||||
env: Option<&MarkerEnvironment>,
|
||||
extras: &[ExtraName],
|
||||
) -> bool {
|
||||
if let Some(marker) = &self.marker {
|
||||
marker.evaluate(env, extras)
|
||||
marker.evaluate_optional_environment(env, extras)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ use uv_configuration::{ConfigSettings, NoBinary, NoBuild, SetupPyStrategy};
|
|||
use uv_dispatch::BuildDispatch;
|
||||
use uv_installer::SitePackages;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, Options, Resolver};
|
||||
use uv_resolver::{
|
||||
ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, Options, PythonRequirement, Resolver,
|
||||
};
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
|
||||
#[derive(ValueEnum, Default, Clone)]
|
||||
|
@ -98,6 +100,9 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
|||
|
||||
// Copied from `BuildDispatch`
|
||||
let tags = venv.interpreter().tags()?;
|
||||
let markers = venv.interpreter().markers();
|
||||
let python_requirement =
|
||||
PythonRequirement::from_marker_environment(venv.interpreter(), markers);
|
||||
let resolver = Resolver::new(
|
||||
Manifest::simple(
|
||||
args.requirements
|
||||
|
@ -107,8 +112,8 @@ pub(crate) async fn resolve_cli(args: ResolveCliArgs) -> Result<()> {
|
|||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
Options::default(),
|
||||
venv.interpreter().markers(),
|
||||
venv.interpreter(),
|
||||
&python_requirement,
|
||||
Some(venv.interpreter().markers()),
|
||||
tags,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
|
|
@ -19,7 +19,7 @@ use uv_client::RegistryClient;
|
|||
use uv_configuration::{BuildKind, ConfigSettings, NoBinary, NoBuild, Reinstall, SetupPyStrategy};
|
||||
use uv_installer::{Downloader, Installer, Plan, Planner, SitePackages};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Manifest, Options, Resolver};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Manifest, Options, PythonRequirement, Resolver};
|
||||
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
|
||||
/// The main implementation of [`BuildContext`], used by the CLI, see [`BuildContext`]
|
||||
|
@ -135,12 +135,14 @@ impl<'a> BuildContext for BuildDispatch<'a> {
|
|||
|
||||
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
|
||||
let markers = self.interpreter.markers();
|
||||
let python_requirement =
|
||||
PythonRequirement::from_marker_environment(self.interpreter, markers);
|
||||
let tags = self.interpreter.tags()?;
|
||||
let resolver = Resolver::new(
|
||||
Manifest::simple(requirements.to_vec()),
|
||||
self.options,
|
||||
markers,
|
||||
self.interpreter,
|
||||
&python_requirement,
|
||||
Some(markers),
|
||||
tags,
|
||||
self.client,
|
||||
self.flat_index,
|
||||
|
|
|
@ -141,7 +141,7 @@ impl<'a> Planner<'a> {
|
|||
|
||||
for requirement in self.requirements {
|
||||
// Filter out incompatible requirements.
|
||||
if !requirement.evaluate_markers(venv.interpreter().markers(), &[]) {
|
||||
if !requirement.evaluate_markers(Some(venv.interpreter().markers()), &[]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -296,7 +296,7 @@ impl<'a> SitePackages<'a> {
|
|||
for entry in requirements {
|
||||
if entry
|
||||
.requirement
|
||||
.evaluate_markers(self.venv.interpreter().markers(), &[])
|
||||
.evaluate_markers(Some(self.venv.interpreter().markers()), &[])
|
||||
{
|
||||
if seen.insert(entry.clone()) {
|
||||
stack.push(entry.clone());
|
||||
|
|
|
@ -96,9 +96,14 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
}
|
||||
|
||||
/// Resolve the requirements from the provided source trees.
|
||||
///
|
||||
/// When the environment is not given, this treats all marker expressions
|
||||
/// that reference the environment as true. In other words, it does
|
||||
/// environment independent expression evaluation. (Which in turn devolves
|
||||
/// to "only evaluate marker expressions that reference an extra name.")
|
||||
pub async fn resolve(
|
||||
self,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
) -> Result<Vec<RequestedRequirements>, LookaheadError> {
|
||||
let mut results = Vec::new();
|
||||
let mut futures = FuturesUnordered::new();
|
||||
|
|
|
@ -29,7 +29,7 @@ impl CandidateSelector {
|
|||
pub(crate) fn for_resolution(
|
||||
options: Options,
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
) -> Self {
|
||||
Self {
|
||||
resolution_strategy: ResolutionStrategy::from_mode(
|
||||
|
|
|
@ -97,42 +97,42 @@ impl Manifest {
|
|||
/// - Determining which requirements should allow local version specifiers (e.g., `torch==2.2.0+cpu`).
|
||||
pub fn requirements<'a>(
|
||||
&'a self,
|
||||
markers: &'a MarkerEnvironment,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
mode: DependencyMode,
|
||||
) -> impl Iterator<Item = &Requirement> {
|
||||
) -> impl Iterator<Item = &Requirement> + 'a {
|
||||
match mode {
|
||||
// Include all direct and transitive requirements, with constraints and overrides applied.
|
||||
DependencyMode::Transitive => Either::Left( self
|
||||
.lookaheads
|
||||
.iter()
|
||||
.flat_map(|lookahead| {
|
||||
.flat_map(move |lookahead| {
|
||||
self.overrides
|
||||
.apply(lookahead.requirements())
|
||||
.filter(|requirement| {
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
})
|
||||
.chain(self.editables.iter().flat_map(|(editable, _metadata, requirements)| {
|
||||
.chain(self.editables.iter().flat_map(move |(editable, _metadata, requirements)| {
|
||||
self.overrides
|
||||
.apply(&requirements.dependencies)
|
||||
.filter(|requirement| {
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
})
|
||||
}))
|
||||
.chain(
|
||||
self.overrides
|
||||
.apply(&self.requirements)
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[])),
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
)
|
||||
.chain(
|
||||
self.constraints
|
||||
.requirements()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[])),
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
)
|
||||
.chain(
|
||||
self.overrides
|
||||
.requirements()
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[])),
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
))
|
||||
,
|
||||
|
||||
|
@ -141,7 +141,7 @@ impl Manifest {
|
|||
self.overrides.apply(& self.requirements)
|
||||
.chain(self.constraints.requirements())
|
||||
.chain(self.overrides.requirements())
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))),
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,9 +157,9 @@ impl Manifest {
|
|||
/// the `lowest-direct` strategy is in use.
|
||||
pub fn user_requirements<'a>(
|
||||
&'a self,
|
||||
markers: &'a MarkerEnvironment,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
mode: DependencyMode,
|
||||
) -> impl Iterator<Item = &PackageName> {
|
||||
) -> impl Iterator<Item = &PackageName> + 'a {
|
||||
match mode {
|
||||
// Include direct requirements, dependencies of editables, and transitive dependencies
|
||||
// of local packages.
|
||||
|
@ -167,17 +167,17 @@ impl Manifest {
|
|||
self.lookaheads
|
||||
.iter()
|
||||
.filter(|lookahead| lookahead.direct())
|
||||
.flat_map(|lookahead| {
|
||||
.flat_map(move |lookahead| {
|
||||
self.overrides
|
||||
.apply(lookahead.requirements())
|
||||
.filter(|requirement| {
|
||||
.filter(move |requirement| {
|
||||
requirement.evaluate_markers(markers, lookahead.extras())
|
||||
})
|
||||
})
|
||||
.chain(self.editables.iter().flat_map(
|
||||
|(editable, _metadata, uv_requirements)| {
|
||||
move |(editable, _metadata, uv_requirements)| {
|
||||
self.overrides.apply(&uv_requirements.dependencies).filter(
|
||||
|requirement| {
|
||||
move |requirement| {
|
||||
requirement.evaluate_markers(markers, &editable.extras)
|
||||
},
|
||||
)
|
||||
|
@ -186,7 +186,7 @@ impl Manifest {
|
|||
.chain(
|
||||
self.overrides
|
||||
.apply(&self.requirements)
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[])),
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
|
||||
)
|
||||
.map(|requirement| &requirement.name),
|
||||
),
|
||||
|
@ -195,7 +195,7 @@ impl Manifest {
|
|||
DependencyMode::Direct => Either::Right(
|
||||
self.overrides
|
||||
.apply(self.requirements.iter())
|
||||
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
|
||||
.map(|requirement| &requirement.name),
|
||||
),
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ impl Preferences {
|
|||
/// to an applicable subset.
|
||||
pub(crate) fn from_iter<PreferenceIterator: IntoIterator<Item = Preference>>(
|
||||
preferences: PreferenceIterator,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
) -> Self {
|
||||
Self(
|
||||
// TODO(zanieb): We should explicitly ensure that when a package name is seen multiple times
|
||||
|
|
|
@ -56,7 +56,7 @@ impl PreReleaseStrategy {
|
|||
pub(crate) fn from_mode(
|
||||
mode: PreReleaseMode,
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
match mode {
|
||||
|
|
|
@ -28,7 +28,7 @@ impl PubGrubDependencies {
|
|||
source_extra: Option<&ExtraName>,
|
||||
urls: &Urls,
|
||||
locals: &Locals,
|
||||
env: &MarkerEnvironment,
|
||||
env: Option<&MarkerEnvironment>,
|
||||
) -> Result<Self, ResolveError> {
|
||||
let mut dependencies = Vec::default();
|
||||
let mut seen = FxHashSet::default();
|
||||
|
@ -70,7 +70,7 @@ fn add_requirements(
|
|||
source_extra: Option<&ExtraName>,
|
||||
urls: &Urls,
|
||||
locals: &Locals,
|
||||
env: &MarkerEnvironment,
|
||||
env: Option<&MarkerEnvironment>,
|
||||
dependencies: &mut Vec<(PubGrubPackage, Range<Version>)>,
|
||||
seen: &mut FxHashSet<ExtraName>,
|
||||
) -> Result<(), ResolveError> {
|
||||
|
|
|
@ -12,13 +12,17 @@ pub struct PythonRequirement {
|
|||
}
|
||||
|
||||
impl PythonRequirement {
|
||||
pub fn new(interpreter: &Interpreter, markers: &MarkerEnvironment) -> Self {
|
||||
pub fn new(interpreter: &Interpreter, target: &StringVersion) -> Self {
|
||||
Self {
|
||||
installed: interpreter.python_full_version().clone(),
|
||||
target: markers.python_full_version.clone(),
|
||||
target: target.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_marker_environment(interpreter: &Interpreter, env: &MarkerEnvironment) -> Self {
|
||||
Self::new(interpreter, &env.python_full_version)
|
||||
}
|
||||
|
||||
/// Return the installed version of Python.
|
||||
pub fn installed(&self) -> &StringVersion {
|
||||
&self.installed
|
||||
|
|
|
@ -37,7 +37,7 @@ impl ResolutionStrategy {
|
|||
pub(crate) fn from_mode(
|
||||
mode: ResolutionMode,
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
match mode {
|
||||
|
|
|
@ -21,7 +21,7 @@ impl Locals {
|
|||
/// Determine the set of permitted local versions in the [`Manifest`].
|
||||
pub(crate) fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
let mut required: FxHashMap<PackageName, Version> = FxHashMap::default();
|
||||
|
|
|
@ -32,7 +32,6 @@ pub(crate) use urls::Urls;
|
|||
use uv_client::RegistryClient;
|
||||
use uv_configuration::{Constraints, Overrides};
|
||||
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
|
||||
use uv_interpreter::Interpreter;
|
||||
use uv_normalize::PackageName;
|
||||
use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
|
||||
|
||||
|
@ -188,8 +187,9 @@ pub struct Resolver<'a, Provider: ResolverProvider, InstalledPackages: Installed
|
|||
locals: Locals,
|
||||
dependency_mode: DependencyMode,
|
||||
hasher: &'a HashStrategy,
|
||||
markers: &'a MarkerEnvironment,
|
||||
python_requirement: PythonRequirement,
|
||||
/// When not set, the resolver is in "universal" mode.
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
python_requirement: &'a PythonRequirement,
|
||||
selector: CandidateSelector,
|
||||
index: &'a InMemoryIndex,
|
||||
installed_packages: &'a InstalledPackages,
|
||||
|
@ -209,12 +209,27 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
|
|||
/// Initialize a new resolver using the default backend doing real requests.
|
||||
///
|
||||
/// Reads the flat index entries.
|
||||
///
|
||||
/// # Marker environment
|
||||
///
|
||||
/// The marker environment is optional.
|
||||
///
|
||||
/// When a marker environment is not provided, the resolver is said to be
|
||||
/// in "universal" mode. When in universal mode, the resolution produced
|
||||
/// may contain multiple versions of the same package. And thus, in order
|
||||
/// to use the resulting resolution, there must be a "universal"-aware
|
||||
/// reader of the resolution that knows to exclude distributions that can't
|
||||
/// be used in the current environment.
|
||||
///
|
||||
/// When a marker environment is provided, the reslver is in
|
||||
/// "non-universal" mode, which corresponds to standard `pip` behavior that
|
||||
/// works only for a specific marker environment.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
manifest: Manifest,
|
||||
options: Options,
|
||||
markers: &'a MarkerEnvironment,
|
||||
interpreter: &'a Interpreter,
|
||||
python_requirement: &'a PythonRequirement,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
tags: &'a Tags,
|
||||
client: &'a RegistryClient,
|
||||
flat_index: &'a FlatIndex,
|
||||
|
@ -228,7 +243,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
|
|||
DistributionDatabase::new(client, build_context),
|
||||
flat_index,
|
||||
tags,
|
||||
PythonRequirement::new(interpreter, markers),
|
||||
python_requirement.clone(),
|
||||
AllowedYanks::from_manifest(&manifest, markers, options.dependency_mode),
|
||||
hasher,
|
||||
options.exclude_newer,
|
||||
|
@ -240,7 +255,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
|
|||
options,
|
||||
hasher,
|
||||
markers,
|
||||
PythonRequirement::new(interpreter, markers),
|
||||
python_requirement,
|
||||
index,
|
||||
provider,
|
||||
installed_packages,
|
||||
|
@ -257,8 +272,8 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
manifest: Manifest,
|
||||
options: Options,
|
||||
hasher: &'a HashStrategy,
|
||||
markers: &'a MarkerEnvironment,
|
||||
python_requirement: PythonRequirement,
|
||||
markers: Option<&'a MarkerEnvironment>,
|
||||
python_requirement: &'a PythonRequirement,
|
||||
index: &'a InMemoryIndex,
|
||||
provider: Provider,
|
||||
installed_packages: &'a InstalledPackages,
|
||||
|
@ -323,12 +338,12 @@ impl<'a, Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvide
|
|||
Err(if let ResolveError::NoSolution(err) = err {
|
||||
ResolveError::NoSolution(
|
||||
err.with_available_versions(
|
||||
&self.python_requirement,
|
||||
self.python_requirement,
|
||||
&self.visited,
|
||||
&self.index.packages,
|
||||
)
|
||||
.with_selector(self.selector.clone())
|
||||
.with_python_requirement(&self.python_requirement)
|
||||
.with_python_requirement(self.python_requirement)
|
||||
.with_index_locations(self.provider.index_locations())
|
||||
.with_unavailable_packages(&self.unavailable_packages)
|
||||
.with_incomplete_packages(&self.incomplete_packages),
|
||||
|
|
|
@ -15,7 +15,7 @@ pub(crate) struct Urls(FxHashMap<PackageName, VerbatimUrl>);
|
|||
impl Urls {
|
||||
pub(crate) fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
dependencies: DependencyMode,
|
||||
) -> Result<Self, ResolveError> {
|
||||
let mut urls: FxHashMap<PackageName, VerbatimUrl> = FxHashMap::default();
|
||||
|
|
|
@ -15,7 +15,7 @@ pub struct AllowedYanks(FxHashMap<PackageName, FxHashSet<Version>>);
|
|||
impl AllowedYanks {
|
||||
pub fn from_manifest(
|
||||
manifest: &Manifest,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
dependencies: DependencyMode,
|
||||
) -> Self {
|
||||
let mut allowed_yanks = FxHashMap::<PackageName, FxHashSet<Version>>::default();
|
||||
|
|
|
@ -19,7 +19,8 @@ use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, Set
|
|||
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
|
||||
use uv_resolver::{
|
||||
DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options,
|
||||
OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
|
||||
OptionsBuilder, PreReleaseMode, Preference, PythonRequirement, ResolutionGraph, ResolutionMode,
|
||||
Resolver,
|
||||
};
|
||||
use uv_types::{
|
||||
BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceBuildTrait,
|
||||
|
@ -126,14 +127,15 @@ async fn resolve(
|
|||
let real_interpreter =
|
||||
find_default_python(&Cache::temp().unwrap()).expect("Expected a python to be installed");
|
||||
let interpreter = Interpreter::artificial(real_interpreter.platform().clone(), markers.clone());
|
||||
let python_requirement = PythonRequirement::from_marker_environment(&interpreter, markers);
|
||||
let build_context = DummyContext::new(Cache::temp()?, interpreter.clone());
|
||||
let hashes = HashStrategy::None;
|
||||
let installed_packages = EmptyInstalledPackages;
|
||||
let resolver = Resolver::new(
|
||||
manifest,
|
||||
options,
|
||||
markers,
|
||||
&interpreter,
|
||||
&python_requirement,
|
||||
Some(markers),
|
||||
tags,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
|
|
@ -85,9 +85,14 @@ impl HashStrategy {
|
|||
}
|
||||
|
||||
/// Generate the required hashes from a set of [`UnresolvedRequirement`] entries.
|
||||
///
|
||||
/// When the environment is not given, this treats all marker expressions
|
||||
/// that reference the environment as true. In other words, it does
|
||||
/// environment independent expression evaluation. (Which in turn devolves
|
||||
/// to "only evaluate marker expressions that reference an extra name.")
|
||||
pub fn from_requirements<'a>(
|
||||
requirements: impl Iterator<Item = (&'a UnresolvedRequirement, &'a [String])>,
|
||||
markers: &MarkerEnvironment,
|
||||
markers: Option<&MarkerEnvironment>,
|
||||
) -> Result<Self, HashStrategyError> {
|
||||
let mut hashes = FxHashMap::<PackageId, Vec<HashDigest>>::default();
|
||||
|
||||
|
|
|
@ -238,6 +238,16 @@ pub(crate) async fn pip_compile(
|
|||
(None, Some(python_version)) => Cow::Owned(python_version.markers(interpreter.markers())),
|
||||
(None, None) => Cow::Borrowed(interpreter.markers()),
|
||||
};
|
||||
// The marker environment to use for evaluating requirements. When
|
||||
// `uv_lock` is enabled, we specifically do environment independent marker
|
||||
// evaluation. (i.e., Only consider extras.)
|
||||
let marker_filter = if uv_lock { None } else { Some(&*markers) };
|
||||
// The Python requirement in "workspace-aware uv" should, I believe, come
|
||||
// from the pyproject.toml. For now, we just take it from the markers
|
||||
// (which does have its Python version set potentially from the CLI, which
|
||||
// I think is spiritually equivalent to setting the Python version in
|
||||
// pyproject.toml).
|
||||
let python_requirement = PythonRequirement::from_marker_environment(&interpreter, &markers);
|
||||
|
||||
// Generate, but don't enforce hashes for the requirements.
|
||||
let hasher = if generate_hashes {
|
||||
|
@ -359,7 +369,7 @@ pub(crate) async fn pip_compile(
|
|||
|
||||
for requirement in requirements
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(&markers, &[]))
|
||||
.filter(|requirement| requirement.evaluate_markers(marker_filter, &[]))
|
||||
{
|
||||
if let Some(path) = &requirement.path {
|
||||
if path.ends_with("pyproject.toml") {
|
||||
|
@ -381,7 +391,7 @@ pub(crate) async fn pip_compile(
|
|||
|
||||
for requirement in constraints
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(&markers, &[]))
|
||||
.filter(|requirement| requirement.evaluate_markers(marker_filter, &[]))
|
||||
{
|
||||
if let Some(path) = &requirement.path {
|
||||
sources.add(
|
||||
|
@ -393,7 +403,7 @@ pub(crate) async fn pip_compile(
|
|||
|
||||
for requirement in overrides
|
||||
.iter()
|
||||
.filter(|requirement| requirement.evaluate_markers(&markers, &[]))
|
||||
.filter(|requirement| requirement.evaluate_markers(marker_filter, &[]))
|
||||
{
|
||||
if let Some(path) = &requirement.path {
|
||||
sources.add(&requirement.name, SourceAnnotation::Override(path.clone()));
|
||||
|
@ -469,15 +479,14 @@ pub(crate) async fn pip_compile(
|
|||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Validate that the editables are compatible with the target Python version.
|
||||
let requirement = PythonRequirement::new(&interpreter, &markers);
|
||||
for (_, metadata, _) in &editables {
|
||||
if let Some(python_requires) = metadata.requires_python.as_ref() {
|
||||
if !python_requires.contains(requirement.target()) {
|
||||
if !python_requires.contains(python_requirement.target()) {
|
||||
return Err(anyhow!(
|
||||
"Editable `{}` requires Python {}, but resolution targets Python {}",
|
||||
metadata.name,
|
||||
python_requires,
|
||||
requirement.target()
|
||||
python_requirement.target()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -511,7 +520,7 @@ pub(crate) async fn pip_compile(
|
|||
&top_level_index,
|
||||
)
|
||||
.with_reporter(ResolverReporter::from(printer))
|
||||
.resolve(&markers)
|
||||
.resolve(marker_filter)
|
||||
.await?
|
||||
}
|
||||
DependencyMode::Direct => Vec::new(),
|
||||
|
@ -542,8 +551,8 @@ pub(crate) async fn pip_compile(
|
|||
let resolver = Resolver::new(
|
||||
manifest.clone(),
|
||||
options,
|
||||
&markers,
|
||||
&interpreter,
|
||||
&python_requirement,
|
||||
marker_filter,
|
||||
&tags,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
|
|
@ -45,7 +45,8 @@ use uv_requirements::{
|
|||
};
|
||||
use uv_resolver::{
|
||||
DependencyMode, ExcludeNewer, Exclusions, FlatIndex, InMemoryIndex, Lock, Manifest, Options,
|
||||
OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
|
||||
OptionsBuilder, PreReleaseMode, Preference, PythonRequirement, ResolutionGraph, ResolutionMode,
|
||||
Resolver,
|
||||
};
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
@ -268,7 +269,7 @@ pub(crate) async fn pip_install(
|
|||
.iter()
|
||||
.chain(overrides.iter())
|
||||
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
|
||||
&markers,
|
||||
Some(&markers),
|
||||
)?
|
||||
} else {
|
||||
HashStrategy::None
|
||||
|
@ -688,6 +689,7 @@ async fn resolve(
|
|||
// Collect constraints and overrides.
|
||||
let constraints = Constraints::from_requirements(constraints);
|
||||
let overrides = Overrides::from_requirements(overrides);
|
||||
let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers);
|
||||
|
||||
// Map the editables to their metadata.
|
||||
let editables: Vec<_> = editables
|
||||
|
@ -726,7 +728,7 @@ async fn resolve(
|
|||
index,
|
||||
)
|
||||
.with_reporter(ResolverReporter::from(printer))
|
||||
.resolve(markers)
|
||||
.resolve(Some(markers))
|
||||
.await?
|
||||
}
|
||||
DependencyMode::Direct => Vec::new(),
|
||||
|
@ -748,8 +750,8 @@ async fn resolve(
|
|||
let resolver = Resolver::new(
|
||||
manifest,
|
||||
options,
|
||||
markers,
|
||||
interpreter,
|
||||
&python_requirement,
|
||||
Some(markers),
|
||||
tags,
|
||||
client,
|
||||
flat_index,
|
||||
|
|
|
@ -31,7 +31,9 @@ use uv_requirements::{
|
|||
ExtrasSpecification, NamedRequirementsResolver, RequirementsSource, RequirementsSpecification,
|
||||
SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{DependencyMode, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, Resolver};
|
||||
use uv_resolver::{
|
||||
DependencyMode, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver,
|
||||
};
|
||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
|
@ -191,7 +193,7 @@ pub(crate) async fn pip_sync(
|
|||
requirements
|
||||
.iter()
|
||||
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
|
||||
&markers,
|
||||
Some(&markers),
|
||||
)?
|
||||
} else {
|
||||
HashStrategy::None
|
||||
|
@ -354,6 +356,7 @@ pub(crate) async fn pip_sync(
|
|||
let interpreter = venv.interpreter();
|
||||
let tags = interpreter.tags()?;
|
||||
let markers = interpreter.markers();
|
||||
let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers);
|
||||
|
||||
// Resolve with `--no-deps`.
|
||||
let options = OptionsBuilder::new()
|
||||
|
@ -367,8 +370,8 @@ pub(crate) async fn pip_sync(
|
|||
let resolver = Resolver::new(
|
||||
Manifest::simple(remote),
|
||||
options,
|
||||
markers,
|
||||
interpreter,
|
||||
&python_requirement,
|
||||
Some(markers),
|
||||
tags,
|
||||
&client,
|
||||
&flat_index,
|
||||
|
|
|
@ -22,7 +22,8 @@ use uv_requirements::{
|
|||
RequirementsSpecification, SourceTreeResolver,
|
||||
};
|
||||
use uv_resolver::{
|
||||
Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, ResolutionGraph, Resolver,
|
||||
Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, PythonRequirement, ResolutionGraph,
|
||||
Resolver,
|
||||
};
|
||||
use uv_types::{EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
|
||||
|
@ -101,6 +102,7 @@ pub(crate) async fn resolve(
|
|||
let preferences = Vec::new();
|
||||
let constraints = Constraints::default();
|
||||
let overrides = Overrides::default();
|
||||
let python_requirement = PythonRequirement::from_marker_environment(interpreter, markers);
|
||||
let editables = Vec::new();
|
||||
let installed_packages = EmptyInstalledPackages;
|
||||
|
||||
|
@ -150,7 +152,7 @@ pub(crate) async fn resolve(
|
|||
index,
|
||||
)
|
||||
.with_reporter(ResolverReporter::from(printer))
|
||||
.resolve(markers)
|
||||
.resolve(Some(markers))
|
||||
.await?;
|
||||
|
||||
// Create a manifest of the requirements.
|
||||
|
@ -169,8 +171,8 @@ pub(crate) async fn resolve(
|
|||
let resolver = Resolver::new(
|
||||
manifest,
|
||||
options,
|
||||
markers,
|
||||
interpreter,
|
||||
&python_requirement,
|
||||
Some(markers),
|
||||
tags,
|
||||
client,
|
||||
flat_index,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue