Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Dependency tree command #1069

Merged
merged 17 commits into from
Apr 3, 2024
Merged

feat: Dependency tree command #1069

merged 17 commits into from
Apr 3, 2024

Conversation

abkfenris
Copy link
Contributor

@abkfenris abkfenris commented Mar 28, 2024

fixes: #238

A really rough pass at a pixi tree command, based off of cargo tree and mamba repoquery.

I had an idea that I didn't actually expect to work that I started tinkering around while working on another branch ( #1055 ), so it isn't very clean, but since folks are interested in playing with it.

It does:

  • It can draw both a full and filtered top down dependency tree
  • For a top down tree it will keep track of dependencies it has already visited, and avoid repeating them
  • It can draw a filtered inverted tree ('who depends on x package')
  • Style Conda/PyPi dependencies

It does not or is not:

  • Keep track of what has been visited in the inverted tree to avoid repeating
  • Know how to start with PyPI dependencies
  • Start at a middle depth of top down tree
  • Show versions on an inverted tree
  • Work in a non UTF-8 terminal?
  • A good example of Rust code as I just got started with it and haven't made it through the book yet. I largely started with pixi list and kept tweaking till something worked.
  • Probably a bunch of other things

Full tree:

pixi tree
├── pre-commit v3.3.3
│   ├── cfgv v3.3.1
│   │   └── python v3.12.2
│   │       ├── bzip2 v1.0.8
│   │       ├── libexpat v2.6.2
│   │       ├── libffi v3.4.2
│   │       ├── libsqlite v3.45.2
│   │       │   └── libzlib v1.2.13
│   │       ├── libzlib v1.2.13 (*)
│   │       ├── ncurses v6.4.20240210
│   │       ├── openssl v3.2.1
│   │       ├── readline v8.2
│   │       │   └── ncurses v6.4.20240210 (*)
│   │       ├── tk v8.6.13
│   │       │   └── libzlib v1.2.13 (*)
│   │       └── xz v5.2.6
│   ├── identify v2.5.35
│   │   └── python v3.12.2 (*)
│   ├── nodeenv v1.8.0
│   │   └── python v3.12.2 (*)
│   ├── python v3.12.2 (*)
│   ├── pyyaml v6.0.1
│   │   ├── python v3.12.2 (*)
│   │   ├── python v3.12.2 (*)
│   │   ├── python_abi v3.12
│   │   └── yaml v0.2.5
│   └── virtualenv v20.25.1
│       ├── distlib v0.3.8
│       │   └── python v3.12.2 (*)
│       ├── filelock v3.13.1
│       │   └── python v3.12.2 (*)
│       ├── platformdirs v4.2.0
│       │   └── python v3.12.2 (*)
│       └── python v3.12.2 (*)
├── rust v1.76.0
│   └── rust-std-aarch64-apple-darwin v1.76.0
├── openssl v3.2.1
├── pkg-config v0.29.2
│   ├── libglib v2.78.4
│   │   ├── gettext v0.21.1
│   │   │   └── libiconv v1.17
│   │   ├── libcxx v16.0.6
│   │   ├── libffi v3.4.2 (*)
│   │   ├── libiconv v1.17 (*)
│   │   ├── libzlib v1.2.13 (*)
│   │   └── pcre2 v10.42
│   │       ├── bzip2 v1.0.8 (*)
│   │       └── libzlib v1.2.13 (*)
│   └── libiconv v1.17 (*)
├── git v2.42.0
│   ├── libexpat v2.6.2 (*)
│   ├── libiconv v1.17 (*)
│   ├── libzlib v1.2.13 (*)
│   ├── openssl v3.2.1 (*)
│   ├── pcre2 v10.42 (*)
│   └── perl v5.32.1
├── cffconvert v2.0.0
│   ├── click v8.1.7
│   │   └── python v3.12.2 (*)
│   ├── jsonschema v3.2.0
│   │   ├── attrs v23.2.0
│   │   │   └── python v3.12.2 (*)
│   │   ├── pyrsistent v0.20.0
│   │   │   ├── python v3.12.2 (*)
│   │   │   ├── python v3.12.2 (*)
│   │   │   └── python_abi v3.12 (*)
│   │   ├── python v3.12.2 (*)
│   │   └── six v1.16.0
│   ├── pykwalify v1.8.0
│   │   ├── docopt v0.6.2
│   │   ├── python v3.12.2 (*)
│   │   ├── python-dateutil v2.9.0
│   │   │   ├── python v3.12.2 (*)
│   │   │   └── six v1.16.0 (*)
│   │   └── ruamel.yaml v0.18.6
│   │       ├── python v3.12.2 (*)
│   │       ├── python v3.12.2 (*)
│   │       ├── python_abi v3.12 (*)
│   │       └── ruamel.yaml.clib v0.2.8
│   │           ├── python v3.12.2 (*)
│   │           ├── python v3.12.2 (*)
│   │           └── python_abi v3.12 (*)
│   ├── python v3.12.2 (*)
│   ├── requests v2.31.0
│   │   ├── certifi v2024.2.2
│   │   │   └── python v3.12.2 (*)
│   │   ├── charset-normalizer v3.3.2
│   │   │   └── python v3.12.2 (*)
│   │   ├── idna v3.6
│   │   │   └── python v3.12.2 (*)
│   │   ├── python v3.12.2 (*)
│   │   └── urllib3 v2.2.1
│   │       ├── brotli-python v1.1.0
│   │       │   ├── libcxx v16.0.6 (*)
│   │       │   ├── python v3.12.2 (*)
│   │       │   ├── python v3.12.2 (*)
│   │       │   └── python_abi v3.12 (*)
│   │       ├── pysocks v1.7.1
│   │       │   └── python v3.12.2 (*)
│   │       └── python v3.12.2 (*)
│   └── ruamel.yaml v0.18.6 (*)
└── 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 (*)
    ├── docopt v0.6.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 (*)

