From c6920c8a2738d129e679baf80e0d31966ee1603a Mon Sep 17 00:00:00 2001 From: Alex Kerney Date: Sat, 30 Mar 2024 14:42:11 -0400 Subject: [PATCH] Style by package sources, clean up docs and some more native rust idioms --- docs/cli.md | 113 ++++------------------------------------------- src/cli/tree.rs | 114 +++++++++++++++++++++++++++++------------------- 2 files changed, 79 insertions(+), 148 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 244333dad..95eabc569 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -328,8 +328,6 @@ List project's packages. Highlighted packages are explicit dependencies. - `--locked`: Only install if the `pixi.lock` is up-to-date with the [manifest file](configuration.md)[^1]. It can also be controlled by the `PIXI_LOCKED` environment variable (example: `PIXI_LOCKED=true`). Conflicts with `--frozen`. - `--no-install`: Don't install the environment for pypi solving, only update the lock-file if it can solve without installing. (Implied by `--frozen` and `--locked`) -```shell - ```shell pixi list pixi list --json-pretty @@ -388,8 +386,6 @@ The package tree can also be inverted (`-i`), to see which packages require a sp - `--locked`: Only install if the `pixi.lock` is up-to-date with the [manifest file](configuration.md)[^1]. It can also be controlled by the `PIXI_LOCKED` environment variable (example: `PIXI_LOCKED=true`). Conflicts with `--frozen`. - `--no-install`: Don't install the environment for pypi solving, only update the lock-file if it can solve without installing. (Implied by `--frozen` and `--locked`) -```shell - ```shell pixi tree pixi tree pre-commit @@ -400,7 +396,10 @@ pixi tree --environment docs !!! warning Use `-v` to show which `pypi` packages are not yet parsed correctly. The `extras` and `markers` parsing is still under development. -Output will look like this, where direct packages in the [manifest file](configuration.md) will be green. Once a package has been displayed once, the tree won't continue to recurse through it's dependencies (compare the first time `python` appears, vs the rest), and it will instead be marked with a star `(*)`. +Output will look like this, where direct packages in the [manifest file](configuration.md) will be green. +Once a package has been displayed once, the tree won't continue to recurse through it's dependencies (compare the first time `livzlib` appears, vs the rest), and it will instead be marked with a star `(*)`. + +Version numbers are colored by the package type, yellow for Conda packages and blue for PyPI. ```shell ➜ pixi tree @@ -421,22 +420,7 @@ Output will look like this, where direct packages in the [manifest file](configu │ │ │ └── libzlib v1.2.13 (*) │ │ └── ncurses v6.4.20240210 (*) │ ├── pyyaml v6.0.1 -│ │ ├── python_abi v3.12 -│ │ ├── python v3.12.2 (*) -│ │ └── yaml v0.2.5 -│ ├── identify v2.5.35 -│ │ └── python v3.12.2 (*) -│ ├── python v3.12.2 (*) -│ ├── virtualenv v20.25.1 -│ │ ├── distlib v0.3.8 -│ │ │ └── python v3.12.2 (*) -│ │ ├── python v3.12.2 (*) -│ │ ├── filelock v3.13.1 -│ │ │ └── python v3.12.2 (*) -│ │ └── platformdirs v4.2.0 -│ │ └── python v3.12.2 (*) -│ └── nodeenv v1.8.0 -│ └── python v3.12.2 (*) +... ├── rust v1.76.0 │ └── rust-std-aarch64-apple-darwin v1.76.0 ├── openssl v3.2.1 @@ -445,78 +429,7 @@ Output will look like this, where direct packages in the [manifest file](configu │ │ ├── libiconv v1.17 │ │ ├── gettext v0.21.1 │ │ │ └── libiconv v1.17 (*) -│ │ ├── libffi v3.4.2 (*) -│ │ ├── libcxx v16.0.6 -│ │ ├── pcre2 v10.42 -│ │ │ ├── libzlib v1.2.13 (*) -│ │ │ └── bzip2 v1.0.8 (*) -│ │ └── libzlib v1.2.13 (*) -│ └── libiconv v1.17 (*) -├── git v2.42.0 -│ ├── libexpat v2.6.2 (*) -│ ├── libzlib v1.2.13 (*) -│ ├── perl v5.32.1 -│ ├── pcre2 v10.42 (*) -│ ├── openssl v3.2.1 (*) -│ └── libiconv v1.17 (*) -├── cffconvert v2.0.0 -│ ├── ruamel.yaml v0.18.6 -│ │ ├── python_abi v3.12 (*) -│ │ ├── ruamel.yaml.clib v0.2.8 -│ │ │ ├── python_abi v3.12 (*) -│ │ │ └── python v3.12.2 (*) -│ │ └── python v3.12.2 (*) -│ ├── jsonschema v3.2.0 -│ │ ├── python v3.12.2 (*) -│ │ ├── pyrsistent v0.20.0 -│ │ │ ├── python_abi v3.12 (*) -│ │ │ └── python v3.12.2 (*) -│ │ ├── six v1.16.0 -│ │ └── attrs v23.2.0 -│ │ └── python v3.12.2 (*) -│ ├── requests v2.31.0 -│ │ ├── idna v3.6 -│ │ │ └── python v3.12.2 (*) -│ │ ├── python v3.12.2 (*) -│ │ ├── urllib3 v2.2.1 -│ │ │ ├── pysocks v1.7.1 -│ │ │ │ └── python v3.12.2 (*) -│ │ │ ├── python v3.12.2 (*) -│ │ │ └── brotli-python v1.1.0 -│ │ │ ├── python v3.12.2 (*) -│ │ │ ├── python_abi v3.12 (*) -│ │ │ └── libcxx v16.0.6 (*) -│ │ ├── certifi v2024.2.2 -│ │ │ └── python v3.12.2 (*) -│ │ └── charset-normalizer v3.3.2 -│ │ └── python v3.12.2 (*) -│ ├── click v8.1.7 -│ │ └── python v3.12.2 (*) -│ ├── pykwalify v1.8.0 -│ │ ├── python v3.12.2 (*) -│ │ ├── docopt v0.6.2 -│ │ ├── python-dateutil v2.9.0 -│ │ │ ├── python v3.12.2 (*) -│ │ │ └── six v1.16.0 (*) -│ │ └── ruamel.yaml v0.18.6 (*) -│ └── python v3.12.2 (*) -└── tbump v6.9.0 - ├── cli-ui v0.17.2 - │ ├── colorama v0.4.6 - │ │ └── python v3.12.2 (*) - │ ├── python v3.12.2 (*) - │ ├── tabulate v0.9.0 - │ │ └── python v3.12.2 (*) - │ └── unidecode v1.3.8 - │ └── python v3.12.2 (*) - ├── python v3.12.2 (*) - ├── schema v0.7.5 - │ ├── contextlib2 v21.6.0 - │ │ └── python v3.12.2 (*) - │ └── python v3.12.2 (*) - ├── tomlkit v0.12.4 - │ └── python v3.12.2 (*) - └── docopt v0.6.2 (*) +... ``` A regex pattern can be specified to filter the tree to just those that show a specific direct dependency: @@ -546,19 +459,11 @@ A regex pattern can be specified to filter the tree to just those that show a sp │ │ └── python v3.12.2 (*) │ └── python v3.12.2 (*) ├── pyyaml v6.0.1 - │ ├── python_abi v3.12 - │ ├── python v3.12.2 (*) - │ └── yaml v0.2.5 - ├── nodeenv v1.8.0 - │ └── python v3.12.2 (*) - ├── python v3.12.2 (*) - ├── cfgv v3.3.1 - │ └── python v3.12.2 (*) - └── identify v2.5.35 - └── python v3.12.2 (*) +... ``` -Additionly the tree can be inverted, and it can show which packages depend on a regex pattern. The packages specified in the manifest will also be highlighted (in this case `cffconvert` and `pre-commit` would be). +Additionly the tree can be inverted, and it can show which packages depend on a regex pattern. +The packages specified in the manifest will also be highlighted (in this case `cffconvert` and `pre-commit` would be). ```shell ➜ pixi tree -i yaml diff --git a/src/cli/tree.rs b/src/cli/tree.rs index d9a084d01..9302f423b 100644 --- a/src/cli/tree.rs +++ b/src/cli/tree.rs @@ -1,11 +1,10 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::path::PathBuf; use clap::Parser; use console::Color; use itertools::Itertools; use rattler_conda_types::Platform; -use rattler_lock::Package; use crate::lock_file::UpdateLockFileOptions; use crate::project::manifest::EnvironmentName; @@ -13,7 +12,17 @@ use crate::Project; /// Show a tree of project dependencies #[derive(Debug, Parser)] -#[clap(arg_required_else_help = false)] +#[clap(arg_required_else_help = false, long_about=format!( + "\ + Show a tree of project dependencies\n\ + \n\ + Dependency names highlighted in {} are directly specified in the manifest. \ + {} version numbers are Conda packages where as PyPI version numbers are {}. + ", + console::style("green").fg(Color::Green).bold(), + console::style("Yellow").fg(Color::Yellow), + console::style("blue").fg(Color::Blue) +))] pub struct Args { /// List only packages matching a regular expression #[arg()] @@ -95,7 +104,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { /// Filter and print an inverted dependency tree fn print_inverted_dependency_tree( - inverted_dep_map: &HashMap, + inverted_dep_map: &HashMap, direct_deps: &Vec, regex: &Option, ) -> Result<(), miette::Error> { @@ -123,7 +132,10 @@ fn print_inverted_dependency_tree( } else { console::style(pkg.name.clone()) }, - pkg.version + match pkg.source { + PackageSource::Conda => console::style(pkg.version.clone()).fg(Color::Yellow), + PackageSource::Pypi => console::style(pkg.version.clone()).fg(Color::Blue), + }, ); print_inverted_leaf(pkg, String::from(""), inverted_dep_map, direct_deps); @@ -135,9 +147,9 @@ fn print_inverted_dependency_tree( /// Recursively print inverted dependency tree leaf nodes fn print_inverted_leaf( - pkg: &InvertedPkg, + pkg: &InvertedPackage, prefix: String, - inverted_dep_map: &HashMap, + inverted_dep_map: &HashMap, direct_deps: &Vec, ) { let needed_count = pkg.needed_by.len(); @@ -161,7 +173,12 @@ fn print_inverted_leaf( } else { console::style(needed_pkg.name.clone()) }, - needed_pkg.version, + match needed_pkg.source { + PackageSource::Conda => + console::style(needed_pkg.version.clone()).fg(Color::Yellow), + PackageSource::Pypi => + console::style(needed_pkg.version.clone()).fg(Color::Blue), + }, ); let new_prefix = if index == needed_count - 1 { @@ -177,7 +194,7 @@ fn print_inverted_leaf( /// Filter and print a top down dependency tree fn print_dependency_tree( - dep_map: &HashMap, + dep_map: &HashMap, direct_deps: &[String], regex: &Option, ) -> Result<(), miette::Error> { @@ -211,7 +228,10 @@ fn print_dependency_tree( "{} {} v{}", symbol, console::style(pkg.name.clone()).fg(Color::Green).bold(), - pkg.version + match pkg.source { + PackageSource::Conda => console::style(pkg.version.clone()).fg(Color::Yellow), + PackageSource::Pypi => console::style(pkg.version.clone()).fg(Color::Blue), + } ); let prefix = if last { @@ -227,9 +247,9 @@ fn print_dependency_tree( /// Recursively print top down dependency tree nodes fn print_dependency_leaf( - pkg: &Pkg, + pkg: &Package, prefix: String, - dep_map: &HashMap, + dep_map: &HashMap, visited_pkgs: &mut Vec, ) { let dep_count = pkg.dependencies.len(); @@ -255,7 +275,10 @@ fn print_dependency_leaf( prefix, symbol, dep.name, - dep.version, + match dep.source { + PackageSource::Conda => console::style(dep.version.clone()).fg(Color::Yellow), + PackageSource::Pypi => console::style(dep.version.clone()).fg(Color::Blue), + }, if visited { "(*)" } else { "" } ); @@ -292,22 +315,30 @@ fn direct_dependencies( project_dependency_names } +#[derive(Debug, Copy, Clone)] +enum PackageSource { + Conda, + Pypi, +} + #[derive(Debug)] -struct Pkg { +struct Package { name: String, version: String, dependencies: Vec, + source: PackageSource, } #[derive(Debug)] -struct InvertedPkg { +struct InvertedPackage { name: String, version: String, needed_by: Vec, + source: PackageSource, } /// Builds a hashmap of dependencies, with names, versions, and what they depend on -fn generate_dependency_map(locked_deps: &Vec) -> HashMap { +fn generate_dependency_map(locked_deps: &Vec) -> HashMap { let mut dep_map = HashMap::new(); for dep in locked_deps { @@ -324,10 +355,11 @@ fn generate_dependency_map(locked_deps: &Vec) -> HashMap { dep_map.insert( name.clone(), - Pkg { + Package { name: name.clone(), version, - dependencies: unique_deps(dependencies), + dependencies: dependencies.into_iter().unique().collect(), + source: PackageSource::Conda, }, ); } else if let Some(dep) = dep.as_pypi() { @@ -337,8 +369,9 @@ fn generate_dependency_map(locked_deps: &Vec) -> HashMap { for p in dep.data().package.requires_dist.iter() { if let Some(markers) = &p.marker { tracing::info!( - "A bunch of markers on {}, skipping for now {:?}", + "Extra and environment markers currently cannot be parsed on {} which is specified by {}, skipping. {:?}", p.name, + name, markers ); } else { @@ -347,10 +380,11 @@ fn generate_dependency_map(locked_deps: &Vec) -> HashMap { } dep_map.insert( name.clone(), - Pkg { + Package { name: name.clone(), version, - dependencies: unique_deps(dependencies), + dependencies: dependencies.into_iter().unique().collect(), + source: PackageSource::Pypi, }, ); } @@ -358,31 +392,23 @@ fn generate_dependency_map(locked_deps: &Vec) -> HashMap { dep_map } -/// Only return the unique dependencies -fn unique_deps(dependencies: Vec) -> Vec { - let mut unique_deps = HashSet::new(); - - for d in dependencies { - unique_deps.insert(d); - } - unique_deps.into_iter().collect() -} - /// Given a map of dependencies, invert it so that it has what a package is needed by, /// rather than what it depends on -fn invert_dep_map(dep_map: &HashMap) -> HashMap { - let mut inverted_deps = HashMap::new(); - - for (pkg_name, pkg) in dep_map { - inverted_deps.insert( - pkg_name.to_string(), - InvertedPkg { - name: pkg.name.clone(), - version: pkg.version.clone(), - needed_by: Vec::new(), - }, - ); - } +fn invert_dep_map(dep_map: &HashMap) -> HashMap { + let mut inverted_deps: HashMap = dep_map + .iter() + .map(|(pkg_name, pkg)| { + ( + pkg_name.clone(), + InvertedPackage { + name: pkg.name.clone(), + version: pkg.version.clone(), + needed_by: Vec::new(), + source: pkg.source, + }, + ) + }) + .collect(); for pkg in dep_map.values() { for dep in pkg.dependencies.iter() {