Parse % as ^ in specs (#49808)

This PR modifies the parser, so that `%` is parsed as a `DEPENDENCY`, and all
node properties that follow are associated to the name after the `%`. e.g.,
in `foo %gcc +binutils` the `+binutils` refers to `gcc` and not to `foo`.

`%` is still parsed as a build-type dependency, at the moment.

Environments, config files and `package.py` files from before Spack v1.0 may have
spec strings with package variants, targets, etc. *after* a build dependency, and these
will need to be updated. You can use the `spack style --spec-strings` command to do this.

To see what strings will be parsed differently under Spack v1.0, run:

```
spack style --spec-strings FILES
```

where `FILES` is a list of filenames that may contain old specs. To update these spec
strings so that they parse correctly under both Spack 1.0 and Spack 0.x, you can run:

```
spack style --fix --spec-strings FILES
```

In the example above, `foo %gcc +binutils` would be rewritten as `foo +binutils %gcc`,
which parses the same in any Spack version.

In addition, this PR fixes several issues with `%` dependencies:

- [x] Ensure we can still constrain compilers on reuse
- [x] Ensure we can reuse a compiler by hash
- [x] Add tests

---------

Signed-off-by: Massimiliano Culpo <massimiliano.culpo@gmail.com>
This commit is contained in:
Massimiliano Culpo 2025-05-15 00:17:34 +02:00 committed by GitHub
parent 1f0aaafc71
commit f8538a1b1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 456 additions and 116 deletions

View File

@ -18,8 +18,8 @@
import spack.repo import spack.repo
import spack.util.git import spack.util.git
import spack.util.spack_yaml import spack.util.spack_yaml
from spack.spec_parser import SPEC_TOKENIZER, SpecTokens from spack.spec_parser import NAME, VERSION_LIST, SpecTokens
from spack.tokenize import Token from spack.tokenize import Token, TokenBase, Tokenizer
from spack.util.executable import Executable, which from spack.util.executable import Executable, which
description = "runs source code style checks on spack" description = "runs source code style checks on spack"
@ -206,8 +206,8 @@ def setup_parser(subparser):
"--spec-strings", "--spec-strings",
action="store_true", action="store_true",
help="upgrade spec strings in Python, JSON and YAML files for compatibility with Spack " help="upgrade spec strings in Python, JSON and YAML files for compatibility with Spack "
"v1.0 and v0.x. Example: spack style --spec-strings $(git ls-files). Note: this flag " "v1.0 and v0.x. Example: spack style --spec-strings $(git ls-files). Note: must be "
"will be removed in Spack v1.0.", "used only on specs from spack v0.X.",
) )
subparser.add_argument("files", nargs=argparse.REMAINDER, help="specific files to check") subparser.add_argument("files", nargs=argparse.REMAINDER, help="specific files to check")
@ -521,20 +521,52 @@ def _bootstrap_dev_dependencies():
IS_PROBABLY_COMPILER = re.compile(r"%[a-zA-Z_][a-zA-Z0-9\-]") IS_PROBABLY_COMPILER = re.compile(r"%[a-zA-Z_][a-zA-Z0-9\-]")
class _LegacySpecTokens(TokenBase):
"""Reconstructs the tokens for previous specs, so we can reuse code to rotate them"""
# Dependency
START_EDGE_PROPERTIES = r"(?:\^\[)"
END_EDGE_PROPERTIES = r"(?:\])"
DEPENDENCY = r"(?:\^)"
# Version
VERSION_HASH_PAIR = SpecTokens.VERSION_HASH_PAIR.regex
GIT_VERSION = SpecTokens.GIT_VERSION.regex
VERSION = SpecTokens.VERSION.regex
# Variants
PROPAGATED_BOOL_VARIANT = SpecTokens.PROPAGATED_BOOL_VARIANT.regex
BOOL_VARIANT = SpecTokens.BOOL_VARIANT.regex
PROPAGATED_KEY_VALUE_PAIR = SpecTokens.PROPAGATED_KEY_VALUE_PAIR.regex
KEY_VALUE_PAIR = SpecTokens.KEY_VALUE_PAIR.regex
# Compilers
COMPILER_AND_VERSION = rf"(?:%\s*(?:{NAME})(?:[\s]*)@\s*(?:{VERSION_LIST}))"
COMPILER = rf"(?:%\s*(?:{NAME}))"
# FILENAME
FILENAME = SpecTokens.FILENAME.regex
# Package name
FULLY_QUALIFIED_PACKAGE_NAME = SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME.regex
UNQUALIFIED_PACKAGE_NAME = SpecTokens.UNQUALIFIED_PACKAGE_NAME.regex
# DAG hash
DAG_HASH = SpecTokens.DAG_HASH.regex
# White spaces
WS = SpecTokens.WS.regex
# Unexpected character(s)
UNEXPECTED = SpecTokens.UNEXPECTED.regex
def _spec_str_reorder_compiler(idx: int, blocks: List[List[Token]]) -> None: def _spec_str_reorder_compiler(idx: int, blocks: List[List[Token]]) -> None:
# only move the compiler to the back if it exists and is not already at the end # only move the compiler to the back if it exists and is not already at the end
if not 0 <= idx < len(blocks) - 1: if not 0 <= idx < len(blocks) - 1:
return return
# if there's only whitespace after the compiler, don't move it # if there's only whitespace after the compiler, don't move it
if all(token.kind == SpecTokens.WS for block in blocks[idx + 1 :] for token in block): if all(token.kind == _LegacySpecTokens.WS for block in blocks[idx + 1 :] for token in block):
return return
# rotate left and always add at least one WS token between compiler and previous token # rotate left and always add at least one WS token between compiler and previous token
compiler_block = blocks.pop(idx) compiler_block = blocks.pop(idx)
if compiler_block[0].kind != SpecTokens.WS: if compiler_block[0].kind != _LegacySpecTokens.WS:
compiler_block.insert(0, Token(SpecTokens.WS, " ")) compiler_block.insert(0, Token(_LegacySpecTokens.WS, " "))
# delete the WS tokens from the new first block if it was at the very start, to prevent leading # delete the WS tokens from the new first block if it was at the very start, to prevent leading
# WS tokens. # WS tokens.
while idx == 0 and blocks[0][0].kind == SpecTokens.WS: while idx == 0 and blocks[0][0].kind == _LegacySpecTokens.WS:
blocks[0].pop(0) blocks[0].pop(0)
blocks.append(compiler_block) blocks.append(compiler_block)
@ -552,11 +584,13 @@ def _spec_str_format(spec_str: str) -> Optional[str]:
compiler_block_idx = -1 compiler_block_idx = -1
in_edge_attr = False in_edge_attr = False
for token in SPEC_TOKENIZER.tokenize(spec_str): legacy_tokenizer = Tokenizer(_LegacySpecTokens)
if token.kind == SpecTokens.UNEXPECTED:
for token in legacy_tokenizer.tokenize(spec_str):
if token.kind == _LegacySpecTokens.UNEXPECTED:
# parsing error, we cannot fix this string. # parsing error, we cannot fix this string.
return None return None
elif token.kind in (SpecTokens.COMPILER, SpecTokens.COMPILER_AND_VERSION): elif token.kind in (_LegacySpecTokens.COMPILER, _LegacySpecTokens.COMPILER_AND_VERSION):
# multiple compilers are not supported in Spack v0.x, so early return # multiple compilers are not supported in Spack v0.x, so early return
if compiler_block_idx != -1: if compiler_block_idx != -1:
return None return None
@ -565,19 +599,19 @@ def _spec_str_format(spec_str: str) -> Optional[str]:
current_block = [] current_block = []
compiler_block_idx = len(blocks) - 1 compiler_block_idx = len(blocks) - 1
elif token.kind in ( elif token.kind in (
SpecTokens.START_EDGE_PROPERTIES, _LegacySpecTokens.START_EDGE_PROPERTIES,
SpecTokens.DEPENDENCY, _LegacySpecTokens.DEPENDENCY,
SpecTokens.UNQUALIFIED_PACKAGE_NAME, _LegacySpecTokens.UNQUALIFIED_PACKAGE_NAME,
SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, _LegacySpecTokens.FULLY_QUALIFIED_PACKAGE_NAME,
): ):
_spec_str_reorder_compiler(compiler_block_idx, blocks) _spec_str_reorder_compiler(compiler_block_idx, blocks)
compiler_block_idx = -1 compiler_block_idx = -1
if token.kind == SpecTokens.START_EDGE_PROPERTIES: if token.kind == _LegacySpecTokens.START_EDGE_PROPERTIES:
in_edge_attr = True in_edge_attr = True
current_block.append(token) current_block.append(token)
blocks.append(current_block) blocks.append(current_block)
current_block = [] current_block = []
elif token.kind == SpecTokens.END_EDGE_PROPERTIES: elif token.kind == _LegacySpecTokens.END_EDGE_PROPERTIES:
in_edge_attr = False in_edge_attr = False
current_block.append(token) current_block.append(token)
blocks.append(current_block) blocks.append(current_block)
@ -585,19 +619,19 @@ def _spec_str_format(spec_str: str) -> Optional[str]:
elif in_edge_attr: elif in_edge_attr:
current_block.append(token) current_block.append(token)
elif token.kind in ( elif token.kind in (
SpecTokens.VERSION_HASH_PAIR, _LegacySpecTokens.VERSION_HASH_PAIR,
SpecTokens.GIT_VERSION, _LegacySpecTokens.GIT_VERSION,
SpecTokens.VERSION, _LegacySpecTokens.VERSION,
SpecTokens.PROPAGATED_BOOL_VARIANT, _LegacySpecTokens.PROPAGATED_BOOL_VARIANT,
SpecTokens.BOOL_VARIANT, _LegacySpecTokens.BOOL_VARIANT,
SpecTokens.PROPAGATED_KEY_VALUE_PAIR, _LegacySpecTokens.PROPAGATED_KEY_VALUE_PAIR,
SpecTokens.KEY_VALUE_PAIR, _LegacySpecTokens.KEY_VALUE_PAIR,
SpecTokens.DAG_HASH, _LegacySpecTokens.DAG_HASH,
): ):
current_block.append(token) current_block.append(token)
blocks.append(current_block) blocks.append(current_block)
current_block = [] current_block = []
elif token.kind == SpecTokens.WS: elif token.kind == _LegacySpecTokens.WS:
current_block.append(token) current_block.append(token)
else: else:
raise ValueError(f"unexpected token {token}") raise ValueError(f"unexpected token {token}")

View File

@ -2556,7 +2556,7 @@ def _spec_clauses(
edges = spec.edges_from_dependents() edges = spec.edges_from_dependents()
virtuals = [x for x in itertools.chain.from_iterable([edge.virtuals for edge in edges])] virtuals = [x for x in itertools.chain.from_iterable([edge.virtuals for edge in edges])]
if not body: if not body and not spec.concrete:
for virtual in virtuals: for virtual in virtuals:
clauses.append(fn.attr("provider_set", spec.name, virtual)) clauses.append(fn.attr("provider_set", spec.name, virtual))
clauses.append(fn.attr("virtual_node", virtual)) clauses.append(fn.attr("virtual_node", virtual))

View File

@ -530,6 +530,32 @@ attr("concrete_variant_set", node(X, A1), Variant, Value, ID)
attr("virtual_on_build_edge", ParentNode, BuildDependency, Virtual), attr("virtual_on_build_edge", ParentNode, BuildDependency, Virtual),
not 1 { pkg_fact(BuildDependency, version_satisfies(Constraint, Version)) : hash_attr(BuildDependencyHash, "version", BuildDependency, Version) } 1. not 1 { pkg_fact(BuildDependency, version_satisfies(Constraint, Version)) : hash_attr(BuildDependencyHash, "version", BuildDependency, Version) } 1.
error(100, "Cannot satisfy the request on {0} to have {1}={2}", BuildDependency, Variant, Value)
:- attr("build_requirement", ParentNode, build_requirement("variant_set", BuildDependency, Variant, Value)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
not hash_attr(BuildDependencyHash, "variant_value", BuildDependency, Variant, Value).
error(100, "Cannot satisfy the request on {0} to have the target set to {1}", BuildDependency, Target)
:- attr("build_requirement", ParentNode, build_requirement("node_target_set", BuildDependency, Target)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
not hash_attr(BuildDependencyHash, "node_target", BuildDependency, Target).
error(100, "Cannot satisfy the request on {0} to have the os set to {1}", BuildDependency, NodeOS)
:- attr("build_requirement", ParentNode, build_requirement("node_os_set", BuildDependency, NodeOS)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
not hash_attr(BuildDependencyHash, "node_os", BuildDependency, NodeOS).
error(100, "Cannot satisfy the request on {0} to have the platform set to {1}", BuildDependency, Platform)
:- attr("build_requirement", ParentNode, build_requirement("node_platform_set", BuildDependency, Platform)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
not hash_attr(BuildDependencyHash, "node_platform", BuildDependency, Platform).
error(100, "Cannot satisfy the request on {0} to have the following hash {1}", BuildDependency, BuildHash)
:- attr("build_requirement", ParentNode, build_requirement("node_target_set", BuildDependency, Target)),
attr("concrete_build_dependency", ParentNode, BuildDependency, BuildDependencyHash),
attr("build_requirement", ParentNode, build_requirement("hash", BuildDependency, BuildHash)),
BuildHash != BuildDependencyHash.
% External nodes % External nodes
:- attr("build_requirement", ParentNode, build_requirement("node", BuildDependency)), :- attr("build_requirement", ParentNode, build_requirement("node", BuildDependency)),
external(ParentNode), external(ParentNode),
@ -576,6 +602,32 @@ attr("node_version_satisfies", node(X, BuildDependency), Constraint) :-
attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)), attr("build_requirement", ParentNode, build_requirement("node_version_satisfies", BuildDependency, Constraint)),
build_requirement(ParentNode, node(X, BuildDependency)). build_requirement(ParentNode, node(X, BuildDependency)).
% Account for properties on the build requirements
%
% root %gcc@12.0 <properties for gcc> ^dep
%
attr("variant_set", node(X, BuildDependency), Variant, Value) :-
attr("build_requirement", ParentNode, build_requirement("variant_set", BuildDependency, Variant, Value)),
build_requirement(ParentNode, node(X, BuildDependency)).
attr("depends_on", node(X, Parent), node(Y, BuildDependency), "build") :- build_requirement(node(X, Parent), node(Y, BuildDependency)).
attr("node_target_set", node(X, BuildDependency), Target) :-
attr("build_requirement", ParentNode, build_requirement("node_target_set", BuildDependency, Target)),
build_requirement(ParentNode, node(X, BuildDependency)).
attr("node_os_set", node(X, BuildDependency), NodeOS) :-
attr("build_requirement", ParentNode, build_requirement("node_os_set", BuildDependency, NodeOS)),
build_requirement(ParentNode, node(X, BuildDependency)).
attr("node_platform_set", node(X, BuildDependency), NodePlatform) :-
attr("build_requirement", ParentNode, build_requirement("node_platform_set", BuildDependency, NodePlatform)),
build_requirement(ParentNode, node(X, BuildDependency)).
attr("hash", node(X, BuildDependency), BuildHash) :-
attr("build_requirement", ParentNode, build_requirement("hash", BuildDependency, BuildHash)),
build_requirement(ParentNode, node(X, BuildDependency)).
1 { attr("provider_set", node(X, BuildDependency), node(0..Y-1, Virtual)) : max_dupes(Virtual, Y) } 1 :- 1 { attr("provider_set", node(X, BuildDependency), node(0..Y-1, Virtual)) : max_dupes(Virtual, Y) } 1 :-
attr("build_requirement", ParentNode, build_requirement("provider_set", BuildDependency, Virtual)), attr("build_requirement", ParentNode, build_requirement("provider_set", BuildDependency, Virtual)),

View File

@ -2233,15 +2233,21 @@ def lookup_hash(self):
spec._dup(self._lookup_hash()) spec._dup(self._lookup_hash())
return spec return spec
# Get dependencies that need to be replaced # Map the dependencies that need to be replaced
for node in self.traverse(root=False): node_lookup = {
if node.abstract_hash: id(node): node._lookup_hash()
spec._add_dependency(node._lookup_hash(), depflag=0, virtuals=()) for node in self.traverse(root=False)
if node.abstract_hash
}
# reattach nodes that were not otherwise satisfied by new dependencies # Reconstruct dependencies
for node in self.traverse(root=False): for edge in self.traverse_edges(root=False):
if not any(n.satisfies(node) for n in spec.traverse()): key = edge.parent.name
spec._add_dependency(node.copy(), depflag=0, virtuals=()) current_node = spec if key == spec.name else spec[key]
child_node = node_lookup.get(id(edge.spec), edge.spec.copy())
current_node._add_dependency(
child_node, depflag=edge.depflag, virtuals=edge.virtuals, direct=edge.direct
)
return spec return spec

View File

@ -101,9 +101,6 @@
SPLIT_KVP = re.compile(rf"^({NAME})(:?==?)(.*)$") SPLIT_KVP = re.compile(rf"^({NAME})(:?==?)(.*)$")
#: Regex with groups to use for splitting %[virtuals=...] tokens
SPLIT_COMPILER_TOKEN = re.compile(rf"^%\[virtuals=({VALUE}|{QUOTED_VALUE})]\s*(.*)$")
#: A filename starts either with a "." or a "/" or a "{name}/, or on Windows, a drive letter #: A filename starts either with a "." or a "/" or a "{name}/, or on Windows, a drive letter
#: followed by a colon and "\" or "." or {name}\ #: followed by a colon and "\" or "." or {name}\
WINDOWS_FILENAME = r"(?:\.|[a-zA-Z0-9-_]*\\|[a-zA-Z]:\\)(?:[a-zA-Z0-9-_\.\\]*)(?:\.json|\.yaml)" WINDOWS_FILENAME = r"(?:\.|[a-zA-Z0-9-_]*\\|[a-zA-Z]:\\)(?:[a-zA-Z0-9-_\.\\]*)(?:\.json|\.yaml)"
@ -124,9 +121,9 @@ class SpecTokens(TokenBase):
""" """
# Dependency # Dependency
START_EDGE_PROPERTIES = r"(?:\^\[)" START_EDGE_PROPERTIES = r"(?:[\^%]\[)"
END_EDGE_PROPERTIES = r"(?:\])" END_EDGE_PROPERTIES = r"(?:\])"
DEPENDENCY = r"(?:\^)" DEPENDENCY = r"(?:[\^\%])"
# Version # Version
VERSION_HASH_PAIR = rf"(?:@(?:{GIT_VERSION_PATTERN})=(?:{VERSION}))" VERSION_HASH_PAIR = rf"(?:@(?:{GIT_VERSION_PATTERN})=(?:{VERSION}))"
GIT_VERSION = rf"@(?:{GIT_VERSION_PATTERN})" GIT_VERSION = rf"@(?:{GIT_VERSION_PATTERN})"
@ -136,14 +133,6 @@ class SpecTokens(TokenBase):
BOOL_VARIANT = rf"(?:[~+-]\s*{NAME})" BOOL_VARIANT = rf"(?:[~+-]\s*{NAME})"
PROPAGATED_KEY_VALUE_PAIR = rf"(?:{NAME}:?==(?:{VALUE}|{QUOTED_VALUE}))" PROPAGATED_KEY_VALUE_PAIR = rf"(?:{NAME}:?==(?:{VALUE}|{QUOTED_VALUE}))"
KEY_VALUE_PAIR = rf"(?:{NAME}:?=(?:{VALUE}|{QUOTED_VALUE}))" KEY_VALUE_PAIR = rf"(?:{NAME}:?=(?:{VALUE}|{QUOTED_VALUE}))"
# Compilers
COMPILER_AND_VERSION = rf"(?:%\s*(?:{NAME})(?:[\s]*)@\s*(?:{VERSION_LIST}))"
COMPILER = rf"(?:%\s*(?:{NAME}))"
COMPILER_AND_VERSION_WITH_VIRTUALS = (
rf"(?:%\[virtuals=(?:{VALUE}|{QUOTED_VALUE})\]"
rf"\s*(?:{NAME})(?:[\s]*)@\s*(?:{VERSION_LIST}))"
)
COMPILER_WITH_VIRTUALS = rf"(?:%\[virtuals=(?:{VALUE}|{QUOTED_VALUE})\]\s*(?:{NAME}))"
# FILENAME # FILENAME
FILENAME = rf"(?:{FILENAME})" FILENAME = rf"(?:{FILENAME})"
# Package name # Package name
@ -275,25 +264,58 @@ def next_spec(
def add_dependency(dep, **edge_properties): def add_dependency(dep, **edge_properties):
"""wrapper around root_spec._add_dependency""" """wrapper around root_spec._add_dependency"""
try: try:
root_spec._add_dependency(dep, **edge_properties) target_spec._add_dependency(dep, **edge_properties)
except spack.error.SpecError as e: except spack.error.SpecError as e:
raise SpecParsingError(str(e), self.ctx.current_token, self.literal_str) from e raise SpecParsingError(str(e), self.ctx.current_token, self.literal_str) from e
initial_spec = initial_spec or spack.spec.Spec() initial_spec = initial_spec or spack.spec.Spec()
root_spec, parser_warnings = SpecNodeParser(self.ctx, self.literal_str).parse(initial_spec) root_spec, parser_warnings = SpecNodeParser(self.ctx, self.literal_str).parse(initial_spec)
current_spec = root_spec
while True: while True:
if self.ctx.accept(SpecTokens.START_EDGE_PROPERTIES): if self.ctx.accept(SpecTokens.START_EDGE_PROPERTIES):
is_direct = self.ctx.current_token.value[0] == "%"
edge_properties = EdgeAttributeParser(self.ctx, self.literal_str).parse() edge_properties = EdgeAttributeParser(self.ctx, self.literal_str).parse()
edge_properties.setdefault("depflag", 0)
edge_properties.setdefault("virtuals", ()) edge_properties.setdefault("virtuals", ())
edge_properties["direct"] = is_direct
dependency, warnings = self._parse_node(root_spec) dependency, warnings = self._parse_node(root_spec)
if is_direct:
target_spec = current_spec
edge_properties.setdefault("depflag", spack.deptypes.BUILD)
if dependency.name in LEGACY_COMPILER_TO_BUILTIN:
dependency.name = LEGACY_COMPILER_TO_BUILTIN[dependency.name]
else:
current_spec = dependency
target_spec = root_spec
edge_properties.setdefault("depflag", 0)
# print(f"[{current_spec}], {target_spec}->{dependency} {is_direct}")
parser_warnings.extend(warnings) parser_warnings.extend(warnings)
add_dependency(dependency, **edge_properties) add_dependency(dependency, **edge_properties)
elif self.ctx.accept(SpecTokens.DEPENDENCY): elif self.ctx.accept(SpecTokens.DEPENDENCY):
is_direct = self.ctx.current_token.value[0] == "%"
dependency, warnings = self._parse_node(root_spec) dependency, warnings = self._parse_node(root_spec)
edge_properties = {}
edge_properties["direct"] = is_direct
edge_properties["virtuals"] = tuple()
if is_direct:
target_spec = current_spec
edge_properties.setdefault("depflag", spack.deptypes.BUILD)
if dependency.name in LEGACY_COMPILER_TO_BUILTIN:
dependency.name = LEGACY_COMPILER_TO_BUILTIN[dependency.name]
else:
current_spec = dependency
target_spec = root_spec
edge_properties.setdefault("depflag", 0)
# print(f"[{current_spec}], {target_spec}->{dependency} {is_direct}")
parser_warnings.extend(warnings) parser_warnings.extend(warnings)
add_dependency(dependency, depflag=0, virtuals=()) add_dependency(dependency, **edge_properties)
else: else:
break break
@ -384,34 +406,6 @@ def warn_if_after_compiler(token: str):
while True: while True:
if ( if (
self.ctx.accept(SpecTokens.COMPILER)
or self.ctx.accept(SpecTokens.COMPILER_AND_VERSION)
or self.ctx.accept(SpecTokens.COMPILER_WITH_VIRTUALS)
or self.ctx.accept(SpecTokens.COMPILER_AND_VERSION_WITH_VIRTUALS)
):
current_token = self.ctx.current_token
if current_token.kind in (
SpecTokens.COMPILER_WITH_VIRTUALS,
SpecTokens.COMPILER_AND_VERSION_WITH_VIRTUALS,
):
m = SPLIT_COMPILER_TOKEN.match(current_token.value)
assert m, "SPLIT_COMPILER_TOKEN and COMPILER_* do not agree."
virtuals_str, compiler_str = m.groups()
virtuals = tuple(virtuals_str.strip("'\" ").split(","))
else:
virtuals = tuple()
compiler_str = current_token.value[1:]
build_dependency = spack.spec.Spec(compiler_str)
if build_dependency.name in LEGACY_COMPILER_TO_BUILTIN:
build_dependency.name = LEGACY_COMPILER_TO_BUILTIN[build_dependency.name]
initial_spec._add_dependency(
build_dependency, depflag=spack.deptypes.BUILD, virtuals=virtuals, direct=True
)
last_compiler = self.ctx.current_token.value
elif (
self.ctx.accept(SpecTokens.VERSION_HASH_PAIR) self.ctx.accept(SpecTokens.VERSION_HASH_PAIR)
or self.ctx.accept(SpecTokens.GIT_VERSION) or self.ctx.accept(SpecTokens.GIT_VERSION)
or self.ctx.accept(SpecTokens.VERSION) or self.ctx.accept(SpecTokens.VERSION)

View File

@ -2,6 +2,7 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os import os
import platform
import sys import sys
import _vendoring.jinja2 import _vendoring.jinja2
@ -3566,3 +3567,144 @@ def test_concrete_multi_valued_variants_when_args(default_mock_concretization):
for c in ("foo:=a", "foo:=a,b,c", "foo:=a,b", "foo:=a,c"): for c in ("foo:=a", "foo:=a,b,c", "foo:=a,b", "foo:=a,c"):
s = default_mock_concretization(f"mvdefaults {c}") s = default_mock_concretization(f"mvdefaults {c}")
assert not s.satisfies("^pkg-b") assert not s.satisfies("^pkg-b")
@pytest.mark.usefixtures("mock_packages")
@pytest.mark.parametrize(
"constraint_in_yaml,unsat_request,sat_request",
[
# Arch parts
pytest.param(
"target=x86_64",
"target=core2",
"target=x86_64",
marks=pytest.mark.skipif(
platform.machine() != "x86_64", reason="only valid for x86_64"
),
),
pytest.param(
"target=core2",
"target=x86_64",
"target=core2",
marks=pytest.mark.skipif(
platform.machine() != "x86_64", reason="only valid for x86_64"
),
),
("os=debian6", "os=redhat6", "os=debian6"),
("platform=test", "platform=linux", "platform=test"),
# Variants
("~lld", "+lld", "~lld"),
("+lld", "~lld", "+lld"),
],
)
def test_spec_parts_on_fresh_compilers(
constraint_in_yaml, unsat_request, sat_request, mutable_config, tmp_path
):
"""Tests that spec parts like targets and variants in `%<package> target=<target> <variants>`
are associated with `package` for `%` just as they would be for `^`, when we concretize
without reusing.
"""
packages_yaml = syaml.load_config(
f"""
packages:
llvm::
buildable: false
externals:
- spec: "llvm+clang@20 {constraint_in_yaml}"
prefix: {tmp_path / 'llvm-20'}
"""
)
mutable_config.set("packages", packages_yaml["packages"])
# Check the abstract spec is formed correctly
abstract_spec = Spec(f"pkg-a %llvm@20 +clang {unsat_request}")
assert abstract_spec["llvm"].satisfies(f"@20 +clang {unsat_request}")
# Check that we can't concretize the spec, since llvm is not buildable
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
spack.concretize.concretize_one(abstract_spec)
# Check we can instead concretize if we use the correct constraint
s = spack.concretize.concretize_one(f"pkg-a %llvm@20 +clang {sat_request}")
assert s["c"].external and s["c"].satisfies(f"@20 +clang {sat_request}")
@pytest.mark.usefixtures("mock_packages", "mutable_database")
@pytest.mark.parametrize(
"constraint_in_yaml,unsat_request,sat_request",
[
# Arch parts
pytest.param(
"target=x86_64",
"target=core2",
"target=x86_64",
marks=pytest.mark.skipif(
platform.machine() != "x86_64", reason="only valid for x86_64"
),
),
pytest.param(
"target=core2",
"target=x86_64",
"target=core2",
marks=pytest.mark.skipif(
platform.machine() != "x86_64", reason="only valid for x86_64"
),
),
("os=debian6", "os=redhat6", "os=debian6"),
("platform=test", "platform=linux", "platform=test"),
# Variants
("~lld", "+lld", "~lld"),
("+lld", "~lld", "+lld"),
],
)
def test_spec_parts_on_reused_compilers(
constraint_in_yaml, unsat_request, sat_request, mutable_config, tmp_path
):
"""Tests that requests of the form <package>%<compiler> <requests> are considered for reused
specs, even though build dependency are not part of the ASP problem.
"""
packages_yaml = syaml.load_config(
f"""
packages:
c:
require: llvm
cxx:
require: llvm
llvm::
buildable: false
externals:
- spec: "llvm+clang@20 {constraint_in_yaml}"
prefix: {tmp_path / 'llvm-20'}
mpileaks:
buildable: true
"""
)
mutable_config.set("packages", packages_yaml["packages"])
# Install the spec
installed_spec = spack.concretize.concretize_one(f"mpileaks %llvm@20 {sat_request}")
PackageInstaller([installed_spec.package], fake=True, explicit=True).install()
# Make mpileaks not buildable
mutable_config.set("packages:mpileaks:buildable", False)
# Check we can't concretize with the unsat request...
with pytest.raises(spack.solver.asp.UnsatisfiableSpecError):
spack.concretize.concretize_one(f"mpileaks %llvm@20 {unsat_request}")
# ...but we can with the original constraint
with spack.config.override("concretizer:reuse", True):
s = spack.concretize.concretize_one(f"mpileaks %llvm@20 {sat_request}")
assert s.dag_hash() == installed_spec.dag_hash()
def test_use_compiler_by_hash(mock_packages, mutable_database, mutable_config):
"""Tests that we can reuse an installed compiler specifying its hash"""
installed_spec = spack.concretize.concretize_one("gcc@14.0")
PackageInstaller([installed_spec.package], fake=True, explicit=True).install()
with spack.config.override("concretizer:reuse", True):
s = spack.concretize.concretize_one(f"mpileaks %gcc/{installed_spec.dag_hash()}")
assert s["c"].dag_hash() == installed_spec.dag_hash()

View File

@ -163,16 +163,15 @@ def test_flag_order_and_grouping(
if cmp_flags: if cmp_flags:
compiler_spec = "%gcc@12.100.100" compiler_spec = "%gcc@12.100.100"
cmd_flags_str = f'cflags="{cmd_flags}"' if cmd_flags else ""
if dflags: if dflags:
spec_str = f"x+activatemultiflag {compiler_spec} ^y" spec_str = f"x+activatemultiflag {compiler_spec} ^y {cmd_flags_str}"
expected_dflags = "-d1 -d2" expected_dflags = "-d1 -d2"
else: else:
spec_str = f"y {compiler_spec}" spec_str = f"y {cmd_flags_str} {compiler_spec}"
expected_dflags = None expected_dflags = None
if cmd_flags:
spec_str += f' cflags="{cmd_flags}"'
root_spec = spack.concretize.concretize_one(spec_str) root_spec = spack.concretize.concretize_one(spec_str)
spec = root_spec["y"] spec = root_spec["y"]
satisfy_flags = " ".join(x for x in [cmd_flags, req_flags, cmp_flags, expected_dflags] if x) satisfy_flags = " ".join(x for x in [cmd_flags, req_flags, cmp_flags, expected_dflags] if x)
@ -277,6 +276,6 @@ def test_flag_injection_different_compilers(mock_packages, mutable_config):
"""Tests that flag propagation is not activated on nodes with a compiler that is different """Tests that flag propagation is not activated on nodes with a compiler that is different
from the propagation source. from the propagation source.
""" """
s = spack.concretize.concretize_one('mpileaks %gcc cflags=="-O2" ^callpath %llvm') s = spack.concretize.concretize_one('mpileaks cflags=="-O2" %gcc ^callpath %llvm')
assert s.satisfies('cflags="-O2"') and s["c"].name == "gcc" assert s.satisfies('cflags="-O2"') and s["c"].name == "gcc"
assert not s["callpath"].satisfies('cflags="-O2"') and s["callpath"]["c"].name == "llvm" assert not s["callpath"].satisfies('cflags="-O2"') and s["callpath"]["c"].name == "llvm"

View File

@ -137,7 +137,14 @@ def test_spec_list_nested_matrices(self, parser_and_speclist):
expected_components = itertools.product( expected_components = itertools.product(
["zlib", "libelf"], ["%gcc", "%intel"], ["+shared", "~shared"] ["zlib", "libelf"], ["%gcc", "%intel"], ["+shared", "~shared"]
) )
expected = [Spec(" ".join(combo)) for combo in expected_components]
def _reduce(*, combo):
root = Spec(combo[0])
for x in combo[1:]:
root.constrain(x)
return root
expected = [_reduce(combo=combo) for combo in expected_components]
assert set(result.specs) == set(expected) assert set(result.specs) == set(expected)
@pytest.mark.regression("16897") @pytest.mark.regression("16897")

View File

@ -51,10 +51,6 @@ def dependency_with_version(text):
) )
def compiler_with_version_range(text):
return text, [Token(SpecTokens.COMPILER_AND_VERSION, value=text)], text
@pytest.fixture() @pytest.fixture()
def specfile_for(default_mock_concretization): def specfile_for(default_mock_concretization):
def _specfile_for(spec_str, filename): def _specfile_for(spec_str, filename):
@ -84,7 +80,6 @@ def _specfile_for(spec_str, filename):
simple_package_name("3dtk"), simple_package_name("3dtk"),
simple_package_name("ns-3-dev"), simple_package_name("ns-3-dev"),
# Single token anonymous specs # Single token anonymous specs
("%intel", [Token(SpecTokens.COMPILER, value="%intel")], "%intel"),
("@2.7", [Token(SpecTokens.VERSION, value="@2.7")], "@2.7"), ("@2.7", [Token(SpecTokens.VERSION, value="@2.7")], "@2.7"),
("@2.7:", [Token(SpecTokens.VERSION, value="@2.7:")], "@2.7:"), ("@2.7:", [Token(SpecTokens.VERSION, value="@2.7:")], "@2.7:"),
("@:2.7", [Token(SpecTokens.VERSION, value="@:2.7")], "@:2.7"), ("@:2.7", [Token(SpecTokens.VERSION, value="@:2.7")], "@:2.7"),
@ -97,6 +92,14 @@ def _specfile_for(spec_str, filename):
"arch=test-None-None", "arch=test-None-None",
), ),
# Multiple tokens anonymous specs # Multiple tokens anonymous specs
(
"%intel",
[
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "intel"),
],
"%intel",
),
( (
"languages=go @4.2:", "languages=go @4.2:",
[ [
@ -159,7 +162,9 @@ def _specfile_for(spec_str, filename):
[ [
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="foo"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="foo"),
Token(SpecTokens.VERSION, value="@2.0"), Token(SpecTokens.VERSION, value="@2.0"),
Token(SpecTokens.COMPILER_AND_VERSION, value="%bar@1.0"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="bar"),
Token(SpecTokens.VERSION, value="@1.0"),
], ],
"foo@2.0 %bar@1.0", "foo@2.0 %bar@1.0",
), ),
@ -178,7 +183,9 @@ def _specfile_for(spec_str, filename):
Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"), Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"),
Token(SpecTokens.BOOL_VARIANT, value="+debug"), Token(SpecTokens.BOOL_VARIANT, value="+debug"),
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"), Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
Token(SpecTokens.VERSION, value="@12.1"),
Token(SpecTokens.DEPENDENCY, value="^"), Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
Token(SpecTokens.VERSION, value="@8.1_1e"), Token(SpecTokens.VERSION, value="@8.1_1e"),
@ -194,7 +201,9 @@ def _specfile_for(spec_str, filename):
Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"), Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"),
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"), Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
Token(SpecTokens.KEY_VALUE_PAIR, value="debug=2"), Token(SpecTokens.KEY_VALUE_PAIR, value="debug=2"),
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
Token(SpecTokens.VERSION, value="@12.1"),
Token(SpecTokens.DEPENDENCY, value="^"), Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
Token(SpecTokens.VERSION, value="@8.1_1e"), Token(SpecTokens.VERSION, value="@8.1_1e"),
@ -212,7 +221,9 @@ def _specfile_for(spec_str, filename):
Token(SpecTokens.KEY_VALUE_PAIR, value="cppflags=-O3"), Token(SpecTokens.KEY_VALUE_PAIR, value="cppflags=-O3"),
Token(SpecTokens.BOOL_VARIANT, value="+debug"), Token(SpecTokens.BOOL_VARIANT, value="+debug"),
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"), Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
Token(SpecTokens.VERSION, value="@12.1"),
Token(SpecTokens.DEPENDENCY, value="^"), Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
Token(SpecTokens.VERSION, value="@8.1_1e"), Token(SpecTokens.VERSION, value="@8.1_1e"),
@ -226,7 +237,9 @@ def _specfile_for(spec_str, filename):
[ [
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="yaml-cpp"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="yaml-cpp"),
Token(SpecTokens.VERSION, value="@0.1.8"), Token(SpecTokens.VERSION, value="@0.1.8"),
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
Token(SpecTokens.VERSION, value="@12.1"),
Token(SpecTokens.DEPENDENCY, value="^"), Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="boost"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="boost"),
Token(SpecTokens.VERSION, value="@3.1.4"), Token(SpecTokens.VERSION, value="@3.1.4"),
@ -237,7 +250,8 @@ def _specfile_for(spec_str, filename):
r"builtin.yaml-cpp%gcc", r"builtin.yaml-cpp%gcc",
[ [
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"), Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
Token(SpecTokens.COMPILER, value="%gcc"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
], ],
"yaml-cpp %gcc", "yaml-cpp %gcc",
), ),
@ -245,7 +259,8 @@ def _specfile_for(spec_str, filename):
r"testrepo.yaml-cpp%gcc", r"testrepo.yaml-cpp%gcc",
[ [
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="testrepo.yaml-cpp"), Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="testrepo.yaml-cpp"),
Token(SpecTokens.COMPILER, value="%gcc"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
], ],
"yaml-cpp %gcc", "yaml-cpp %gcc",
), ),
@ -254,7 +269,9 @@ def _specfile_for(spec_str, filename):
[ [
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"), Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
Token(SpecTokens.VERSION, value="@0.1.8"), Token(SpecTokens.VERSION, value="@0.1.8"),
Token(SpecTokens.COMPILER_AND_VERSION, value="%gcc@7.2.0"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@7.2.0"),
Token(SpecTokens.DEPENDENCY, value="^"), Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="boost"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="boost"),
Token(SpecTokens.VERSION, value="@3.1.4"), Token(SpecTokens.VERSION, value="@3.1.4"),
@ -419,11 +436,51 @@ def _specfile_for(spec_str, filename):
f"develop-branch-version@git.{'a' * 40}=develop+var1+var2", f"develop-branch-version@git.{'a' * 40}=develop+var1+var2",
), ),
# Compiler with version ranges # Compiler with version ranges
compiler_with_version_range("%gcc@10.2.1:"), (
compiler_with_version_range("%gcc@:10.2.1"), "%gcc@10.2.1:",
compiler_with_version_range("%gcc@10.2.1:12.1.0"), [
compiler_with_version_range("%gcc@10.1.0,12.2.1:"), Token(SpecTokens.DEPENDENCY, value="%"),
compiler_with_version_range("%gcc@:8.4.3,10.2.1:12.1.0"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@10.2.1:"),
],
"%gcc@10.2.1:",
),
(
"%gcc@:10.2.1",
[
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@:10.2.1"),
],
"%gcc@:10.2.1",
),
(
"%gcc@10.2.1:12.1.0",
[
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@10.2.1:12.1.0"),
],
"%gcc@10.2.1:12.1.0",
),
(
"%gcc@10.1.0,12.2.1:",
[
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@10.1.0,12.2.1:"),
],
"%gcc@10.1.0,12.2.1:",
),
(
"%gcc@:8.4.3,10.2.1:12.1.0",
[
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@:8.4.3,10.2.1:12.1.0"),
],
"%gcc@:8.4.3,10.2.1:12.1.0",
),
# Special key value arguments # Special key value arguments
("dev_path=*", [Token(SpecTokens.KEY_VALUE_PAIR, value="dev_path=*")], "dev_path='*'"), ("dev_path=*", [Token(SpecTokens.KEY_VALUE_PAIR, value="dev_path=*")], "dev_path='*'"),
( (
@ -484,7 +541,9 @@ def _specfile_for(spec_str, filename):
"+ debug % intel @ 12.1:12.6", "+ debug % intel @ 12.1:12.6",
[ [
Token(SpecTokens.BOOL_VARIANT, value="+ debug"), Token(SpecTokens.BOOL_VARIANT, value="+ debug"),
Token(SpecTokens.COMPILER_AND_VERSION, value="% intel @ 12.1:12.6"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
Token(SpecTokens.VERSION, value="@ 12.1:12.6"),
], ],
"+debug %intel@12.1:12.6", "+debug %intel@12.1:12.6",
), ),
@ -509,7 +568,8 @@ def _specfile_for(spec_str, filename):
"@:0.4 % nvhpc", "@:0.4 % nvhpc",
[ [
Token(SpecTokens.VERSION, value="@:0.4"), Token(SpecTokens.VERSION, value="@:0.4"),
Token(SpecTokens.COMPILER, value="% nvhpc"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="nvhpc"),
], ],
"@:0.4 %nvhpc", "@:0.4 %nvhpc",
), ),
@ -602,7 +662,10 @@ def _specfile_for(spec_str, filename):
"zlib %[virtuals=c] gcc", "zlib %[virtuals=c] gcc",
[ [
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
Token(SpecTokens.COMPILER_WITH_VIRTUALS, "%[virtuals=c] gcc"), Token(SpecTokens.START_EDGE_PROPERTIES, value="%["),
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=c"),
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
], ],
"zlib %[virtuals=c] gcc", "zlib %[virtuals=c] gcc",
), ),
@ -610,7 +673,10 @@ def _specfile_for(spec_str, filename):
"zlib %[virtuals=c,cxx] gcc", "zlib %[virtuals=c,cxx] gcc",
[ [
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
Token(SpecTokens.COMPILER_WITH_VIRTUALS, "%[virtuals=c,cxx] gcc"), Token(SpecTokens.START_EDGE_PROPERTIES, value="%["),
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=c,cxx"),
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
], ],
"zlib %[virtuals=c,cxx] gcc", "zlib %[virtuals=c,cxx] gcc",
), ),
@ -618,7 +684,11 @@ def _specfile_for(spec_str, filename):
"zlib %[virtuals=c,cxx] gcc@14.1", "zlib %[virtuals=c,cxx] gcc@14.1",
[ [
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
Token(SpecTokens.COMPILER_AND_VERSION_WITH_VIRTUALS, "%[virtuals=c,cxx] gcc@14.1"), Token(SpecTokens.START_EDGE_PROPERTIES, value="%["),
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=c,cxx"),
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@14.1"),
], ],
"zlib %[virtuals=c,cxx] gcc@14.1", "zlib %[virtuals=c,cxx] gcc@14.1",
), ),
@ -626,10 +696,15 @@ def _specfile_for(spec_str, filename):
"zlib %[virtuals=fortran] gcc@14.1 %[virtuals=c,cxx] clang", "zlib %[virtuals=fortran] gcc@14.1 %[virtuals=c,cxx] clang",
[ [
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
Token( Token(SpecTokens.START_EDGE_PROPERTIES, value="%["),
SpecTokens.COMPILER_AND_VERSION_WITH_VIRTUALS, "%[virtuals=fortran] gcc@14.1" Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=fortran"),
), Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
Token(SpecTokens.COMPILER_WITH_VIRTUALS, "%[virtuals=c,cxx] clang"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.VERSION, value="@14.1"),
Token(SpecTokens.START_EDGE_PROPERTIES, value="%["),
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=c,cxx"),
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="clang"),
], ],
"zlib %[virtuals=fortran] gcc@14.1 %[virtuals=c,cxx] clang", "zlib %[virtuals=fortran] gcc@14.1 %[virtuals=c,cxx] clang",
), ),
@ -650,6 +725,18 @@ def _specfile_for(spec_str, filename):
], ],
"gcc languages:=='c,c++'", "gcc languages:=='c,c++'",
), ),
# test <variants> etc. after %
(
"mvapich %gcc languages:=c,c++ target=x86_64",
[
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "mvapich"),
Token(SpecTokens.DEPENDENCY, "%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "gcc"),
Token(SpecTokens.KEY_VALUE_PAIR, "languages:=c,c++"),
Token(SpecTokens.KEY_VALUE_PAIR, "target=x86_64"),
],
"mvapich %gcc languages:='c,c++' arch=None-None-x86_64",
),
], ],
) )
def test_parse_single_spec(spec_str, tokens, expected_roundtrip, mock_git_test_package): def test_parse_single_spec(spec_str, tokens, expected_roundtrip, mock_git_test_package):
@ -694,7 +781,8 @@ def test_parse_single_spec(spec_str, tokens, expected_roundtrip, mock_git_test_p
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
Token(SpecTokens.VERSION, value="@1.1.1"), Token(SpecTokens.VERSION, value="@1.1.1"),
Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=-O3"), Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=-O3"),
Token(SpecTokens.COMPILER, value="%intel"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
], ],
["mvapich", "emacs @1.1.1 cflags=-O3 %intel"], ["mvapich", "emacs @1.1.1 cflags=-O3 %intel"],
), ),
@ -706,10 +794,27 @@ def test_parse_single_spec(spec_str, tokens, expected_roundtrip, mock_git_test_p
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
Token(SpecTokens.DEPENDENCY, value="^"), Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="ncurses"), Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="ncurses"),
Token(SpecTokens.COMPILER, value="%intel"), Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="intel"),
], ],
['mvapich cflags="-O3 -fPIC"', "emacs ^ncurses%intel"], ['mvapich cflags="-O3 -fPIC"', "emacs ^ncurses%intel"],
), ),
(
"mvapich %gcc languages=c,c++ emacs ^ncurses%gcc languages:=c",
[
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.KEY_VALUE_PAIR, value="languages=c,c++"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
Token(SpecTokens.DEPENDENCY, value="^"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="ncurses"),
Token(SpecTokens.DEPENDENCY, value="%"),
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="gcc"),
Token(SpecTokens.KEY_VALUE_PAIR, value="languages:=c"),
],
["mvapich %gcc languages=c,c++", "emacs ^ncurses%gcc languages:=c"],
),
], ],
) )
def test_parse_multiple_specs(text, tokens, expected_specs): def test_parse_multiple_specs(text, tokens, expected_specs):

View File

@ -2935,7 +2935,7 @@ complete -c spack -n '__fish_spack_using_command style' -s t -l tool -r -d 'spec
complete -c spack -n '__fish_spack_using_command style' -s s -l skip -r -f -a skip complete -c spack -n '__fish_spack_using_command style' -s s -l skip -r -f -a skip
complete -c spack -n '__fish_spack_using_command style' -s s -l skip -r -d 'specify tools to skip (choose from import, isort, black, flake8, mypy)' complete -c spack -n '__fish_spack_using_command style' -s s -l skip -r -d 'specify tools to skip (choose from import, isort, black, flake8, mypy)'
complete -c spack -n '__fish_spack_using_command style' -l spec-strings -f -a spec_strings complete -c spack -n '__fish_spack_using_command style' -l spec-strings -f -a spec_strings
complete -c spack -n '__fish_spack_using_command style' -l spec-strings -d 'upgrade spec strings in Python, JSON and YAML files for compatibility with Spack v1.0 and v0.x. Example: spack style --spec-strings $(git ls-files). Note: this flag will be removed in Spack v1.0.' complete -c spack -n '__fish_spack_using_command style' -l spec-strings -d 'upgrade spec strings in Python, JSON and YAML files for compatibility with Spack v1.0 and v0.x. Example: spack style --spec-strings $(git ls-files). Note: must be used only on specs from spack v0.X.'
# spack tags # spack tags
set -g __fish_spack_optspecs_spack_tags h/help i/installed a/all set -g __fish_spack_optspecs_spack_tags h/help i/installed a/all

View File

@ -17,6 +17,7 @@ class Llvm(Package, CompilerPackage):
variant( variant(
"clang", default=True, description="Build the LLVM C/C++/Objective-C compiler frontend" "clang", default=True, description="Build the LLVM C/C++/Objective-C compiler frontend"
) )
variant("lld", default=True, description="Build the LLVM linker")
provides("c", "cxx", when="+clang") provides("c", "cxx", when="+clang")