Move FlatIndex into the uv-resolver crate (#2972)

## Summary

This lets us remove circular dependencies (in the future, e.g., #2945)
that arise from `FlatIndex` needing a bunch of resolver-specific
abstractions (like incompatibilities, required hashes, etc.) that aren't
necessary to _fetch_ the flat index entries.
This commit is contained in:
Charlie Marsh 2024-04-10 14:38:42 -04:00 committed by GitHub
parent a9d554fa90
commit 48ba7df98a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 222 additions and 216 deletions

View file

@ -0,0 +1,185 @@
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use rustc_hash::FxHashMap;
use tracing::instrument;
use distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
use distribution_types::{
BuiltDist, Dist, File, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist,
RegistryBuiltDist, RegistrySourceDist, SourceDist, SourceDistCompatibility, WheelCompatibility,
};
use pep440_rs::Version;
use platform_tags::Tags;
use uv_client::FlatIndexEntries;
use uv_configuration::{NoBinary, NoBuild};
use uv_normalize::PackageName;
/// A set of [`PrioritizedDist`] from a `--find-links` entry, indexed by [`PackageName`]
/// and [`Version`].
#[derive(Debug, Clone, Default)]
pub struct FlatIndex {
/// The list of [`FlatDistributions`] from the `--find-links` entries, indexed by package name.
index: FxHashMap<PackageName, FlatDistributions>,
/// Whether any `--find-links` entries could not be resolved due to a lack of network
/// connectivity.
offline: bool,
}
impl FlatIndex {
/// Collect all files from a `--find-links` target into a [`FlatIndex`].
#[instrument(skip_all)]
pub fn from_entries(
entries: FlatIndexEntries,
tags: &Tags,
no_build: &NoBuild,
no_binary: &NoBinary,
) -> Self {
// Collect compatible distributions.
let mut index = FxHashMap::default();
for (filename, file, url) in entries.entries {
let distributions = index.entry(filename.name().clone()).or_default();
Self::add_file(
distributions,
file,
filename,
tags,
no_build,
no_binary,
url,
);
}
// Collect offline entries.
let offline = entries.offline;
Self { index, offline }
}
fn add_file(
distributions: &mut FlatDistributions,
file: File,
filename: DistFilename,
tags: &Tags,
no_build: &NoBuild,
no_binary: &NoBinary,
index: IndexUrl,
) {
// No `requires-python` here: for source distributions, we don't have that information;
// for wheels, we read it lazily only when selected.
match filename {
DistFilename::WheelFilename(filename) => {
let version = filename.version.clone();
let compatibility = Self::wheel_compatibility(&filename, tags, no_binary);
let dist = Dist::Built(BuiltDist::Registry(RegistryBuiltDist {
filename,
file: Box::new(file),
index,
}));
match distributions.0.entry(version) {
Entry::Occupied(mut entry) => {
entry.get_mut().insert_built(dist, vec![], compatibility);
}
Entry::Vacant(entry) => {
entry.insert(PrioritizedDist::from_built(dist, vec![], compatibility));
}
}
}
DistFilename::SourceDistFilename(filename) => {
let compatibility = Self::source_dist_compatibility(&filename, no_build);
let dist = Dist::Source(SourceDist::Registry(RegistrySourceDist {
filename: filename.clone(),
file: Box::new(file),
index,
}));
match distributions.0.entry(filename.version) {
Entry::Occupied(mut entry) => {
entry.get_mut().insert_source(dist, vec![], compatibility);
}
Entry::Vacant(entry) => {
entry.insert(PrioritizedDist::from_source(dist, vec![], compatibility));
}
}
}
}
}
fn source_dist_compatibility(
filename: &SourceDistFilename,
no_build: &NoBuild,
) -> SourceDistCompatibility {
// Check if source distributions are allowed for this package.
let no_build = match no_build {
NoBuild::None => false,
NoBuild::All => true,
NoBuild::Packages(packages) => packages.contains(&filename.name),
};
if no_build {
return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild);
}
SourceDistCompatibility::Compatible
}
fn wheel_compatibility(
filename: &WheelFilename,
tags: &Tags,
no_binary: &NoBinary,
) -> WheelCompatibility {
// Check if binaries are allowed for this package.
let no_binary = match no_binary {
NoBinary::None => false,
NoBinary::All => true,
NoBinary::Packages(packages) => packages.contains(&filename.name),
};
if no_binary {
return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary);
}
// Determine a compatibility for the wheel based on tags.
WheelCompatibility::from(filename.compatibility(tags))
}
/// Get the [`FlatDistributions`] for the given package name.
pub fn get(&self, package_name: &PackageName) -> Option<&FlatDistributions> {
self.index.get(package_name)
}
/// Returns `true` if there are any offline `--find-links` entries.
pub fn offline(&self) -> bool {
self.offline
}
}
/// A set of [`PrioritizedDist`] from a `--find-links` entry for a single package, indexed
/// by [`Version`].
#[derive(Debug, Clone, Default)]
pub struct FlatDistributions(BTreeMap<Version, PrioritizedDist>);
impl FlatDistributions {
pub fn iter(&self) -> impl Iterator<Item = (&Version, &PrioritizedDist)> {
self.0.iter()
}
pub fn remove(&mut self, version: &Version) -> Option<PrioritizedDist> {
self.0.remove(version)
}
}
impl IntoIterator for FlatDistributions {
type Item = (Version, PrioritizedDist);
type IntoIter = std::collections::btree_map::IntoIter<Version, PrioritizedDist>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl From<FlatDistributions> for BTreeMap<Version, PrioritizedDist> {
fn from(distributions: FlatDistributions) -> Self {
distributions.0
}
}

View file

@ -1,6 +1,7 @@
pub use dependency_mode::DependencyMode;
pub use error::ResolveError;
pub use exclusions::Exclusions;
pub use flat_index::FlatIndex;
pub use manifest::Manifest;
pub use options::{Options, OptionsBuilder};
pub use preferences::{Preference, PreferenceError};
@ -24,6 +25,7 @@ mod dependency_provider;
mod editables;
mod error;
mod exclusions;
mod flat_index;
mod manifest;
mod options;
mod pins;

View file

@ -26,7 +26,7 @@ use pep508_rs::{MarkerEnvironment, Requirement};
use platform_tags::Tags;
use pypi_types::Metadata23;
pub(crate) use urls::Urls;
use uv_client::{FlatIndex, RegistryClient};
use uv_client::RegistryClient;
use uv_configuration::{Constraints, Overrides};
use uv_distribution::DistributionDatabase;
use uv_interpreter::Interpreter;
@ -36,6 +36,7 @@ use uv_types::{BuildContext, InstalledPackagesProvider};
use crate::candidate_selector::{CandidateDist, CandidateSelector};
use crate::editables::Editables;
use crate::error::ResolveError;
use crate::flat_index::FlatIndex;
use crate::manifest::Manifest;
use crate::pins::FilePins;
use crate::preferences::Preferences;

View file

@ -6,12 +6,13 @@ use chrono::{DateTime, Utc};
use distribution_types::{Dist, IndexLocations};
use platform_tags::Tags;
use pypi_types::Metadata23;
use uv_client::{FlatIndex, RegistryClient};
use uv_client::RegistryClient;
use uv_configuration::{NoBinary, NoBuild};
use uv_distribution::DistributionDatabase;
use uv_normalize::PackageName;
use uv_types::BuildContext;
use crate::flat_index::FlatIndex;
use crate::python_requirement::PythonRequirement;
use crate::version_map::VersionMap;
use crate::yanks::AllowedYanks;

View file

@ -14,11 +14,12 @@ use pep440_rs::{Version, VersionSpecifiers};
use platform_tags::Tags;
use pypi_types::{HashDigest, Yanked};
use rkyv::{de::deserializers::SharedDeserializeMap, Deserialize};
use uv_client::{FlatDistributions, OwnedArchive, SimpleMetadata, VersionFiles};
use uv_client::{OwnedArchive, SimpleMetadata, VersionFiles};
use uv_configuration::{NoBinary, NoBuild};
use uv_normalize::PackageName;
use uv_warnings::warn_user_once;
use crate::flat_index::FlatDistributions;
use crate::{python_requirement::PythonRequirement, yanks::AllowedYanks};
/// A map from versions to distributions.

View file

@ -14,12 +14,12 @@ use distribution_types::{IndexLocations, Resolution, SourceDist};
use pep508_rs::{MarkerEnvironment, Requirement, StringVersion};
use platform_tags::{Arch, Os, Platform, Tags};
use uv_cache::Cache;
use uv_client::{FlatIndex, RegistryClientBuilder};
use uv_client::RegistryClientBuilder;
use uv_configuration::{BuildKind, Constraints, NoBinary, NoBuild, Overrides, SetupPyStrategy};
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
use uv_resolver::{
DisplayResolutionGraph, Exclusions, InMemoryIndex, Manifest, Options, OptionsBuilder,
PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
DisplayResolutionGraph, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options,
OptionsBuilder, PreReleaseMode, Preference, ResolutionGraph, ResolutionMode, Resolver,
};
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, SourceBuildTrait};