Skip to content

concretizer: use only attr() for Spec attributes#31202

Merged
alalazo merged 1 commit intodevelopfrom
attr-encoding
Dec 2, 2022
Merged

concretizer: use only attr() for Spec attributes#31202
alalazo merged 1 commit intodevelopfrom
attr-encoding

Conversation

@tgamblin
Copy link
Copy Markdown
Member

@tgamblin tgamblin commented Jun 21, 2022

concretizer: use only attr() for Spec attributes

All Spec attributes are now represented as attr(attribute_name, ... args ...), e.g.
attr(node, "hdf5") instead of node("hdf5"), as we have to maintain the attr()
form anyway, and it simplifies the encoding to just maintain one form of the Spec
information.

Background

In #20644, we unified the way conditionals are done in the concretizer, but this
introduced a nasty aspect to the encoding: we have to maintain everything we want in
general conditions in two forms: predicate(...) and attr("predicate", ...). For
example, here's the start of the table of spec attributes we had to maintain:

node(Package)                      :- attr("node", Package).
virtual_node(Virtual)              :- attr("virtual_node", Virtual).
hash(Package, Hash)                :- attr("hash", Package, Hash).
version(Package, Version)          :- attr("version", Package, Version).
...
attr("node", Package)              :- node(Package).
attr("virtual_node", Virtual)      :- virtual_node(Virtual).
attr("hash", Package, Hash)        :- hash(Package, Hash).
attr("version", Package, Version)  :- version(Package, Version).
...

This adds cognitive load to understanding how the concretizer works, as you have to
understand the equivalence between the two forms of spec attributes. It also makes the
general condition logic in #20644 hard to explain, and it's easy to forget to add a new
equivalence to this list when adding new spec attributes (at least two people have been
bitten by this).

Solution

  • remove the equivalence list from concretize.lp
  • simplify spec_clauses(), condition(), and other functions in asp.py that need
    to deal with Spec attributes.
  • Convert all old-form spec attributes in concretize.lp to the attr() form
  • Simplify display.lp, where we also had to maintain a list of spec attributes. Now
    we only need to show attr/2, attr/3, and attr/4.
  • Simplify model extraction logic in asp.py.

Performance

This seems to result in a smaller grounded problem (as there are no longer duplicated
attr("foo", ...) / foo(...) predicates in the program), but it also adds a slight
performance overhead vs. develop. Ultimately, simplifying the encoding will be a win,
particularly for improving error messages.

Notes

This will simplify future node refactors in concretize.lp (e.g., not identifying nodes
by package name, which we need for separate build dependencies).

I'm still not entirely used to reading attr() notation, but I thnk it's ultimately
clearer than what we did before. We need more uniform naming, and it's now clear what is
part of a solution. We should probably continue making the encoding of concretize.lp
simpler and more self-explanatory. It may make sense to rename attr to something like
node_attr and to simplify the names of node attributes. It also might make sense to do
something similar for other types of predicates in concretize.lp.

@trws
Copy link
Copy Markdown
Contributor

trws commented Jun 22, 2022

This looks really good to me, I took a relatively close pass over it and found no issues at all past what @becker33 already mentioned.

@tgamblin tgamblin force-pushed the attr-encoding branch 4 times, most recently from 784b292 to 9f1ec6b Compare June 24, 2022 07:10
Copy link
Copy Markdown
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

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

It seems there are tests that need their assertion slightly changed:

        # ensure that variant diffs are in here the result
>       assert ['variant_value', 'mpileaks debug False'] in c['a_not_b']
E       AssertionError: assert ['variant_value', 'mpileaks debug False'] in [['attr', 'hash mpileaks qek6lz5qlm7tgckn5j3pcnlxxlj57nqe'], ['attr', 'variant_value mpileaks debug False']]

@alalazo alalazo self-assigned this Jun 30, 2022
@spackbot-app spackbot-app bot added the core PR affects Spack core functionality label Nov 25, 2022
@tgamblin tgamblin force-pushed the attr-encoding branch 3 times, most recently from b4de145 to 419c050 Compare November 27, 2022 19:25
@tgamblin tgamblin force-pushed the attr-encoding branch 2 times, most recently from b719fe2 to 1030f8d Compare November 28, 2022 01:08
@tgamblin tgamblin requested review from alalazo and becker33 November 28, 2022 01:17
@tgamblin tgamblin force-pushed the attr-encoding branch 2 times, most recently from 34feb96 to 61c6b74 Compare November 28, 2022 06:53
@tgamblin tgamblin added this to the v0.20.0 milestone Nov 28, 2022
Comment on lines -2229 to -2250
if name == "error":
priority = function_tuple[1][0]
return (-5, priority)
elif name == "hash":
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to be restored for error messages to work properly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment on lines -2094 to -2121
def error(self, priority, msg, *args):
msg = msg.format(*args)

