Skip to content

Commit a236f75

Browse files
committed
Add support for uv sync --all
1 parent 58a9811 commit a236f75

File tree

16 files changed

+407
-83
lines changed

16 files changed

+407
-83
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,7 +2119,7 @@ pub struct BuildArgs {
21192119
/// directory if no source directory is provided.
21202120
///
21212121
/// If the workspace member does not exist, uv will exit with an error.
2122-
#[arg(long, conflicts_with("all"))]
2122+
#[arg(long, conflicts_with("all_packages"))]
21232123
pub package: Option<PackageName>,
21242124

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

21342134
/// The output directory to which distributions should be written.
21352135
///
@@ -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_packages: 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_packages")]
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/build_frontend.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(crate) async fn build_frontend(
4343
project_dir: &Path,
4444
src: Option<PathBuf>,
4545
package: Option<PackageName>,
46-
all: bool,
46+
all_packages: bool,
4747
output_dir: Option<PathBuf>,
4848
sdist: bool,
4949
wheel: bool,
@@ -65,7 +65,7 @@ pub(crate) async fn build_frontend(
6565
project_dir,
6666
src.as_deref(),
6767
package.as_ref(),
68-
all,
68+
all_packages,
6969
output_dir.as_deref(),
7070
sdist,
7171
wheel,
@@ -105,7 +105,7 @@ async fn build_impl(
105105
project_dir: &Path,
106106
src: Option<&Path>,
107107
package: Option<&PackageName>,
108-
all: bool,
108+
all_packages: bool,
109109
output_dir: Option<&Path>,
110110
sdist: bool,
111111
wheel: bool,
@@ -171,7 +171,7 @@ async fn build_impl(
171171
// Attempt to discover the workspace; on failure, save the error for later.
172172
let workspace = Workspace::discover(src.directory(), &DiscoveryOptions::default()).await;
173173

174-
// If a `--package` or `--all` was provided, adjust the source directory.
174+
// If a `--package` or `--all-packages` was provided, adjust the source directory.
175175
let packages = if let Some(package) = package {
176176
if matches!(src, Source::File(_)) {
177177
return Err(anyhow::anyhow!(
@@ -201,18 +201,18 @@ async fn build_impl(
201201
vec![AnnotatedSource::from(Source::Directory(Cow::Borrowed(
202202
package.root(),
203203
)))]
204-
} else if all {
204+
} else if all_packages {
205205
if matches!(src, Source::File(_)) {
206206
return Err(anyhow::anyhow!(
207-
"Cannot specify `--all` when building from a file"
207+
"Cannot specify `--all-packages` when building from a file"
208208
));
209209
}
210210

211211
let workspace = match workspace {
212212
Ok(ref workspace) => workspace,
213213
Err(err) => {
214214
return Err(anyhow::Error::from(err)
215-
.context("`--all` was provided, but no workspace was found"));
215+
.context("`--all-packages` was provided, but no workspace was found"));
216216
}
217217
};
218218

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,

0 commit comments

Comments
 (0)