Skip to content

Commit f7f1d4a

Browse files
Carbonhelltdejagerruben-arts
authored
feat(cli): add new pixi global tree command (#4427)
Co-authored-by: Tim de Jager <[email protected]> Co-authored-by: Ruben Arts <[email protected]>
1 parent 4c50e35 commit f7f1d4a

File tree

9 files changed

+768
-401
lines changed

9 files changed

+768
-401
lines changed

crates/pixi_cli/src/global/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ mod list;
1111
mod remove;
1212
mod shortcut;
1313
mod sync;
14+
mod tree;
1415
mod uninstall;
1516
mod update;
1617
mod upgrade;
@@ -41,6 +42,8 @@ pub enum Command {
4142
#[clap(alias = "ua")]
4243
#[command(hide = true)]
4344
UpgradeAll(upgrade_all::Args),
45+
#[clap(visible_alias = "t")]
46+
Tree(tree::Args),
4447
}
4548

4649
/// Subcommand for global package management actions.
@@ -53,6 +56,7 @@ pub struct Args {
5356
command: Command,
5457
}
5558

59+
/// Maps global command enum variants to their function handlers.
5660
pub async fn execute(cmd: Args) -> miette::Result<()> {
5761
match cmd.command {
5862
Command::Add(args) => add::execute(args).await?,
@@ -67,6 +71,7 @@ pub async fn execute(cmd: Args) -> miette::Result<()> {
6771
Command::Update(args) => update::execute(args).await?,
6872
Command::Upgrade(args) => upgrade::execute(args).await?,
6973
Command::UpgradeAll(args) => upgrade_all::execute(args).await?,
74+
Command::Tree(args) => tree::execute(args).await?,
7075
};
7176
Ok(())
7277
}

crates/pixi_cli/src/global/tree.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use crate::shared::tree::{
2+
Package, PackageSource, build_reverse_dependency_map, print_dependency_tree,
3+
print_inverted_dependency_tree,
4+
};
5+
use ahash::HashSet;
6+
use clap::Parser;
7+
use console::Color;
8+
use itertools::Itertools;
9+
use miette::Context;
10+
use pixi_consts::consts;
11+
use pixi_global::common::find_package_records;
12+
use pixi_global::{EnvRoot, EnvironmentName, Project};
13+
use std::collections::HashMap;
14+
use std::str::FromStr;
15+
16+
/// Show a tree of dependencies for a specific global environment.
17+
#[derive(Debug, Parser)]
18+
#[clap(arg_required_else_help = false, long_about = format!(
19+
"\
20+
Show a tree of a global environment dependencies\n\
21+
\n\
22+
Dependency names highlighted in {} are directly specified in the manifest.
23+
",
24+
console::style("green").fg(Color::Green).bold(),
25+
))]
26+
pub struct Args {
27+
/// The environment to list packages for.
28+
#[arg(short, long)]
29+
pub environment: String,
30+
31+
/// List only packages matching a regular expression
32+
#[arg()]
33+
pub regex: Option<String>,
34+
35+
/// Invert tree and show what depends on a given package in the regex argument
36+
#[arg(short, long, requires = "regex")]
37+
pub invert: bool,
38+
}
39+
40+
pub async fn execute(args: Args) -> miette::Result<()> {
41+
let project = Project::discover_or_create().await?;
42+
let stdout = std::io::stdout();
43+
let mut handle = stdout.lock();
44+
let env_name = EnvironmentName::from_str(args.environment.as_str())?;
45+
let environment = project
46+
.environment(&env_name)
47+
.wrap_err("Environment not found")?;
48+
// Contains all the dependencies under conda-meta
49+
let records = find_package_records(
50+
&EnvRoot::from_env()
51+
.await?
52+
.path()
53+
.join(env_name.as_str())
54+
.join(consts::CONDA_META_DIR),
55+
)
56+
.await?;
57+
58+
let packages: HashMap<String, Package> = records
59+
.iter()
60+
.map(|record| {
61+
let name = record
62+
.repodata_record
63+
.package_record
64+
.name
65+
.as_normalized()
66+
.to_string();
67+
let package = Package {
68+
name: name.clone(),
69+
version: record
70+
.repodata_record
71+
.package_record
72+
.version
73+
.version()
74+
.to_string(),
75+
dependencies: record
76+
.repodata_record
77+
.package_record
78+
.as_ref()
79+
.depends
80+
.iter()
81+
.filter_map(|dep| {
82+
dep.split([' ', '='])
83+
.next()
84+
.map(|dep_name| dep_name.to_string())
85+
})
86+
.filter(|dep_name| !dep_name.starts_with("__")) // Filter virtual packages
87+
.unique() // A package may be listed with multiple constraints
88+
.collect(),
89+
needed_by: Vec::new(),
90+
source: PackageSource::Conda, // Global environments can only manage Conda packages
91+
};
92+
(name, package)
93+
})
94+
.collect();
95+
96+
let direct_deps = HashSet::from_iter(
97+
environment
98+
.dependencies
99+
.specs
100+
.iter()
101+
.map(|(name, _)| name.as_normalized().to_string()),
102+
);
103+
if args.invert {
104+
print_inverted_dependency_tree(
105+
&mut handle,
106+
&build_reverse_dependency_map(&packages),
107+
&direct_deps,
108+
&args.regex,
109+
)
110+
.wrap_err("Couldn't print the inverted dependency tree")?;
111+
} else {
112+
print_dependency_tree(&mut handle, &packages, &direct_deps, &args.regex)
113+
.wrap_err("Couldn't print the dependency tree")?;
114+
}
115+
Ok(())
116+
}

crates/pixi_cli/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
//! # Pixi CLI
2+
//!
3+
//! This module implements the CLI interface of Pixi.
4+
//!
5+
//! ## Structure
6+
//!
7+
//! - The [`Command`] enum defines the top-level commands available.
8+
//! - The [`execute_command`] function matches on [`Command`] and calls the corresponding logic.
19
#![deny(clippy::dbg_macro, clippy::unwrap_used)]
210

311
use clap::builder::styling::{AnsiColor, Color, Style};
@@ -32,6 +40,7 @@ pub mod remove;
3240
pub mod run;
3341
pub mod search;
3442
pub mod self_update;
43+
mod shared;
3544
pub mod shell;
3645
pub mod shell_hook;
3746
pub mod task;
@@ -387,7 +396,7 @@ fn setup_logging(args: &Args, use_colors: bool) -> miette::Result<()> {
387396
Ok(())
388397
}
389398

390-
/// Execute the actual command
399+
/// Maps command enum variants to their actual function handlers.
391400
pub async fn execute_command(
392401
command: Command,
393402
global_options: &GlobalOptions,

crates/pixi_cli/src/shared/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//! This file contains utilities shared by the implementation of command logic
2+
3+
pub mod tree;

0 commit comments

Comments
 (0)