# For variant formatting, we sometimes have to construct specs
# to format values properly. Find/replace all occurances of
# Spec(...) with the string representation of the spec mentioned
specs_to_construct = re.findall(r"Spec\(([^)]*)\)", msg)
for spec_str in specs_to_construct:
msg = msg.replace("Spec(%s)" % spec_str, str(spack.spec.Spec(spec_str)))
raise UnsatisfiableSpecError(msg)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to be restored, looks like a rebase issue that removed it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

#defined virtual_node/1.
#defined virtual_root/1.
#defined virtual_condition_holds/2.
#defined external/1.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't external be an attr, since it's something we use in the reconstruction of the spec?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't see where we have a handler for it. We have a handle for external_spec_selected, and attr("external_spec_selected", Package, LocalIndex) is used to get the information back to the solver. That was previously in display.lp and is used by SpecBuilder, but external/1 is just input metadata.

Comment on lines +13 to +15
#show attr/2.
#show attr/3.
#show attr/4.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there any way to print only attr/N atoms which match a certain first argument? Before in complex debugging situations we could comment all but a few atoms. I wonder if now, that are all aggregated under the same name, we can do something similar.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unfortunately, there isn't.

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Nov 29, 2022

I timed the solve on this list of specs:

and obtained the following results:

Plotting an histogram in seconds of the various phases, on this branch:
radiuss_attr

while on develop:

radiuss_develop

Copy link
Copy Markdown
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

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

Only minor comments to the code. I'll try to check clingo internal stats next, to see if I can make any sense of them 🤯

node_flag = fn.node_flag
node_flag_propagate = fn.node_flag_propagate
variant_propagate = fn.variant_propagate
node = fn.attr("node")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wondering if using enum to collect all attr is a good idea or not 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not all attr need to be herein spec_clauses. There are internal solver attributes that are also passed through our general conditions that need to be attr's but we don't want them to be listed here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sure. I meant if we should enumerate all the literal strings we use in attr and use the enum throughout this entire file (instead of repeating the string everywhere).

becker33
becker33 previously approved these changes Nov 30, 2022
@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Nov 30, 2022

@alalazo the performance numbers you reported are between 15 - 32% overhead for grounding vs. develop which is weird for an encoding that's logically equivalent to what we had before. I'll be interested to see what you come up with -- might also dig into the numbers myself. Let's not merge until we decide whether this is acceptable or whether it can be mitigated.

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Nov 30, 2022

I'm attaching a tarball radiuss.tar.gz with some analysis I'm trying to do to get insight on the timing. All the numbers (atoms, choices, etc.) seem slightly lower in this new encoding, as we expect. The only one that is higher is "Problems".

Using:

clingo --stats=2 --configuration=tweety --opt-strategy=usc,one concretize.lp os_compatibility.lp display.lp lbann.lp

I obtain the following statistics on develop:

Optimization: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 32 27 0 0 0
OPTIMUM FOUND

Models       : 10
  Optimum    : yes
Optimization : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 32 27 0 0 0
Calls        : 1
Time         : 17.686s (Solving: 6.79s 1st Model: 3.51s Unsat: 0.00s)
CPU Time     : 17.663s

Choices      : 112092  
Conflicts    : 658      (Analyzed: 652)
Restarts     : 5        (Average: 130.40 Last: 1)
Model-Level  : 22617.0 
Problems     : 7        (Average Length: 0.00 Splits: 0)
Lemmas       : 8839     (Deleted: 0)
  Binary     : 3750     (Ratio:  42.43%)
  Ternary    : 1064     (Ratio:  12.04%)
  Conflict   : 652      (Average Length:   12.1 Ratio:   7.38%) 
  Loop       : 8187     (Average Length:   21.4 Ratio:  92.62%) 
  Other      : 0        (Average Length:    0.0 Ratio:   0.00%) 
Backjumps    : 652      (Average: 3510.83 Max: 192904 Sum: 2289062)
  Executed   : 606      (Average: 138.83 Max: 192904 Sum:  90520 Ratio:   3.95%)
  Bounded    : 46       (Average: 47794.39 Max: 190267 Sum: 2198542 Ratio:  96.05%)

Rules        : 2239567  (Original: 2179418)
  Choice     : 60586   
  Minimize   : 39      
  Heuristic  : 5876     (Original: 5878)
Atoms        : 953741  
Bodies       : 1697374  (Original: 1703260)
  Count      : 0        (Original: 5970)
Equivalences : 2117871  (Atom=Atom: 206153 Body=Body: 363106 Other: 1548612)
Tight        : No       (SCCs: 1810 Non-Hcfs: 0 Nodes: 237176 Gammas: 0)
Variables    : 1200500  (Eliminated:    0 Frozen: 454445)
Constraints  : 4951653  (Binary:  73.4% Ternary:  17.3% Other:   9.3%)

and the following from this PR:

Optimization: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 32 27 0 0 0
OPTIMUM FOUND

Models       : 9
  Optimum    : yes
Optimization : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 32 27 0 0 0
Calls        : 1
Time         : 18.986s (Solving: 6.63s 1st Model: 3.69s Unsat: 0.02s)
CPU Time     : 18.904s

Choices      : 107824  
Conflicts    : 591      (Analyzed: 582)
Restarts     : 4        (Average: 145.50 Last: 0)
Model-Level  : 25502.3 
Problems     : 10       (Average Length: 0.00 Splits: 0)
Lemmas       : 8512     (Deleted: 0)
  Binary     : 3517     (Ratio:  41.32%)
  Ternary    : 985      (Ratio:  11.57%)
  Conflict   : 582      (Average Length:   16.0 Ratio:   6.84%) 
  Loop       : 7930     (Average Length:   21.3 Ratio:  93.16%) 
  Other      : 0        (Average Length:    0.0 Ratio:   0.00%) 
Backjumps    : 582      (Average: 4496.39 Max: 193116 Sum: 2616898)
  Executed   : 528      (Average: 142.67 Max: 193116 Sum:  83034 Ratio:   3.17%)
  Bounded    : 54       (Average: 46923.41 Max: 189627 Sum: 2533864 Ratio:  96.83%)

Rules        : 2137286  (Original: 2077137)
  Choice     : 60586   
  Minimize   : 39      
  Heuristic  : 5876     (Original: 5878)
Atoms        : 906889  
Bodies       : 1639341  (Original: 1645223)
  Count      : 0        (Original: 5970)
Equivalences : 1973868  (Atom=Atom: 155585 Body=Body: 361218 Other: 1457065)
Tight        : No       (SCCs: 1695 Non-Hcfs: 0 Nodes: 234103 Gammas: 0)
Variables    : 1198930  (Eliminated:    0 Frozen: 452653)
Constraints  : 4942471  (Binary:  73.4% Ternary:  17.4% Other:   9.3%)

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Nov 30, 2022

I think this line might be interesting:

# develop
Conflict   : 652      (Average Length:   12.1 Ratio:   7.38%) 

# This PR
Conflict   : 582      (Average Length:   16.0 Ratio:   6.84%) 

which suggest to me that it takes more chaining of rules in this PR to find a conflict and backtrack.

All Spec attributes are now represented as `attr(attribute_name, ... args ...)`, e.g.
`attr(node, "hdf5")` instead of `node("hdf5")`, as we *have* to maintain the `attr()`
form anyway, and it simplifies the encoding to just maintain one form of the Spec
information.

Background
----------

In #20644, we unified the way conditionals are done in the concretizer, but this
introduced a nasty aspect to the encoding: we have to maintain everything we want in
general conditions in two forms: `predicate(...)` and `attr("predicate", ...)`. For
example, here's the start of the table of spec attributes we had to maintain:

```prolog
node(Package)                      :- attr("node", Package).
virtual_node(Virtual)              :- attr("virtual_node", Virtual).
hash(Package, Hash)                :- attr("hash", Package, Hash).
version(Package, Version)          :- attr("version", Package, Version).
...
```

```prolog
attr("node", Package)              :- node(Package).
attr("virtual_node", Virtual)      :- virtual_node(Virtual).
attr("hash", Package, Hash)        :- hash(Package, Hash).
attr("version", Package, Version)  :- version(Package, Version).
...
```

This adds cognitive load to understanding how the concretizer works, as you have to
understand the equivalence between the two forms of spec attributes. It also makes the
general condition logic in #20644 hard to explain, and it's easy to forget to add a new
equivalence to this list when adding new spec attributes (at least two people have been
bitten by this).

Solution
--------

- [x] remove the equivalence list from `concretize.lp`
- [x] simplify `spec_clauses()`, `condition()`, and other functions in `asp.py` that need
      to deal with `Spec` attributes.
- [x] Convert all old-form spec attributes in `concretize.lp` to the `attr()` form
- [x] Simplify `display.lp`, where we also had to maintain a list of spec attributes. Now
      we only need to show `attr/2`, `attr/3`, and `attr/4`.
- [x] Simplify model extraction logic in `asp.py`.

Performance
-----------

This seems to result in a smaller grounded problem (as there are no longer duplicated
`attr("foo", ...)` / `foo(...)` predicates in the program), but it also adds a slight
performance overhead vs. develop. Ultimately, simplifying the encoding will be a win,
particularly for improving error messages.

Notes
-----

This will simplify future node refactors in `concretize.lp` (e.g., not identifying nodes
by package name, which we need for separate build dependencies).

I'm still not entirely used to reading `attr()` notation, but I thnk it's ultimately
clearer than what we did before. We need more uniform naming, and it's now clear what is
part of a solution. We should probably continue making the encoding of `concretize.lp`
simpler and more self-explanatory. It may make sense to rename `attr` to something like
`node_attr` and to simplify the names of node attributes. It also might make sense to do
something similar for other types of predicates in `concretize.lp`.
@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Dec 2, 2022

I'm a little confused because on my M1 I see basically no difference in timing (for hdf5 and ascent), and if I output the ASP and run it through clingo on the CLI, the attr-encoding is actually somewhat faster (at least with the timings shown for --outf=1).

One thought: are you running with a bootstrapped clingo or a more recent one that you've installed? Curious if you see the same performance degradation with a more recent clingo. I'm using 5.5.2, and it's a spack-built 5.5.2. We should maybe think about bootstrapping a more recent one for develop now that we are no longer supporting Python 2.7.

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Dec 2, 2022

I got the stats in #31202 (comment) using clingo 5.5.2 and they show a degradation of less than 10%. The plots have been done with the binaries we provide, so last version of clingo supporting Python 2.7

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Dec 2, 2022

Also, fwiw the details on my setup are:

  • Spack: 0.20.0.dev0 (65a5369)
  • Python: 3.8.10
  • Platform: linux-ubuntu20.04-icelake
  • Concretizer: clingo

so icelake instead of m1.

@alalazo alalazo dismissed their stale review December 2, 2022 11:30

Review addressed

Copy link
Copy Markdown
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

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

The average slowdown from the plots in #31202 (comment) is 5.85% - so I think it's acceptable even though I couldn't figure out the exact cause of it 😟

@tgamblin
Copy link
Copy Markdown
Member Author

tgamblin commented Dec 2, 2022

Ok looking at this again -- the total overhead vs. develop with older, bootstrapped clingo is ~9%, which is not a whole lot. I think this is not a huge concern given the simplification in the encoding. Considering that new clingo seems to be faster with 5.85% overhead I think it's even less of a worry.

I really wish I knew where the differences are coming from.

We should probably start updating the bootstrap binaries to newer clingo now that we require Python 3.6

@alalazo alalazo merged commit 8756204 into develop Dec 2, 2022
@alalazo alalazo deleted the attr-encoding branch December 2, 2022 17:56
luke-dt pushed a commit to dantaslab/spack that referenced this pull request Dec 5, 2022
All Spec attributes are now represented as `attr(attribute_name, ... args ...)`, e.g.
`attr(node, "hdf5")` instead of `node("hdf5")`, as we *have* to maintain the `attr()`
form anyway, and it simplifies the encoding to just maintain one form of the Spec
information.

Background
----------

In spack#20644, we unified the way conditionals are done in the concretizer, but this
introduced a nasty aspect to the encoding: we have to maintain everything we want in
general conditions in two forms: `predicate(...)` and `attr("predicate", ...)`. For
example, here's the start of the table of spec attributes we had to maintain:

```prolog
node(Package)                      :- attr("node", Package).
virtual_node(Virtual)              :- attr("virtual_node", Virtual).
hash(Package, Hash)                :- attr("hash", Package, Hash).
version(Package, Version)          :- attr("version", Package, Version).
...
```

```prolog
attr("node", Package)              :- node(Package).
attr("virtual_node", Virtual)      :- virtual_node(Virtual).
attr("hash", Package, Hash)        :- hash(Package, Hash).
attr("version", Package, Version)  :- version(Package, Version).
...
```

This adds cognitive load to understanding how the concretizer works, as you have to
understand the equivalence between the two forms of spec attributes. It also makes the
general condition logic in spack#20644 hard to explain, and it's easy to forget to add a new
equivalence to this list when adding new spec attributes (at least two people have been
bitten by this).

Solution
--------

- [x] remove the equivalence list from `concretize.lp`
- [x] simplify `spec_clauses()`, `condition()`, and other functions in `asp.py` that need
      to deal with `Spec` attributes.
- [x] Convert all old-form spec attributes in `concretize.lp` to the `attr()` form
- [x] Simplify `display.lp`, where we also had to maintain a list of spec attributes. Now
      we only need to show `attr/2`, `attr/3`, and `attr/4`.
- [x] Simplify model extraction logic in `asp.py`.

Performance
-----------

This seems to result in a smaller grounded problem (as there are no longer duplicated
`attr("foo", ...)` / `foo(...)` predicates in the program), but it also adds a slight
performance overhead vs. develop. Ultimately, simplifying the encoding will be a win,
particularly for improving error messages.

Notes
-----

This will simplify future node refactors in `concretize.lp` (e.g., not identifying nodes
by package name, which we need for separate build dependencies).

I'm still not entirely used to reading `attr()` notation, but I thnk it's ultimately
clearer than what we did before. We need more uniform naming, and it's now clear what is
part of a solution. We should probably continue making the encoding of `concretize.lp`
simpler and more self-explanatory. It may make sense to rename `attr` to something like
`node_attr` and to simplify the names of node attributes. It also might make sense to do
something similar for other types of predicates in `concretize.lp`.
amd-toolchain-support pushed a commit to amd-toolchain-support/spack that referenced this pull request Feb 16, 2023
All Spec attributes are now represented as `attr(attribute_name, ... args ...)`, e.g.
`attr(node, "hdf5")` instead of `node("hdf5")`, as we *have* to maintain the `attr()`
form anyway, and it simplifies the encoding to just maintain one form of the Spec
information.

Background
----------

In spack#20644, we unified the way conditionals are done in the concretizer, but this
introduced a nasty aspect to the encoding: we have to maintain everything we want in
general conditions in two forms: `predicate(...)` and `attr("predicate", ...)`. For
example, here's the start of the table of spec attributes we had to maintain:

```prolog
node(Package)                      :- attr("node", Package).
virtual_node(Virtual)              :- attr("virtual_node", Virtual).
hash(Package, Hash)                :- attr("hash", Package, Hash).
version(Package, Version)          :- attr("version", Package, Version).
...
```

```prolog
attr("node", Package)              :- node(Package).
attr("virtual_node", Virtual)      :- virtual_node(Virtual).
attr("hash", Package, Hash)        :- hash(Package, Hash).
attr("version", Package, Version)  :- version(Package, Version).
...
```

This adds cognitive load to understanding how the concretizer works, as you have to
understand the equivalence between the two forms of spec attributes. It also makes the
general condition logic in spack#20644 hard to explain, and it's easy to forget to add a new
equivalence to this list when adding new spec attributes (at least two people have been
bitten by this).

Solution
--------

- [x] remove the equivalence list from `concretize.lp`
- [x] simplify `spec_clauses()`, `condition()`, and other functions in `asp.py` that need
      to deal with `Spec` attributes.
- [x] Convert all old-form spec attributes in `concretize.lp` to the `attr()` form
- [x] Simplify `display.lp`, where we also had to maintain a list of spec attributes. Now
      we only need to show `attr/2`, `attr/3`, and `attr/4`.
- [x] Simplify model extraction logic in `asp.py`.

Performance
-----------

This seems to result in a smaller grounded problem (as there are no longer duplicated
`attr("foo", ...)` / `foo(...)` predicates in the program), but it also adds a slight
performance overhead vs. develop. Ultimately, simplifying the encoding will be a win,
particularly for improving error messages.

Notes
-----

This will simplify future node refactors in `concretize.lp` (e.g., not identifying nodes
by package name, which we need for separate build dependencies).

I'm still not entirely used to reading `attr()` notation, but I thnk it's ultimately
clearer than what we did before. We need more uniform naming, and it's now clear what is
part of a solution. We should probably continue making the encoding of `concretize.lp`
simpler and more self-explanatory. It may make sense to rename `attr` to something like
`node_attr` and to simplify the names of node attributes. It also might make sense to do
something similar for other types of predicates in `concretize.lp`.
@alalazo
Copy link
Copy Markdown
Member

alalazo commented Mar 27, 2023

develop (10d10b6, blue) vs. PR (8756204, orange)

radiuss_develop.csv
radiuss_31202.csv
radiuss.txt

totals

There seems to be a small slowdown in solve and ground, comparable to what reported in #31202 (review)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands core PR affects Spack core functionality dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants