From e67d87301a19a75326e04b82646db99787f19fd6 Mon Sep 17 00:00:00 2001 From: bluss Date: Sat, 12 Oct 2024 15:10:56 +0200 Subject: [PATCH] Implement `uv tree --no-dev` (#8109) ## Summary Allow pruning dev-dependencies in uv tree. This is not inherently in conflict with --invert, but this pruning is not yet implemented there. --- crates/uv-cli/src/lib.rs | 11 +++++++++++ crates/uv-resolver/src/lock/tree.rs | 10 +++++++++- crates/uv/src/commands/project/tree.rs | 4 +++- crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 4 ++++ crates/uv/tests/it/tree.rs | 14 +++++++++++++- docs/reference/cli.md | 2 ++ 7 files changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 8f3cd3d4beaf..f1d94f44cfc6 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3070,6 +3070,17 @@ pub struct TreeArgs { #[command(flatten)] pub tree: DisplayTreeArgs, + /// Include development dependencies. + /// + /// Development dependencies are defined via `tool.uv.dev-dependencies` in a + /// `pyproject.toml`. + #[arg(long, overrides_with("no_dev"), hide = true)] + pub dev: bool, + + /// Omit development dependencies. + #[arg(long, overrides_with("dev"), conflicts_with = "invert")] + pub no_dev: bool, + /// Assert that the `uv.lock` will remain unchanged. /// /// Requires that the lockfile is up-to-date. If the lockfile is missing or diff --git a/crates/uv-resolver/src/lock/tree.rs b/crates/uv-resolver/src/lock/tree.rs index f5a3484e8517..3ce6a2dbf311 100644 --- a/crates/uv-resolver/src/lock/tree.rs +++ b/crates/uv-resolver/src/lock/tree.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; +use uv_configuration::DevMode; use uv_normalize::{ExtraName, GroupName, PackageName}; use uv_pypi_types::ResolverMarkerEnvironment; @@ -22,8 +23,10 @@ pub struct TreeDisplay<'env> { optional_dependencies: FxHashMap<&'env PackageId, FxHashMap>>>, dev_dependencies: FxHashMap<&'env PackageId, FxHashMap>>>, - /// Maximum display depth of the dependency tree + /// Maximum display depth of the dependency tree. depth: usize, + /// Whether to include development dependencies in the display. + dev: DevMode, /// Prune the given packages from the display of the dependency tree. prune: Vec, /// Display only the specified packages. @@ -40,6 +43,7 @@ impl<'env> TreeDisplay<'env> { depth: usize, prune: Vec, packages: Vec, + dev: DevMode, no_dedupe: bool, invert: bool, ) -> Self { @@ -180,6 +184,7 @@ impl<'env> TreeDisplay<'env> { optional_dependencies, dev_dependencies, depth, + dev, prune, packages, no_dedupe, @@ -231,12 +236,14 @@ impl<'env> TreeDisplay<'env> { let dependencies: Vec> = self .dependencies .get(node.package_id()) + .filter(|_| self.dev != DevMode::Only) .into_iter() .flatten() .map(|dep| Node::Dependency(dep.as_ref())) .chain( self.optional_dependencies .get(node.package_id()) + .filter(|_| self.dev != DevMode::Only) .into_iter() .flatten() .flat_map(|(extra, deps)| { @@ -247,6 +254,7 @@ impl<'env> TreeDisplay<'env> { .chain( self.dev_dependencies .get(node.package_id()) + .filter(|_| self.dev != DevMode::Exclude) .into_iter() .flatten() .flat_map(|(group, deps)| { diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 0ddbec93fdbd..d6857ef1a36f 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -4,7 +4,7 @@ use std::path::Path; use uv_cache::Cache; use uv_client::Connectivity; -use uv_configuration::{Concurrency, TargetTriple}; +use uv_configuration::{Concurrency, DevMode, TargetTriple}; use uv_pep508::PackageName; use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion}; use uv_resolver::TreeDisplay; @@ -21,6 +21,7 @@ use crate::settings::ResolverSettings; #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn tree( project_dir: &Path, + dev: DevMode, locked: bool, frozen: bool, universal: bool, @@ -89,6 +90,7 @@ pub(crate) async fn tree( depth.into(), prune, package, + dev, no_dedupe, invert, ); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9ea879d69667..243532fab10f 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1444,6 +1444,7 @@ async fn run_project( commands::tree( project_dir, + args.dev, args.locked, args.frozen, args.universal, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 9f2474293b50..7de3dae71410 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -936,6 +936,7 @@ impl RemoveSettings { #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub(crate) struct TreeSettings { + pub(crate) dev: DevMode, pub(crate) locked: bool, pub(crate) frozen: bool, pub(crate) universal: bool, @@ -956,6 +957,8 @@ impl TreeSettings { let TreeArgs { tree, universal, + dev, + no_dev, locked, frozen, build, @@ -966,6 +969,7 @@ impl TreeSettings { } = args; Self { + dev: DevMode::from_args(dev, no_dev, false), locked, frozen, universal, diff --git a/crates/uv/tests/it/tree.rs b/crates/uv/tests/it/tree.rs index 70b4c37f20f9..a91461f68b81 100644 --- a/crates/uv/tests/it/tree.rs +++ b/crates/uv/tests/it/tree.rs @@ -438,7 +438,7 @@ fn dev_dependencies() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.tree().arg("--universal"), @r###" + uv_snapshot!(context.filters(), context.tree(), @r###" success: true exit_code: 0 ----- stdout ----- @@ -453,6 +453,18 @@ fn dev_dependencies() -> Result<()> { "### ); + uv_snapshot!(context.filters(), context.tree().arg("--no-dev"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + project v0.1.0 + └── iniconfig v2.0.0 + + ----- stderr ----- + Resolved 5 packages in [TIME] + "### + ); + // `uv tree` should update the lockfile let lock = context.read("uv.lock"); assert!(!lock.is_empty()); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 5076dde2b757..5add2f5ddb46 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -2329,6 +2329,8 @@ uv tree [OPTIONS]

May also be set with the UV_NO_CONFIG environment variable.

--no-dedupe

Do not de-duplicate repeated dependencies. Usually, when a package has already displayed its dependencies, further occurrences will not re-display its dependencies, and will include a (*) to indicate it has already been shown. This flag will cause those duplicates to be repeated

+
--no-dev

Omit development dependencies

+
--no-index

Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via --find-links

--no-progress

Hide all progress outputs.