diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 5eed10878..595e8e0ce 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -1709,6 +1709,10 @@ pub struct PipShowArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct PipTreeArgs { + /// Show the version constraint(s) imposed on each package. + #[arg(long)] + pub show_version_specifiers: bool, + #[command(flatten)] pub tree: DisplayTreeArgs, @@ -2344,6 +2348,11 @@ pub struct RemoveArgs { #[derive(Args)] #[allow(clippy::struct_excessive_bools)] pub struct TreeArgs { + /// Show the resolved package versions for all Python versions and platforms, rather than + /// filtering to those that are relevant for the current environment. + #[arg(long)] + pub universal: bool, + #[command(flatten)] pub tree: DisplayTreeArgs, @@ -3054,8 +3063,4 @@ pub struct DisplayTreeArgs { /// Show the reverse dependencies for the given package. This flag will invert the tree and display the packages that depend on the given package. #[arg(long, alias = "reverse")] pub invert: bool, - - /// Show the version constraint(s) imposed on each package. - #[arg(long)] - pub show_version_specifiers: bool, } diff --git a/crates/uv-resolver/src/lock.rs b/crates/uv-resolver/src/lock.rs index d66d4c6f0..640618590 100644 --- a/crates/uv-resolver/src/lock.rs +++ b/crates/uv-resolver/src/lock.rs @@ -2650,6 +2650,7 @@ impl<'env> TreeDisplay<'env> { /// Create a new [`DisplayDependencyGraph`] for the set of installed distributions. pub fn new( lock: &'env Lock, + markers: Option<&'env MarkerEnvironment>, depth: usize, prune: Vec, package: Vec, @@ -2676,6 +2677,15 @@ impl<'env> TreeDisplay<'env> { // Mark the dependency as a non-root node. non_roots.insert(child); + // Skip dependencies that don't apply to the current environment. + if let Some(environment_markers) = markers { + if let Some(dependency_markers) = dependency.marker.as_ref() { + if !dependency_markers.evaluate(environment_markers, &[]) { + continue; + } + } + } + edges.entry(parent).or_default().push(child); } diff --git a/crates/uv/src/commands/pip/tree.rs b/crates/uv/src/commands/pip/tree.rs index 84fb57f8d..b5d3014dd 100644 --- a/crates/uv/src/commands/pip/tree.rs +++ b/crates/uv/src/commands/pip/tree.rs @@ -24,12 +24,12 @@ use crate::printer::Printer; /// Display the installed packages in the current environment as a dependency tree. #[allow(clippy::fn_params_excessive_bools)] pub(crate) fn pip_tree( + show_version_specifiers: bool, depth: u8, prune: Vec, package: Vec, no_dedupe: bool, invert: bool, - show_version_specifiers: bool, strict: bool, python: Option<&str>, system: bool, diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 46a37a0cf..1e39385a4 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -24,6 +24,7 @@ use super::SharedState; pub(crate) async fn tree( locked: bool, frozen: bool, + universal: bool, depth: u8, prune: Vec, package: Vec, @@ -79,7 +80,15 @@ pub(crate) async fn tree( .await?; // Render the tree. - let tree = TreeDisplay::new(&lock.lock, depth.into(), prune, package, no_dedupe, invert); + let tree = TreeDisplay::new( + &lock.lock, + (!universal).then(|| interpreter.markers()), + depth.into(), + prune, + package, + no_dedupe, + invert, + ); write!(printer.stdout(), "{tree}")?; diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 14a568616..3137ae4e7 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -562,12 +562,12 @@ async fn run(cli: Cli) -> Result { let cache = cache.init()?; commands::pip_tree( + args.show_version_specifiers, args.depth, args.prune, args.package, args.no_dedupe, args.invert, - args.show_version_specifiers, args.shared.strict, args.shared.python.as_deref(), args.shared.system, @@ -1121,6 +1121,7 @@ async fn run_project( commands::tree( args.locked, args.frozen, + args.universal, args.depth, args.prune, args.package, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 21477d3db..521a3bc78 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -769,6 +769,7 @@ impl RemoveSettings { pub(crate) struct TreeSettings { pub(crate) locked: bool, pub(crate) frozen: bool, + pub(crate) universal: bool, pub(crate) depth: u8, pub(crate) prune: Vec, pub(crate) package: Vec, @@ -783,6 +784,7 @@ impl TreeSettings { pub(crate) fn resolve(args: TreeArgs, filesystem: Option) -> Self { let TreeArgs { tree, + universal, locked, frozen, build, @@ -793,6 +795,7 @@ impl TreeSettings { Self { locked, frozen, + universal, depth: tree.depth, prune: tree.prune, package: tree.package, @@ -1357,12 +1360,12 @@ impl PipShowSettings { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct PipTreeSettings { + pub(crate) show_version_specifiers: bool, pub(crate) depth: u8, pub(crate) prune: Vec, pub(crate) package: Vec, pub(crate) no_dedupe: bool, pub(crate) invert: bool, - pub(crate) show_version_specifiers: bool, // CLI-only settings. pub(crate) shared: PipSettings, } @@ -1371,6 +1374,7 @@ impl PipTreeSettings { /// Resolve the [`PipTreeSettings`] from the CLI and workspace configuration. pub(crate) fn resolve(args: PipTreeArgs, filesystem: Option) -> Self { let PipTreeArgs { + show_version_specifiers, tree, strict, no_strict, @@ -1381,11 +1385,11 @@ impl PipTreeSettings { } = args; Self { + show_version_specifiers, depth: tree.depth, prune: tree.prune, no_dedupe: tree.no_dedupe, invert: tree.invert, - show_version_specifiers: tree.show_version_specifiers, package: tree.package, // Shared settings. shared: PipSettings::combine( diff --git a/crates/uv/tests/tree.rs b/crates/uv/tests/tree.rs index fa8273b80..e2bf4c9f3 100644 --- a/crates/uv/tests/tree.rs +++ b/crates/uv/tests/tree.rs @@ -26,7 +26,7 @@ fn nested_dependencies() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.tree(), @r###" + uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -132,7 +132,7 @@ fn frozen() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.tree(), @r###" + uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -200,12 +200,31 @@ fn platform_dependencies() -> Result<()> { "#, )?; - // Should include `colorama`, even though it's only included on Windows. + // When `--universal` is _not_ provided, `colorama` should _not_ be included. + #[cfg(not(windows))] uv_snapshot!(context.filters(), context.tree(), @r###" success: true exit_code: 0 ----- stdout ----- project v0.1.0 + └── black v24.3.0 + ├── click v8.1.7 + ├── mypy-extensions v1.0.0 + ├── packaging v24.0 + ├── pathspec v0.12.1 + └── platformdirs v4.2.0 + + ----- stderr ----- + warning: `uv tree` is experimental and may change without warning + Resolved 8 packages in [TIME] + "###); + + // Should include `colorama`, even though it's only included on Windows. + uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + project v0.1.0 └── black v24.3.0 ├── click v8.1.7 │ └── colorama v0.4.6 @@ -247,7 +266,7 @@ fn repeated_dependencies() -> Result<()> { )?; // Should include both versions of `anyio`, which have different dependencies. - uv_snapshot!(context.filters(), context.tree(), @r###" + uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -321,7 +340,7 @@ fn repeated_version() -> Result<()> { Url::from_file_path(context.temp_dir.join("v2")).unwrap(), })?; - uv_snapshot!(context.filters(), context.tree(), @r###" + uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -361,13 +380,14 @@ fn dev_dependencies() -> Result<()> { # ... requires-python = ">=3.12" dependencies = ["iniconfig"] + [tool.uv] dev-dependencies = ["anyio"] "#, )?; // Dev dependencies should be omitted. - uv_snapshot!(context.filters(), context.tree(), @r###" + uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" success: true exit_code: 0 ----- stdout -----