Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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