Warn when %compiler precedes +variant (#49410)

This commit is contained in:
Harmen Stoppels 2025-03-13 10:45:09 +01:00 committed by GitHub
parent 63895b39f0
commit 11f52ce2f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -60,13 +60,17 @@
import pathlib import pathlib
import re import re
import sys import sys
from typing import Iterator, List, Optional import traceback
import warnings
from typing import Iterator, List, Optional, Tuple
from llnl.util.tty import color from llnl.util.tty import color
import spack.deptypes import spack.deptypes
import spack.error import spack.error
import spack.paths
import spack.spec import spack.spec
import spack.util.spack_yaml
import spack.version import spack.version
from spack.tokenize import Token, TokenBase, Tokenizer from spack.tokenize import Token, TokenBase, Tokenizer
@ -204,6 +208,32 @@ def __init__(self, tokens: List[Token], text: str):
super().__init__(message) super().__init__(message)
def _warn_about_variant_after_compiler(literal_str: str, issues: List[str]):
"""Issue a warning if variant or other token is preceded by a compiler token. The warning is
only issued if it's actionable: either we know the config file it originates from, or we have
call site that's not internal to Spack."""
ignore = [spack.paths.lib_path, spack.paths.bin_path]
mark = spack.util.spack_yaml.get_mark_from_yaml_data(literal_str)
issue_str = ", ".join(issues)
error = f"{issue_str} in `{literal_str}`"
# warning from config file
if mark:
warnings.warn(f"{mark.name}:{mark.line + 1}: {error}")
return
# warning from hopefully package.py
for frame in reversed(traceback.extract_stack()):
if frame.lineno and not any(frame.filename.startswith(path) for path in ignore):
warnings.warn_explicit(
error,
category=spack.error.SpackAPIWarning,
filename=frame.filename,
lineno=frame.lineno,
)
return
class SpecParser: class SpecParser:
"""Parse text into specs""" """Parse text into specs"""
@ -242,26 +272,31 @@ def add_dependency(dep, **edge_properties):
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 = SpecNodeParser(self.ctx, self.literal_str).parse(initial_spec) root_spec, parser_warnings = SpecNodeParser(self.ctx, self.literal_str).parse(initial_spec)
while True: while True:
if self.ctx.accept(SpecTokens.START_EDGE_PROPERTIES): if self.ctx.accept(SpecTokens.START_EDGE_PROPERTIES):
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("depflag", 0)
edge_properties.setdefault("virtuals", ()) edge_properties.setdefault("virtuals", ())
dependency = self._parse_node(root_spec) dependency, warnings = self._parse_node(root_spec)
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):
dependency = self._parse_node(root_spec) dependency, warnings = self._parse_node(root_spec)
parser_warnings.extend(warnings)
add_dependency(dependency, depflag=0, virtuals=()) add_dependency(dependency, depflag=0, virtuals=())
else: else:
break break
if parser_warnings:
_warn_about_variant_after_compiler(self.literal_str, parser_warnings)
return root_spec return root_spec
def _parse_node(self, root_spec): def _parse_node(self, root_spec):
dependency = SpecNodeParser(self.ctx, self.literal_str).parse() dependency, parser_warnings = SpecNodeParser(self.ctx, self.literal_str).parse()
if dependency is None: if dependency is None:
msg = ( msg = (
"the dependency sigil and any optional edge attributes must be followed by a " "the dependency sigil and any optional edge attributes must be followed by a "
@ -270,7 +305,7 @@ def _parse_node(self, root_spec):
raise SpecParsingError(msg, self.ctx.current_token, self.literal_str) raise SpecParsingError(msg, self.ctx.current_token, self.literal_str)
if root_spec.concrete: if root_spec.concrete:
raise spack.spec.RedundantSpecError(root_spec, "^" + str(dependency)) raise spack.spec.RedundantSpecError(root_spec, "^" + str(dependency))
return dependency return dependency, parser_warnings
def all_specs(self) -> List["spack.spec.Spec"]: def all_specs(self) -> List["spack.spec.Spec"]:
"""Return all the specs that remain to be parsed""" """Return all the specs that remain to be parsed"""
@ -290,7 +325,7 @@ def __init__(self, ctx, literal_str):
def parse( def parse(
self, initial_spec: Optional["spack.spec.Spec"] = None self, initial_spec: Optional["spack.spec.Spec"] = None
) -> Optional["spack.spec.Spec"]: ) -> Tuple["spack.spec.Spec", List[str]]:
"""Parse a single spec node from a stream of tokens """Parse a single spec node from a stream of tokens
Args: Args:
@ -299,12 +334,15 @@ def parse(
Return Return
The object passed as argument The object passed as argument
""" """
if not self.ctx.next_token or self.ctx.expect(SpecTokens.DEPENDENCY): parser_warnings: List[str] = []
return initial_spec last_compiler = None
if initial_spec is None: if initial_spec is None:
initial_spec = spack.spec.Spec() initial_spec = spack.spec.Spec()
if not self.ctx.next_token or self.ctx.expect(SpecTokens.DEPENDENCY):
return initial_spec, parser_warnings
# If we start with a package name we have a named spec, we cannot # If we start with a package name we have a named spec, we cannot
# accept another package name afterwards in a node # accept another package name afterwards in a node
if self.ctx.accept(SpecTokens.UNQUALIFIED_PACKAGE_NAME): if self.ctx.accept(SpecTokens.UNQUALIFIED_PACKAGE_NAME):
@ -318,7 +356,7 @@ def parse(
initial_spec.namespace = namespace initial_spec.namespace = namespace
elif self.ctx.accept(SpecTokens.FILENAME): elif self.ctx.accept(SpecTokens.FILENAME):
return FileParser(self.ctx).parse(initial_spec) return FileParser(self.ctx).parse(initial_spec), parser_warnings
def raise_parsing_error(string: str, cause: Optional[Exception] = None): def raise_parsing_error(string: str, cause: Optional[Exception] = None):
"""Raise a spec parsing error with token context.""" """Raise a spec parsing error with token context."""
@ -331,6 +369,12 @@ def add_flag(name: str, value: str, propagate: bool):
except Exception as e: except Exception as e:
raise_parsing_error(str(e), e) raise_parsing_error(str(e), e)
def warn_if_after_compiler(token: str):
"""Register a warning for %compiler followed by +variant that will in the future apply
to the compiler instead of the current root."""
if last_compiler:
parser_warnings.append(f"`{token}` should go before `{last_compiler}`")
while True: while True:
if self.ctx.accept(SpecTokens.COMPILER): if self.ctx.accept(SpecTokens.COMPILER):
if self.has_compiler: if self.has_compiler:
@ -339,6 +383,7 @@ def add_flag(name: str, value: str, propagate: bool):
compiler_name = self.ctx.current_token.value[1:] compiler_name = self.ctx.current_token.value[1:]
initial_spec.compiler = spack.spec.CompilerSpec(compiler_name.strip(), ":") initial_spec.compiler = spack.spec.CompilerSpec(compiler_name.strip(), ":")
self.has_compiler = True self.has_compiler = True
last_compiler = self.ctx.current_token.value
elif self.ctx.accept(SpecTokens.COMPILER_AND_VERSION): elif self.ctx.accept(SpecTokens.COMPILER_AND_VERSION):
if self.has_compiler: if self.has_compiler:
@ -349,6 +394,7 @@ def add_flag(name: str, value: str, propagate: bool):
compiler_name.strip(), compiler_version compiler_name.strip(), compiler_version
) )
self.has_compiler = True self.has_compiler = True
last_compiler = self.ctx.current_token.value
elif ( elif (
self.ctx.accept(SpecTokens.VERSION_HASH_PAIR) self.ctx.accept(SpecTokens.VERSION_HASH_PAIR)
@ -363,14 +409,17 @@ def add_flag(name: str, value: str, propagate: bool):
) )
initial_spec.attach_git_version_lookup() initial_spec.attach_git_version_lookup()
self.has_version = True self.has_version = True
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.BOOL_VARIANT): elif self.ctx.accept(SpecTokens.BOOL_VARIANT):
variant_value = self.ctx.current_token.value[0] == "+" variant_value = self.ctx.current_token.value[0] == "+"
add_flag(self.ctx.current_token.value[1:].strip(), variant_value, propagate=False) add_flag(self.ctx.current_token.value[1:].strip(), variant_value, propagate=False)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.PROPAGATED_BOOL_VARIANT): elif self.ctx.accept(SpecTokens.PROPAGATED_BOOL_VARIANT):
variant_value = self.ctx.current_token.value[0:2] == "++" variant_value = self.ctx.current_token.value[0:2] == "++"
add_flag(self.ctx.current_token.value[2:].strip(), variant_value, propagate=True) add_flag(self.ctx.current_token.value[2:].strip(), variant_value, propagate=True)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.KEY_VALUE_PAIR): elif self.ctx.accept(SpecTokens.KEY_VALUE_PAIR):
match = SPLIT_KVP.match(self.ctx.current_token.value) match = SPLIT_KVP.match(self.ctx.current_token.value)
@ -378,6 +427,7 @@ def add_flag(name: str, value: str, propagate: bool):
name, _, value = match.groups() name, _, value = match.groups()
add_flag(name, strip_quotes_and_unescape(value), propagate=False) add_flag(name, strip_quotes_and_unescape(value), propagate=False)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.accept(SpecTokens.PROPAGATED_KEY_VALUE_PAIR): elif self.ctx.accept(SpecTokens.PROPAGATED_KEY_VALUE_PAIR):
match = SPLIT_KVP.match(self.ctx.current_token.value) match = SPLIT_KVP.match(self.ctx.current_token.value)
@ -385,17 +435,19 @@ def add_flag(name: str, value: str, propagate: bool):
name, _, value = match.groups() name, _, value = match.groups()
add_flag(name, strip_quotes_and_unescape(value), propagate=True) add_flag(name, strip_quotes_and_unescape(value), propagate=True)
warn_if_after_compiler(self.ctx.current_token.value)
elif self.ctx.expect(SpecTokens.DAG_HASH): elif self.ctx.expect(SpecTokens.DAG_HASH):
if initial_spec.abstract_hash: if initial_spec.abstract_hash:
break break
self.ctx.accept(SpecTokens.DAG_HASH) self.ctx.accept(SpecTokens.DAG_HASH)
initial_spec.abstract_hash = self.ctx.current_token.value[1:] initial_spec.abstract_hash = self.ctx.current_token.value[1:]
warn_if_after_compiler(self.ctx.current_token.value)
else: else:
break break
return initial_spec return initial_spec, parser_warnings
class FileParser: class FileParser: