Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow uv sync --package without copying member pyproject.toml #6943

Merged
merged 2 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ use uv_fs::{relative_to, PortablePath, PortablePathBuf};
use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference};
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_types::BuildContext;
use uv_workspace::{VirtualProject, Workspace};
use uv_workspace::{InstallTarget, Workspace};

pub use crate::lock::requirements_txt::RequirementsTxtExport;
pub use crate::lock::tree::TreeDisplay;
Expand Down Expand Up @@ -426,7 +426,7 @@ impl Lock {
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
pub fn to_resolution(
&self,
project: &VirtualProject,
project: InstallTarget<'_>,
marker_env: &ResolverMarkerEnvironment,
tags: &Tags,
extras: &ExtrasSpecification,
Expand All @@ -439,8 +439,12 @@ impl Lock {
for root_name in project.packages() {
let root = self
.find_by_name(root_name)
.expect("found too many packages matching root")
.expect("could not find root");
.map_err(|_| LockErrorKind::MultipleRootPackages {
name: root_name.clone(),
})?
.ok_or_else(|| LockErrorKind::MissingRootPackage {
name: root_name.clone(),
})?;

// Add the base package.
queue.push_back((root, None));
Expand All @@ -466,10 +470,15 @@ impl Lock {
for group in dev {
for dependency in project.group(group) {
if dependency.marker.evaluate(marker_env, &[]) {
let root_name = &dependency.name;
let root = self
.find_by_markers(&dependency.name, marker_env)
.expect("found too many packages matching root")
.expect("could not find root");
.find_by_markers(root_name, marker_env)
.map_err(|_| LockErrorKind::MultipleRootPackages {
name: root_name.clone(),
})?
.ok_or_else(|| LockErrorKind::MissingRootPackage {
name: root_name.clone(),
})?;

// Add the base package.
queue.push_back((root, None));
Expand Down Expand Up @@ -3605,6 +3614,19 @@ enum LockErrorKind {
/// An error that occurs when converting a URL to a path
#[error("failed to convert URL to path")]
UrlToPath,
/// An error that occurs when multiple packages with the same
/// name were found when identifying the root packages.
#[error("found multiple packages matching `{name}`")]
MultipleRootPackages {
/// The ID of the package.
name: PackageName,
},
/// An error that occurs when a root package can't be found.
#[error("could not find root package `{name}`")]
MissingRootPackage {
/// The ID of the package.
name: PackageName,
},
}

/// An error that occurs when a source string could not be parsed.
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-workspace/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub use workspace::{
check_nested_workspaces, DiscoveryOptions, MemberDiscovery, ProjectWorkspace, VirtualProject,
Workspace, WorkspaceError, WorkspaceMember,
check_nested_workspaces, DiscoveryOptions, InstallTarget, MemberDiscovery, ProjectWorkspace,
VirtualProject, Workspace, WorkspaceError, WorkspaceMember,
};

pub mod pyproject;
Expand Down
96 changes: 70 additions & 26 deletions crates/uv-workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,7 +1219,7 @@ fn is_included_in_workspace(
Ok(false)
}

/// A project that can be synced.
/// A project that can be discovered.
///
/// The project could be a package within a workspace, a real workspace root, or a (legacy)
/// non-project workspace root, which can define its own dev dependencies.
Expand Down Expand Up @@ -1311,13 +1311,13 @@ impl VirtualProject {
#[must_use]
pub fn with_pyproject_toml(self, pyproject_toml: PyProjectToml) -> Option<Self> {
match self {
VirtualProject::Project(project) => Some(VirtualProject::Project(
project.with_pyproject_toml(pyproject_toml)?,
)),
VirtualProject::NonProject(workspace) => {
Self::Project(project) => {
Some(Self::Project(project.with_pyproject_toml(pyproject_toml)?))
}
Self::NonProject(workspace) => {
// If this is a non-project workspace root, then by definition the root isn't a
// member, so we can just update the top-level `pyproject.toml`.
Some(VirtualProject::NonProject(Workspace {
Some(Self::NonProject(Workspace {
pyproject_toml,
..workspace.clone()
}))
Expand All @@ -1328,38 +1328,77 @@ impl VirtualProject {
/// Return the root of the project.
pub fn root(&self) -> &Path {
match self {
VirtualProject::Project(project) => project.project_root(),
VirtualProject::NonProject(workspace) => workspace.install_path(),
Self::Project(project) => project.project_root(),
Self::NonProject(workspace) => workspace.install_path(),
}
}

/// Return the [`PyProjectToml`] of the project.
pub fn pyproject_toml(&self) -> &PyProjectToml {
match self {
VirtualProject::Project(project) => project.current_project().pyproject_toml(),
VirtualProject::NonProject(workspace) => &workspace.pyproject_toml,
Self::Project(project) => project.current_project().pyproject_toml(),
Self::NonProject(workspace) => &workspace.pyproject_toml,
}
}

/// Return the [`Workspace`] of the project.
pub fn workspace(&self) -> &Workspace {
match self {
VirtualProject::Project(project) => project.workspace(),
VirtualProject::NonProject(workspace) => workspace,
Self::Project(project) => project.workspace(),
Self::NonProject(workspace) => workspace,
}
}

/// Return the [`PackageName`] of the project, if available.
pub fn project_name(&self) -> Option<&PackageName> {
match self {
VirtualProject::Project(project) => Some(project.project_name()),
VirtualProject::NonProject(_) => None,
}
}

/// Return the [`PackageName`] of the project.
/// Returns `true` if the project is a virtual workspace root.
pub fn is_non_project(&self) -> bool {
matches!(self, VirtualProject::NonProject(_))
}
}

/// A target that can be installed.
#[derive(Debug, Clone, Copy)]
pub enum InstallTarget<'env> {
/// A project (which could be a workspace root or member).
Project(&'env ProjectWorkspace),
/// A (legacy) non-project workspace root.
NonProject(&'env Workspace),
/// A frozen member within a [`Workspace`].
FrozenMember(&'env Workspace, &'env PackageName),
}

impl<'env> InstallTarget<'env> {
/// Create an [`InstallTarget`] for a frozen member within a workspace.
pub fn frozen_member(project: &'env VirtualProject, package_name: &'env PackageName) -> Self {
Self::FrozenMember(project.workspace(), package_name)
}

/// Return the [`Workspace`] of the target.
pub fn workspace(&self) -> &Workspace {
match self {
Self::Project(project) => project.workspace(),
Self::NonProject(workspace) => workspace,
Self::FrozenMember(workspace, _) => workspace,
}
}

/// Return the [`PackageName`] of the target.
pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
match self {
VirtualProject::Project(project) => {
Either::Left(std::iter::once(project.project_name()))
}
VirtualProject::NonProject(workspace) => Either::Right(workspace.packages().keys()),
Self::Project(project) => Either::Left(std::iter::once(project.project_name())),
Self::NonProject(workspace) => Either::Right(workspace.packages().keys()),
Self::FrozenMember(_, package_name) => Either::Left(std::iter::once(*package_name)),
}
}

/// Return the [`VirtualProject`] dependencies for the given group name.
/// Return the [`InstallTarget`] dependencies for the given group name.
///
/// 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
Expand All @@ -1369,11 +1408,11 @@ impl VirtualProject {
name: &GroupName,
) -> impl Iterator<Item = &pep508_rs::Requirement<VerbatimParsedUrl>> {
match self {
VirtualProject::Project(_) => {
Self::Project(_) | Self::FrozenMember(..) => {
// For projects, dev dependencies are attached to the members.
Either::Left(std::iter::empty())
}
VirtualProject::NonProject(workspace) => {
Self::NonProject(workspace) => {
// For non-projects, we might have dev dependencies that are attached to the
// workspace root (which isn't a member).
if name == &*DEV_DEPENDENCIES {
Expand All @@ -1395,17 +1434,22 @@ impl VirtualProject {
}
}

/// Return the [`PackageName`] of the project, if available.
/// Return the [`PackageName`] of the target, if available.
pub fn project_name(&self) -> Option<&PackageName> {
match self {
VirtualProject::Project(project) => Some(project.project_name()),
VirtualProject::NonProject(_) => None,
Self::Project(project) => Some(project.project_name()),
Self::NonProject(_) => None,
Self::FrozenMember(_, package_name) => Some(package_name),
}
}
}

/// Returns `true` if the project is a virtual workspace root.
pub fn is_non_project(&self) -> bool {
matches!(self, VirtualProject::NonProject(_))
impl<'env> From<&'env VirtualProject> for InstallTarget<'env> {
fn from(project: &'env VirtualProject) -> Self {
match project {
VirtualProject::Project(project) => Self::Project(project),
VirtualProject::NonProject(workspace) => Self::NonProject(workspace),
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use uv_types::{BuildIsolation, HashStrategy};
use uv_warnings::warn_user_once;
use uv_workspace::pyproject::{DependencyType, Source, SourceError};
use uv_workspace::pyproject_mut::{ArrayEdit, DependencyTarget, PyProjectTomlMut};
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
use uv_workspace::{DiscoveryOptions, InstallTarget, VirtualProject, Workspace};

use crate::commands::pip::loggers::{
DefaultInstallLogger, DefaultResolveLogger, SummaryResolveLogger,
Expand Down Expand Up @@ -668,7 +668,7 @@ pub(crate) async fn add(
let install_options = InstallOptions::default();

if let Err(err) = project::sync::do_sync(
&project,
InstallTarget::from(&project),
&venv,
&lock,
&extras,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use uv_scripts::Pep723Script;
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::pyproject::DependencyType;
use uv_workspace::pyproject_mut::{DependencyTarget, PyProjectTomlMut};
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
use uv_workspace::{DiscoveryOptions, InstallTarget, VirtualProject, Workspace};

use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
use crate::commands::pip::operations::Modifications;
Expand Down Expand Up @@ -196,7 +196,7 @@ pub(crate) async fn remove(
let state = SharedState::default();

project::sync::do_sync(
&project,
InstallTarget::from(&project),
&venv,
&lock,
&extras,
Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use uv_python::{
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_scripts::Pep723Script;
use uv_warnings::warn_user;
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace, WorkspaceError};
use uv_workspace::{DiscoveryOptions, InstallTarget, VirtualProject, Workspace, WorkspaceError};

use crate::commands::pip::loggers::{
DefaultInstallLogger, DefaultResolveLogger, SummaryInstallLogger, SummaryResolveLogger,
Expand Down Expand Up @@ -432,7 +432,7 @@ pub(crate) async fn run(
uv_resolver::ResolveError::NoSolution(err),
))) => {
let report = miette::Report::msg(format!("{err}")).context(err.header());
anstream::eprint!("{report:?}");
eprint!("{report:?}");
return Ok(ExitStatus::Failure);
}
Err(err) => return Err(err.into()),
Expand All @@ -441,7 +441,7 @@ pub(crate) async fn run(
let install_options = InstallOptions::default();

project::sync::do_sync(
&project,
InstallTarget::from(&project),
&venv,
result.lock(),
&extras,
Expand Down
Loading
Loading