Filtered tree

pixi tree pre-commit
└── pre-commit v3.3.3
    ├── cfgv v3.3.1
    │   └── python v3.12.2
    │       ├── bzip2 v1.0.8
    │       ├── libexpat v2.6.2
    │       ├── libffi v3.4.2
    │       ├── libsqlite v3.45.2
    │       │   └── libzlib v1.2.13
    │       ├── libzlib v1.2.13 (*)
    │       ├── ncurses v6.4.20240210
    │       ├── openssl v3.2.1
    │       ├── readline v8.2
    │       │   └── ncurses v6.4.20240210 (*)
    │       ├── tk v8.6.13
    │       │   └── libzlib v1.2.13 (*)
    │       └── xz v5.2.6
    ├── identify v2.5.35
    │   └── python v3.12.2 (*)
    ├── nodeenv v1.8.0
    │   └── python v3.12.2 (*)
    ├── python v3.12.2 (*)
    ├── pyyaml v6.0.1
    │   ├── python v3.12.2 (*)
    │   ├── python v3.12.2 (*)
    │   ├── python_abi v3.12
    │   └── yaml v0.2.5
    └── virtualenv v20.25.1
        ├── distlib v0.3.8
        │   └── python v3.12.2 (*)
        ├── filelock v3.13.1
        │   └── python v3.12.2 (*)
        ├── platformdirs v4.2.0
        │   └── python v3.12.2 (*)
        └── python v3.12.2 (*)

Inverted

pixi tree -i yaml

ruamel.yaml
├── cffconvert
└── pykwalify
    └── cffconvert

ruamel.yaml.clib
└── ruamel.yaml
    ├── cffconvert
    └── pykwalify
        └── cffconvert

pyyaml
└── pre-commit

yaml
└── pyyaml
    └── pre-commit

Help

pixi tree --help
Lock file usage from the CLI

Usage: pixi tree [OPTIONS] [REGEX]

Arguments:
  [REGEX]  List only packages matching a regular expression

Options:
      --platform <PLATFORM>            The platform to list packages for. Defaults to the current platform
      --manifest-path <MANIFEST_PATH>  The path to 'pixi.toml' [env: PIXI_PROJECT_MANIFEST=]
  -e, --environment <ENVIRONMENT>      The environment to list packages for. Defaults to the default environment
      --frozen                         Don't check or update the lockfile, continue with previously installed environment
                                       [env: PIXI_FROZEN=]
      --locked                         Check if lockfile is up to date, aborts when lockfile isn't up to date with the
                                       manifest file [env: PIXI_LOCKED=]
      --no-install                     Don't install the environment for pypi solving, only update the lock-file if it can
                                       solve without installing
  -i, --invert                         Invert tree and show what depends on given package
  -v, --verbose...                     Increase logging verbosity
  -q, --quiet...                       Decrease logging verbosity
      --color <COLOR>                  Whether the log needs to be colored [env: PIXI_COLOR=] [default: auto] [possible
                                       values: always, never, auto]
  -h, --help                           Print help

@ruben-arts
Copy link
Contributor

He @abkfenris, thanks for the PR.

This is already a very good start, I gave it a go and it does most of what I expect from a first version.

To improve and align with the pixi list, I would like to have the packages defined in the pixi.toml to be visualized in green.

The pypi dependencies should also be able to depend on conda packages so the dependencies from the lock file need to be cross matched with the pypi-conda mapping. This however also gets updated at the moment of typing this. So lets not worry about this in the first PR.

It would be nice if you could also write some documentation. I'll do a technical review later!

@pavelzw
Copy link
Contributor

pavelzw commented Mar 28, 2024

In the help text

  -i, --invert                         Invert tree and show what depends on given package

I assume this will require a package/multiple packages as another argument, otherwise it will fail?

So maybe make it

  -i, --invert [PACKAGE]               Invert tree and show what depends on given package

@abkfenris
Copy link
Contributor Author

Having the direct dependencies styled is especially nice for inverted.

image

In the help text

  -i, --invert                         Invert tree and show what depends on given package

I assume this will require a package/multiple packages as another argument, otherwise it will fail?

So maybe make it

  -i, --invert [PACKAGE]               Invert tree and show what depends on given package

The package that invert is matching against comes from the optional positional argument. I added a note for that, and figured out how to make clap mark it as a dependency, but it doesn't show in quite that form.

pixi tree --help
Lock file usage from the CLI

Usage: pixi tree [OPTIONS] [REGEX]

Arguments:
  [REGEX]  List only packages matching a regular expression

Options:
      --platform <PLATFORM>            The platform to list packages for. Defaults to the current platform
      --manifest-path <MANIFEST_PATH>  The path to 'pixi.toml' [env: PIXI_PROJECT_MANIFEST=]
  -e, --environment <ENVIRONMENT>      The environment to list packages for. Defaults to the default environment
      --frozen                         Don't check or update the lockfile, continue with previously installed environment
                                       [env: PIXI_FROZEN=]
      --locked                         Check if lockfile is up to date, aborts when lockfile isn't up to date with the
                                       manifest file [env: PIXI_LOCKED=]
      --no-install                     Don't install the environment for pypi solving, only update the lock-file if it can
                                       solve without installing
  -i, --invert                         Invert tree and show what depends on given package in the regex argument
  -v, --verbose...                     Increase logging verbosity
  -q, --quiet...                       Decrease logging verbosity
      --color <COLOR>                  Whether the log needs to be colored [env: PIXI_COLOR=] [default: auto] [possible
                                       values: always, never, auto]
  -h, --help                           Print help

fixes: prefix-dev#238

Adds a tree command for the CLI that can print out the tree of dependencies based on those defined in pixi.toml. Additionally it can filter the regular dependency tree, or show what tree of packages need a given package.

It keeps track of visits for the regular tree, but currently does not for the inverted one.
@abkfenris
Copy link
Contributor Author

Ok, I went and quickly rebased it off of main to split it off from my tinkering with pixi shell.

