mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-20 03:49:54 +00:00
Add uv workspace list to list workspace members (#16691)
I'm a little wary here, in the sense that it might be silly to have a command that does something so simple that's covered by `uv workspace metadata`? but I think this could be stabilized much faster than `uv workspace metadata` and makes it easier to write scripts against workspace members. --------- Co-authored-by: liam <liam@scalzulli.com>
This commit is contained in:
parent
6f525f9462
commit
07e03ee776
11 changed files with 284 additions and 2 deletions
|
|
@ -7167,6 +7167,11 @@ pub enum WorkspaceCommand {
|
|||
///
|
||||
/// If used outside of a workspace, i.e., if a `pyproject.toml` cannot be found, uv will exit with an error.
|
||||
Dir(WorkspaceDirArgs),
|
||||
/// List the members of a workspace.
|
||||
///
|
||||
/// Displays newline separated names of workspace members.
|
||||
#[command(hide = true)]
|
||||
List(WorkspaceListArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
|
|
@ -7179,6 +7184,9 @@ pub struct WorkspaceDirArgs {
|
|||
pub package: Option<PackageName>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct WorkspaceListArgs;
|
||||
|
||||
/// See [PEP 517](https://peps.python.org/pep-0517/) and
|
||||
/// [PEP 660](https://peps.python.org/pep-0660/) for specifications of the parameters.
|
||||
#[derive(Subcommand)]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ bitflags::bitflags! {
|
|||
const INIT_PROJECT_FLAG = 1 << 12;
|
||||
const WORKSPACE_METADATA = 1 << 13;
|
||||
const WORKSPACE_DIR = 1 << 14;
|
||||
const WORKSPACE_LIST = 1 << 15;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ impl PreviewFeatures {
|
|||
Self::INIT_PROJECT_FLAG => "init-project-flag",
|
||||
Self::WORKSPACE_METADATA => "workspace-metadata",
|
||||
Self::WORKSPACE_DIR => "workspace-dir",
|
||||
Self::WORKSPACE_LIST => "workspace-list",
|
||||
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
|
||||
}
|
||||
}
|
||||
|
|
@ -100,6 +102,7 @@ impl FromStr for PreviewFeatures {
|
|||
"init-project-flag" => Self::INIT_PROJECT_FLAG,
|
||||
"workspace-metadata" => Self::WORKSPACE_METADATA,
|
||||
"workspace-dir" => Self::WORKSPACE_DIR,
|
||||
"workspace-list" => Self::WORKSPACE_LIST,
|
||||
_ => {
|
||||
warn_user_once!("Unknown preview feature: `{part}`");
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ use uv_python::PythonEnvironment;
|
|||
use uv_scripts::Pep723Script;
|
||||
pub(crate) use venv::venv;
|
||||
pub(crate) use workspace::dir::dir;
|
||||
pub(crate) use workspace::list::list;
|
||||
pub(crate) use workspace::metadata::metadata;
|
||||
|
||||
use crate::printer::Printer;
|
||||
|
|
|
|||
36
crates/uv/src/commands/workspace/list.rs
Normal file
36
crates/uv/src/commands/workspace/list.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use std::fmt::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use owo_colors::OwoColorize;
|
||||
use uv_preview::{Preview, PreviewFeatures};
|
||||
use uv_warnings::warn_user;
|
||||
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache};
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
|
||||
/// List workspace members
|
||||
pub(crate) async fn list(
|
||||
project_dir: &Path,
|
||||
preview: Preview,
|
||||
printer: Printer,
|
||||
) -> Result<ExitStatus> {
|
||||
if !preview.is_enabled(PreviewFeatures::WORKSPACE_LIST) {
|
||||
warn_user!(
|
||||
"The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
|
||||
PreviewFeatures::WORKSPACE_LIST
|
||||
);
|
||||
}
|
||||
|
||||
let workspace_cache = WorkspaceCache::default();
|
||||
let workspace =
|
||||
Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache).await?;
|
||||
|
||||
for name in workspace.packages().keys() {
|
||||
writeln!(printer.stdout(), "{}", name.cyan())?;
|
||||
}
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub(crate) mod dir;
|
||||
pub(crate) mod list;
|
||||
pub(crate) mod metadata;
|
||||
|
|
|
|||
|
|
@ -1746,6 +1746,9 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
|
|||
WorkspaceCommand::Dir(args) => {
|
||||
commands::dir(args.package, &project_dir, globals.preview, printer).await
|
||||
}
|
||||
WorkspaceCommand::List(_args) => {
|
||||
commands::list(&project_dir, globals.preview, printer).await
|
||||
}
|
||||
},
|
||||
Commands::BuildBackend { command } => spawn_blocking(move || match command {
|
||||
BuildBackendCommand::BuildSdist { sdist_directory } => {
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,14 @@ impl TestContext {
|
|||
command
|
||||
}
|
||||
|
||||
/// Create a `uv workspace list` command with options shared across scenarios.
|
||||
pub fn workspace_list(&self) -> Command {
|
||||
let mut command = Self::new_command();
|
||||
command.arg("workspace").arg("list");
|
||||
self.add_shared_options(&mut command, false);
|
||||
command
|
||||
}
|
||||
|
||||
/// Create a `uv export` command with options shared across scenarios.
|
||||
pub fn export(&self) -> Command {
|
||||
let mut command = Self::new_command();
|
||||
|
|
|
|||
|
|
@ -142,4 +142,5 @@ mod workflow;
|
|||
mod extract;
|
||||
mod workspace;
|
||||
mod workspace_dir;
|
||||
mod workspace_list;
|
||||
mod workspace_metadata;
|
||||
|
|
|
|||
|
|
@ -7831,7 +7831,7 @@ fn preview_features() {
|
|||
show_settings: true,
|
||||
preview: Preview {
|
||||
flags: PreviewFeatures(
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR,
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR | WORKSPACE_LIST,
|
||||
),
|
||||
},
|
||||
python_preference: Managed,
|
||||
|
|
@ -8059,7 +8059,7 @@ fn preview_features() {
|
|||
show_settings: true,
|
||||
preview: Preview {
|
||||
flags: PreviewFeatures(
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR,
|
||||
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_AUTH | S3_ENDPOINT | CACHE_SIZE | INIT_PROJECT_FLAG | WORKSPACE_METADATA | WORKSPACE_DIR | WORKSPACE_LIST,
|
||||
),
|
||||
},
|
||||
python_preference: Managed,
|
||||
|
|
|
|||
220
crates/uv/tests/it/workspace_list.rs
Normal file
220
crates/uv/tests/it/workspace_list.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
use anyhow::Result;
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::fixture::PathChild;
|
||||
|
||||
use crate::common::{TestContext, copy_dir_ignore, uv_snapshot};
|
||||
|
||||
/// Test basic list output for a simple workspace with one member.
|
||||
#[test]
|
||||
fn workspace_list_simple() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Initialize a workspace with one member
|
||||
context.init().arg("foo").assert().success();
|
||||
|
||||
let workspace = context.temp_dir.child("foo");
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
foo
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test list output for a root workspace (workspace with a root package).
|
||||
#[test]
|
||||
fn workspace_list_root_workspace() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
copy_dir_ignore(
|
||||
context
|
||||
.workspace_root
|
||||
.join("scripts/workspaces/albatross-root-workspace"),
|
||||
&workspace,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
albatross
|
||||
bird-feeder
|
||||
seeds
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test list output for a virtual workspace (no root package).
|
||||
#[test]
|
||||
fn workspace_list_virtual_workspace() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
copy_dir_ignore(
|
||||
context
|
||||
.workspace_root
|
||||
.join("scripts/workspaces/albatross-virtual-workspace"),
|
||||
&workspace,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
albatross
|
||||
bird-feeder
|
||||
seeds
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test list output when run from a workspace member directory.
|
||||
#[test]
|
||||
fn workspace_list_from_member() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
copy_dir_ignore(
|
||||
context
|
||||
.workspace_root
|
||||
.join("scripts/workspaces/albatross-root-workspace"),
|
||||
&workspace,
|
||||
)?;
|
||||
|
||||
let member_dir = workspace.join("packages").join("bird-feeder");
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&member_dir), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
albatross
|
||||
bird-feeder
|
||||
seeds
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test list output for a workspace with multiple packages.
|
||||
#[test]
|
||||
fn workspace_list_multiple_members() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Initialize workspace root
|
||||
context.init().arg("pkg-a").assert().success();
|
||||
|
||||
let workspace_root = context.temp_dir.child("pkg-a");
|
||||
|
||||
// Add more members
|
||||
context
|
||||
.init()
|
||||
.arg("pkg-b")
|
||||
.current_dir(&workspace_root)
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
context
|
||||
.init()
|
||||
.arg("pkg-c")
|
||||
.current_dir(&workspace_root)
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&workspace_root), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
pkg-a
|
||||
pkg-b
|
||||
pkg-c
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test list output for a single project (not a workspace).
|
||||
#[test]
|
||||
fn workspace_list_single_project() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
context.init().arg("my-project").assert().success();
|
||||
|
||||
let project = context.temp_dir.child("my-project");
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&project), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
my-project
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
/// Test list output with excluded packages.
|
||||
#[test]
|
||||
fn workspace_list_with_excluded() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let workspace = context.temp_dir.child("workspace");
|
||||
|
||||
copy_dir_ignore(
|
||||
context
|
||||
.workspace_root
|
||||
.join("scripts/workspaces/albatross-project-in-excluded"),
|
||||
&workspace,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list().current_dir(&workspace), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
albatross
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test list error output when not in a project.
|
||||
#[test]
|
||||
fn workspace_list_no_project() {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
uv_snapshot!(context.filters(), context.workspace_list(), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: The `uv workspace list` command is experimental and may change without warning. Pass `--preview-features workspace-list` to disable this warning.
|
||||
error: No `pyproject.toml` found in current directory or any parent directory
|
||||
"
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue