mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-02 01:52:22 +00:00
Resolver module improvements (#9773)
Further small refactorings for the resolver.
This commit is contained in:
parent
441ed3bdcc
commit
509dc83fd3
2 changed files with 84 additions and 64 deletions
|
@ -39,13 +39,31 @@ enum BatchPrefetchStrategy {
|
||||||
/// have to fetch the metadata for a lot of versions.
|
/// have to fetch the metadata for a lot of versions.
|
||||||
///
|
///
|
||||||
/// Note that these all heuristics that could totally prefetch lots of irrelevant versions.
|
/// Note that these all heuristics that could totally prefetch lots of irrelevant versions.
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct BatchPrefetcher {
|
pub(crate) struct BatchPrefetcher {
|
||||||
|
// Internal types.
|
||||||
tried_versions: FxHashMap<PackageName, usize>,
|
tried_versions: FxHashMap<PackageName, usize>,
|
||||||
last_prefetch: FxHashMap<PackageName, usize>,
|
last_prefetch: FxHashMap<PackageName, usize>,
|
||||||
|
// Shared (e.g., `Arc`) types.
|
||||||
|
capabilities: IndexCapabilities,
|
||||||
|
index: InMemoryIndex,
|
||||||
|
request_sink: Sender<Request>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BatchPrefetcher {
|
impl BatchPrefetcher {
|
||||||
|
pub(crate) fn new(
|
||||||
|
capabilities: IndexCapabilities,
|
||||||
|
index: InMemoryIndex,
|
||||||
|
request_sink: Sender<Request>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
tried_versions: FxHashMap::default(),
|
||||||
|
last_prefetch: FxHashMap::default(),
|
||||||
|
capabilities,
|
||||||
|
index,
|
||||||
|
request_sink,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Prefetch a large number of versions if we already unsuccessfully tried many versions.
|
/// Prefetch a large number of versions if we already unsuccessfully tried many versions.
|
||||||
pub(crate) fn prefetch_batches(
|
pub(crate) fn prefetch_batches(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -55,9 +73,6 @@ impl BatchPrefetcher {
|
||||||
current_range: &Range<Version>,
|
current_range: &Range<Version>,
|
||||||
unchangeable_constraints: Option<&Term<Range<Version>>>,
|
unchangeable_constraints: Option<&Term<Range<Version>>>,
|
||||||
python_requirement: &PythonRequirement,
|
python_requirement: &PythonRequirement,
|
||||||
request_sink: &Sender<Request>,
|
|
||||||
in_memory: &InMemoryIndex,
|
|
||||||
capabilities: &IndexCapabilities,
|
|
||||||
selector: &CandidateSelector,
|
selector: &CandidateSelector,
|
||||||
env: &ResolverEnvironment,
|
env: &ResolverEnvironment,
|
||||||
) -> anyhow::Result<(), ResolveError> {
|
) -> anyhow::Result<(), ResolveError> {
|
||||||
|
@ -79,12 +94,12 @@ impl BatchPrefetcher {
|
||||||
|
|
||||||
// This is immediate, we already fetched the version map.
|
// This is immediate, we already fetched the version map.
|
||||||
let versions_response = if let Some(index) = index {
|
let versions_response = if let Some(index) = index {
|
||||||
in_memory
|
self.index
|
||||||
.explicit()
|
.explicit()
|
||||||
.wait_blocking(&(name.clone(), index.clone()))
|
.wait_blocking(&(name.clone(), index.clone()))
|
||||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
||||||
} else {
|
} else {
|
||||||
in_memory
|
self.index
|
||||||
.implicit()
|
.implicit()
|
||||||
.wait_blocking(name)
|
.wait_blocking(name)
|
||||||
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
.ok_or_else(|| ResolveError::UnregisteredTask(name.to_string()))?
|
||||||
|
@ -166,7 +181,7 @@ impl BatchPrefetcher {
|
||||||
// Avoid prefetching built distributions that don't support _either_ PEP 658 (`.metadata`)
|
// Avoid prefetching built distributions that don't support _either_ PEP 658 (`.metadata`)
|
||||||
// or range requests.
|
// or range requests.
|
||||||
if !(wheel.file.dist_info_metadata
|
if !(wheel.file.dist_info_metadata
|
||||||
|| capabilities.supports_range_requests(&wheel.index))
|
|| self.capabilities.supports_range_requests(&wheel.index))
|
||||||
{
|
{
|
||||||
debug!("Abandoning prefetch for {wheel} due to missing registry capabilities");
|
debug!("Abandoning prefetch for {wheel} due to missing registry capabilities");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -214,9 +229,9 @@ impl BatchPrefetcher {
|
||||||
);
|
);
|
||||||
prefetch_count += 1;
|
prefetch_count += 1;
|
||||||
|
|
||||||
if in_memory.distributions().register(candidate.version_id()) {
|
if self.index.distributions().register(candidate.version_id()) {
|
||||||
let request = Request::from(dist);
|
let request = Request::from(dist);
|
||||||
request_sink.blocking_send(request)?;
|
self.request_sink.blocking_send(request)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
|
|
||||||
let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
|
let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
|
||||||
let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
|
let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
|
||||||
let mut prefetcher = BatchPrefetcher::default();
|
let mut prefetcher = BatchPrefetcher::new(
|
||||||
|
self.capabilities.clone(),
|
||||||
|
self.index.clone(),
|
||||||
|
request_sink.clone(),
|
||||||
|
);
|
||||||
let state = ForkState::new(pubgrub, self.env.clone(), self.python_requirement.clone());
|
let state = ForkState::new(pubgrub, self.env.clone(), self.python_requirement.clone());
|
||||||
let mut preferences = self.preferences.clone();
|
let mut preferences = self.preferences.clone();
|
||||||
let mut forked_states = self.env.initial_forked_states(state);
|
let mut forked_states = self.env.initial_forked_states(state);
|
||||||
|
@ -497,9 +501,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
.partial_solution
|
.partial_solution
|
||||||
.unchanging_term_for_package(next_id),
|
.unchanging_term_for_package(next_id),
|
||||||
&state.python_requirement,
|
&state.python_requirement,
|
||||||
&request_sink,
|
|
||||||
&self.index,
|
|
||||||
&self.capabilities,
|
|
||||||
&self.selector,
|
&self.selector,
|
||||||
&state.env,
|
&state.env,
|
||||||
)?;
|
)?;
|
||||||
|
@ -1078,57 +1079,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate the Python requirement.
|
// Check whether the version is incompatible due to its Python requirement.
|
||||||
let requires_python = match dist {
|
if let Some(incompatibility) = Self::check_requires_python(dist, python_requirement) {
|
||||||
CompatibleDist::InstalledDist(_) => None,
|
|
||||||
CompatibleDist::SourceDist { sdist, .. }
|
|
||||||
| CompatibleDist::IncompatibleWheel { sdist, .. } => {
|
|
||||||
sdist.file.requires_python.as_ref()
|
|
||||||
}
|
|
||||||
CompatibleDist::CompatibleWheel { wheel, .. } => wheel.file.requires_python.as_ref(),
|
|
||||||
};
|
|
||||||
let incompatibility = requires_python.and_then(|requires_python| {
|
|
||||||
if python_requirement.installed() == python_requirement.target() {
|
|
||||||
if !python_requirement
|
|
||||||
.installed()
|
|
||||||
.is_contained_by(requires_python)
|
|
||||||
{
|
|
||||||
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
|
|
||||||
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
|
|
||||||
requires_python.clone(),
|
|
||||||
PythonRequirementKind::Installed,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Some(IncompatibleDist::Source(
|
|
||||||
IncompatibleSource::RequiresPython(
|
|
||||||
requires_python.clone(),
|
|
||||||
PythonRequirementKind::Installed,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !python_requirement.target().is_contained_by(requires_python) {
|
|
||||||
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
|
|
||||||
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
|
|
||||||
requires_python.clone(),
|
|
||||||
PythonRequirementKind::Target,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
Some(IncompatibleDist::Source(
|
|
||||||
IncompatibleSource::RequiresPython(
|
|
||||||
requires_python.clone(),
|
|
||||||
PythonRequirementKind::Target,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
});
|
|
||||||
|
|
||||||
// The version is incompatible due to its Python requirement.
|
|
||||||
if let Some(incompatibility) = incompatibility {
|
|
||||||
return Ok(Some(ResolverVersion::Unavailable(
|
return Ok(Some(ResolverVersion::Unavailable(
|
||||||
candidate.version().clone(),
|
candidate.version().clone(),
|
||||||
UnavailableVersion::IncompatibleDist(incompatibility),
|
UnavailableVersion::IncompatibleDist(incompatibility),
|
||||||
|
@ -1180,6 +1132,59 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
||||||
Ok(Some(ResolverVersion::Available(version)))
|
Ok(Some(ResolverVersion::Available(version)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the distribution is incompatible with the Python requirement, and if so, return
|
||||||
|
/// the incompatibility.
|
||||||
|
fn check_requires_python(
|
||||||
|
dist: &CompatibleDist,
|
||||||
|
python_requirement: &PythonRequirement,
|
||||||
|
) -> Option<IncompatibleDist> {
|
||||||
|
let requires_python = match dist {
|
||||||
|
CompatibleDist::InstalledDist(_) => None,
|
||||||
|
CompatibleDist::SourceDist { sdist, .. }
|
||||||
|
| CompatibleDist::IncompatibleWheel { sdist, .. } => {
|
||||||
|
sdist.file.requires_python.as_ref()
|
||||||
|
}
|
||||||
|
CompatibleDist::CompatibleWheel { wheel, .. } => wheel.file.requires_python.as_ref(),
|
||||||
|
}?;
|
||||||
|
if python_requirement.installed() == python_requirement.target() {
|
||||||
|
if !python_requirement
|
||||||
|
.installed()
|
||||||
|
.is_contained_by(requires_python)
|
||||||
|
{
|
||||||
|
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
|
||||||
|
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
|
||||||
|
requires_python.clone(),
|
||||||
|
PythonRequirementKind::Installed,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Some(IncompatibleDist::Source(
|
||||||
|
IncompatibleSource::RequiresPython(
|
||||||
|
requires_python.clone(),
|
||||||
|
PythonRequirementKind::Installed,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !python_requirement.target().is_contained_by(requires_python) {
|
||||||
|
return if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
|
||||||
|
Some(IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
|
||||||
|
requires_python.clone(),
|
||||||
|
PythonRequirementKind::Target,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Some(IncompatibleDist::Source(
|
||||||
|
IncompatibleSource::RequiresPython(
|
||||||
|
requires_python.clone(),
|
||||||
|
PythonRequirementKind::Target,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a candidate package and version, return its dependencies.
|
/// Given a candidate package and version, return its dependencies.
|
||||||
#[instrument(skip_all, fields(%package, %version))]
|
#[instrument(skip_all, fields(%package, %version))]
|
||||||
fn get_dependencies_forking(
|
fn get_dependencies_forking(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue