Support deps with constraints based on package versions#37418
Support deps with constraints based on package versions#37418greenc-FNAL wants to merge 3 commits intospack:developfrom
Conversation
|
Can you add a section to the docs explaining how this would work, for example if you want to keep versions the same? Also, are there any potential use cases aside from version matching? Just want to keep things simple if possible. |
lib/spack/spack/directives.py
Outdated
|
|
||
|
|
||
| def _depends_on(pkg, spec, when=None, type=default_deptype, patches=None): | ||
| def pkg_depends_on(pkg, spec, when=None, type=default_deptype, patches=None): |
There was a problem hiding this comment.
Is this stuff related to one of your other PRs?
NP.
One might want to do something similar with variants, but Let me know if you'd rather I do that. |
|
If we also want to handle variants, should we change the name to something more generic? |
How about, Do bear in mind that it would become less simple to write a same-version constraint function, but we could provide a bridge function. |
|
Honestly the only thing I really need this for at the moment is for packages that require matching versions, so I would be fine with something even simpler. Curious what @alalazo thinks, this is right up his alley. |
|
@adamjstewart Please find an example implementation of the general case at https://github.com/greenc-FNAL/spack/tree/feature/depends_on-constraint-generator. An example depends_on("my_dep", ..., constraint_generator=version_constraint_generator())For a minor-version match: depends_on("my_dep", ..., constraint_generator=version_constraint_generator(lambda v: v.up_to(2)))A more general constraint generator would have to look something like: That is just a first pass. If you want something this general I can submit a draft PR to replace this one and we can iterate. Otherwise, we can go with this PR as-is and I can put tests and docs together ready for review. Please let me know. |
|
@adamjstewart Bump? |
aade3c8 to
1b73817
Compare
|
|
@spackbot fix style |
|
Let me see if I can fix that for you! |
|
I was able to run spack style --fix==> Running style checks on spack
selected: isort, black, flake8, mypy
==> Modified files
lib/spack/spack/directives.py
lib/spack/spack/test/directives.py
==> Running isort checks
isort checks were clean
==> Running black checks
reformatted lib/spack/spack/directives.py
All done! ✨ 🍰 ✨
1 file reformatted, 1 file left unchanged.
black checks were clean
==> Running flake8 checks
flake8 checks were clean
==> Running mypy checks
Success: no issues found in 577 source files
mypy checks were clean
==> spack style checks were clean
I've updated the branch with style fixes. |
888e9a8 to
537c939
Compare
|
@adamjstewart @scheibelp Bump? |
537c939 to
1fe636e
Compare
scheibelp
left a comment
There was a problem hiding this comment.
I have some preliminary remarks.
Also broadly speaking of the intent, this appears to generalize #14002, but my thought is: if the version_translator function becomes complex, it is not much different than defining a for-loop in the parent package, so I'm wondering what cases you expect it to be useful in.
lib/spack/spack/directives.py
Outdated
| # If we haven't already produced one or more version-specific | ||
| # dependencies, do the basic dependency for spec. | ||
| if not executed: | ||
| pkg_depends_on(pkg, spec, when=when, type=type, patches=patches) |
There was a problem hiding this comment.
I don't see why depends_on was renamed to _depends_on (this confusion persists for me after reading through #37380).
There was a problem hiding this comment.
depends_on wasn't renamed to _depends_on by me: _depends_on() is a private implementation function called by the depends_on directive. #37380 proposed renaming _depends_on() to pkg_depends_on() to make it public and accessible directly from recipes.
|
|
||
| # If we haven't already produced one or more version-specific | ||
| # dependencies, do the basic dependency for spec. | ||
| if not executed: |
There was a problem hiding this comment.
Remove executed variable: make this an else:
There was a problem hiding this comment.
That's not the logic of this function—if version_translator is defined, then a single depends_on() directive produces up to length(pkg.versions) distinct dependency relationships, depending on how many non-vacuous calls to version_translator(pkg_version) produce a constraint that actually alters the spec.
This number may be zero, in which case a "basic" dependency must be generated as it would be in the case where no version_translator is defined.
| version_ish = version_translator(pkg_version) | ||
| if version_ish is not None: | ||
| constrained_spec = spack.spec.Spec(spec) | ||
| if constrained_spec.constrain(f"@{version_ish}"): |
There was a problem hiding this comment.
This method always returns something or raises an exception, so if will not be the way to handle this.
Also, what is a version-ish? I'm thinking version_translator ought to be returning a Spec or a Version; the former if we want to attach a variant specification like +cuda to the dependencies that we define - although I'm not sure if that's necessary since we can separately say
# 1.0 needs [email protected], etc..
depends_on(child, version_translator=...)
# When we enable +variant, child should be built that way too
depends_on(child+variant, when=+variant)
Adding examples to the PR description of what you want to be able to write in a package.py would be useful, and we'd also want tests to enable this.
There was a problem hiding this comment.
I don't understand what you mean, here: "This method always returns ..."
The depends_on() directive here always returns the _execute_depends_on function. _execute_depends_on() always results in at least one execution of the _depends_on() function, just as did the pre-PR implementation.
version_translator()—if defined by the user—may return anything, but the intention is for it to return either None or something that converts to a string which is a valid version part of a version constraint (hence version_ish). This could be a valid Version instance (which could be a standard version, range, list, or any other valid version type), or it could be a string.
| pkg_depends_on( | ||
| pkg, | ||
| constrained_spec, | ||
| when=f"@{pkg_version} {when}" if when else f"@{pkg_version}", |
There was a problem hiding this comment.
This will need to constrain to @={pkg_version} rather than @{pkg_version}
There was a problem hiding this comment.
See #37418 (comment): version_translator() may not return a single, exact, version constraint.
lib/spack/spack/directives.py
Outdated
| ``str`` to be passed to ``patch``, or a list of these | ||
| version_translator (function): function to generate a version-ish for spec | ||
| from a parent version. If provided, this function will be called for | ||
| each version defined for pkg, and any non-vacuous result will be used to |
There was a problem hiding this comment.
this function will be called for each version defined for pkg
Imprecise wording, I suggest: the when will be applied for each parent version which yields a result when passed to version_translator.
|
I agree with @scheibelp, I think this could be made simpler unless there is a need to make it more generic. |
1fe636e to
d51e1dc
Compare
Tweak test for function name change Test directive enhancement
d51e1dc to
face9b1
Compare
|
@spackbot help |
|
You can interact with me in many ways!
I'll also help to label your pull request and assign reviewers! |
|
@spackbot run pipeline |
|
I'm sorry, gitlab does not have your latest revision yet, I can't run that pipeline for you right now. One likely possibility is that your PR pipeline has been temporarily deferred, in which case, it is awaiting a Please check the gitlab commit status message to see if more information is available. DetailsUnexpected response from gitlab: {'message': '404 Commit Not Found'} |
|
So not to jump in too late here, but what do people think of this syntax? depends_on("geant4-data@{my.version}", when="@10:")I am trying to think through the details, but in this model, Expressions could be more complex as @greenc-FNAL has demonstrated above: depends_on("geant4-data@{my.version.up_to(2)}", when="@10:")And you could use this on variants as well: depends_on("hdf5 mpi={my.variants['mpi'].value}")Or maybe more like our format strings: depends_on("hdf5 mpi={my.variants.mpi.value}")The final advantage I see here is that for certain spec attributes (e.g. variants) we may not know all possible values until we set up the solve (integer valued variants are an example). Using a syntax like this (vs. an imperative callback) would allow us to push the implementation down to ASP where needed, though we would probably start with something package-level. I think this could be made to work -- thinking through how best to explode the expression for different values. Thoughts? |
|
@tgamblin This seems like a great idea in principle. I'm a little worried about the possible difficulties of referring to variants (say) in the expression that may only exist conditionally. I also don't have any feel for exactly where this would be implemented and how it would reasonably integrate with the existing directive-handling logic or the ASP. |
|
Related to this, JAX has a slightly different constraint that I haven't seen before in other packages. Basically, every version of depends_on("py-jaxlib@:{my.version}", when="@{my.version}")May be a good test case for whichever solution we end up going with. |
Add optional
version_translatorfunction argument todepends_on()Closes #14002.
This allows for packages to require—with a single directive—a dependency to have version requirements that are related to the selected version of the parent. This may be an exact match to the parent's version, a major-minor version match, or any other reasonable relation.
For example: the
geant4-datapackage nominally defines a set of versioned dependencies on data packages which are required by thegeant4package whose version matches that ofgeant4-data. Prior to this PR, the following code is necessary in thegeant4recipe:With this PR, this could be replaced by the single line:
If one looks more closely at
geant4-data, one might discover that the datasets are generally the same within a minor version. This could be handled by using:with mostly only minor versions (
10.1,10.2, etc.) defined for thegeant4-datapackage and special cases handled by patch versions andconflicts()directives in thegeant4-datarecipe.