Skip to content

Commit 3ccad58

Browse files
authored
Add uv workspace metadata (#16516)
This adds the scaffolding for a `uv workspace metadata` command, as an equivalent to `cargo metadata`, for integration with downstream tools. I didn't do much here beyond emit the workspace root path and the paths of the workspace members. I explored doing a bit more in #16638, but I think we're actually going to want to come up with a fairly comprehensive schema like `cargo metadata` has. I've started exploring that too, but I don't have a concrete proposal to share yet. I don't want this to be a top-level command because I think people would expect `uv metadata <PACKAGE>` to show metadata about arbitrary packages (this has been requested several times). I also think we can do other things in the workspace namespace to make trivial integrations simpler, like `uv workspace list` (enumerate members) and `uv workspace dir` (show the path to the workspace root). I don't expect this to be stable at all to start. I've both gated it with preview and hidden it from the help. The intent is to merge so we can iterate on it as we figure out what integrations need.
1 parent 5b517bb commit 3ccad58

File tree

11 files changed

+458
-4
lines changed

11 files changed

+458
-4
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,13 @@ pub enum Commands {
517517
Build(BuildArgs),
518518
/// Upload distributions to an index.
519519
Publish(PublishArgs),
520+
/// Manage workspaces.
521+
#[command(
522+
after_help = "Use `uv help workspace` for more details.",
523+
after_long_help = "",
524+
hide = true
525+
)]
526+
Workspace(WorkspaceNamespace),
520527
/// The implementation of the build backend.
521528
///
522529
/// These commands are not directly exposed to the user, instead users invoke their build
@@ -6835,6 +6842,22 @@ pub struct PublishArgs {
68356842
pub dry_run: bool,
68366843
}
68376844

6845+
#[derive(Args)]
6846+
pub struct WorkspaceNamespace {
6847+
#[command(subcommand)]
6848+
pub command: WorkspaceCommand,
6849+
}
6850+
6851+
#[derive(Subcommand)]
6852+
pub enum WorkspaceCommand {
6853+
/// Display package metadata.
6854+
#[command(hide = true)]
6855+
Metadata(MetadataArgs),
6856+
}
6857+
6858+
#[derive(Args, Debug)]
6859+
pub struct MetadataArgs;
6860+
68386861
/// See [PEP 517](https://peps.python.org/pep-0517/) and
68396862
/// [PEP 660](https://peps.python.org/pep-0660/) for specifications of the parameters.
68406863
#[derive(Subcommand)]

crates/uv-preview/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bitflags::bitflags! {
2222
const S3_ENDPOINT = 1 << 10;
2323
const CACHE_SIZE = 1 << 11;
2424
const INIT_PROJECT_FLAG = 1 << 12;
25+
const WORKSPACE_METADATA = 1 << 13;
2526
}
2627
}
2728

@@ -44,6 +45,7 @@ impl PreviewFeatures {
4445
Self::S3_ENDPOINT => "s3-endpoint",
4546
Self::CACHE_SIZE => "cache-size",
4647
Self::INIT_PROJECT_FLAG => "init-project-flag",
48+
Self::WORKSPACE_METADATA => "workspace-metadata",
4749
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
4850
}
4951
}
@@ -94,12 +96,12 @@ impl FromStr for PreviewFeatures {
9496
"s3-endpoint" => Self::S3_ENDPOINT,
9597
"cache-size" => Self::CACHE_SIZE,
9698
"init-project-flag" => Self::INIT_PROJECT_FLAG,
99+
"workspace-metadata" => Self::WORKSPACE_METADATA,
97100
_ => {
98101
warn_user_once!("Unknown preview feature: `{part}`");
99102
continue;
100103
}
101104
};
102-
103105
flags |= flag;
104106
}
105107

crates/uv/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ use uv_normalize::PackageName;
6767
use uv_python::PythonEnvironment;
6868
use uv_scripts::Pep723Script;
6969
pub(crate) use venv::venv;
70+
pub(crate) use workspace::metadata::metadata;
7071

7172
use crate::printer::Printer;
7273

@@ -88,6 +89,7 @@ pub(crate) mod reporters;
8889
mod self_update;
8990
mod tool;
9091
mod venv;
92+
mod workspace;
9193

9294
#[derive(Copy, Clone)]
9395
pub(crate) enum ExitStatus {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use std::fmt::Write;
2+
use std::path::Path;
3+
4+
use anyhow::Result;
5+
use serde::Serialize;
6+
7+
use uv_fs::PortablePathBuf;
8+
use uv_normalize::PackageName;
9+
use uv_preview::{Preview, PreviewFeatures};
10+
use uv_warnings::warn_user;
11+
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache};
12+
13+
use crate::commands::ExitStatus;
14+
use crate::printer::Printer;
15+
16+
/// The schema version for the metadata report.
17+
#[derive(Serialize, Debug, Default)]
18+
#[serde(rename_all = "snake_case")]
19+
enum SchemaVersion {
20+
/// An unstable, experimental schema.
21+
#[default]
22+
Preview,
23+
}
24+
25+
/// The schema metadata for the metadata report.
26+
#[derive(Serialize, Debug, Default)]
27+
struct SchemaReport {
28+
/// The version of the schema.
29+
version: SchemaVersion,
30+
}
31+
32+
/// Report for a single workspace member.
33+
#[derive(Serialize, Debug)]
34+
struct WorkspaceMemberReport {
35+
/// The name of the workspace member.
36+
name: PackageName,
37+
/// The path to the workspace member's root directory.
38+
path: PortablePathBuf,
39+
}
40+
41+
/// The report for a metadata operation.
42+
#[derive(Serialize, Debug)]
43+
struct MetadataReport {
44+
/// The schema of this report.
45+
schema: SchemaReport,
46+
/// The workspace root directory.
47+
workspace_root: PortablePathBuf,
48+
/// The workspace members.
49+
members: Vec<WorkspaceMemberReport>,
50+
}
51+
52+
/// Display metadata about the workspace.
53+
pub(crate) async fn metadata(
54+
project_dir: &Path,
55+
preview: Preview,
56+
printer: Printer,
57+
) -> Result<ExitStatus> {
58+
if preview.is_enabled(PreviewFeatures::WORKSPACE_METADATA) {
59+
warn_user!(
60+
"The `uv workspace metadata` command is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
61+
PreviewFeatures::WORKSPACE_METADATA
62+
);
63+
}
64+
65+
let workspace_cache = WorkspaceCache::default();
66+
let workspace =
67+
Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache).await?;
68+
69+
let members = workspace
70+
.packages()
71+
.values()
72+
.map(|package| WorkspaceMemberReport {
73+
name: package.project().name.clone(),
74+
path: PortablePathBuf::from(package.root().as_path()),
75+
})
76+
.collect();
77+
78+
let report = MetadataReport {
79+
schema: SchemaReport::default(),
80+
workspace_root: PortablePathBuf::from(workspace.install_path().as_path()),
81+
members,
82+
};
83+
84+
writeln!(
85+
printer.stdout(),
86+
"{}",
87+
serde_json::to_string_pretty(&report)?
88+
)?;
89+
90+
Ok(ExitStatus::Success)
91+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub(crate) mod metadata;

crates/uv/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ use uv_cli::SelfUpdateArgs;
2626
use uv_cli::{
2727
AuthCommand, AuthNamespace, BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands,
2828
PipCommand, PipNamespace, ProjectCommand, PythonCommand, PythonNamespace, SelfCommand,
29-
SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs, compat::CompatArgs,
29+
SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs, WorkspaceCommand, WorkspaceNamespace,
30+
compat::CompatArgs,
3031
};
3132
use uv_client::BaseClientBuilder;
3233
use uv_configuration::min_stack_size;
@@ -1733,6 +1734,11 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
17331734
)
17341735
.await
17351736
}
1737+
Commands::Workspace(WorkspaceNamespace { command }) => match command {
1738+
WorkspaceCommand::Metadata(_args) => {
1739+
commands::metadata(&project_dir, globals.preview, printer).await
1740+
}
1741+
},
17361742
Commands::BuildBackend { command } => spawn_blocking(move || match command {
17371743
BuildBackendCommand::BuildSdist { sdist_directory } => {
17381744
commands::build_backend::build_sdist(&sdist_directory)

crates/uv/tests/it/common/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,6 +1064,14 @@ impl TestContext {
10641064
command
10651065
}
10661066

1067+
/// Create a `uv workspace metadata` command with options shared across scenarios.
1068+
pub fn workspace_metadata(&self) -> Command {
1069+
let mut command = Self::new_command();
1070+
command.arg("workspace").arg("metadata");
1071+
self.add_shared_options(&mut command, false);
1072+
command
1073+
}
1074+
10671075
/// Create a `uv export` command with options shared across scenarios.
10681076
pub fn export(&self) -> Command {
10691077
let mut command = Self::new_command();

crates/uv/tests/it/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,4 @@ mod workflow;
141141

142142
mod extract;
143143
mod workspace;
144+
mod workspace_metadata;

crates/uv/tests/it/show_settings.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7831,7 +7831,7 @@ fn preview_features() {
78317831
show_settings: true,
78327832
preview: Preview {
78337833
flags: PreviewFeatures(
7834-
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,
7834+
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,
78357835
),
78367836
},
78377837
python_preference: Managed,
@@ -8059,7 +8059,7 @@ fn preview_features() {
80598059
show_settings: true,
80608060
preview: Preview {
80618061
flags: PreviewFeatures(
8062-
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,
8062+
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,
80638063
),
80648064
},
80658065
python_preference: Managed,

0 commit comments

Comments
 (0)