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

Add support for uv sync --all-packages #8739

Merged
merged 1 commit into from
Nov 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
18 changes: 14 additions & 4 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2119,7 +2119,7 @@ pub struct BuildArgs {
/// directory if no source directory is provided.
///
/// If the workspace member does not exist, uv will exit with an error.
#[arg(long, conflicts_with("all"))]
#[arg(long, conflicts_with("all_packages"))]
pub package: Option<PackageName>,

/// Builds all packages in the workspace.
Expand All @@ -2128,8 +2128,8 @@ pub struct BuildArgs {
/// directory if no source directory is provided.
///
/// If the workspace member does not exist, uv will exit with an error.
#[arg(long, conflicts_with("package"))]
pub all: bool,
#[arg(long, alias = "all", conflicts_with("package"))]
pub all_packages: bool,

/// The output directory to which distributions should be written.
///
Expand Down Expand Up @@ -2912,13 +2912,23 @@ pub struct SyncArgs {
#[command(flatten)]
pub refresh: RefreshArgs,

/// Sync all packages in the workspace.
///
/// The workspace's environment (`.venv`) is updated to include all workspace
/// members.
///
/// Any extras or groups specified via `--extra`, `--group`, or related options
/// will be applied to all workspace members.
#[arg(long, conflicts_with = "package")]
pub all_packages: bool,

/// Sync for a specific package in the workspace.
///
/// The workspace's environment (`.venv`) is updated to reflect the subset
/// of dependencies declared by the specified workspace member package.
///
/// If the workspace member does not exist, uv will exit with an error.
#[arg(long)]
#[arg(long, conflicts_with = "all_packages")]
pub package: Option<PackageName>,

/// The Python interpreter to use for the project environment.
Expand Down
10 changes: 5 additions & 5 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ impl Lock {
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
pub fn to_resolution(
&self,
project: InstallTarget<'_>,
target: InstallTarget<'_>,
marker_env: &ResolverMarkerEnvironment,
tags: &Tags,
extras: &ExtrasSpecification,
Expand All @@ -588,7 +588,7 @@ impl Lock {
let mut seen = FxHashSet::default();

// Add the workspace packages to the queue.
for root_name in project.packages() {
for root_name in target.packages() {
let root = self
.find_by_name(root_name)
.map_err(|_| LockErrorKind::MultipleRootPackages {
Expand Down Expand Up @@ -638,7 +638,7 @@ impl Lock {

// Add any dependency groups that are exclusive to the workspace root (e.g., dev
// dependencies in (legacy) non-project workspace roots).
let groups = project
let groups = target
.groups()
.map_err(|err| LockErrorKind::DependencyGroup { err })?;
for group in dev.iter() {
Expand Down Expand Up @@ -688,13 +688,13 @@ impl Lock {
}
if install_options.include_package(
&dist.id.name,
project.project_name(),
target.project_name(),
&self.manifest.members,
) {
map.insert(
dist.id.name.clone(),
ResolvedDist::Installable(dist.to_dist(
project.workspace().install_path(),
target.workspace().install_path(),
TagPolicy::Required(tags),
build_options,
)?),
Expand Down
44 changes: 27 additions & 17 deletions crates/uv-workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,35 +1496,39 @@ impl VirtualProject {
/// A target that can be installed.
#[derive(Debug, Copy, Clone)]
pub enum InstallTarget<'env> {
/// An entire workspace.
Workspace(&'env Workspace),
/// A (legacy) non-project workspace root.
NonProjectWorkspace(&'env Workspace),
/// 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),
FrozenProject(&'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)
pub fn frozen(project: &'env VirtualProject, package_name: &'env PackageName) -> Self {
Self::FrozenProject(project.workspace(), package_name)
}

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

/// Return the [`PackageName`] of the target.
pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
match self {
Self::Workspace(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)),
Self::NonProjectWorkspace(workspace) => Either::Right(workspace.packages().keys()),
Self::FrozenProject(_, package_name) => Either::Left(std::iter::once(*package_name)),
}
}

Expand All @@ -1540,8 +1544,8 @@ impl<'env> InstallTarget<'env> {
DependencyGroupError,
> {
match self {
Self::Project(_) | Self::FrozenMember(..) => Ok(BTreeMap::new()),
Self::NonProject(workspace) => {
Self::Workspace(_) | Self::Project(_) | Self::FrozenProject(..) => Ok(BTreeMap::new()),
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).

Expand Down Expand Up @@ -1591,18 +1595,24 @@ impl<'env> InstallTarget<'env> {
/// Return the [`PackageName`] of the target, if available.
pub fn project_name(&self) -> Option<&PackageName> {
match self {
Self::Workspace(_) => None,
Self::Project(project) => Some(project.project_name()),
Self::NonProject(_) => None,
Self::FrozenMember(_, package_name) => Some(package_name),
Self::NonProjectWorkspace(_) => None,
Self::FrozenProject(_, package_name) => Some(package_name),
}
}

pub fn from_workspace(workspace: &'env VirtualProject) -> Self {
match workspace {
VirtualProject::Project(project) => Self::Workspace(project.workspace()),
VirtualProject::NonProject(workspace) => Self::NonProjectWorkspace(workspace),
}
}
}

impl<'env> From<&'env VirtualProject> for InstallTarget<'env> {
fn from(project: &'env VirtualProject) -> Self {
pub fn from_project(project: &'env VirtualProject) -> Self {
match project {
VirtualProject::Project(project) => Self::Project(project),
VirtualProject::NonProject(workspace) => Self::NonProject(workspace),
VirtualProject::NonProject(workspace) => Self::NonProjectWorkspace(workspace),
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions crates/uv/src/commands/build_frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub(crate) async fn build_frontend(
project_dir: &Path,
src: Option<PathBuf>,
package: Option<PackageName>,
all: bool,
all_packages: bool,
output_dir: Option<PathBuf>,
sdist: bool,
wheel: bool,
Expand All @@ -65,7 +65,7 @@ pub(crate) async fn build_frontend(
project_dir,
src.as_deref(),
package.as_ref(),
all,
all_packages,
output_dir.as_deref(),
sdist,
wheel,
Expand Down Expand Up @@ -105,7 +105,7 @@ async fn build_impl(
project_dir: &Path,
src: Option<&Path>,
package: Option<&PackageName>,
all: bool,
all_packages: bool,
output_dir: Option<&Path>,
sdist: bool,
wheel: bool,
Expand Down Expand Up @@ -171,7 +171,7 @@ async fn build_impl(
// Attempt to discover the workspace; on failure, save the error for later.
let workspace = Workspace::discover(src.directory(), &DiscoveryOptions::default()).await;

// If a `--package` or `--all` was provided, adjust the source directory.
// If a `--package` or `--all-packages` was provided, adjust the source directory.
let packages = if let Some(package) = package {
if matches!(src, Source::File(_)) {
return Err(anyhow::anyhow!(
Expand Down Expand Up @@ -201,18 +201,18 @@ async fn build_impl(
vec![AnnotatedSource::from(Source::Directory(Cow::Borrowed(
package.root(),
)))]
} else if all {
} else if all_packages {
if matches!(src, Source::File(_)) {
return Err(anyhow::anyhow!(
"Cannot specify `--all` when building from a file"
"Cannot specify `--all-packages` when building from a file"
));
}

let workspace = match workspace {
Ok(ref workspace) => workspace,
Err(err) => {
return Err(anyhow::Error::from(err)
.context("`--all` was provided, but no workspace was found"));
.context("`--all-packages` was provided, but no workspace was found"));
}
};

Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ async fn lock_and_sync(
};

project::sync::do_sync(
InstallTarget::from(&project),
InstallTarget::from_project(&project),
venv,
&lock,
&extras,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use uv_configuration::{
use uv_normalize::PackageName;
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_resolver::RequirementsTxtExport;
use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace};
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};

use crate::commands::pip::loggers::DefaultResolveLogger;
use crate::commands::project::lock::{do_safe_lock, LockMode};
Expand Down Expand Up @@ -73,7 +73,7 @@ pub(crate) async fn export(
};

// Determine the default groups to include.
validate_dependency_groups(&project, &dev)?;
validate_dependency_groups(InstallTarget::from_project(&project), &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?;

let VirtualProject::Project(project) = project else {
Expand Down
34 changes: 11 additions & 23 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::dependency_groups::DependencyGroupError;
use uv_workspace::pyproject::PyProjectToml;
use uv_workspace::{VirtualProject, Workspace};
use uv_workspace::{InstallTarget, Workspace};

use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
use crate::commands::pip::operations::{Changelog, Modifications};
Expand Down Expand Up @@ -1369,16 +1369,22 @@ pub(crate) async fn script_python_requirement(
/// Validate the dependency groups requested by the [`DevGroupsSpecification`].
#[allow(clippy::result_large_err)]
pub(crate) fn validate_dependency_groups(
project: &VirtualProject,
target: InstallTarget<'_>,
dev: &DevGroupsSpecification,
) -> Result<(), ProjectError> {
for group in dev
.groups()
.into_iter()
.flat_map(GroupsSpecification::names)
{
match project {
VirtualProject::Project(project) => {
match target {
InstallTarget::Workspace(workspace) | InstallTarget::NonProjectWorkspace(workspace) => {
// The group must be defined in the workspace.
if !workspace.groups().contains(group) {
return Err(ProjectError::MissingGroupWorkspace(group.clone()));
}
}
InstallTarget::Project(project) => {
// The group must be defined in the target project.
if !project
.current_project()
Expand All @@ -1390,25 +1396,7 @@ pub(crate) fn validate_dependency_groups(
return Err(ProjectError::MissingGroupProject(group.clone()));
}
}
VirtualProject::NonProject(workspace) => {
// The group must be defined in at least one workspace package.
if !workspace
.pyproject_toml()
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
{
if workspace.packages().values().all(|package| {
!package
.pyproject_toml()
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
}) {
return Err(ProjectError::MissingGroupWorkspace(group.clone()));
}
}
}
InstallTarget::FrozenProject(_, _) => {}
}
}
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ pub(crate) async fn remove(
let defaults = default_dependency_groups(project.pyproject_toml())?;

project::sync::do_sync(
InstallTarget::from(&project),
InstallTarget::from_project(&project),
&venv,
&lock,
&extras,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ pub(crate) async fn run(
}
} else {
// Determine the default groups to include.
validate_dependency_groups(&project, &dev)?;
validate_dependency_groups(InstallTarget::from_project(&project), &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?;

// Determine the lock mode.
Expand Down Expand Up @@ -607,7 +607,7 @@ pub(crate) async fn run(
let install_options = InstallOptions::default();

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