Skip to content

Spec syntax: semantics for % (percent) and ^ (caret) for Spack v1.0 #44379

@alalazo

Description

@alalazo

Summary

Currently the caret sigil ^ is used to indicate a transitive dependency of any type, while the percent sigil % is a node attribute that denotes a "compiler". This semantic need to change when compilers will turn into dependencies. Below is a sketched description of the proposed new semantic, with some discussion on backward compatibility.

Semantic: ^ transitive vs. % direct

When compilers are turned into nodes, we should use:

  • ^ to denote transitive dependencies
  • % to denote direct dependencies

We can distinguish the edge types with the [] syntax:

# bar is a direct run dependency of foo
foo %[deptypes=run] bar
# bar is in the transitive link graph of foo
foo ^[deptypes=link] bar

If no type is specified, we should use these defaults:

# % without types is a direct build dependency
foo %bar -> foo %[deptypes=build] bar
# ^ without types is either a transitive link/run dependency, or a direct build dependency
foo ^bar -> foo ^[deptypes=link,run] bar + foo %[deptypes=build] bar

The second default allows us to say that ^cmake must be in the condition_set of its root, i.e. it is either a transitive link/run dependency, or a direct build dependency.

Specify dependencies with more structure

All the specs after a ^ or a % sigil refer to the root spec of the context. We need some symbols (() or {}) to allow opening and closing new contexts:

# bar is a link,run transitive dependency of foo
foo^bar
# like above, and baz is a transitive link,run dep of bar
foo^{bar ^baz}

All these symbols can be used with satisfies or intersects:

# Was the spec built using cmake 3.14 or later?
spec.satisfies("%[email protected]:")
# Is the spec transitively linked to a foo built with cmake?
spec.satisfies("^[type=link]{foo %[email protected]:}")

Considerations on backward compatibility

The current spec syntax established a few idioms that we need to respect, if we don't want to break each and every user.

% without parens should accept only a name and an optional version

Currently the % sigil in a spec denotes a node attribute, and admits only a name and an optional version specified after it. Very frequently this node attribute is inter-mixed with variants that refer to the root spec, for instance:

foo @X.Y %gcc@13 +bar +baz

In the spec above +bar and +baz refer to foo, not to gcc.

In order to avoid breakage, if % is not followed by parens, we need to accept only a name and an optional version even when gcc will be a node in the DAG. Specifying a variant on the gcc node can be done like:

foo +bar +baz %{gcc +binutils}

Need to introduce custom aliases for specs

With the proposed changes usage in spec literals has a similar meaning in most cases, for "pure" toolchains. For instance:

foo %gcc@13

doesn't need any modifications, and still denotes a foo built with gcc@13.

The only exceptions are compilers associated with specs having a different name:

foo %clang -> foo %llvm+clang
foo %oneapi -> foo %intel-oneapi-compilers
...

To avoid breakage in those cases we can introduce aliases for specs, and ship with these defaults:

packages:
  aliases:
    clang: "llvm+clang"
    oneapi: "intel-oneapi-compilers"

In this way specs will be translated automatically, and people could continue to write %oneapi or %clang.

Note that this is particularly important for clang, which is provided by llvm+clang and would need this cumbersome syntax otherwise:

foo %{llvm+clang}

due to the fact that % without parens associate following variants to the root spec.

There's no way to allow mixed toolchain with the same syntax

Using mixed toolchains will be requiring more boilerplate from the command line:

foo %clang %[virtuals=fortran] gcc

but on the other hand, it can be easily configured:

packages:
  fortran:
    require:
    - gcc
  c:
    require:
    - llvm+clang

Using a ^ for direct build dependencies works, because of defaults

We might have usage in directives or in constraints of expressions using ^ and a build type dependency. For instance:

conflicts("~hdf5", when="@0.6.0 ^cmake@:3.26")
patch("linux-gcc-cmakev3.11-plus.patch", when="@:1.9.1%gcc^[email protected]:")

if spec.satisfies("^[email protected]:3.21.2"):
    ...

This works because the default behavior for ^ includes direct build dependencies.

The same applies for input specs like:

foo ^cmake@3

In this case we can add an additional constraint that the cmake must be in the condition_set of foo.

For specs like:

foo %cmake@3

we can emit additional constraints for the solver, like:

depends_on(node(0, foo), node(0, cmake), "build).

Metadata

Metadata

Assignees

Labels

compilersepicA high level task that is broken down into smaller, more focused, units of workspecs

Type

No type

Projects

Status

In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions