Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/pixi_cli/src/global/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod list;
mod remove;
mod shortcut;
mod sync;
mod tree;
mod uninstall;
mod update;
mod upgrade;
Expand Down Expand Up @@ -41,6 +42,8 @@ pub enum Command {
#[clap(alias = "ua")]
#[command(hide = true)]
UpgradeAll(upgrade_all::Args),
#[clap(visible_alias = "t")]
Tree(tree::Args),
}

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

/// Maps global command enum variants to their function handlers.
pub async fn execute(cmd: Args) -> miette::Result<()> {
match cmd.command {
Command::Add(args) => add::execute(args).await?,
Expand All @@ -67,6 +71,7 @@ pub async fn execute(cmd: Args) -> miette::Result<()> {
Command::Update(args) => update::execute(args).await?,
Command::Upgrade(args) => upgrade::execute(args).await?,
Command::UpgradeAll(args) => upgrade_all::execute(args).await?,
Command::Tree(args) => tree::execute(args).await?,
};
Ok(())
}
Expand Down
116 changes: 116 additions & 0 deletions crates/pixi_cli/src/global/tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::shared::tree::{
Package, PackageSource, build_reverse_dependency_map, print_dependency_tree,
print_inverted_dependency_tree,
};
use ahash::HashSet;
use clap::Parser;
use console::Color;
use itertools::Itertools;
use miette::Context;
use pixi_consts::consts;
use pixi_global::common::find_package_records;
use pixi_global::{EnvRoot, EnvironmentName, Project};
use std::collections::HashMap;
use std::str::FromStr;

/// Show a tree of dependencies for a specific global environment.
#[derive(Debug, Parser)]
#[clap(arg_required_else_help = false, long_about = format!(
"\
Show a tree of a global environment dependencies\n\
\n\
Dependency names highlighted in {} are directly specified in the manifest.
",
console::style("green").fg(Color::Green).bold(),
))]
pub struct Args {
/// The environment to list packages for.
#[arg(short, long)]
pub environment: String,

/// List only packages matching a regular expression
#[arg()]
pub regex: Option<String>,

/// Invert tree and show what depends on a given package in the regex argument
#[arg(short, long, requires = "regex")]
pub invert: bool,
}

pub async fn execute(args: Args) -> miette::Result<()> {
let project = Project::discover_or_create().await?;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let env_name = EnvironmentName::from_str(args.environment.as_str())?;
let environment = project
.environment(&env_name)
.wrap_err("Environment not found")?;
// Contains all the dependencies under conda-meta
let records = find_package_records(
&EnvRoot::from_env()
.await?
.path()
.join(env_name.as_str())
.join(consts::CONDA_META_DIR),
)
.await?;

let packages: HashMap<String, Package> = records
.iter()
.map(|record| {
let name = record
.repodata_record
.package_record
.name
.as_normalized()
.to_string();
let package = Package {
name: name.clone(),
version: record
.repodata_record
.package_record
.version
.version()
.to_string(),
dependencies: record
.repodata_record
.package_record
.as_ref()
.depends
.iter()
.filter_map(|dep| {
dep.split([' ', '='])
.next()
.map(|dep_name| dep_name.to_string())
})
.filter(|dep_name| !dep_name.starts_with("__")) // Filter virtual packages
.unique() // A package may be listed with multiple constraints
.collect(),
needed_by: Vec::new(),
source: PackageSource::Conda, // Global environments can only manage Conda packages
};
(name, package)
})
.collect();

let direct_deps = HashSet::from_iter(
environment
.dependencies
.specs
.iter()
.map(|(name, _)| name.as_normalized().to_string()),
);
if args.invert {
print_inverted_dependency_tree(
&mut handle,
&build_reverse_dependency_map(&packages),
&direct_deps,
&args.regex,
)
.wrap_err("Couldn't print the inverted dependency tree")?;
} else {
print_dependency_tree(&mut handle, &packages, &direct_deps, &args.regex)
.wrap_err("Couldn't print the dependency tree")?;
}
Ok(())
}
11 changes: 10 additions & 1 deletion crates/pixi_cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
//! # Pixi CLI
//!
//! This module implements the CLI interface of Pixi.
//!
//! ## Structure
//!
//! - The [`Command`] enum defines the top-level commands available.
//! - The [`execute_command`] function matches on [`Command`] and calls the corresponding logic.
#![deny(clippy::dbg_macro, clippy::unwrap_used)]

use clap::builder::styling::{AnsiColor, Color, Style};
Expand Down Expand Up @@ -32,6 +40,7 @@ pub mod remove;
pub mod run;
pub mod search;
pub mod self_update;
mod shared;
pub mod shell;
pub mod shell_hook;
pub mod task;
Expand Down Expand Up @@ -387,7 +396,7 @@ fn setup_logging(args: &Args, use_colors: bool) -> miette::Result<()> {
Ok(())
}

/// Execute the actual command
/// Maps command enum variants to their actual function handlers.
pub async fn execute_command(
command: Command,
global_options: &GlobalOptions,
Expand Down
3 changes: 3 additions & 0 deletions crates/pixi_cli/src/shared/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! This file contains utilities shared by the implementation of command logic

pub mod tree;
Loading
Loading