Add a new drop directive for extending packages#35045
Add a new drop directive for extending packages#35045
drop directive for extending packages#35045Conversation
When you inherit from a package in Spack, you inherit all the metadata from its
directives, including `version`, `provides`, `depends_on`, `conflicts`, etc.
In some cases, you may not want this metadata. This PR adds a new `disinherit` directive
that allows you to throw out things inherited from the base class. For example:
```python
from spack.pkg.builtin.mpich import Mpich
class MyMpich(Mpich):
disinherit("versions") # don't inherit any versions from builtin Mpich
version("5.0", "08721a102fefcea2ae4add8c9cc548df77e9224f5385ad0872a9150fdd26a415")
version("6.0", "9cc39dd33dd4227bb82301d285437588d705290846d22ab6b8791c7e631ce385")
```
Without the `disinherit("versions")` directive, this package would have versions `5.0`,
`6.0`, *and* anything inherited from `Mpich`. With it, this package has only versions
`5.0` and `6.0`.
You can `disinherit` any of: `conflicts`, `dependencies`, `extendees`, `patches`,
`provided`, `resources`, `variants`, or `versions`.
- [x] add new `disinherit directive`
- [x] Two packages were modifying their `versions` dictionary in their constructors to
achieve this, but this causes `spack url stats` to fail with a concurrent
modification exception as it iterates over all packages. Fixed these to use
`disinherit` instead.
- [x] Update documentation
- [x] Add tests
|
Would like to get some thoughts on this before it gets merged, so don't just merge it after one approval :) |
|
The change looks great. Thank you for the effort that you put into adding it. Coming to implementation, disinherit could accept predicate what to remove, but it would be also unnecessary complication. Current solution covers my use case and looks simple. So again, it looks good to me :) |
alalazo
left a comment
There was a problem hiding this comment.
I think this works very well for simple packages.
The current implementation though require users to duplicate code, if they want to get rid of e.g. a single variant. This is fine for small packages, less so if we want to remove a single variant, or conflict, from a package like trilinos.
Another consideration is that methods inherited from base classes might refer to entities that got pruned, and produce some inconsistent behavior if people don't carefully check the recipe.
I was thinking of adding: disinherit("versions", except=["1.0", "2.0", ...])and, e.g.: # keeps all provides("mpi...") (by matching the spec)
disinherit("provided", except="mpi")and: # get rid of any conflicts with hdf5 > 10
disinherit("conflicts", except="hdf5@:10")Of the things you could disinherit:
If that sounds good it wouldn't be hard to throw something together. |
|
This looks substantially like a duplicate of #33506
We definitely need to be able to disinherit patches, but I don't off the top of my head have a use-case for |
|
@becker33: Thanks -- I guess I missed #33506. Sorry @iarspider -- I didn't mean to step on your toes. Personally I like this better b/c it's a single directive, not a bunch of them, and I think we need to be able to drop all of something and also drop specific things with I would be curious what @iarspider thinks of this -- with the changes mentioned in #35045 (comment) would this work for you? I think I can bring over the filename/url naming for patches from #33506 and adapt @iarspider's test too. |
|
@tgamblin I would prefer the opposite syntax - |
|
@iarspider: We were thinking we'd go with your I don't mind doing both |
|
Sure. I don't care about the actual name.
…On Fri, Jan 20, 2023, 23:41 Todd Gamblin ***@***.***> wrote:
@iarspider <https://github.com/iarspider>: We were thinking we'd go with
your drop() over disinherit -- is that ok?
I don't mind doing both drop(..., only="") and drop(..., except="")... I
suspect someone will ask for both eventually.
—
Reply to this email directly, view it on GitHub
<#35045 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAE3NOQ46MHXHQFMOXRVTTLWTMH73ANCNFSM6AAAAAAUA453WU>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
disinherit directive for extending packagesdrop directive for extending packages
|
@tgamblin here is the use case for this that we discussed on our call earlier: On RHEL systems, RHEL provides special We want to be able to install e.g. The ideal interface for this use case would probably be something like: |
|
The use case that originally drew my interest to this PR does not require
I can submit a PR to loosen the constraints in
So it would be nice to inherit ideally, we could drop only specific dependencies and keep a subset of them, but for this use case it shouldn't be a major issue to rewrite the |
|
I have a couple of use cases where In that case I am using The other one is a from a user (can't find the PR right now). That user wanted to override for a package the list of supported |
I put in an implementation that allows for the removal of individual directives in addition to entire categories at #48947 as that has primarily been my use case. I often need to remove just one conflict when I'm inheriting a package. I didn't see this issue earlier so I was dumb and wrote a completely separate implementation. |
|
This pull request has been automatically marked as stale because it has not had |
|
This pull request was closed because it had no activity for 30 days after being marked stale. If you feel this is in error, please feel free to reopen this pull request. |
becker33
left a comment
There was a problem hiding this comment.
There are two levels of additions to this that I think are necessary, but I don't know that they need to be part of this PR.
- The use case to tighten a when constraint:
If we have a builtin package foo with some dependencies
class Foo(Package):
depends_on("bar", when="+extra_capabilities")
... # 5000 lines of additional dependencies
and a user discovers that bar is not actually necessary for +extra_capabilities after some version cut-off X.Y.Z. Then the user will want to drop the specific dependency on "bar" without having to recreate the rest of the dependencies, so that they can recreate the constrained bar dependencies.
from spack_repo.builtin.packages.foo.package import Foo as BuiltinFoo
class Foo(BuiltinFoo):
drop("dependencies", "bar")
depends_on("bar", when="@:X.Y +extra_capabilities")
- The further case that we've discussed more, regarding
whenclauses on the drop directive
I'm fully convinced that (2) should wait for a follow-on PR (if we determine it's necessary. But (1) is much more essential to the use case in my mind. It seems we would want to be able to drop:
- A particular version by its version string
- A conflict by its first parameter (arbitrarily, this one is less clear how it maps to user intuition than the others
- A dependency by either its name or by a matching spec (not sure which is better)
- An extension by the same as a dependency
- A provider by the same as a dependency (more likely name in this case)
- A patch by its name/url
- A variant by its name
- A resource by its name/url
- (no need for dropping a specific build_system, since it's implemented as a variant)
All of these seem reasonably easy to match on if we can settle on a clear interface and documentation of what the additional argument does in terms of constraining the drop.
The obvious next step of conditions/exceptions to the constraint is clearly a future work issue.
|
|
||
| .. code-block:: python | ||
|
|
||
| from spack.pkg.builtin.mpich import Mpich |
There was a problem hiding this comment.
| from spack.pkg.builtin.mpich import Mpich | |
| from spack_repo.builtin.packages.mpich.package import Mpich |
|
|
||
| .. code-block:: python | ||
|
|
||
| from spack.pkg.myrepo.my_package import MyPackage |
There was a problem hiding this comment.
| from spack.pkg.myrepo.my_package import MyPackage | |
| from spack_repo.myrepo.packages.my_package.package import MyPackage |
| version("1.0", "0209444070d9c8af9b62c94095a217e3bc6843692d1e3fdc1ff5371e03aac47c") | ||
| version("2.0", "5dda192154047d6296ba14a4ab2d869c6926fd7f44dce8ce94f63aae2e359c5b") | ||
|
|
||
| Every repository registered with Spack ends up in a submodule of ``spack.pkg`` with a |
There was a problem hiding this comment.
| Every repository registered with Spack ends up in a submodule of ``spack.pkg`` with a | |
| Every repository registered with Spack ends up in a submodule of ``spack_repo`` with a |
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| ``disinherit`` | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
|
||
| When you inherit from a package in Spack, you inherit all the metadata from its | ||
| directives, including ``version``, ``provides``, ``depends_on``, ``conflicts``, etc. For | ||
| example, ``NewPackage`` above will have four versions: ``1.0`` and ``2.0`` inherited | ||
| from ``MyPackage``, as well as, ``3.0``, and ``4.0`` defined in ``NewPackage``. | ||
|
|
||
| If you do not want your package to define all the same things as its base class, you can | ||
| use the ``disinherit`` directive to start fresh in your subclass: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| from spack.pkg.myrepo.my_package import MyPackage | ||
|
|
||
| class NewerPackage(MyPackage): | ||
| disinherit("versions") # don't inherit any versions from MyPackage | ||
| version("5.0", "08721a102fefcea2ae4add8c9cc548df77e9224f5385ad0872a9150fdd26a415") | ||
| version("6.0", "9cc39dd33dd4227bb82301d285437588d705290846d22ab6b8791c7e631ce385") | ||
|
|
||
| Now, ``NewerPackage`` will have **only** versions ``5.0`` and ``6.0``, and will not | ||
| inherit ``1.0`` or ``2.0`` from ``MyPackage``. You can ``disinherit`` many different | ||
| properties from base packages. The full list of options is: | ||
|
|
||
| * ``conflicts`` | ||
| * ``dependencies`` | ||
| * ``extendees`` | ||
| * ``patches`` | ||
| * ``provided`` | ||
| * ``resources`` | ||
| * ``variants`` | ||
| * ``versions`` |
There was a problem hiding this comment.
- This is out of date in the directive name.
- This talks about the composition of the versions as if no versions come from
mpich. - We should discuss an example where you only want to remove one version -- in that case, you have to remove them all and reimplement the ones you want to remove
|
|
||
|
|
||
| @directive() | ||
| def disinherit(dict_name: str): |
There was a problem hiding this comment.
We agreed to change this to drop
| assert "4.0" not in pkg.versions | ||
|
|
||
|
|
||
| def test_disinherit(config, mock_packages): |
There was a problem hiding this comment.
| def test_disinherit(config, mock_packages): | |
| def test_disinherit_invalid(config, mock_packages): |
| # SPDX-License-Identifier: (Apache-2.0 OR MIT) | ||
|
|
||
| from spack.package import * | ||
| from spack.pkg.builtin.mock.a import A |
There was a problem hiding this comment.
| from spack.pkg.builtin.mock.a import A | |
| from spack_repo.builtin_mock.packages.a.package import A |
|
This pull request has been automatically marked as stale because it has not had any activity in the last 6 months. It will be closed in 30 days if there is no further activity. If the pull request is waiting for a reply from reviewers, feel free to ping them as a reminder. If it is waiting and has no assigned reviewer, feel free to ping To get more eyes on your pull request, you can post a link in the #pull-requests channel of the Spack Slack. |
|
This is likely to be superseded by #48947 for v1.2. Closing in anticipation of that; we can reopen if it becomes interesting again. |
Closes #33506; related to #33057.
When you inherit from a package in Spack, you inherit all the metadata from its directives, including
version,provides,depends_on,conflicts, etc.In some cases, you may not want this metadata. This PR adds a new
dropdirective that allows you to throw out things inherited from the base class. For example:Without the
drop("versions")directive, this package would have versions5.0,6.0, and anything inherited fromMpich. With it, this package has only versions5.0and6.0.You can
dropany of:conflicts,dependencies,extendees,patches,provided,resources,variants, orversions.dropdirectiveversionsdictionary in their constructors to achieve this, but this causesspack url statsto fail with a concurrent modification exception as it iterates over all packages. Fixed these to usedropinstead.