mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-26 20:19:08 +00:00
Add support for uv sync --all-packages
(#8739)
## Summary This PR enables `uv sync --all-packages` to sync all packages in a workspace. It removes a common use-case for the legacy non-`[project]` packages that we're trying to move away from. Closes https://github.com/astral-sh/uv/issues/8724.
This commit is contained in:
parent
58a9811881
commit
3c9dd97fe9
16 changed files with 407 additions and 83 deletions
|
@ -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.
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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,
|
||||
)?),
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'env> From<&'env VirtualProject> for InstallTarget<'env> {
|
||||
fn from(project: &'env VirtualProject) -> Self {
|
||||
pub fn from_workspace(workspace: &'env VirtualProject) -> Self {
|
||||
match workspace {
|
||||
VirtualProject::Project(project) => Self::Workspace(project.workspace()),
|
||||
VirtualProject::NonProject(workspace) => Self::NonProjectWorkspace(workspace),
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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!(
|
||||
|
@ -201,10 +201,10 @@ 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"
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,7 @@ async fn build_impl(
|
|||
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"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -870,7 +870,7 @@ async fn lock_and_sync(
|
|||
};
|
||||
|
||||
project::sync::do_sync(
|
||||
InstallTarget::from(&project),
|
||||
InstallTarget::from_project(&project),
|
||||
venv,
|
||||
&lock,
|
||||
&extras,
|
||||
|
|
|
@ -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};
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
|
@ -1369,7 +1369,7 @@ 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
|
||||
|
@ -1377,8 +1377,14 @@ pub(crate) fn validate_dependency_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()
|
||||
|
@ -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(())
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
@ -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,
|
||||
|
|
|
@ -33,7 +33,7 @@ use crate::commands::project::lock::{do_safe_lock, LockMode};
|
|||
use crate::commands::project::{
|
||||
default_dependency_groups, validate_dependency_groups, ProjectError, SharedState,
|
||||
};
|
||||
use crate::commands::{diagnostics, pip, project, ExitStatus};
|
||||
use crate::commands::{diagnostics, project, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings};
|
||||
|
||||
|
@ -43,6 +43,7 @@ pub(crate) async fn sync(
|
|||
project_dir: &Path,
|
||||
locked: bool,
|
||||
frozen: bool,
|
||||
all_packages: bool,
|
||||
package: Option<PackageName>,
|
||||
extras: ExtrasSpecification,
|
||||
dev: DevGroupsSpecification,
|
||||
|
@ -60,7 +61,7 @@ pub(crate) async fn sync(
|
|||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
// Identify the project.
|
||||
let project = if frozen {
|
||||
let project = if frozen && !all_packages {
|
||||
VirtualProject::discover(
|
||||
project_dir,
|
||||
&DiscoveryOptions {
|
||||
|
@ -82,9 +83,11 @@ pub(crate) async fn sync(
|
|||
|
||||
// Identify the target.
|
||||
let target = if let Some(package) = package.as_ref().filter(|_| frozen) {
|
||||
InstallTarget::frozen_member(&project, package)
|
||||
InstallTarget::frozen(&project, package)
|
||||
} else if all_packages {
|
||||
InstallTarget::from_workspace(&project)
|
||||
} else {
|
||||
InstallTarget::from(&project)
|
||||
InstallTarget::from_project(&project)
|
||||
};
|
||||
|
||||
// TODO(lucab): improve warning content
|
||||
|
@ -96,7 +99,7 @@ pub(crate) async fn sync(
|
|||
}
|
||||
|
||||
// Determine the default groups to include.
|
||||
validate_dependency_groups(&project, &dev)?;
|
||||
validate_dependency_groups(target, &dev)?;
|
||||
let defaults = default_dependency_groups(project.pyproject_toml())?;
|
||||
|
||||
// Discover or create the virtual environment.
|
||||
|
@ -363,7 +366,7 @@ pub(super) async fn do_sync(
|
|||
let site_packages = SitePackages::from_environment(venv)?;
|
||||
|
||||
// Sync the environment.
|
||||
pip::operations::install(
|
||||
operations::install(
|
||||
&resolution,
|
||||
site_packages,
|
||||
modifications,
|
||||
|
|
|
@ -9,7 +9,7 @@ use uv_configuration::{Concurrency, DevGroupsSpecification, LowerBound, TargetTr
|
|||
use uv_pep508::PackageName;
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion};
|
||||
use uv_resolver::TreeDisplay;
|
||||
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
|
||||
use uv_workspace::{DiscoveryOptions, InstallTarget, Workspace};
|
||||
|
||||
use crate::commands::pip::loggers::DefaultResolveLogger;
|
||||
use crate::commands::pip::resolution_markers;
|
||||
|
@ -50,7 +50,7 @@ pub(crate) async fn tree(
|
|||
let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;
|
||||
|
||||
// Determine the default groups to include.
|
||||
validate_dependency_groups(&VirtualProject::NonProject(workspace.clone()), &dev)?;
|
||||
validate_dependency_groups(InstallTarget::Workspace(&workspace), &dev)?;
|
||||
let defaults = default_dependency_groups(workspace.pyproject_toml())?;
|
||||
|
||||
// Find an interpreter for the project, unless `--frozen` and `--universal` are both set.
|
||||
|
|
|
@ -712,7 +712,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
&project_dir,
|
||||
args.src,
|
||||
args.package,
|
||||
args.all,
|
||||
args.all_packages,
|
||||
args.out_dir,
|
||||
args.sdist,
|
||||
args.wheel,
|
||||
|
@ -1327,6 +1327,7 @@ async fn run_project(
|
|||
project_dir,
|
||||
args.locked,
|
||||
args.frozen,
|
||||
args.all_packages,
|
||||
args.package,
|
||||
args.extras,
|
||||
args.dev,
|
||||
|
|
|
@ -720,6 +720,7 @@ pub(crate) struct SyncSettings {
|
|||
pub(crate) editable: EditableMode,
|
||||
pub(crate) install_options: InstallOptions,
|
||||
pub(crate) modifications: Modifications,
|
||||
pub(crate) all_packages: bool,
|
||||
pub(crate) package: Option<PackageName>,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) refresh: Refresh,
|
||||
|
@ -751,6 +752,7 @@ impl SyncSettings {
|
|||
installer,
|
||||
build,
|
||||
refresh,
|
||||
all_packages,
|
||||
package,
|
||||
python,
|
||||
} = args;
|
||||
|
@ -781,6 +783,7 @@ impl SyncSettings {
|
|||
} else {
|
||||
Modifications::Sufficient
|
||||
},
|
||||
all_packages,
|
||||
package,
|
||||
python: python.and_then(Maybe::into_option),
|
||||
refresh: Refresh::from(refresh),
|
||||
|
@ -1797,7 +1800,7 @@ impl PipCheckSettings {
|
|||
pub(crate) struct BuildSettings {
|
||||
pub(crate) src: Option<PathBuf>,
|
||||
pub(crate) package: Option<PackageName>,
|
||||
pub(crate) all: bool,
|
||||
pub(crate) all_packages: bool,
|
||||
pub(crate) out_dir: Option<PathBuf>,
|
||||
pub(crate) sdist: bool,
|
||||
pub(crate) wheel: bool,
|
||||
|
@ -1816,7 +1819,7 @@ impl BuildSettings {
|
|||
src,
|
||||
out_dir,
|
||||
package,
|
||||
all,
|
||||
all_packages,
|
||||
sdist,
|
||||
wheel,
|
||||
build_constraint,
|
||||
|
@ -1835,7 +1838,7 @@ impl BuildSettings {
|
|||
Self {
|
||||
src,
|
||||
package,
|
||||
all,
|
||||
all_packages,
|
||||
out_dir,
|
||||
sdist,
|
||||
wheel,
|
||||
|
|
|
@ -1206,7 +1206,7 @@ fn workspace() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: `--all` was provided, but no workspace was found
|
||||
error: `--all-packages` was provided, but no workspace was found
|
||||
Caused by: No `pyproject.toml` found in current directory or any parent directory
|
||||
"###);
|
||||
|
||||
|
|
|
@ -263,7 +263,7 @@ fn package() -> Result<()> {
|
|||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>1"]
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
|
@ -415,7 +415,7 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> {
|
|||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>1"]
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
|
@ -500,7 +500,7 @@ fn sync_legacy_non_project_group() -> Result<()> {
|
|||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>1"]
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[dependency-groups]
|
||||
baz = ["typing-extensions"]
|
||||
|
@ -1805,7 +1805,7 @@ fn no_install_workspace() -> Result<()> {
|
|||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>1"]
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
|
@ -3073,7 +3073,7 @@ fn transitive_dev() -> Result<()> {
|
|||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = ["iniconfig>1"]
|
||||
dev-dependencies = ["iniconfig>=1"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
|
@ -3425,7 +3425,7 @@ fn build_system_requires_workspace() -> Result<()> {
|
|||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>1"]
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42", "backend==0.1.0"]
|
||||
|
@ -3508,7 +3508,7 @@ fn build_system_requires_path() -> Result<()> {
|
|||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>1"]
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42", "backend==0.1.0"]
|
||||
|
@ -3798,3 +3798,306 @@ fn sync_explicit() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sync all members in a workspace.
|
||||
#[test]
|
||||
fn sync_all() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio>3", "child"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { workspace = true }
|
||||
"#,
|
||||
)?;
|
||||
context
|
||||
.temp_dir
|
||||
.child("src")
|
||||
.child("project")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// Add a workspace member.
|
||||
let child = context.temp_dir.child("child");
|
||||
child.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
child
|
||||
.child("src")
|
||||
.child("child")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// Generate a lockfile.
|
||||
context.lock().assert().success();
|
||||
|
||||
// Sync all workspace members.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
Prepared 6 packages in [TIME]
|
||||
Installed 6 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
+ idna==3.6
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sync all members in a workspace with extras attached.
|
||||
#[test]
|
||||
fn sync_all_extras() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["child"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
types = ["sniffio>1"]
|
||||
async = ["anyio>3"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { workspace = true }
|
||||
"#,
|
||||
)?;
|
||||
context
|
||||
.temp_dir
|
||||
.child("src")
|
||||
.child("project")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// Add a workspace member.
|
||||
let child = context.temp_dir.child("child");
|
||||
child.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
types = ["typing-extensions>=4"]
|
||||
testing = ["packaging>=24"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
child
|
||||
.child("src")
|
||||
.child("child")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// Generate a lockfile.
|
||||
context.lock().assert().success();
|
||||
|
||||
// Sync an extra that exists in both the parent and child.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 8 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
+ typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
// Sync an extra that only exists in the child.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("testing"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 8 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ packaging==24.0
|
||||
- sniffio==1.3.1
|
||||
- typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
// Sync all extras.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 8 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ idna==3.6
|
||||
+ sniffio==1.3.1
|
||||
+ typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sync all members in a workspace with dependency groups attached.
|
||||
#[test]
|
||||
fn sync_all_groups() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["child"]
|
||||
|
||||
[dependency-groups]
|
||||
types = ["sniffio>=1"]
|
||||
async = ["anyio>=3"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { workspace = true }
|
||||
"#,
|
||||
)?;
|
||||
context
|
||||
.temp_dir
|
||||
.child("src")
|
||||
.child("project")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// Add a workspace member.
|
||||
let child = context.temp_dir.child("child");
|
||||
child.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>=1"]
|
||||
|
||||
[dependency-groups]
|
||||
types = ["typing-extensions>=4"]
|
||||
testing = ["packaging>=24"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
child
|
||||
.child("src")
|
||||
.child("child")
|
||||
.child("__init__.py")
|
||||
.touch()?;
|
||||
|
||||
// Generate a lockfile.
|
||||
context.lock().assert().success();
|
||||
|
||||
// Sync a group that exists in both the parent and child.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("types"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 8 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
+ typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
// Sync a group that only exists in the child.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("testing"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 8 packages in [TIME]
|
||||
Prepared 1 package in [TIME]
|
||||
Uninstalled 2 packages in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ packaging==24.0
|
||||
- sniffio==1.3.1
|
||||
- typing-extensions==4.10.0
|
||||
"###);
|
||||
|
||||
// Sync a group that doesn't exist.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("foo"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: Group `foo` is not defined in any project's `dependency-group` table
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1369,6 +1369,12 @@ uv sync [OPTIONS]
|
|||
|
||||
<p>Note that all optional dependencies are always included in the resolution; this option only affects the selection of packages to install.</p>
|
||||
|
||||
</dd><dt><code>--all-packages</code></dt><dd><p>Sync all packages in the workspace.</p>
|
||||
|
||||
<p>The workspace’s environment (<code>.venv</code>) is updated to include all workspace members.</p>
|
||||
|
||||
<p>Any extras or groups specified via <code>--extra</code>, <code>--group</code>, or related options will be applied to all workspace members.</p>
|
||||
|
||||
</dd><dt><code>--allow-insecure-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
|
||||
|
||||
<p>Can be provided multiple times.</p>
|
||||
|
@ -7260,7 +7266,7 @@ uv build [OPTIONS] [SRC]
|
|||
|
||||
<h3 class="cli-reference">Options</h3>
|
||||
|
||||
<dl class="cli-reference"><dt><code>--all</code></dt><dd><p>Builds all packages in the workspace.</p>
|
||||
<dl class="cli-reference"><dt><code>--all-packages</code></dt><dd><p>Builds all packages in the workspace.</p>
|
||||
|
||||
<p>The workspace will be discovered from the provided source directory, or the current directory if no source directory is provided.</p>
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue