Skip to content

Commit 1e997d5

Browse files
committed
Add support for uv sync --all
1 parent 40b4826 commit 1e997d5

File tree

14 files changed

+392
-68
lines changed

14 files changed

+392
-68
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2912,13 +2912,23 @@ pub struct SyncArgs {
29122912
#[command(flatten)]
29132913
pub refresh: RefreshArgs,
29142914

2915+
/// Sync all packages in the workspace.
2916+
///
2917+
/// The workspace's environment (`.venv`) is updated to include all workspace
2918+
/// members.
2919+
///
2920+
/// Any extras or groups specified via `--extra`, `--group`, or related options
2921+
/// will be applied to all workspace members.
2922+
#[arg(long, conflicts_with = "package")]
2923+
pub all: bool,
2924+
29152925
/// Sync for a specific package in the workspace.
29162926
///
29172927
/// The workspace's environment (`.venv`) is updated to reflect the subset
29182928
/// of dependencies declared by the specified workspace member package.
29192929
///
29202930
/// If the workspace member does not exist, uv will exit with an error.
2921-
#[arg(long)]
2931+
#[arg(long, conflicts_with = "all")]
29222932
pub package: Option<PackageName>,
29232933

29242934
/// The Python interpreter to use for the project environment.

crates/uv-resolver/src/lock/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,7 @@ impl Lock {
576576
/// Convert the [`Lock`] to a [`Resolution`] using the given marker environment, tags, and root.
577577
pub fn to_resolution(
578578
&self,
579-
project: InstallTarget<'_>,
579+
target: InstallTarget<'_>,
580580
marker_env: &ResolverMarkerEnvironment,
581581
tags: &Tags,
582582
extras: &ExtrasSpecification,
@@ -588,7 +588,7 @@ impl Lock {
588588
let mut seen = FxHashSet::default();
589589

590590
// Add the workspace packages to the queue.
591-
for root_name in project.packages() {
591+
for root_name in target.packages() {
592592
let root = self
593593
.find_by_name(root_name)
594594
.map_err(|_| LockErrorKind::MultipleRootPackages {
@@ -638,7 +638,7 @@ impl Lock {
638638

639639
// Add any dependency groups that are exclusive to the workspace root (e.g., dev
640640
// dependencies in (legacy) non-project workspace roots).
641-
let groups = project
641+
let groups = target
642642
.groups()
643643
.map_err(|err| LockErrorKind::DependencyGroup { err })?;
644644
for group in dev.iter() {
@@ -688,13 +688,13 @@ impl Lock {
688688
}
689689
if install_options.include_package(
690690
&dist.id.name,
691-
project.project_name(),
691+
target.project_name(),
692692
&self.manifest.members,
693693
) {
694694
map.insert(
695695
dist.id.name.clone(),
696696
ResolvedDist::Installable(dist.to_dist(
697-
project.workspace().install_path(),
697+
target.workspace().install_path(),
698698
TagPolicy::Required(tags),
699699
build_options,
700700
)?),

crates/uv-workspace/src/workspace.rs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1496,35 +1496,39 @@ impl VirtualProject {
14961496
/// A target that can be installed.
14971497
#[derive(Debug, Copy, Clone)]
14981498
pub enum InstallTarget<'env> {
1499+
/// An entire workspace.
1500+
Workspace(&'env Workspace),
1501+
/// A (legacy) non-project workspace root.
1502+
NonProjectWorkspace(&'env Workspace),
14991503
/// A project (which could be a workspace root or member).
15001504
Project(&'env ProjectWorkspace),
1501-
/// A (legacy) non-project workspace root.
1502-
NonProject(&'env Workspace),
15031505
/// A frozen member within a [`Workspace`].
1504-
FrozenMember(&'env Workspace, &'env PackageName),
1506+
FrozenProject(&'env Workspace, &'env PackageName),
15051507
}
15061508

15071509
impl<'env> InstallTarget<'env> {
15081510
/// Create an [`InstallTarget`] for a frozen member within a workspace.
1509-
pub fn frozen_member(project: &'env VirtualProject, package_name: &'env PackageName) -> Self {
1510-
Self::FrozenMember(project.workspace(), package_name)
1511+
pub fn frozen(project: &'env VirtualProject, package_name: &'env PackageName) -> Self {
1512+
Self::FrozenProject(project.workspace(), package_name)
15111513
}
15121514

15131515
/// Return the [`Workspace`] of the target.
15141516
pub fn workspace(&self) -> &Workspace {
15151517
match self {
1518+
Self::Workspace(workspace) => workspace,
15161519
Self::Project(project) => project.workspace(),
1517-
Self::NonProject(workspace) => workspace,
1518-
Self::FrozenMember(workspace, _) => workspace,
1520+
Self::NonProjectWorkspace(workspace) => workspace,
1521+
Self::FrozenProject(workspace, _) => workspace,
15191522
}
15201523
}
15211524

15221525
/// Return the [`PackageName`] of the target.
15231526
pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
15241527
match self {
1528+
Self::Workspace(workspace) => Either::Right(workspace.packages().keys()),
15251529
Self::Project(project) => Either::Left(std::iter::once(project.project_name())),
1526-
Self::NonProject(workspace) => Either::Right(workspace.packages().keys()),
1527-
Self::FrozenMember(_, package_name) => Either::Left(std::iter::once(*package_name)),
1530+
Self::NonProjectWorkspace(workspace) => Either::Right(workspace.packages().keys()),
1531+
Self::FrozenProject(_, package_name) => Either::Left(std::iter::once(*package_name)),
15281532
}
15291533
}
15301534

@@ -1540,8 +1544,8 @@ impl<'env> InstallTarget<'env> {
15401544
DependencyGroupError,
15411545
> {
15421546
match self {
1543-
Self::Project(_) | Self::FrozenMember(..) => Ok(BTreeMap::new()),
1544-
Self::NonProject(workspace) => {
1547+
Self::Workspace(_) | Self::Project(_) | Self::FrozenProject(..) => Ok(BTreeMap::new()),
1548+
Self::NonProjectWorkspace(workspace) => {
15451549
// For non-projects, we might have `dependency-groups` or `tool.uv.dev-dependencies`
15461550
// that are attached to the workspace root (which isn't a member).
15471551

@@ -1591,18 +1595,24 @@ impl<'env> InstallTarget<'env> {
15911595
/// Return the [`PackageName`] of the target, if available.
15921596
pub fn project_name(&self) -> Option<&PackageName> {
15931597
match self {
1598+
Self::Workspace(_) => None,
15941599
Self::Project(project) => Some(project.project_name()),
1595-
Self::NonProject(_) => None,
1596-
Self::FrozenMember(_, package_name) => Some(package_name),
1600+
Self::NonProjectWorkspace(_) => None,
1601+
Self::FrozenProject(_, package_name) => Some(package_name),
1602+
}
1603+
}
1604+
1605+
pub fn from_workspace(workspace: &'env VirtualProject) -> Self {
1606+
match workspace {
1607+
VirtualProject::Project(project) => Self::Workspace(project.workspace()),
1608+
VirtualProject::NonProject(workspace) => Self::NonProjectWorkspace(workspace),
15971609
}
15981610
}
1599-
}
16001611

1601-
impl<'env> From<&'env VirtualProject> for InstallTarget<'env> {
1602-
fn from(project: &'env VirtualProject) -> Self {
1612+
pub fn from_project(project: &'env VirtualProject) -> Self {
16031613
match project {
16041614
VirtualProject::Project(project) => Self::Project(project),
1605-
VirtualProject::NonProject(workspace) => Self::NonProject(workspace),
1615+
VirtualProject::NonProject(workspace) => Self::NonProjectWorkspace(workspace),
16061616
}
16071617
}
16081618
}

crates/uv/src/commands/project/add.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ async fn lock_and_sync(
870870
};
871871

872872
project::sync::do_sync(
873-
InstallTarget::from(&project),
873+
InstallTarget::from_project(&project),
874874
venv,
875875
&lock,
876876
&extras,

crates/uv/src/commands/project/export.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use uv_configuration::{
1414
use uv_normalize::PackageName;
1515
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
1616
use uv_resolver::RequirementsTxtExport;
17-
use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace};
17+
use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProject, Workspace};
1818

1919
use crate::commands::pip::loggers::DefaultResolveLogger;
2020
use crate::commands::project::lock::{do_safe_lock, LockMode};
@@ -73,7 +73,7 @@ pub(crate) async fn export(
7373
};
7474

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

7979
let VirtualProject::Project(project) = project else {

crates/uv/src/commands/project/mod.rs

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
3838
use uv_warnings::{warn_user, warn_user_once};
3939
use uv_workspace::dependency_groups::DependencyGroupError;
4040
use uv_workspace::pyproject::PyProjectToml;
41-
use uv_workspace::{VirtualProject, Workspace};
41+
use uv_workspace::{InstallTarget, Workspace};
4242

4343
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
4444
use crate::commands::pip::operations::{Changelog, Modifications};
@@ -1369,16 +1369,22 @@ pub(crate) async fn script_python_requirement(
13691369
/// Validate the dependency groups requested by the [`DevGroupsSpecification`].
13701370
#[allow(clippy::result_large_err)]
13711371
pub(crate) fn validate_dependency_groups(
1372-
project: &VirtualProject,
1372+
target: InstallTarget<'_>,
13731373
dev: &DevGroupsSpecification,
13741374
) -> Result<(), ProjectError> {
13751375
for group in dev
13761376
.groups()
13771377
.into_iter()
13781378
.flat_map(GroupsSpecification::names)
13791379
{
1380-
match project {
1381-
VirtualProject::Project(project) => {
1380+
match target {
1381+
InstallTarget::Workspace(workspace) | InstallTarget::NonProjectWorkspace(workspace) => {
1382+
// The group must be defined in the workspace.
1383+
if !workspace.groups().contains(group) {
1384+
return Err(ProjectError::MissingGroupWorkspace(group.clone()));
1385+
}
1386+
}
1387+
InstallTarget::Project(project) => {
13821388
// The group must be defined in the target project.
13831389
if !project
13841390
.current_project()
@@ -1390,25 +1396,7 @@ pub(crate) fn validate_dependency_groups(
13901396
return Err(ProjectError::MissingGroupProject(group.clone()));
13911397
}
13921398
}
1393-
VirtualProject::NonProject(workspace) => {
1394-
// The group must be defined in at least one workspace package.
1395-
if !workspace
1396-
.pyproject_toml()
1397-
.dependency_groups
1398-
.as_ref()
1399-
.is_some_and(|groups| groups.contains_key(group))
1400-
{
1401-
if workspace.packages().values().all(|package| {
1402-
!package
1403-
.pyproject_toml()
1404-
.dependency_groups
1405-
.as_ref()
1406-
.is_some_and(|groups| groups.contains_key(group))
1407-
}) {
1408-
return Err(ProjectError::MissingGroupWorkspace(group.clone()));
1409-
}
1410-
}
1411-
}
1399+
InstallTarget::FrozenProject(_, _) => {}
14121400
}
14131401
}
14141402
Ok(())

crates/uv/src/commands/project/remove.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ pub(crate) async fn remove(
236236
let defaults = default_dependency_groups(project.pyproject_toml())?;
237237

238238
project::sync::do_sync(
239-
InstallTarget::from(&project),
239+
InstallTarget::from_project(&project),
240240
&venv,
241241
&lock,
242242
&extras,

crates/uv/src/commands/project/run.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ pub(crate) async fn run(
551551
}
552552
} else {
553553
// Determine the default groups to include.
554-
validate_dependency_groups(&project, &dev)?;
554+
validate_dependency_groups(InstallTarget::from_project(&project), &dev)?;
555555
let defaults = default_dependency_groups(project.pyproject_toml())?;
556556

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

609609
project::sync::do_sync(
610-
InstallTarget::from(&project),
610+
InstallTarget::from_project(&project),
611611
&venv,
612612
result.lock(),
613613
&extras,

crates/uv/src/commands/project/sync.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::commands::project::lock::{do_safe_lock, LockMode};
3333
use crate::commands::project::{
3434
default_dependency_groups, validate_dependency_groups, ProjectError, SharedState,
3535
};
36-
use crate::commands::{diagnostics, pip, project, ExitStatus};
36+
use crate::commands::{diagnostics, project, ExitStatus};
3737
use crate::printer::Printer;
3838
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings};
3939

@@ -43,6 +43,7 @@ pub(crate) async fn sync(
4343
project_dir: &Path,
4444
locked: bool,
4545
frozen: bool,
46+
all: bool,
4647
package: Option<PackageName>,
4748
extras: ExtrasSpecification,
4849
dev: DevGroupsSpecification,
@@ -60,7 +61,7 @@ pub(crate) async fn sync(
6061
printer: Printer,
6162
) -> Result<ExitStatus> {
6263
// Identify the project.
63-
let project = if frozen {
64+
let project = if frozen && !all {
6465
VirtualProject::discover(
6566
project_dir,
6667
&DiscoveryOptions {
@@ -82,9 +83,11 @@ pub(crate) async fn sync(
8283

8384
// Identify the target.
8485
let target = if let Some(package) = package.as_ref().filter(|_| frozen) {
85-
InstallTarget::frozen_member(&project, package)
86+
InstallTarget::frozen(&project, package)
87+
} else if all {
88+
InstallTarget::from_workspace(&project)
8689
} else {
87-
InstallTarget::from(&project)
90+
InstallTarget::from_project(&project)
8891
};
8992

9093
// TODO(lucab): improve warning content
@@ -96,7 +99,7 @@ pub(crate) async fn sync(
9699
}
97100

98101
// Determine the default groups to include.
99-
validate_dependency_groups(&project, &dev)?;
102+
validate_dependency_groups(target, &dev)?;
100103
let defaults = default_dependency_groups(project.pyproject_toml())?;
101104

102105
// Discover or create the virtual environment.
@@ -363,7 +366,7 @@ pub(super) async fn do_sync(
363366
let site_packages = SitePackages::from_environment(venv)?;
364367

365368
// Sync the environment.
366-
pip::operations::install(
369+
operations::install(
367370
&resolution,
368371
site_packages,
369372
modifications,

crates/uv/src/commands/project/tree.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use uv_configuration::{Concurrency, DevGroupsSpecification, LowerBound, TargetTr
99
use uv_pep508::PackageName;
1010
use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion};
1111
use uv_resolver::TreeDisplay;
12-
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
12+
use uv_workspace::{DiscoveryOptions, InstallTarget, Workspace};
1313

1414
use crate::commands::pip::loggers::DefaultResolveLogger;
1515
use crate::commands::pip::resolution_markers;
@@ -50,7 +50,7 @@ pub(crate) async fn tree(
5050
let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;
5151

5252
// Determine the default groups to include.
53-
validate_dependency_groups(&VirtualProject::NonProject(workspace.clone()), &dev)?;
53+
validate_dependency_groups(InstallTarget::Workspace(&workspace), &dev)?;
5454
let defaults = default_dependency_groups(workspace.pyproject_toml())?;
5555

5656
// Find an interpreter for the project, unless `--frozen` and `--universal` are both set.

0 commit comments

Comments
 (0)