Skip to content

Commit

Permalink
Move installable targets out of uv-resolver crate
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Dec 24, 2024
1 parent 473e274 commit 3e31c52
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 141 deletions.
2 changes: 1 addition & 1 deletion crates/uv-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub use exclusions::Exclusions;
pub use flat_index::{FlatDistributions, FlatIndex};
pub use fork_strategy::ForkStrategy;
pub use lock::{
InstallTarget, Lock, LockError, LockVersion, PackageMap, RequirementsTxtExport,
Installable, Lock, LockError, LockVersion, Package, PackageMap, RequirementsTxtExport,
ResolverManifest, SatisfiesResult, TreeDisplay, VERSION,
};
pub use manifest::Manifest;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,154 +1,49 @@
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, VecDeque};
use std::path::Path;

use either::Either;
use petgraph::Graph;
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, VecDeque};

use uv_configuration::{BuildOptions, DevGroupsManifest, ExtrasSpecification, InstallOptions};
use uv_distribution_types::{Edge, Node, Resolution, ResolvedDist};
use uv_normalize::{ExtraName, GroupName, PackageName, DEV_DEPENDENCIES};
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep508::MarkerTree;
use uv_platform_tags::Tags;
use uv_pypi_types::{ResolverMarkerEnvironment, VerbatimParsedUrl};
use uv_workspace::dependency_groups::{DependencyGroupError, FlatDependencyGroups};
use uv_workspace::Workspace;
use uv_workspace::dependency_groups::DependencyGroupError;

use crate::lock::{LockErrorKind, Package, TagPolicy};
use crate::{Lock, LockError};

/// A target that can be installed from a lockfile.
#[derive(Debug, Copy, Clone)]
pub enum InstallTarget<'env> {
/// A project (which could be a workspace root or member).
Project {
workspace: &'env Workspace,
name: &'env PackageName,
lock: &'env Lock,
},
/// An entire workspace.
Workspace {
workspace: &'env Workspace,
lock: &'env Lock,
},
/// An entire workspace with a (legacy) non-project root.
NonProjectWorkspace {
workspace: &'env Workspace,
lock: &'env Lock,
},
}

impl<'env> InstallTarget<'env> {
/// Return the [`Workspace`] of the target.
pub fn workspace(&self) -> &'env Workspace {
match self {
Self::Project { workspace, .. } => workspace,
Self::Workspace { workspace, .. } => workspace,
Self::NonProjectWorkspace { workspace, .. } => workspace,
}
}
pub trait Installable<'lock> {
/// Return the root install path.
fn install_path(&self) -> &'lock Path;

/// Return the [`Lock`] of the target.
pub fn lock(&self) -> &'env Lock {
match self {
Self::Project { lock, .. } => lock,
Self::Workspace { lock, .. } => lock,
Self::NonProjectWorkspace { lock, .. } => lock,
}
}
/// Return the [`Lock`] to install.
fn lock(&self) -> &'lock Lock;

/// Return the [`PackageName`] of the target.
pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
match self {
Self::Project { name, .. } => Either::Right(Either::Left(std::iter::once(*name))),
Self::NonProjectWorkspace { lock, .. } => Either::Left(lock.members().iter()),
Self::Workspace { lock, .. } => {
// Identify the workspace members.
//
// The members are encoded directly in the lockfile, unless the workspace contains a
// single member at the root, in which case, we identify it by its source.
if lock.members().is_empty() {
Either::Right(Either::Right(
lock.root().into_iter().map(|package| &package.id.name),
))
} else {
Either::Left(lock.members().iter())
}
}
}
}
/// Return the [`PackageName`] of the root packages in the target.
fn roots(&self) -> impl Iterator<Item = &PackageName>;

/// Return the [`InstallTarget`] dependency groups.
///
/// Returns dependencies that apply to the workspace root, but not any of its members. As such,
/// only returns a non-empty iterator for virtual workspaces, which can include dev dependencies
/// on the virtual root.
pub fn groups(
fn groups(
&self,
) -> Result<
BTreeMap<GroupName, Vec<uv_pep508::Requirement<VerbatimParsedUrl>>>,
DependencyGroupError,
> {
match self {
Self::Project { .. } => Ok(BTreeMap::default()),
Self::Workspace { .. } => Ok(BTreeMap::default()),
Self::NonProjectWorkspace { workspace, .. } => {
// For non-projects, we might have `dependency-groups` or `tool.uv.dev-dependencies`
// that are attached to the workspace root (which isn't a member).

// First, collect `tool.uv.dev_dependencies`
let dev_dependencies = workspace
.pyproject_toml()
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.dev_dependencies.as_ref());

// Then, collect `dependency-groups`
let dependency_groups = workspace
.pyproject_toml()
.dependency_groups
.iter()
.flatten()
.collect::<BTreeMap<_, _>>();

// Merge any overlapping groups.
let mut map = BTreeMap::new();
for (name, dependencies) in
FlatDependencyGroups::from_dependency_groups(&dependency_groups)
.map_err(|err| err.with_dev_dependencies(dev_dependencies))?
.into_iter()
.chain(
// Only add the `dev` group if `dev-dependencies` is defined.
dev_dependencies.into_iter().map(|requirements| {
(DEV_DEPENDENCIES.clone(), requirements.clone())
}),
)
{
match map.entry(name) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(dependencies);
}
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().extend(dependencies);
}
}
}

Ok(map)
}
}
}
>;

/// Return the [`PackageName`] of the target, if available.
pub fn project_name(&self) -> Option<&PackageName> {
match self {
Self::Project { name, .. } => Some(name),
Self::Workspace { .. } => None,
Self::NonProjectWorkspace { .. } => None,
}
}
fn project_name(&self) -> Option<&PackageName>;

/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
pub fn to_resolution(
fn to_resolution(
&self,
marker_env: &ResolverMarkerEnvironment,
tags: &Tags,
Expand All @@ -169,7 +64,7 @@ impl<'env> InstallTarget<'env> {
let root = petgraph.add_node(Node::Root);

// Add the workspace packages to the queue.
for root_name in self.packages() {
for root_name in self.roots() {
let dist = self
.lock()
.find_by_name(root_name)
Expand Down Expand Up @@ -419,7 +314,7 @@ impl<'env> InstallTarget<'env> {
build_options: &BuildOptions,
) -> Result<Node, LockError> {
let dist = package.to_dist(
self.workspace().install_path(),
self.install_path(),
TagPolicy::Required(tags),
build_options,
)?;
Expand All @@ -436,7 +331,7 @@ impl<'env> InstallTarget<'env> {
/// Create a non-installable [`Node`] from a [`Package`].
fn non_installable_node(&self, package: &Package, tags: &Tags) -> Result<Node, LockError> {
let dist = package.to_dist(
self.workspace().install_path(),
self.install_path(),
TagPolicy::Preferred(tags),
&BuildOptions::default(),
)?;
Expand Down
6 changes: 3 additions & 3 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use toml_edit::{value, Array, ArrayOfTables, InlineTable, Item, Table, Value};
use url::Url;

use crate::fork_strategy::ForkStrategy;
pub use crate::lock::installable::Installable;
pub use crate::lock::map::PackageMap;
pub use crate::lock::requirements_txt::RequirementsTxtExport;
pub use crate::lock::target::InstallTarget;
pub use crate::lock::tree::TreeDisplay;
use crate::requires_python::SimplifiedMarkerTree;
use crate::resolution::{AnnotatedDist, ResolutionGraphNode};
Expand Down Expand Up @@ -51,9 +51,9 @@ use uv_types::{BuildContext, HashStrategy};
use uv_workspace::dependency_groups::DependencyGroupError;
use uv_workspace::Workspace;

mod installable;
mod map;
mod requirements_txt;
mod target;
mod tree;

/// The current version of the lockfile format.
Expand Down Expand Up @@ -630,7 +630,7 @@ impl Lock {
.iter()
.copied()
.map(|marker| SimplifiedMarkerTree::new(&self.requires_python, marker))
.filter_map(super::requires_python::SimplifiedMarkerTree::try_to_string),
.filter_map(SimplifiedMarkerTree::try_to_string),
);
doc.insert("supported-markers", value(supported_environments));
}
Expand Down
6 changes: 3 additions & 3 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
use crate::graph_ops::marker_reachability;
use crate::lock::{Package, PackageId, Source};
use crate::universal_marker::{ConflictMarker, UniversalMarker};
use crate::{InstallTarget, LockError};
use crate::{Installable, LockError};

/// An export of a [`Lock`] that renders in `requirements.txt` format.
#[derive(Debug)]
Expand All @@ -33,7 +33,7 @@ pub struct RequirementsTxtExport<'lock> {

impl<'lock> RequirementsTxtExport<'lock> {
pub fn from_lock(
target: InstallTarget<'lock>,
target: &impl Installable<'lock>,
prune: &[PackageName],
extras: &ExtrasSpecification,
dev: &DevGroupsManifest,
Expand All @@ -51,7 +51,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
let root = petgraph.add_node(Node::Root);

// Add the workspace package to the queue.
for root_name in target.packages() {
for root_name in target.roots() {
if prune.contains(root_name) {
continue;
}
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use uv_pep508::{ExtraName, Requirement, UnnamedRequirement, VersionOrUrl};
use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
use uv_resolver::{FlatIndex, InstallTarget};
use uv_resolver::FlatIndex;
use uv_scripts::{Pep723Item, Pep723Script};
use uv_settings::PythonInstallMirrors;
use uv_types::{BuildIsolation, HashStrategy};
Expand All @@ -41,6 +41,7 @@ use crate::commands::pip::loggers::{
DefaultInstallLogger, DefaultResolveLogger, SummaryResolveLogger,
};
use crate::commands::pip::operations::Modifications;
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::{
init_script_python_requirement, lock, ProjectError, ProjectInterpreter, ScriptInterpreter,
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ use uv_configuration::{
use uv_dispatch::SharedState;
use uv_normalize::PackageName;
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_resolver::{InstallTarget, RequirementsTxtExport};
use uv_resolver::RequirementsTxtExport;
use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace};

use crate::commands::pip::loggers::DefaultResolveLogger;
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::{do_safe_lock, LockMode};
use crate::commands::project::{
default_dependency_groups, detect_conflicts, DependencyGroupsTarget, ProjectError,
Expand Down Expand Up @@ -186,7 +187,7 @@ pub(crate) async fn export(
match format {
ExportFormat::RequirementsTxt => {
let export = RequirementsTxtExport::from_lock(
target,
&target,
&prune,
&extras,
&dev,
Expand Down
Loading

0 comments on commit 3e31c52

Please sign in to comment.