Does not fully understand normalized names and markers
@abkfenris
Copy link
Contributor Author

I taught it how to love understand PyPI dependencies, and deal with versions when inverted.

image

I've also generally cleaned up the tree code, and refactored it to standardize functionality. I'm not however dealing with markers/extras, or always correctly figuring out normalized package names (xpublish-edr is a directly specified dependency, so it should be highlighted). For markers/extras, right now I'm just discarding those dependencies.

@abkfenris abkfenris marked this pull request as ready for review March 30, 2024 15:52
@abkfenris
Copy link
Contributor Author

I think the error with build-docs is the same one that is happening on most other runs, rather than an issue with something I changed. I can locally pixi run doc and pixi run build-docs without errors.

@pavelzw
Copy link
Contributor

pavelzw commented Mar 30, 2024

see #1074

@abkfenris
Copy link
Contributor Author

see #1074

Ah, I missed that.

Copy link
Contributor

@ruben-arts ruben-arts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, it already works pretty nicely! I left some ideas for improvement.

docs/cli.md Show resolved Hide resolved
docs/cli.md Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
docs/cli.md Outdated Show resolved Hide resolved
docs/cli.md Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
docs/cli.md Outdated Show resolved Hide resolved
Copy link
Contributor

@ruben-arts ruben-arts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, it already works pretty nicely! I left some ideas for improvement.

Copy link
Contributor

@baszalmstra baszalmstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR!

The code is not really handling the dependencies between conda and pypi properly (see PypiPackageIdentifier) but for a first implementation this LGTM.

I left a few small comments

src/cli/tree.rs Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
src/cli/tree.rs Outdated Show resolved Hide resolved
docs/cli.md Outdated Show resolved Hide resolved
docs/cli.md Outdated Show resolved Hide resolved
@abkfenris
Copy link
Contributor Author

Thanks for taking a look everyone.

The code is not really handling the dependencies between conda and pypi properly (see PypiPackageIdentifier) but for a first implementation this LGTM.

Is there an example that you can point me too? My messier test environment seems to be getting things right (Xarray from Conda, Xpublish* local PyPI).

image

@pavelzw
Copy link
Contributor

pavelzw commented Apr 3, 2024

I've had the chance to already play around with pixi tree in a real-world scenario where i needed to debug some dependencies, so this PR came exactly at the right time. Thanks @abkfenris 💜

What I noticed is that in some cases it doesn't behave as it should(?)

[project]
name = "tmp.yqbLiIBFp9"
channels = ["conda-forge"]
platforms = ["osx-arm64", "linux-64"]

[tasks]

[dependencies]
mlflow = "1.27.0"
❯ ../pixi-aarch64-apple-darwin list --platform linux-64 | grep pandas
pandas                     2.2.1         py310hcc13569_0      12.4 MiB    conda  pandas-2.2.1-py310hcc13569_0.conda
pandas-profiling           3.0.0         pyhd8ed1ab_0         165.3 KiB   conda  pandas-profiling-3.0.0-pyhd8ed1ab_0.tar.bz2

❯ ../pixi-aarch64-apple-darwin tree --platform linux-64 | grep pandas
T/tmp.yqbLiIBFp9/c 

❯ ../pixi-aarch64-apple-darwin tree --platform linux-64 -i pandas

pandas-profiling 3.0.0 

pandas 2.2.1 
├── seaborn-base 0.13.2 
│   ├── pandas-profiling 3.0.0 
│   └── seaborn 0.13.2 
├── pandas-profiling 3.0.0 
├── statsmodels 0.14.1 
│   └── seaborn 0.13.2 
├── phik 0.12.3 
│   └── pandas-profiling 3.0.0 
└── visions 0.7.1 
    └── pandas-profiling 3.0.0 

For some reason, pandas and pandas-profiling are not part of the tree 🤔

In pixi.lock, it's part of the depends block of mlflow...
image

@abkfenris
Copy link
Contributor Author

Hmm, I'd have to try with that environment to be sure, but have you tried the latest commit? I had some issues with normalization for a few which meant that things were getting dropped.

@pavelzw
Copy link
Contributor

pavelzw commented Apr 3, 2024

i thought that i used the latest commit but i'm not sure anymore... i'll try again after lunch 🙌🏻

@ruben-arts
Copy link
Contributor

I can reproduce it on my machine @pavelzw

@pavelzw
Copy link
Contributor

pavelzw commented Apr 3, 2024

I also just tested again with 3f237e9 (#1069) (using gh run download -R prefix-dev/pixi -n pixi-aarch64-apple-darwin 8528389447) and can still reproduce the issue.

@ruben-arts
Copy link
Contributor

All dependencies without a version in the "dependencies" of the package are missing from the tree. So also alembic etc. but it does include click

@abkfenris
Copy link
Contributor Author

All dependencies without a version in the "dependencies" of the package are missing from the tree. So also alembic etc. but it does include click

Thanks! I think I know right where that would be coming from. I'll tweak that and test it after I grab breakfast.

@ruben-arts
Copy link
Contributor

Ah sorry I now read this, but I hope you don't mind me fixing it. I also renamed parts of the function as it was hard to grasp the variables at first. Let me know what you think.

@abkfenris
Copy link
Contributor Author

Ah sorry I now read this, but I hope you don't mind me fixing it. I also renamed parts of the function as it was hard to grasp the variables at first. Let me know what you think.

That works for me. I don't have that great of a handle on all the possible iterators, so I was gonna slap an else block in the loop, so it's cool to see how it can be done more efficiently.

@abkfenris
Copy link
Contributor Author

I've had the chance to already play around with pixi tree in a real-world scenario where i needed to debug some dependencies, so this PR came exactly at the right time.

Talking about real-world scenarious, I ended up using tree on Friday to check if anything depended on a vulnerable version of xz.

@pavelzw
Copy link
Contributor

pavelzw commented Apr 3, 2024

xz from conda-forge did not ship the infected versions (at least the infections that we know of at this point in time) because the build ci was failing 😄

image

@pavelzw
Copy link
Contributor

pavelzw commented Apr 3, 2024

Another thing I stumbled upon:

when running pixi tree package and package is not explicitly mentioned in pixi.toml (i.e. is a transitive dependency from some other package), pixi fails with

  × No top level dependencies matched the given regular expression

The error message is good but there might be use cases where you want to only have a sub-tree of your whole tree.
(especially if you want to see all dependencies (without (*)) for a certain package that you didn't request manually)

not a deal-breaker though and i'm really happy how his turned out 🚀

@ruben-arts
Copy link
Contributor

ruben-arts commented Apr 3, 2024

@pavelzw that is a good point, lets get this in and fix that in a following PR, as I also can't wait for this PR.

@abkfenris
Copy link
Contributor Author

I took a quick swing at matching transitive dependencies, but I can push that to a new PR rather than hold this one on another round of debugging.

image

@ruben-arts ruben-arts merged commit 1f88603 into prefix-dev:main Apr 3, 2024
26 of 27 checks passed
@ruben-arts
Copy link
Contributor

Go ahead @abkfenris 👍

abkfenris added a commit to abkfenris/pixi that referenced this pull request Apr 3, 2024
When a regex pattern is given to `pixi tree` and a top level dependency isn't matched, it currently errors rather than showing any matching transitive dependencies.

This prints transitive dependencies in separate trees, similar to `pixi tree -i <package_name>`.

Builds on prefix-dev#1069
abkfenris added a commit to abkfenris/pixi that referenced this pull request Apr 3, 2024
When a regex pattern is given to `pixi tree` and a top level dependency isn't matched, it currently errors rather than showing any matching transitive dependencies.

This prints transitive dependencies in separate trees, similar to `pixi tree -i <package_name>`.

Builds on prefix-dev#1069
@abkfenris
Copy link
Contributor Author

Ok, draft of transitive matching over on #1111

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add pixi tree and pixi tree -i command
4 participants