From 7c47a457d981042f9c871cd4a1dd1307150178c9 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 24 Dec 2024 19:01:50 -0500 Subject: [PATCH] Move installable targets out of `uv-resolver` crate (#10126) ## Summary The proximate motivation is that I want to add new variant for scripts, but `uv-resolver` can't depend on `uv-scripts` without creating a circular dependency. However, I think this _does_ just make more sense -- the resolver crate shouldn't be coupled to the various kinds of workspaces, and these details are mostly encoded in `projects/lock.rs` and similar files. --- crates/uv-resolver/src/lib.rs | 2 +- .../src/lock/{target.rs => installable.rs} | 83 +++---------------- crates/uv-resolver/src/lock/mod.rs | 4 +- .../uv-resolver/src/lock/requirements_txt.rs | 6 +- crates/uv/src/commands/project/add.rs | 3 +- crates/uv/src/commands/project/export.rs | 5 +- .../uv/src/commands/project/install_target.rs | 83 +++++++++++++++++++ crates/uv/src/commands/project/mod.rs | 1 + crates/uv/src/commands/project/remove.rs | 7 +- crates/uv/src/commands/project/run.rs | 3 +- crates/uv/src/commands/project/sync.rs | 3 +- 11 files changed, 116 insertions(+), 84 deletions(-) rename crates/uv-resolver/src/lock/{target.rs => installable.rs} (84%) create mode 100644 crates/uv/src/commands/project/install_target.rs diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index d9d1930b94b0..629539bace9e 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -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; diff --git a/crates/uv-resolver/src/lock/target.rs b/crates/uv-resolver/src/lock/installable.rs similarity index 84% rename from crates/uv-resolver/src/lock/target.rs rename to crates/uv-resolver/src/lock/installable.rs index 7a21e0ba2ff2..f9b759f6fdc0 100644 --- a/crates/uv-resolver/src/lock/target.rs +++ b/crates/uv-resolver/src/lock/installable.rs @@ -1,5 +1,6 @@ use std::collections::hash_map::Entry; use std::collections::VecDeque; +use std::path::Path; use either::Either; use petgraph::Graph; @@ -11,83 +12,25 @@ use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pep508::MarkerTree; use uv_platform_tags::Tags; use uv_pypi_types::ResolverMarkerEnvironment; -use uv_workspace::Workspace; 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, - }, -} +pub trait Installable<'lock> { + /// Return the root install path. + fn install_path(&self) -> &'lock Path; -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, - } - } + /// Return the [`Lock`] to install. + fn lock(&self) -> &'lock Lock; - /// 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 [`PackageName`] of the target. - pub fn packages(&self) -> impl Iterator { - 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; /// 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, @@ -108,7 +51,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) @@ -397,7 +340,7 @@ impl<'env> InstallTarget<'env> { build_options: &BuildOptions, ) -> Result { let dist = package.to_dist( - self.workspace().install_path(), + self.install_path(), TagPolicy::Required(tags), build_options, )?; @@ -414,7 +357,7 @@ impl<'env> InstallTarget<'env> { /// Create a non-installable [`Node`] from a [`Package`]. fn non_installable_node(&self, package: &Package, tags: &Tags) -> Result { let dist = package.to_dist( - self.workspace().install_path(), + self.install_path(), TagPolicy::Preferred(tags), &BuildOptions::default(), )?; diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 0500776916d4..10767a67bcd9 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -40,9 +40,9 @@ use uv_types::{BuildContext, HashStrategy}; use uv_workspace::Workspace; 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}; @@ -52,9 +52,9 @@ use crate::{ ResolverOutput, }; +mod installable; mod map; mod requirements_txt; -mod target; mod tree; /// The current version of the lockfile format. diff --git a/crates/uv-resolver/src/lock/requirements_txt.rs b/crates/uv-resolver/src/lock/requirements_txt.rs index e6942bdf0026..2209ed89f207 100644 --- a/crates/uv-resolver/src/lock/requirements_txt.rs +++ b/crates/uv-resolver/src/lock/requirements_txt.rs @@ -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)] @@ -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, @@ -51,7 +51,7 @@ impl<'lock> RequirementsTxtExport<'lock> { let root = petgraph.add_node(Node::Root); // Add the workspace packages to the queue. - for root_name in target.packages() { + for root_name in target.roots() { if prune.contains(root_name) { continue; } diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 55288defffa7..520d44c822f6 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -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}; @@ -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, diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 2690f84dfbb3..6700da4860a0 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -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, @@ -215,7 +216,7 @@ pub(crate) async fn export( match format { ExportFormat::RequirementsTxt => { let export = RequirementsTxtExport::from_lock( - target, + &target, &prune, &extras, &dev, diff --git a/crates/uv/src/commands/project/install_target.rs b/crates/uv/src/commands/project/install_target.rs new file mode 100644 index 000000000000..69999d4d77cf --- /dev/null +++ b/crates/uv/src/commands/project/install_target.rs @@ -0,0 +1,83 @@ +use std::path::Path; + +use itertools::Either; + +use uv_normalize::PackageName; +use uv_resolver::{Installable, Lock, Package}; +use uv_workspace::Workspace; + +/// A target that can be installed from a lockfile. +#[derive(Debug, Copy, Clone)] +pub(crate) enum InstallTarget<'lock> { + /// A project (which could be a workspace root or member). + Project { + workspace: &'lock Workspace, + name: &'lock PackageName, + lock: &'lock Lock, + }, + /// An entire workspace. + Workspace { + workspace: &'lock Workspace, + lock: &'lock Lock, + }, + /// An entire workspace with a (legacy) non-project root. + NonProjectWorkspace { + workspace: &'lock Workspace, + lock: &'lock Lock, + }, +} + +impl<'lock> Installable<'lock> for InstallTarget<'lock> { + fn install_path(&self) -> &'lock Path { + match self { + Self::Project { workspace, .. } => workspace.install_path(), + Self::Workspace { workspace, .. } => workspace.install_path(), + Self::NonProjectWorkspace { workspace, .. } => workspace.install_path(), + } + } + + fn lock(&self) -> &'lock Lock { + match self { + Self::Project { lock, .. } => lock, + Self::Workspace { lock, .. } => lock, + Self::NonProjectWorkspace { lock, .. } => lock, + } + } + + fn roots(&self) -> impl Iterator { + 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::name))) + } else { + Either::Left(lock.members().iter()) + } + } + } + } + + fn project_name(&self) -> Option<&PackageName> { + match self { + Self::Project { name, .. } => Some(name), + Self::Workspace { .. } => None, + Self::NonProjectWorkspace { .. } => None, + } + } +} + +impl<'lock> InstallTarget<'lock> { + /// Return the [`Workspace`] of the target. + pub(crate) fn workspace(&self) -> &'lock Workspace { + match self { + Self::Project { workspace, .. } => workspace, + Self::Workspace { workspace, .. } => workspace, + Self::NonProjectWorkspace { workspace, .. } => workspace, + } + } +} diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 3c218fd3322d..4869aaf38c04 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -53,6 +53,7 @@ pub(crate) mod add; pub(crate) mod environment; pub(crate) mod export; pub(crate) mod init; +mod install_target; pub(crate) mod lock; pub(crate) mod remove; pub(crate) mod run; diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index df9e8ee9228a..b032487c2b16 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -1,9 +1,9 @@ -use anyhow::{Context, Result}; use std::fmt::Write; use std::path::Path; -use uv_settings::PythonInstallMirrors; +use anyhow::{Context, Result}; use owo_colors::OwoColorize; + use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::{ @@ -15,8 +15,8 @@ use uv_fs::Simplified; use uv_normalize::DEV_DEPENDENCIES; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; -use uv_resolver::InstallTarget; use uv_scripts::Pep723Script; +use uv_settings::PythonInstallMirrors; use uv_warnings::warn_user_once; use uv_workspace::pyproject::DependencyType; use uv_workspace::pyproject_mut::{DependencyTarget, PyProjectTomlMut}; @@ -24,6 +24,7 @@ use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace}; use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger}; use crate::commands::pip::operations::Modifications; +use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; use crate::commands::project::{default_dependency_groups, ProjectError}; use crate::commands::{diagnostics, project, ExitStatus}; diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index fccbe068afc8..9fb3ae476ba4 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -31,7 +31,7 @@ use uv_python::{ PythonPreference, PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions, }; use uv_requirements::{RequirementsSource, RequirementsSpecification}; -use uv_resolver::{InstallTarget, Lock}; +use uv_resolver::Lock; use uv_scripts::Pep723Item; use uv_settings::PythonInstallMirrors; use uv_static::EnvVars; @@ -43,6 +43,7 @@ use crate::commands::pip::loggers::{ }; use crate::commands::pip::operations::Modifications; use crate::commands::project::environment::CachedEnvironment; +use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; use crate::commands::project::{ default_dependency_groups, validate_project_requires_python, DependencyGroupsTarget, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 81bc978ffe11..a87860c1f142 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -23,7 +23,7 @@ use uv_pypi_types::{ LenientRequirement, ParsedArchiveUrl, ParsedGitUrl, ParsedUrl, VerbatimParsedUrl, }; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; -use uv_resolver::{FlatIndex, InstallTarget}; +use uv_resolver::{FlatIndex, Installable}; use uv_settings::PythonInstallMirrors; use uv_types::{BuildIsolation, HashStrategy}; use uv_warnings::warn_user; @@ -33,6 +33,7 @@ use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace} use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger}; use crate::commands::pip::operations; use crate::commands::pip::operations::Modifications; +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,