a proposal for how to represent virtual dependencies in the spec syntax #20256
Replies: 14 comments
-
attempting to diagram "Spec v2": [provides] (pkg@ver) [requires => same members and grammar as the current compiler section (after %)],
but with [...] groups!
___V_ V ________V__________
[%py]python@2.6.*[%py@<3 type=build] => SPEC-NODE
^
desugars to python@2.6:2.6.999
alternatively (same):
[%py]python@2.6.*%py@<3+build
that's a SPEC-NODE, what's a SPEC?
SPEC = {
SPEC-NODE |
SPEC '^' SPEC
}
[...] are not just for decoration: these are the objects that can be grouped:
[%py], [%py@<3], [%py@<3+a~b], [%py+a~b], [+a~b], [+a], [~b], [%py type=[build]]
[python], [python@2.6.*], [2.6.*], [2], [6.*], ...
also, everything that can be grouped with [...] can be negated with [!...] !
this would correspond to a `conflicts()` directive. in class Python(AutotoolsPackage):
...
# "I, the python package, will build a python
# which will add artifacts to the spack environment before spack runs all of these phases"
provides('%py+build+link+run') # this is actually the default value of those variants when using provides()
add('[%py+build+link+run]') # add() adds a parsed constraint to the model -- this is what the above becomes
# only one provider of %py (and therefore only one version) can be registered during any phase:
depends_on('%py@<3+build') # default here too is +build+link+run if *none* are specified
# -- but if any *are* specified, others are assumed to be false (~link~run, in this case) ^note the most recent expression above: if this is the desired behavior (to assume in class ResourceDependency(object):
...
variant('type', default=','.join(self.phases), description='...') one thing i really like about this approach (the "requires" and "provides" registration -- i think @tgamblin described wanting something like this in a github comment using different words) is that it makes it a lot easier to think about a spec as a conjunction of logical expressions (which is already what we're doing with the ASP solver anyway), where one can imagine the requires "imply" the the specified outputs are available. |
Beta Was this translation helpful? Give feedback.
-
I also think that the existing dependency operator
and it works (and it's linear) because specs just get flattened by name -- there is no recursion necessary to merge two |
Beta Was this translation helpful? Give feedback.
-
Ok, I think I now have a strongly reduced version of the first draft in the OP: Problems
|
Beta Was this translation helpful? Give feedback.
-
Much more succinct version, in order of expected completion: 1. [#20258] Support pip requirements.txt version comparators and wildcards.
2. [TODO] Differentiate virtual dependencies from normal dependencies by pooling them in a new section of Spec syntax.
Changes to Spec Syntax:
3. [ALREADY PLANNED] Use virtual dependencies to represent language frameworks e.g. with
|
Beta Was this translation helpful? Give feedback.
-
Reading through #20256 (comment), adding a few comments for discussion:
Currently we can do this:
since the compiler is a node attribute. The limitations we have are that:
The general idea going forward is that by:
we can improve on the two limitations above and we can also start modeling language runtimes. #20127 is a bug with the new concretizer that I need to fix rather sooner than later. The background for that is that our concretizers can currently deal with tree-like DAGs i.e. there needs to be a single root node - we can't at the moment concretize a "forest" with different roots. When we added Spack environments as a feature there was an increasing request to be able to specify specs in a list and concretize them in a way that unifies all the constraints so that the resulting installations were compatible with each other and could be merged in the same view. The solution we came up with was to emulate this concretization by making all the user specs direct dependencies of a mock root that exists only as an implementation detail. See: spack/lib/spack/spack/concretize.py Lines 677 to 686 in 1681c19 This is currently broken for the ASP-based solver if the spec contains a |
Beta Was this translation helpful? Give feedback.
-
Concerning #15569, the proposal for the extension of the syntax after the merge of #15256 (that introduces the use of anonymous specs like
Right now we have only one type of edge attribute, the dependency type, that is set implicitly during the concretization process and cannot be influenced by the command line. In #15569 the idea is to add another attribute (the virtual being satisfied by the edge) and make edge attributes specifiable from the command line:
This leaves room for later extensions, for instance if we want to move compiler flags from nodes to edges:
This syntax requires to write a lot more characters than before, so the other proposal was to reuse the
Essentially I was seeing a dependency expression started by the Concerning the separation in the syntax of virtual dependencies from normal dependencies: we need to take into account packages that are both a provider of something and a normal dependency at the same time. This might happen with packages that are bundle of many different things, like |
Beta Was this translation helpful? Give feedback.
-
SummaryThank you so much for that brilliant overview! It looks like there are several directions we can expand spack's spec syntax right now, and I was super excited to see where our thoughts met. Here are the remaining useful ones I identified from our discussion:
Below I define Edge Syntax 1 from your comments, and Edge Syntax 2 from my comments. I am likely exaggerating somewhere but I have tried not to. I'm going to use numbered list items like the below to denote new expressions to add to the spec syntax!
Edge Syntax 1:
|
Beta Was this translation helpful? Give feedback.
-
@cosmicexplorer I don't know that I can fully respond to every point here, but there are a couple things I have strong feelings about. The first is that I do not think we should use the Second, I think language version support should be expressed as a version on the virtual dependency, rather than variants. So a package could request |
Beta Was this translation helpful? Give feedback.
-
I think it's very reasonable to avoid using
We would have to agree to all of that first, or something else totally different. |
Beta Was this translation helpful? Give feedback.
-
The issue I saw with not using
This is so far just assuming we want #15569, none of the @becker33: I'm going to move the A Spec is a Conjunction of Assertions <=> A Package Definition is Serializable Consider: That means we will have created a spec syntax which has a bijection with the exact information we provide to clingo. We may then be able to avoid creating any tables at all within spack (see (7)) while building up clingo inputs, instead computing effects on-demand. This is likely to make spec resolution easier to test and easier to implement in a different type of solver. It is also likely to make the task of pretty-printing .unsat_core()s much easier, if we can unambiguously map each constraint provided to the ASP solver back to a single unique depends_on(...) directive, or perhaps to a single CLI argument. |
Beta Was this translation helpful? Give feedback.
-
I also should have made it clear that
I believe this could be done right now as a proof of concept. Do we think compilers and virtual dependencies should eventually be merged? would be a good question to ask. I think that virtual dependencies have no definition except "has a name" right now. If that's not considered as much of a problem to others, I take no issue with that. |
Beta Was this translation helpful? Give feedback.
-
Compilers need to be virtual dependencies for the compilers-as-dependencies work that @alalazo has already started on, but there is a difference between a virtual build dependency ( |
Beta Was this translation helpful? Give feedback.
-
Ok, thank you so much for that context!
I am pretty sure I agree with that. Here's how I was planning to do that:
I believe this satisfies the conditions. Am I mistaken?
|
Beta Was this translation helpful? Give feedback.
-
With #20258, #20260, and #20523 all broken out, I have renamed this issue to represent its narrowed focus. I would say that my comment immediately above: #20256 (comment) should be the entire amount that hasn't been addressed or moved to another issue. cc @tgamblin |
Beta Was this translation helpful? Give feedback.
-
#15569 expands the spec syntax really thoughtfully, making use of our new magic powers from the clingo concretizer. These are several ways we could consider expanding on that, along with an example spec grammar.
"DO NOT ERASE"
The current state of this issue lies at comment #20256 (comment)! Everything below this line is for archival purposes!
Rationale
depends_on(...)
directive to the current node (not ideal).package.py
.depends_on(...)
directive is able to specify a compiler (just for that node, see (3)), a user can't do that on the command line.%gcc@7
intended to be applied on one dependency, but%gcc@8
in another, when provided with the current spec syntax.- Finer selection of virtual providers #15569 introduces the first paired delimiter --
^[virtual=package@version]
, which exposes this new type of constraint: "please ensure that my mpi implementation is going to come from exactly this package"..999:
(imho) to emulate the behavior of<
or>
with the single:
operator as we currently parse it.depends_on('x@y.z')
versiony.z
to something else, what conflicts will arise?"spack/lib/spack/spack/provider_index.py
Lines 340 to 352 in 4622489
Description
Three major areas I think we could consider:
Generalized Non-Nestable Scoping
It seems that the spec syntax intentionally avoids any paired delimiters or scopes (one reason: in order to ensure command-line specs can be unambiguously interpreted). However, there is likely room to extend the square brackets
[^virtual=x@y.z]
proposed in #15569 in order to start representing further new classes of information.In particular, if we can establish the types of spec syntax items that it makes sense to be able to group together, that also would immediately allow us to implement the
conflicts()
directive by allowing a negation operator to be applied to any grouped item. i.e. if we can agree that[x@y.z]
makes sense, then we can also express the unambiguous idea of[!x@y.z]
.Establishing an unobstrusive, non-nestable scoping paradigm by generalizing the
[]
from #15569 may allow us to avoid many further changes to the spec semantics, even if we end up adding a few new tokens.I described some ways we could specify and interact with scoped specs in
spack/test.py
Lines 64 to 67 in 8777b1d
This addresses (3), (4), (5), (6).
A Spec is a Conjunction of Assertions <=> A Package Definition is Serializable
Consider:
That means we will have created a spec syntax which has a bijection with the exact information we provide to clingo. We may then be able to avoid creating any tables at all within spack (see (7)) while building up clingo inputs, instead computing effects on-demand. This is likely to make spec resolution easier to test and easier to implement in a different type of solver. It is also likely to make the task of pretty-printing .unsat_core()s much easier, if we can unambiguously map each constraint provided to the ASP solver back to a single unique
depends_on(...)
directive, or perhaps to a single CLI argument.Some further paths:
We would be able to unambiguously specify the identity of each package (i.e. the change to the global constraint set induced by evaluating package.py) with the exact same representation the user provides on the command line.
We would then be able to make clingo work offline to produce all (or some subset of) valid (possibly overlapping) models which would satisfy a given spec. This opens the possibility of pre-concretizing for all architectures, e.g. on an x86 laptop. Since a spack environment can now be represented as a spec, we can avoid bootstrapping full binaries for difficult platforms, and suddenly we just created a highly compressed file format that can be executed on all supported platforms at once without modification.
Require Explicit Dependencies on
.libs
and.headers
, and Validate ThemThis is a paradigm that pants, another build tool, has been moving to over the past few years for defining packages ("targets" in pants parlance). The site has a great blurb on what this means (at https://www.pantsbuild.org/docs/target-api-concepts#a-field-driven-api):
And the part I think is particularly relevant here is that
Field
has its own entire class hierarchy which decouples validating outputs from consuming inputs. As described in adding custom validation, it's mainly expected that theField
will take care of validating itself once you add it. The pants approach is particularly nice because both the provider and the consumer need to explicitly turn it on, and because it attaches a validation process that is always run to check that the output artifacts (libs, headers) actually work. Spack essentially has all of this, but the validation part isn't shared across packages at all, possibly attracting mild bitrot.I ranted and raved about this to @becker33 a while ago: pants implements this via this very basic "UnionRule" concept I made up: pantsbuild/pants#7116. I don't think any of the pants rule DSL is relevant to this, but I can explain the mechanics of it further if necessary.
For these explicit dependencies to be visible to clingo, they should be represented in an extension of the spec syntax. This is one proposal for representing that "provides" / "consumes" relationship as a spec (which luckily ended up extremely similar to #15569):
spack/test.py
Lines 60 to 62 in 8777b1d
This "explicit resource dependency on a particular package, implicit resource validation" paradigm seems like a possible first step to eventually being able to infer which resources are available via binary analysis (CMake does a pale but useful imitation of binary analysis here).
The concrete proposal here is:
(1) Set up backwards-compatible "resource dependency" registration mechanisms.
- Create a shared base class/mixin which has hook methods to register resource dependencies with
provides(...)
.- (for example) All resources must be associated with a statically-known
type
(not a name), which is validated at runtime.- Each resource class (like the Field concept above) overrides some hook methods to add themselves to the environment correctly.
(2) Write adapters for "virtual dependencies" and "compiler dependencies" to the new "resource dependency".
- Expose any validation methods in
compiler.py
as resource dependencies via (1).- Convert all virtual dependencies into explicit resource dependencies (the resource validation can just be a no-op at first).
(3) Remove the distinction between virtual and compiler dependencies.
- Remove #15569's
[^virtual=x@y]
to instead add unambiguous optional "provides" and "requires" sections, both of which require being enclosed by a[]
scope:Here's an excerpt from the prototype syntax I made up earlier again:
spack/test.py
Lines 231 to 247 in 8777b1d
Additional information
Feedback from @alalazo when I mentioned I had been thinking about this:
These points should be used to guide the discussion above. In particular:
var/spack/repos/runtime
would correspond to e.g.[%cc type=run]
, i.e. a resource dependency (probably justLibraryList
)?imposed_dependencies
would be the name of the method to access the resource dependencies produced by gcc.So I agree these points would still stand, as you said.
Beta Was this translation helpful? Give feedback.
All reactions