8000 CEP for the next evolution of Repodata (v2) and MatchSpec by wolfv · Pull Request #111 · conda/ceps · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

CEP for the next evolution of Repodata (v2) and MatchSpec #111

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

wolfv
Copy link
Contributor
@wolfv wolfv commented Feb 5, 2025

This defines new repodata with two additional fields and a modfied matchspec syntax:

  • extra (optional) dependency sets that can be activated by the user or other packages
  • conditional dependencies to be activated on a specific platform, or when other package versions are also pulled in (e.g. require six; if python <3.8)
  • flags to make it easier to select variants confidently (instead of globbing the build string)

Some initial discussion happened over the past days on:

TODO:

  • specify that we need to understand full-blown matchspecs in the repodata.v2 (e.g. with bracket syntax), because we want other packages to be able to request extras or flags).
  • Decide whether flag negation should be ~foo, !foo, or -foo.
  • Decide if we need to add a default flags entry to the repodata record, or another way to mark the default variant (can also still use down-priorization via track records)
  • Investigate ordering of flags and whether we should have some weighing e.g. for numerical flags (higher == better)? Or wether this should be decided by another order integer that the users can compute.

@wolfv
Copy link
Contributor Author
wolfv commented Feb 5, 2025

Here are comments from the HackMD:

Should we consider a system like Spack has (https://spack.readthedocs.io/en/latest/basic_usage.html#specs-dependencies) or maybe Conan (https://docs.conan.io/2/reference/conanfile/attributes.html#options)?
Additionally, how would a package declare which flag is the default? (Edited)

For default flags I think we might want to deprecate the track_features field and instead add a priority: integer field that can hold any value. The user could mark the default this way. We could also use the inverse of priority, so that higher values are less desirable.

Or, maybe a better thought: we could also make it so that if priority: -1 we never enable this package, except if we request its flags directly.

Regarding Spack and Conan: I would be super hyped if you can distill this on how it could work for the conda ecosystem. I haven't had the time yet to read into it deeply. I am not a fan of super arbitrary code execution though.

- `release`: only packages with `release` flag are used
- `~release`: disallow packages with `release` flag
- `?release`: if release flag available, filter on it, otherwise use any other
- `gpu:*`: any flag starting with `gpu:` will be matched

Choose a reason for hiding this comment

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

can you add an example for the string matching for say blas:mkl

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would an exact match not work fine?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would an exact match not work fine?

Choose a reason for hiding this comment

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

It would, we should just state how to do an exact match

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You coudl just ask for flags = ["blas:mkl"] for an exact match :)


The proposed syntax is to extend the `MatchSpec` syntax by appending `; if <CONDITION>` after the current MatchSpec.

We would like to also allow for AND and OR with the following syntax:
10000 Copy link
Contributor

Choose a reason for hiding this comment

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

You probably need NOT and parentheses for precedence overrides, right?

Choose a reason for hiding this comment

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

In regular MatchSepc, we have , and | used for versions for and and or. I think we should keep thing similar, even if it means supporting and and or in versions.

Copy link
Contributor

Choose a reason for hiding this comment

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

If find this hard to distinguish when parsing the version. E.g.

python <3.8|>3.9 | numpy >=2.0
python <3.8|>3.9 or numpy >=2.0

I find the version with or easier to read than the one with pipes.

- six; if python <3.8
```

The proposed syntax is to extend the `MatchSpec` syntax by appending `; if <CONDITION>` after the current MatchSpec.
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice if we didn't need the ; because somehow conda will happily ignore the if parts while parsing MatchSpecs.

>>> from conda.models.match_spec import MatchSpec as M
>>> M("python 3.8 * if python")
MatchSpec("python==3.8[build=*]")
>>> M("python 3.8 'if' if  __win")  # quote 'if' to force parse it as a build string
MatchSpec("python==3.8='if'")

Copy link
Contributor

Choose a reason for hiding this comment

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

It will also ignore parenthesised blocks:

>>> M("python 3.8 * (__win)")
MatchSpec("python==3.8[build=*]")
>>> M("python 3.8 (__win)")
MatchSpec("python==3.8")
>>> M("python 3.8 (__win and __osx)")
MatchSpec("python==3.8")
>>> M("python 3.8 (if __win and __osx)")
MatchSpec("python==3.8")

Copy link
Contributor
@jaimergp jaimergp Mar 30, 2025

Choose a reason for hiding this comment

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

libmamba also ignores parentheses:

>>> from libmambapy.specs import MatchSpec as LibmambaMatchSpec
>>>print(LibmambaMatchSpec.parse("python 3.8 * (__win and __osx)"))
python==3.8
>>> print(LibmambaMatchSpec.parse("python 3.8 * (if __win and __osx)"))
python==3.8

Copy link
Contributor
@jaimergp jaimergp Mar 30, 2025

Choose a reason for hiding this comment

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

My suggestion would be to design the syntax like this:

name [version [build]] ('if' condition)

The if literal could be omitted, or replaced with with, if folks feel it's clearer that way. See discussion in #conda-maintainers > Conditional dependencies syntax in v2 environments & recipes @ 💬.

Choose a reason for hiding this comment

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

Another idea (not sure if a good one) could be: rather than make MatchSpecs more complex than they already are, build on the idea of selectors from the new recipe format and allow depends to contain objects like so:

depends:
 - python >=3.8
 - if: python <3.8
    then: six

In the recipe format, this is expressed via a variation on the selector to avoid being process at build time (if(run): for instance).

This disallow basically disallow conditionals outside of recipes (or format with selector), but makes a more consistent narrative around conditionals.

Copy link
Contributor

Choose a reason for hiding this comment

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

The idea was indeed also posted on zulip. One issue is that it then becomes harder for cli tools to adopt.

Copy link
Contributor

Choose a reason for hiding this comment

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

I propose we use the when syntax without a semicolon to force an error on older versions of conda that dont support it and the distinguish between the already established if syntax in recipe v1. We can use the same keyword in a more expanded form as the "build spec"

foobar when python >=3.8

E.g. in a recipe:

- if: unix
  then: foobar
  when: python >=3.8
  
# OR
  
- when: python >=3.8
  then: foobar
 
# OR 
  
- "foobar when python >=3.8"


## Conditional dependencies

Conditional dependencies are activated when the condition is true. The most straight-forward conditions are `__unix`, `__win` and other platform specifiers. However, we would also like to support matchspecs in conditions such as `python >=3`.

Choose a reason for hiding this comment

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

I very much like this idea. An implementation note: while if __unix is reasonably easy to implement because it is "static", if python <3.8 is conceptually much harder as it is not something that can be decided ahead of solving. I requires to be able to adapt the dependencies of a package as partial candidates are investigated during solve.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, this is a form of boolean dependencies in rpm (already supported by libsolv I believe)

Copy link
Contributor

Choose a reason for hiding this comment

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

This is the PR that implements this for resolvo: prefix-dev/resolvo#136


However, it would be nice if we could have a flexible, powerful and simple syntax to enable or disable "flags" on packages in order to select a variant.

A RepodataRecord should get a new field "flags" that is a list of strings, such as:

Choose a reason for hiding this comment

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

How do flags and extra mix and overlap? Wouldn't conditional dependencies on a flag be enough to generate the extra category?

Copy link
Contributor

Choose a reason for hiding this comment

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

Flags are part of a variant. So there is no variation of flags for a single variant. E.g. flags could be used to say a particular variant is using cuda and another is used to target cpu. Extras are a way to select additional dependencies for a particular variant. If a variant also adds a CLI tool it provides the extra "cli". Only if that extra is requested by another package are particular dependencies also requested.

In technical terms extras can indeed be implemented as conditional dependencies. E.g. for a package my_package we could express it as typer when my_package[cli]. If there is a package that depends on my_package[cli] typer would also be required.


The proposed syntax is to extend the `MatchSpec` syntax by appending `; if <CONDITION>` after the current MatchSpec.

We would like to also allow for AND and OR with the following syntax:
Copy link
Contributor

Choose a reason for hiding this comment

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

If find this hard to distinguish when parsing the version. E.g.

python <3.8|>3.9 | numpy >=2.0
python <3.8|>3.9 or numpy >=2.0

I find the version with or easier to read than the one with pipes.

- six; if python <3.8
```

The proposed syntax is to extend the `MatchSpec` syntax by appending `; if <CONDITION>` after the current MatchSpec.
Copy link
Contributor

Choose a reason for hiding this comment

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

I propose we use the when syntax without a semicolon to force an error on older versions of conda that dont support it and the distinguish between the already established if syntax in recipe v1. We can use the same keyword in a more expanded form as the "build spec"

foobar when python >=3.8

E.g. in a recipe:

- if: unix
  then: foobar
  when: python >=3.8
  
# OR
  
- when: python >=3.8
  then: foobar
 
# OR 
  
- "foobar when python >=3.8"


However, it would be nice if we could have a flexible, powerful and simple syntax to enable or disable "flags" on packages in order to select a variant.

A RepodataRecord should get a new field "flags" that is a list of strings, such as:
Copy link
Contributor

Choose a reason for hiding this comment

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

Flags are part of a variant. So there is no variation of flags for a single variant. E.g. flags could be used to say a particular variant is using cuda and another is used to target cpu. Extras are a way to select additional dependencies for a particular variant. If a variant also adds a CLI tool it provides the extra "cli". Only if that extra is requested by another package are particular dependencies also requested.

In technical terms extras can indeed be implemented as conditional dependencies. E.g. for a package my_package we could express it as typer when my_package[cli]. If there is a package that depends on my_package[cli] typer would also be required.


## Conditional dependencies

Conditional dependencies are activated when the condition is true. The most straight-forward conditions are `__unix`, `__win` and other platform specifiers. However, we would also like to support matchspecs in conditions such as `python >=3`.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the PR that implements this for resolvo: prefix-dev/resolvo#136

- pyxpgres >=8
```

When a user, or a dependency, selects an extra through a MatchSpec, the extra and it's dependencies are "activated". This is conceptually the same as having three packages with "exact" dependencies from the "extra" to the base package: `sqlalchemy`, `sqlalchemy-sqlite` and `sqlalchemy-postgres` – which is the workaround currently employed by a number of packages on conda-forge.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should clarify what happens if an extra is requested for a package but the selected variant doesnt provide that extra. E.g. what happens if I depend on a foobar[extras=["doesntexist"]]

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I missed it but its also not defined how to depend on an extra?

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.

5 participants
0