spec.parser / spec.token: improvements (#48063)
Follow-up to #47956 * Rename `token.py` -> `tokenize.py` * Rename `parser.py` -> `spec_parser.py` * Move common code related to iterating over tokens into `tokenize.py` * Add "unexpected character token" (i.e. `.`) to `SpecTokens` by default instead of having a separate tokenizer / regex.
This commit is contained in:
parent
396a701860
commit
687766b8ab
@ -178,8 +178,8 @@ Spec-related modules
|
|||||||
Contains :class:`~spack.spec.Spec`. Also implements most of the logic for concretization
|
Contains :class:`~spack.spec.Spec`. Also implements most of the logic for concretization
|
||||||
of specs.
|
of specs.
|
||||||
|
|
||||||
:mod:`spack.parser`
|
:mod:`spack.spec_parser`
|
||||||
Contains :class:`~spack.parser.SpecParser` and functions related to parsing specs.
|
Contains :class:`~spack.spec_parser.SpecParser` and functions related to parsing specs.
|
||||||
|
|
||||||
:mod:`spack.version`
|
:mod:`spack.version`
|
||||||
Implements a simple :class:`~spack.version.Version` class with simple
|
Implements a simple :class:`~spack.version.Version` class with simple
|
||||||
|
@ -24,12 +24,11 @@
|
|||||||
import spack.environment as ev
|
import spack.environment as ev
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.extensions
|
import spack.extensions
|
||||||
import spack.parser
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.spec
|
import spack.spec
|
||||||
|
import spack.spec_parser
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.token
|
|
||||||
import spack.traverse as traverse
|
import spack.traverse as traverse
|
||||||
import spack.user_environment as uenv
|
import spack.user_environment as uenv
|
||||||
import spack.util.spack_json as sjson
|
import spack.util.spack_json as sjson
|
||||||
@ -164,12 +163,12 @@ def quote_kvp(string: str) -> str:
|
|||||||
or ``name==``, and we assume the rest of the argument is the value. This covers the
|
or ``name==``, and we assume the rest of the argument is the value. This covers the
|
||||||
common cases of passign flags, e.g., ``cflags="-O2 -g"`` on the command line.
|
common cases of passign flags, e.g., ``cflags="-O2 -g"`` on the command line.
|
||||||
"""
|
"""
|
||||||
match = spack.parser.SPLIT_KVP.match(string)
|
match = spack.spec_parser.SPLIT_KVP.match(string)
|
||||||
if not match:
|
if not match:
|
||||||
return string
|
return string
|
||||||
|
|
||||||
key, delim, value = match.groups()
|
key, delim, value = match.groups()
|
||||||
return f"{key}{delim}{spack.token.quote_if_needed(value)}"
|
return f"{key}{delim}{spack.spec_parser.quote_if_needed(value)}"
|
||||||
|
|
||||||
|
|
||||||
def parse_specs(
|
def parse_specs(
|
||||||
@ -181,7 +180,7 @@ def parse_specs(
|
|||||||
args = [args] if isinstance(args, str) else args
|
args = [args] if isinstance(args, str) else args
|
||||||
arg_string = " ".join([quote_kvp(arg) for arg in args])
|
arg_string = " ".join([quote_kvp(arg) for arg in args])
|
||||||
|
|
||||||
specs = spack.parser.parse(arg_string)
|
specs = spack.spec_parser.parse(arg_string)
|
||||||
if not concretize:
|
if not concretize:
|
||||||
return specs
|
return specs
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.mirrors.mirror
|
import spack.mirrors.mirror
|
||||||
import spack.token
|
import spack.tokenize
|
||||||
import spack.util.web
|
import spack.util.web
|
||||||
|
|
||||||
from .image import ImageReference
|
from .image import ImageReference
|
||||||
@ -57,7 +57,7 @@ def dispatch_open(fullurl, data=None, timeout=None):
|
|||||||
quoted_string = rf'"(?:({qdtext}*)|{quoted_pair})*"'
|
quoted_string = rf'"(?:({qdtext}*)|{quoted_pair})*"'
|
||||||
|
|
||||||
|
|
||||||
class TokenType(spack.token.TokenBase):
|
class WwwAuthenticateTokens(spack.tokenize.TokenBase):
|
||||||
AUTH_PARAM = rf"({token}){BWS}={BWS}({token}|{quoted_string})"
|
AUTH_PARAM = rf"({token}){BWS}={BWS}({token}|{quoted_string})"
|
||||||
# TOKEN68 = r"([A-Za-z0-9\-._~+/]+=*)" # todo... support this?
|
# TOKEN68 = r"([A-Za-z0-9\-._~+/]+=*)" # todo... support this?
|
||||||
TOKEN = rf"{tchar}+"
|
TOKEN = rf"{tchar}+"
|
||||||
@ -68,9 +68,7 @@ class TokenType(spack.token.TokenBase):
|
|||||||
ANY = r"."
|
ANY = r"."
|
||||||
|
|
||||||
|
|
||||||
TOKEN_REGEXES = [rf"(?P<{token}>{token.regex})" for token in TokenType]
|
WWW_AUTHENTICATE_TOKENIZER = spack.tokenize.Tokenizer(WwwAuthenticateTokens)
|
||||||
|
|
||||||
ALL_TOKENS = re.compile("|".join(TOKEN_REGEXES))
|
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
class State(Enum):
|
||||||
@ -81,18 +79,6 @@ class State(Enum):
|
|||||||
AUTH_PARAM_OR_SCHEME = auto()
|
AUTH_PARAM_OR_SCHEME = auto()
|
||||||
|
|
||||||
|
|
||||||
def tokenize(input: str):
|
|
||||||
scanner = ALL_TOKENS.scanner(input) # type: ignore[attr-defined]
|
|
||||||
|
|
||||||
for match in iter(scanner.match, None): # type: ignore[var-annotated]
|
|
||||||
yield spack.token.Token(
|
|
||||||
TokenType.__members__[match.lastgroup], # type: ignore[attr-defined]
|
|
||||||
match.group(), # type: ignore[attr-defined]
|
|
||||||
match.start(), # type: ignore[attr-defined]
|
|
||||||
match.end(), # type: ignore[attr-defined]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Challenge:
|
class Challenge:
|
||||||
__slots__ = ["scheme", "params"]
|
__slots__ = ["scheme", "params"]
|
||||||
|
|
||||||
@ -128,7 +114,7 @@ def parse_www_authenticate(input: str):
|
|||||||
unquote = lambda s: _unquote(r"\1", s[1:-1])
|
unquote = lambda s: _unquote(r"\1", s[1:-1])
|
||||||
|
|
||||||
mode: State = State.CHALLENGE
|
mode: State = State.CHALLENGE
|
||||||
tokens = tokenize(input)
|
tokens = WWW_AUTHENTICATE_TOKENIZER.tokenize(input)
|
||||||
|
|
||||||
current_challenge = Challenge()
|
current_challenge = Challenge()
|
||||||
|
|
||||||
@ -141,36 +127,36 @@ def extract_auth_param(input: str) -> Tuple[str, str]:
|
|||||||
return key, value
|
return key, value
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
token: spack.token.Token = next(tokens)
|
token: spack.tokenize.Token = next(tokens)
|
||||||
|
|
||||||
if mode == State.CHALLENGE:
|
if mode == State.CHALLENGE:
|
||||||
if token.kind == TokenType.EOF:
|
if token.kind == WwwAuthenticateTokens.EOF:
|
||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
elif token.kind == TokenType.TOKEN:
|
elif token.kind == WwwAuthenticateTokens.TOKEN:
|
||||||
current_challenge.scheme = token.value
|
current_challenge.scheme = token.value
|
||||||
mode = State.AUTH_PARAM_LIST_START
|
mode = State.AUTH_PARAM_LIST_START
|
||||||
else:
|
else:
|
||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
|
|
||||||
elif mode == State.AUTH_PARAM_LIST_START:
|
elif mode == State.AUTH_PARAM_LIST_START:
|
||||||
if token.kind == TokenType.EOF:
|
if token.kind == WwwAuthenticateTokens.EOF:
|
||||||
challenges.append(current_challenge)
|
challenges.append(current_challenge)
|
||||||
break
|
break
|
||||||
elif token.kind == TokenType.COMMA:
|
elif token.kind == WwwAuthenticateTokens.COMMA:
|
||||||
# Challenge without param list, followed by another challenge.
|
# Challenge without param list, followed by another challenge.
|
||||||
challenges.append(current_challenge)
|
challenges.append(current_challenge)
|
||||||
current_challenge = Challenge()
|
current_challenge = Challenge()
|
||||||
mode = State.CHALLENGE
|
mode = State.CHALLENGE
|
||||||
elif token.kind == TokenType.SPACE:
|
elif token.kind == WwwAuthenticateTokens.SPACE:
|
||||||
# A space means it must be followed by param list
|
# A space means it must be followed by param list
|
||||||
mode = State.AUTH_PARAM
|
mode = State.AUTH_PARAM
|
||||||
else:
|
else:
|
||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
|
|
||||||
elif mode == State.AUTH_PARAM:
|
elif mode == State.AUTH_PARAM:
|
||||||
if token.kind == TokenType.EOF:
|
if token.kind == WwwAuthenticateTokens.EOF:
|
||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
elif token.kind == TokenType.AUTH_PARAM:
|
elif token.kind == WwwAuthenticateTokens.AUTH_PARAM:
|
||||||
key, value = extract_auth_param(token.value)
|
key, value = extract_auth_param(token.value)
|
||||||
current_challenge.params.append((key, value))
|
current_challenge.params.append((key, value))
|
||||||
mode = State.NEXT_IN_LIST
|
mode = State.NEXT_IN_LIST
|
||||||
@ -178,22 +164,22 @@ def extract_auth_param(input: str) -> Tuple[str, str]:
|
|||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
|
|
||||||
elif mode == State.NEXT_IN_LIST:
|
elif mode == State.NEXT_IN_LIST:
|
||||||
if token.kind == TokenType.EOF:
|
if token.kind == WwwAuthenticateTokens.EOF:
|
||||||
challenges.append(current_challenge)
|
challenges.append(current_challenge)
|
||||||
break
|
break
|
||||||
elif token.kind == TokenType.COMMA:
|
elif token.kind == WwwAuthenticateTokens.COMMA:
|
||||||
mode = State.AUTH_PARAM_OR_SCHEME
|
mode = State.AUTH_PARAM_OR_SCHEME
|
||||||
else:
|
else:
|
||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
|
|
||||||
elif mode == State.AUTH_PARAM_OR_SCHEME:
|
elif mode == State.AUTH_PARAM_OR_SCHEME:
|
||||||
if token.kind == TokenType.EOF:
|
if token.kind == WwwAuthenticateTokens.EOF:
|
||||||
raise ValueError(token)
|
raise ValueError(token)
|
||||||
elif token.kind == TokenType.TOKEN:
|
elif token.kind == WwwAuthenticateTokens.TOKEN:
|
||||||
challenges.append(current_challenge)
|
challenges.append(current_challenge)
|
||||||
current_challenge = Challenge(token.value)
|
current_challenge = Challenge(token.value)
|
||||||
mode = State.AUTH_PARAM_LIST_START
|
mode = State.AUTH_PARAM_LIST_START
|
||||||
elif token.kind == TokenType.AUTH_PARAM:
|
elif token.kind == WwwAuthenticateTokens.AUTH_PARAM:
|
||||||
key, value = extract_auth_param(token.value)
|
key, value = extract_auth_param(token.value)
|
||||||
current_challenge.params.append((key, value))
|
current_challenge.params.append((key, value))
|
||||||
mode = State.NEXT_IN_LIST
|
mode = State.NEXT_IN_LIST
|
||||||
|
@ -26,14 +26,14 @@ def _validate_spec(validator, is_spec, instance, schema):
|
|||||||
"""Check if the attributes on instance are valid specs."""
|
"""Check if the attributes on instance are valid specs."""
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
import spack.parser
|
import spack.spec_parser
|
||||||
|
|
||||||
if not validator.is_type(instance, "object"):
|
if not validator.is_type(instance, "object"):
|
||||||
return
|
return
|
||||||
|
|
||||||
for spec_str in instance:
|
for spec_str in instance:
|
||||||
try:
|
try:
|
||||||
spack.parser.parse(spec_str)
|
spack.spec_parser.parse(spec_str)
|
||||||
except SpecSyntaxError as e:
|
except SpecSyntaxError as e:
|
||||||
yield jsonschema.ValidationError(str(e))
|
yield jsonschema.ValidationError(str(e))
|
||||||
|
|
||||||
|
@ -77,14 +77,13 @@
|
|||||||
import spack.deptypes as dt
|
import spack.deptypes as dt
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.hash_types as ht
|
import spack.hash_types as ht
|
||||||
import spack.parser
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.platforms
|
import spack.platforms
|
||||||
import spack.provider_index
|
import spack.provider_index
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.solver
|
import spack.solver
|
||||||
|
import spack.spec_parser
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.token
|
|
||||||
import spack.traverse as traverse
|
import spack.traverse as traverse
|
||||||
import spack.util.executable
|
import spack.util.executable
|
||||||
import spack.util.hash
|
import spack.util.hash
|
||||||
@ -613,7 +612,7 @@ def __init__(self, *args):
|
|||||||
# If there is one argument, it's either another CompilerSpec
|
# If there is one argument, it's either another CompilerSpec
|
||||||
# to copy or a string to parse
|
# to copy or a string to parse
|
||||||
if isinstance(arg, str):
|
if isinstance(arg, str):
|
||||||
spec = spack.parser.parse_one_or_raise(f"%{arg}")
|
spec = spack.spec_parser.parse_one_or_raise(f"%{arg}")
|
||||||
self.name = spec.compiler.name
|
self.name = spec.compiler.name
|
||||||
self.versions = spec.compiler.versions
|
self.versions = spec.compiler.versions
|
||||||
|
|
||||||
@ -951,11 +950,13 @@ def __str__(self):
|
|||||||
for flag_type, flags in sorted_items:
|
for flag_type, flags in sorted_items:
|
||||||
normal = [f for f in flags if not f.propagate]
|
normal = [f for f in flags if not f.propagate]
|
||||||
if normal:
|
if normal:
|
||||||
result += f" {flag_type}={spack.token.quote_if_needed(' '.join(normal))}"
|
value = spack.spec_parser.quote_if_needed(" ".join(normal))
|
||||||
|
result += f" {flag_type}={value}"
|
||||||
|
|
||||||
propagated = [f for f in flags if f.propagate]
|
propagated = [f for f in flags if f.propagate]
|
||||||
if propagated:
|
if propagated:
|
||||||
result += f" {flag_type}=={spack.token.quote_if_needed(' '.join(propagated))}"
|
value = spack.spec_parser.quote_if_needed(" ".join(propagated))
|
||||||
|
result += f" {flag_type}=={value}"
|
||||||
|
|
||||||
# TODO: somehow add this space only if something follows in Spec.format()
|
# TODO: somehow add this space only if something follows in Spec.format()
|
||||||
if sorted_items:
|
if sorted_items:
|
||||||
@ -1514,7 +1515,7 @@ def __init__(
|
|||||||
self._build_spec = None
|
self._build_spec = None
|
||||||
|
|
||||||
if isinstance(spec_like, str):
|
if isinstance(spec_like, str):
|
||||||
spack.parser.parse_one_or_raise(spec_like, self)
|
spack.spec_parser.parse_one_or_raise(spec_like, self)
|
||||||
|
|
||||||
elif spec_like is not None:
|
elif spec_like is not None:
|
||||||
raise TypeError("Can't make spec out of %s" % type(spec_like))
|
raise TypeError("Can't make spec out of %s" % type(spec_like))
|
||||||
|
@ -57,9 +57,11 @@
|
|||||||
specs to avoid ambiguity. Both are provided because ~ can cause shell
|
specs to avoid ambiguity. Both are provided because ~ can cause shell
|
||||||
expansion when it is the first character in an id typed on the command line.
|
expansion when it is the first character in an id typed on the command line.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
from typing import Iterator, List, Match, Optional
|
import sys
|
||||||
|
from typing import Iterator, List, Optional
|
||||||
|
|
||||||
from llnl.util.tty import color
|
from llnl.util.tty import color
|
||||||
|
|
||||||
@ -67,7 +69,7 @@
|
|||||||
import spack.error
|
import spack.error
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.version
|
import spack.version
|
||||||
from spack.token import FILENAME, Token, TokenBase, strip_quotes_and_unescape
|
from spack.tokenize import Token, TokenBase, Tokenizer
|
||||||
|
|
||||||
#: Valid name for specs and variants. Here we are not using
|
#: Valid name for specs and variants. Here we are not using
|
||||||
#: the previous "w[\w.-]*" since that would match most
|
#: the previous "w[\w.-]*" since that would match most
|
||||||
@ -96,8 +98,20 @@
|
|||||||
#: Regex with groups to use for splitting (optionally propagated) key-value pairs
|
#: Regex with groups to use for splitting (optionally propagated) key-value pairs
|
||||||
SPLIT_KVP = re.compile(rf"^({NAME})(==?)(.*)$")
|
SPLIT_KVP = re.compile(rf"^({NAME})(==?)(.*)$")
|
||||||
|
|
||||||
|
#: 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}\
|
||||||
|
WINDOWS_FILENAME = r"(?:\.|[a-zA-Z0-9-_]*\\|[a-zA-Z]:\\)(?:[a-zA-Z0-9-_\.\\]*)(?:\.json|\.yaml)"
|
||||||
|
UNIX_FILENAME = r"(?:\.|\/|[a-zA-Z0-9-_]*\/)(?:[a-zA-Z0-9-_\.\/]*)(?:\.json|\.yaml)"
|
||||||
|
FILENAME = WINDOWS_FILENAME if sys.platform == "win32" else UNIX_FILENAME
|
||||||
|
|
||||||
class TokenType(TokenBase):
|
#: Regex to strip quotes. Group 2 will be the unquoted string.
|
||||||
|
STRIP_QUOTES = re.compile(r"^(['\"])(.*)\1$")
|
||||||
|
|
||||||
|
#: Values that match this (e.g., variants, flags) can be left unquoted in Spack output
|
||||||
|
NO_QUOTES_NEEDED = re.compile(r"^[a-zA-Z0-9,/_.-]+$")
|
||||||
|
|
||||||
|
|
||||||
|
class SpecTokens(TokenBase):
|
||||||
"""Enumeration of the different token kinds in the spec grammar.
|
"""Enumeration of the different token kinds in the spec grammar.
|
||||||
Order of declaration is extremely important, since text containing specs is parsed with a
|
Order of declaration is extremely important, since text containing specs is parsed with a
|
||||||
single regex obtained by ``"|".join(...)`` of all the regex in the order of declaration.
|
single regex obtained by ``"|".join(...)`` of all the regex in the order of declaration.
|
||||||
@ -128,56 +142,24 @@ class TokenType(TokenBase):
|
|||||||
DAG_HASH = rf"(?:/(?:{HASH}))"
|
DAG_HASH = rf"(?:/(?:{HASH}))"
|
||||||
# White spaces
|
# White spaces
|
||||||
WS = r"(?:\s+)"
|
WS = r"(?:\s+)"
|
||||||
|
# Unexpected character(s)
|
||||||
|
|
||||||
class ErrorTokenType(TokenBase):
|
|
||||||
"""Enum with regexes for error analysis"""
|
|
||||||
|
|
||||||
# Unexpected character
|
|
||||||
UNEXPECTED = r"(?:.[\s]*)"
|
UNEXPECTED = r"(?:.[\s]*)"
|
||||||
|
|
||||||
|
|
||||||
#: List of all the regexes used to match spec parts, in order of precedence
|
#: Tokenizer that includes all the regexes in the SpecTokens enum
|
||||||
TOKEN_REGEXES = [rf"(?P<{token}>{token.regex})" for token in TokenType]
|
SPEC_TOKENIZER = Tokenizer(SpecTokens)
|
||||||
#: List of all valid regexes followed by error analysis regexes
|
|
||||||
ERROR_HANDLING_REGEXES = TOKEN_REGEXES + [
|
|
||||||
rf"(?P<{token}>{token.regex})" for token in ErrorTokenType
|
|
||||||
]
|
|
||||||
#: Regex to scan a valid text
|
|
||||||
ALL_TOKENS = re.compile("|".join(TOKEN_REGEXES))
|
|
||||||
#: Regex to analyze an invalid text
|
|
||||||
ANALYSIS_REGEX = re.compile("|".join(ERROR_HANDLING_REGEXES))
|
|
||||||
|
|
||||||
|
|
||||||
def tokenize(text: str) -> Iterator[Token]:
|
def tokenize(text: str) -> Iterator[Token]:
|
||||||
"""Return a token generator from the text passed as input.
|
"""Return a token generator from the text passed as input.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
SpecTokenizationError: if we can't tokenize anymore, but didn't reach the
|
SpecTokenizationError: when unexpected characters are found in the text
|
||||||
end of the input text.
|
|
||||||
"""
|
"""
|
||||||
scanner = ALL_TOKENS.scanner(text) # type: ignore[attr-defined]
|
for token in SPEC_TOKENIZER.tokenize(text):
|
||||||
match: Optional[Match] = None
|
if token.kind == SpecTokens.UNEXPECTED:
|
||||||
for match in iter(scanner.match, None):
|
raise SpecTokenizationError(list(SPEC_TOKENIZER.tokenize(text)), text)
|
||||||
# The following two assertions are to help mypy
|
yield token
|
||||||
msg = (
|
|
||||||
"unexpected value encountered during parsing. Please submit a bug report "
|
|
||||||
"at https://github.com/spack/spack/issues/new/choose"
|
|
||||||
)
|
|
||||||
assert match is not None, msg
|
|
||||||
assert match.lastgroup is not None, msg
|
|
||||||
yield Token(
|
|
||||||
TokenType.__members__[match.lastgroup], match.group(), match.start(), match.end()
|
|
||||||
)
|
|
||||||
|
|
||||||
if match is None and not text:
|
|
||||||
# We just got an empty string
|
|
||||||
return
|
|
||||||
|
|
||||||
if match is None or match.end() != len(text):
|
|
||||||
scanner = ANALYSIS_REGEX.scanner(text) # type: ignore[attr-defined]
|
|
||||||
matches = [m for m in iter(scanner.match, None)] # type: ignore[var-annotated]
|
|
||||||
raise SpecTokenizationError(matches, text)
|
|
||||||
|
|
||||||
|
|
||||||
class TokenContext:
|
class TokenContext:
|
||||||
@ -195,7 +177,7 @@ def advance(self):
|
|||||||
"""Advance one token"""
|
"""Advance one token"""
|
||||||
self.current_token, self.next_token = self.next_token, next(self.token_stream, None)
|
self.current_token, self.next_token = self.next_token, next(self.token_stream, None)
|
||||||
|
|
||||||
def accept(self, kind: TokenType):
|
def accept(self, kind: SpecTokens):
|
||||||
"""If the next token is of the specified kind, advance the stream and return True.
|
"""If the next token is of the specified kind, advance the stream and return True.
|
||||||
Otherwise return False.
|
Otherwise return False.
|
||||||
"""
|
"""
|
||||||
@ -204,23 +186,20 @@ def accept(self, kind: TokenType):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def expect(self, *kinds: TokenType):
|
def expect(self, *kinds: SpecTokens):
|
||||||
return self.next_token and self.next_token.kind in kinds
|
return self.next_token and self.next_token.kind in kinds
|
||||||
|
|
||||||
|
|
||||||
class SpecTokenizationError(spack.error.SpecSyntaxError):
|
class SpecTokenizationError(spack.error.SpecSyntaxError):
|
||||||
"""Syntax error in a spec string"""
|
"""Syntax error in a spec string"""
|
||||||
|
|
||||||
def __init__(self, matches, text):
|
def __init__(self, tokens: List[Token], text: str):
|
||||||
message = "unexpected tokens in the spec string\n"
|
message = f"unexpected characters in the spec string\n{text}\n"
|
||||||
message += f"{text}"
|
|
||||||
|
|
||||||
underline = "\n"
|
underline = ""
|
||||||
for match in matches:
|
for token in tokens:
|
||||||
if match.lastgroup == str(ErrorTokenType.UNEXPECTED):
|
is_error = token.kind == SpecTokens.UNEXPECTED
|
||||||
underline += f"{'^' * (match.end() - match.start())}"
|
underline += ("^" if is_error else " ") * (token.end - token.start)
|
||||||
continue
|
|
||||||
underline += f"{' ' * (match.end() - match.start())}"
|
|
||||||
|
|
||||||
message += color.colorize(f"@*r{{{underline}}}")
|
message += color.colorize(f"@*r{{{underline}}}")
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
@ -233,13 +212,13 @@ class SpecParser:
|
|||||||
|
|
||||||
def __init__(self, literal_str: str):
|
def __init__(self, literal_str: str):
|
||||||
self.literal_str = literal_str
|
self.literal_str = literal_str
|
||||||
self.ctx = TokenContext(filter(lambda x: x.kind != TokenType.WS, tokenize(literal_str)))
|
self.ctx = TokenContext(filter(lambda x: x.kind != SpecTokens.WS, tokenize(literal_str)))
|
||||||
|
|
||||||
def tokens(self) -> List[Token]:
|
def tokens(self) -> List[Token]:
|
||||||
"""Return the entire list of token from the initial text. White spaces are
|
"""Return the entire list of token from the initial text. White spaces are
|
||||||
filtered out.
|
filtered out.
|
||||||
"""
|
"""
|
||||||
return list(filter(lambda x: x.kind != TokenType.WS, tokenize(self.literal_str)))
|
return list(filter(lambda x: x.kind != SpecTokens.WS, tokenize(self.literal_str)))
|
||||||
|
|
||||||
def next_spec(
|
def next_spec(
|
||||||
self, initial_spec: Optional["spack.spec.Spec"] = None
|
self, initial_spec: Optional["spack.spec.Spec"] = None
|
||||||
@ -266,14 +245,14 @@ def add_dependency(dep, **edge_properties):
|
|||||||
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 = SpecNodeParser(self.ctx, self.literal_str).parse(initial_spec)
|
||||||
while True:
|
while True:
|
||||||
if self.ctx.accept(TokenType.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 = self._parse_node(root_spec)
|
||||||
add_dependency(dependency, **edge_properties)
|
add_dependency(dependency, **edge_properties)
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.DEPENDENCY):
|
elif self.ctx.accept(SpecTokens.DEPENDENCY):
|
||||||
dependency = self._parse_node(root_spec)
|
dependency = self._parse_node(root_spec)
|
||||||
add_dependency(dependency, depflag=0, virtuals=())
|
add_dependency(dependency, depflag=0, virtuals=())
|
||||||
|
|
||||||
@ -321,7 +300,7 @@ def parse(
|
|||||||
Return
|
Return
|
||||||
The object passed as argument
|
The object passed as argument
|
||||||
"""
|
"""
|
||||||
if not self.ctx.next_token or self.ctx.expect(TokenType.DEPENDENCY):
|
if not self.ctx.next_token or self.ctx.expect(SpecTokens.DEPENDENCY):
|
||||||
return initial_spec
|
return initial_spec
|
||||||
|
|
||||||
if initial_spec is None:
|
if initial_spec is None:
|
||||||
@ -329,17 +308,17 @@ def parse(
|
|||||||
|
|
||||||
# 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(TokenType.UNQUALIFIED_PACKAGE_NAME):
|
if self.ctx.accept(SpecTokens.UNQUALIFIED_PACKAGE_NAME):
|
||||||
initial_spec.name = self.ctx.current_token.value
|
initial_spec.name = self.ctx.current_token.value
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.FULLY_QUALIFIED_PACKAGE_NAME):
|
elif self.ctx.accept(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME):
|
||||||
parts = self.ctx.current_token.value.split(".")
|
parts = self.ctx.current_token.value.split(".")
|
||||||
name = parts[-1]
|
name = parts[-1]
|
||||||
namespace = ".".join(parts[:-1])
|
namespace = ".".join(parts[:-1])
|
||||||
initial_spec.name = name
|
initial_spec.name = name
|
||||||
initial_spec.namespace = namespace
|
initial_spec.namespace = namespace
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.FILENAME):
|
elif self.ctx.accept(SpecTokens.FILENAME):
|
||||||
return FileParser(self.ctx).parse(initial_spec)
|
return FileParser(self.ctx).parse(initial_spec)
|
||||||
|
|
||||||
def raise_parsing_error(string: str, cause: Optional[Exception] = None):
|
def raise_parsing_error(string: str, cause: Optional[Exception] = None):
|
||||||
@ -354,7 +333,7 @@ def add_flag(name: str, value: str, propagate: bool):
|
|||||||
raise_parsing_error(str(e), e)
|
raise_parsing_error(str(e), e)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if self.ctx.accept(TokenType.COMPILER):
|
if self.ctx.accept(SpecTokens.COMPILER):
|
||||||
if self.has_compiler:
|
if self.has_compiler:
|
||||||
raise_parsing_error("Spec cannot have multiple compilers")
|
raise_parsing_error("Spec cannot have multiple compilers")
|
||||||
|
|
||||||
@ -362,7 +341,7 @@ def add_flag(name: str, value: str, propagate: bool):
|
|||||||
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
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.COMPILER_AND_VERSION):
|
elif self.ctx.accept(SpecTokens.COMPILER_AND_VERSION):
|
||||||
if self.has_compiler:
|
if self.has_compiler:
|
||||||
raise_parsing_error("Spec cannot have multiple compilers")
|
raise_parsing_error("Spec cannot have multiple compilers")
|
||||||
|
|
||||||
@ -373,9 +352,9 @@ def add_flag(name: str, value: str, propagate: bool):
|
|||||||
self.has_compiler = True
|
self.has_compiler = True
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
self.ctx.accept(TokenType.VERSION_HASH_PAIR)
|
self.ctx.accept(SpecTokens.VERSION_HASH_PAIR)
|
||||||
or self.ctx.accept(TokenType.GIT_VERSION)
|
or self.ctx.accept(SpecTokens.GIT_VERSION)
|
||||||
or self.ctx.accept(TokenType.VERSION)
|
or self.ctx.accept(SpecTokens.VERSION)
|
||||||
):
|
):
|
||||||
if self.has_version:
|
if self.has_version:
|
||||||
raise_parsing_error("Spec cannot have multiple versions")
|
raise_parsing_error("Spec cannot have multiple versions")
|
||||||
@ -386,32 +365,32 @@ 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
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.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)
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.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)
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.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)
|
||||||
assert match, "SPLIT_KVP and KEY_VALUE_PAIR do not agree."
|
assert match, "SPLIT_KVP and KEY_VALUE_PAIR do not agree."
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
elif self.ctx.accept(TokenType.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)
|
||||||
assert match, "SPLIT_KVP and PROPAGATED_KEY_VALUE_PAIR do not agree."
|
assert match, "SPLIT_KVP and PROPAGATED_KEY_VALUE_PAIR do not agree."
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
elif self.ctx.expect(TokenType.DAG_HASH):
|
elif self.ctx.expect(SpecTokens.DAG_HASH):
|
||||||
if initial_spec.abstract_hash:
|
if initial_spec.abstract_hash:
|
||||||
break
|
break
|
||||||
self.ctx.accept(TokenType.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:]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -461,7 +440,7 @@ def __init__(self, ctx, literal_str):
|
|||||||
def parse(self):
|
def parse(self):
|
||||||
attributes = {}
|
attributes = {}
|
||||||
while True:
|
while True:
|
||||||
if self.ctx.accept(TokenType.KEY_VALUE_PAIR):
|
if self.ctx.accept(SpecTokens.KEY_VALUE_PAIR):
|
||||||
name, value = self.ctx.current_token.value.split("=", maxsplit=1)
|
name, value = self.ctx.current_token.value.split("=", maxsplit=1)
|
||||||
name = name.strip("'\" ")
|
name = name.strip("'\" ")
|
||||||
value = value.strip("'\" ").split(",")
|
value = value.strip("'\" ").split(",")
|
||||||
@ -473,7 +452,7 @@ def parse(self):
|
|||||||
)
|
)
|
||||||
raise SpecParsingError(msg, self.ctx.current_token, self.literal_str)
|
raise SpecParsingError(msg, self.ctx.current_token, self.literal_str)
|
||||||
# TODO: Add code to accept bool variants here as soon as use variants are implemented
|
# TODO: Add code to accept bool variants here as soon as use variants are implemented
|
||||||
elif self.ctx.accept(TokenType.END_EDGE_PROPERTIES):
|
elif self.ctx.accept(SpecTokens.END_EDGE_PROPERTIES):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
msg = "unexpected token in edge attributes"
|
msg = "unexpected token in edge attributes"
|
||||||
@ -536,3 +515,33 @@ def __init__(self, message, token, text):
|
|||||||
underline = f"\n{' '*token.start}{'^'*(token.end - token.start)}"
|
underline = f"\n{' '*token.start}{'^'*(token.end - token.start)}"
|
||||||
message += color.colorize(f"@*r{{{underline}}}")
|
message += color.colorize(f"@*r{{{underline}}}")
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_quotes_and_unescape(string: str) -> str:
|
||||||
|
"""Remove surrounding single or double quotes from string, if present."""
|
||||||
|
match = STRIP_QUOTES.match(string)
|
||||||
|
if not match:
|
||||||
|
return string
|
||||||
|
|
||||||
|
# replace any escaped quotes with bare quotes
|
||||||
|
quote, result = match.groups()
|
||||||
|
return result.replace(rf"\{quote}", quote)
|
||||||
|
|
||||||
|
|
||||||
|
def quote_if_needed(value: str) -> str:
|
||||||
|
"""Add quotes around the value if it requires quotes.
|
||||||
|
|
||||||
|
This will add quotes around the value unless it matches ``NO_QUOTES_NEEDED``.
|
||||||
|
|
||||||
|
This adds:
|
||||||
|
* single quotes by default
|
||||||
|
* double quotes around any value that contains single quotes
|
||||||
|
|
||||||
|
If double quotes are used, we json-escape the string. That is, we escape ``\\``,
|
||||||
|
``"``, and control codes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if NO_QUOTES_NEEDED.match(value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
return json.dumps(value) if "'" in value else f"'{value}'"
|
@ -338,10 +338,10 @@ def test_install_conflicts(conflict_spec):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_packages", "mock_archive", "mock_fetch", "install_mockery")
|
@pytest.mark.usefixtures("mock_packages", "mock_archive", "mock_fetch", "install_mockery")
|
||||||
def test_install_invalid_spec(invalid_spec):
|
def test_install_invalid_spec():
|
||||||
# Make sure that invalid specs raise a SpackError
|
# Make sure that invalid specs raise a SpackError
|
||||||
with pytest.raises(SpecSyntaxError, match="unexpected tokens"):
|
with pytest.raises(SpecSyntaxError, match="unexpected characters"):
|
||||||
install(invalid_spec)
|
install("conflict%~")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("noop_install", "mock_packages", "config")
|
@pytest.mark.usefixtures("noop_install", "mock_packages", "config")
|
||||||
|
@ -146,7 +146,7 @@ def test_spec_parse_error():
|
|||||||
spec("1.15:")
|
spec("1.15:")
|
||||||
|
|
||||||
# make sure the error is formatted properly
|
# make sure the error is formatted properly
|
||||||
error_msg = "unexpected tokens in the spec string\n1.15:\n ^"
|
error_msg = "unexpected characters in the spec string\n1.15:\n ^"
|
||||||
assert error_msg in str(e.value)
|
assert error_msg in str(e.value)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1676,12 +1676,6 @@ def conflict_spec(request):
|
|||||||
return request.param
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=["conflict%~"])
|
|
||||||
def invalid_spec(request):
|
|
||||||
"""Specs that do not parse cleanly due to invalid formatting."""
|
|
||||||
return request.param
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def mock_test_repo(tmpdir_factory):
|
def mock_test_repo(tmpdir_factory):
|
||||||
"""Create an empty repository."""
|
"""Create an empty repository."""
|
||||||
|
@ -65,7 +65,7 @@ def test_validate_spec(validate_spec_schema):
|
|||||||
|
|
||||||
# Check that invalid data throws
|
# Check that invalid data throws
|
||||||
data["^python@3.7@"] = "baz"
|
data["^python@3.7@"] = "baz"
|
||||||
with pytest.raises(jsonschema.ValidationError, match="unexpected tokens"):
|
with pytest.raises(jsonschema.ValidationError, match="unexpected characters"):
|
||||||
v.validate(data)
|
v.validate(data)
|
||||||
|
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ def test_module_suffixes(module_suffixes_schema):
|
|||||||
v = spack.schema.Validator(module_suffixes_schema)
|
v = spack.schema.Validator(module_suffixes_schema)
|
||||||
data = {"tcl": {"all": {"suffixes": {"^python@2.7@": "py2.7"}}}}
|
data = {"tcl": {"all": {"suffixes": {"^python@2.7@": "py2.7"}}}}
|
||||||
|
|
||||||
with pytest.raises(jsonschema.ValidationError, match="unexpected tokens"):
|
with pytest.raises(jsonschema.ValidationError, match="unexpected characters"):
|
||||||
v.validate(data)
|
v.validate(data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@
|
|||||||
import spack.deptypes as dt
|
import spack.deptypes as dt
|
||||||
import spack.directives
|
import spack.directives
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.parser
|
|
||||||
import spack.paths
|
import spack.paths
|
||||||
import spack.solver.asp
|
import spack.solver.asp
|
||||||
import spack.spec
|
import spack.spec
|
||||||
|
import spack.spec_parser
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.variant
|
import spack.variant
|
||||||
import spack.version as vn
|
import spack.version as vn
|
||||||
@ -639,7 +639,7 @@ def test_satisfied_namespace(self):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_propagate_reserved_variant_names(self, spec_string):
|
def test_propagate_reserved_variant_names(self, spec_string):
|
||||||
with pytest.raises(spack.parser.SpecParsingError, match="Propagation"):
|
with pytest.raises(spack.spec_parser.SpecParsingError, match="Propagation"):
|
||||||
Spec(spec_string)
|
Spec(spec_string)
|
||||||
|
|
||||||
def test_unsatisfiable_multi_value_variant(self, default_mock_concretization):
|
def test_unsatisfiable_multi_value_variant(self, default_mock_concretization):
|
||||||
@ -1004,11 +1004,11 @@ def test_spec_formatting_bad_formats(self, default_mock_concretization, fmt_str)
|
|||||||
|
|
||||||
def test_combination_of_wildcard_or_none(self):
|
def test_combination_of_wildcard_or_none(self):
|
||||||
# Test that using 'none' and another value raises
|
# Test that using 'none' and another value raises
|
||||||
with pytest.raises(spack.parser.SpecParsingError, match="cannot be combined"):
|
with pytest.raises(spack.spec_parser.SpecParsingError, match="cannot be combined"):
|
||||||
Spec("multivalue-variant foo=none,bar")
|
Spec("multivalue-variant foo=none,bar")
|
||||||
|
|
||||||
# Test that using wildcard and another value raises
|
# Test that using wildcard and another value raises
|
||||||
with pytest.raises(spack.parser.SpecParsingError, match="cannot be combined"):
|
with pytest.raises(spack.spec_parser.SpecParsingError, match="cannot be combined"):
|
||||||
Spec("multivalue-variant foo=*,bar")
|
Spec("multivalue-variant foo=*,bar")
|
||||||
|
|
||||||
def test_errors_in_variant_directive(self):
|
def test_errors_in_variant_directive(self):
|
||||||
|
@ -14,8 +14,15 @@
|
|||||||
import spack.platforms.test
|
import spack.platforms.test
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.spec
|
import spack.spec
|
||||||
from spack.parser import SpecParser, SpecParsingError, SpecTokenizationError, TokenType
|
from spack.spec_parser import (
|
||||||
from spack.token import UNIX_FILENAME, WINDOWS_FILENAME, Token
|
UNIX_FILENAME,
|
||||||
|
WINDOWS_FILENAME,
|
||||||
|
SpecParser,
|
||||||
|
SpecParsingError,
|
||||||
|
SpecTokenizationError,
|
||||||
|
SpecTokens,
|
||||||
|
)
|
||||||
|
from spack.tokenize import Token
|
||||||
|
|
||||||
FAIL_ON_WINDOWS = pytest.mark.xfail(
|
FAIL_ON_WINDOWS = pytest.mark.xfail(
|
||||||
sys.platform == "win32",
|
sys.platform == "win32",
|
||||||
@ -30,7 +37,7 @@
|
|||||||
|
|
||||||
def simple_package_name(name):
|
def simple_package_name(name):
|
||||||
"""A simple package name in canonical form"""
|
"""A simple package name in canonical form"""
|
||||||
return name, [Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value=name)], name
|
return name, [Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value=name)], name
|
||||||
|
|
||||||
|
|
||||||
def dependency_with_version(text):
|
def dependency_with_version(text):
|
||||||
@ -39,17 +46,17 @@ def dependency_with_version(text):
|
|||||||
return (
|
return (
|
||||||
text,
|
text,
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value=root.strip()),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value=root.strip()),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value=dependency.strip()),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value=dependency.strip()),
|
||||||
Token(TokenType.VERSION, value=f"@{version}"),
|
Token(SpecTokens.VERSION, value=f"@{version}"),
|
||||||
],
|
],
|
||||||
text,
|
text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def compiler_with_version_range(text):
|
def compiler_with_version_range(text):
|
||||||
return text, [Token(TokenType.COMPILER_AND_VERSION, value=text)], text
|
return text, [Token(SpecTokens.COMPILER_AND_VERSION, value=text)], text
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@ -81,40 +88,40 @@ 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(TokenType.COMPILER, value="%intel")], "%intel"),
|
("%intel", [Token(SpecTokens.COMPILER, value="%intel")], "%intel"),
|
||||||
("@2.7", [Token(TokenType.VERSION, value="@2.7")], "@2.7"),
|
("@2.7", [Token(SpecTokens.VERSION, value="@2.7")], "@2.7"),
|
||||||
("@2.7:", [Token(TokenType.VERSION, value="@2.7:")], "@2.7:"),
|
("@2.7:", [Token(SpecTokens.VERSION, value="@2.7:")], "@2.7:"),
|
||||||
("@:2.7", [Token(TokenType.VERSION, value="@:2.7")], "@:2.7"),
|
("@:2.7", [Token(SpecTokens.VERSION, value="@:2.7")], "@:2.7"),
|
||||||
("+foo", [Token(TokenType.BOOL_VARIANT, value="+foo")], "+foo"),
|
("+foo", [Token(SpecTokens.BOOL_VARIANT, value="+foo")], "+foo"),
|
||||||
("~foo", [Token(TokenType.BOOL_VARIANT, value="~foo")], "~foo"),
|
("~foo", [Token(SpecTokens.BOOL_VARIANT, value="~foo")], "~foo"),
|
||||||
("-foo", [Token(TokenType.BOOL_VARIANT, value="-foo")], "~foo"),
|
("-foo", [Token(SpecTokens.BOOL_VARIANT, value="-foo")], "~foo"),
|
||||||
(
|
(
|
||||||
"platform=test",
|
"platform=test",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="platform=test")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="platform=test")],
|
||||||
"arch=test-None-None",
|
"arch=test-None-None",
|
||||||
),
|
),
|
||||||
# Multiple tokens anonymous specs
|
# Multiple tokens anonymous specs
|
||||||
(
|
(
|
||||||
"languages=go @4.2:",
|
"languages=go @4.2:",
|
||||||
[
|
[
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="languages=go"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="languages=go"),
|
||||||
Token(TokenType.VERSION, value="@4.2:"),
|
Token(SpecTokens.VERSION, value="@4.2:"),
|
||||||
],
|
],
|
||||||
"@4.2: languages=go",
|
"@4.2: languages=go",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"@4.2: languages=go",
|
"@4.2: languages=go",
|
||||||
[
|
[
|
||||||
Token(TokenType.VERSION, value="@4.2:"),
|
Token(SpecTokens.VERSION, value="@4.2:"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="languages=go"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="languages=go"),
|
||||||
],
|
],
|
||||||
"@4.2: languages=go",
|
"@4.2: languages=go",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"^zlib",
|
"^zlib",
|
||||||
[
|
[
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
||||||
],
|
],
|
||||||
"^zlib",
|
"^zlib",
|
||||||
),
|
),
|
||||||
@ -122,31 +129,31 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
"openmpi ^hwloc",
|
"openmpi ^hwloc",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="hwloc"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="hwloc"),
|
||||||
],
|
],
|
||||||
"openmpi ^hwloc",
|
"openmpi ^hwloc",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"openmpi ^hwloc ^libunwind",
|
"openmpi ^hwloc ^libunwind",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="hwloc"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="hwloc"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="libunwind"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="libunwind"),
|
||||||
],
|
],
|
||||||
"openmpi ^hwloc ^libunwind",
|
"openmpi ^hwloc ^libunwind",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"openmpi ^hwloc^libunwind",
|
"openmpi ^hwloc^libunwind",
|
||||||
[ # White spaces are tested
|
[ # White spaces are tested
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="hwloc"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="hwloc"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="libunwind"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="libunwind"),
|
||||||
],
|
],
|
||||||
"openmpi ^hwloc ^libunwind",
|
"openmpi ^hwloc ^libunwind",
|
||||||
),
|
),
|
||||||
@ -154,9 +161,9 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
"foo %bar@1.0 @2.0",
|
"foo %bar@1.0 @2.0",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="foo"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="foo"),
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="%bar@1.0"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="%bar@1.0"),
|
||||||
Token(TokenType.VERSION, value="@2.0"),
|
Token(SpecTokens.VERSION, value="@2.0"),
|
||||||
],
|
],
|
||||||
"foo@2.0%bar@1.0",
|
"foo@2.0%bar@1.0",
|
||||||
),
|
),
|
||||||
@ -169,32 +176,32 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1+debug~qt_4 ^stackwalker@8.1_1e",
|
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1+debug~qt_4 ^stackwalker@8.1_1e",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich_foo"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich_foo"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
Token(TokenType.VERSION, value="@1.2:1.4,1.6"),
|
Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"),
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="%intel@12.1"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+debug"),
|
Token(SpecTokens.BOOL_VARIANT, value="+debug"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
||||||
Token(TokenType.VERSION, value="@8.1_1e"),
|
Token(SpecTokens.VERSION, value="@8.1_1e"),
|
||||||
],
|
],
|
||||||
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1+debug~qt_4 ^stackwalker@8.1_1e",
|
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1+debug~qt_4 ^stackwalker@8.1_1e",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1~qt_4 debug=2 ^stackwalker@8.1_1e",
|
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1~qt_4 debug=2 ^stackwalker@8.1_1e",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich_foo"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich_foo"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
Token(TokenType.VERSION, value="@1.2:1.4,1.6"),
|
Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"),
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="%intel@12.1"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="debug=2"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="debug=2"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
||||||
Token(TokenType.VERSION, value="@8.1_1e"),
|
Token(SpecTokens.VERSION, value="@8.1_1e"),
|
||||||
],
|
],
|
||||||
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1~qt_4 debug=2 ^stackwalker@8.1_1e",
|
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1~qt_4 debug=2 ^stackwalker@8.1_1e",
|
||||||
),
|
),
|
||||||
@ -202,17 +209,17 @@ def _specfile_for(spec_str, filename):
|
|||||||
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags=-O3 +debug~qt_4 "
|
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags=-O3 +debug~qt_4 "
|
||||||
"^stackwalker@8.1_1e",
|
"^stackwalker@8.1_1e",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich_foo"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich_foo"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
Token(TokenType.VERSION, value="@1.2:1.4,1.6"),
|
Token(SpecTokens.VERSION, value="@1.2:1.4,1.6"),
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="%intel@12.1"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="cppflags=-O3"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="cppflags=-O3"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+debug"),
|
Token(SpecTokens.BOOL_VARIANT, value="+debug"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
||||||
Token(TokenType.VERSION, value="@8.1_1e"),
|
Token(SpecTokens.VERSION, value="@8.1_1e"),
|
||||||
],
|
],
|
||||||
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags=-O3 +debug~qt_4 "
|
"mvapich_foo ^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags=-O3 +debug~qt_4 "
|
||||||
"^stackwalker@8.1_1e",
|
"^stackwalker@8.1_1e",
|
||||||
@ -221,51 +228,51 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
"yaml-cpp@0.1.8%intel@12.1 ^boost@3.1.4",
|
"yaml-cpp@0.1.8%intel@12.1 ^boost@3.1.4",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="yaml-cpp"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="yaml-cpp"),
|
||||||
Token(TokenType.VERSION, value="@0.1.8"),
|
Token(SpecTokens.VERSION, value="@0.1.8"),
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="%intel@12.1"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="%intel@12.1"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="boost"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="boost"),
|
||||||
Token(TokenType.VERSION, value="@3.1.4"),
|
Token(SpecTokens.VERSION, value="@3.1.4"),
|
||||||
],
|
],
|
||||||
"yaml-cpp@0.1.8%intel@12.1 ^boost@3.1.4",
|
"yaml-cpp@0.1.8%intel@12.1 ^boost@3.1.4",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"builtin.yaml-cpp%gcc",
|
r"builtin.yaml-cpp%gcc",
|
||||||
[
|
[
|
||||||
Token(TokenType.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
|
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
|
||||||
Token(TokenType.COMPILER, value="%gcc"),
|
Token(SpecTokens.COMPILER, value="%gcc"),
|
||||||
],
|
],
|
||||||
"yaml-cpp%gcc",
|
"yaml-cpp%gcc",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"testrepo.yaml-cpp%gcc",
|
r"testrepo.yaml-cpp%gcc",
|
||||||
[
|
[
|
||||||
Token(TokenType.FULLY_QUALIFIED_PACKAGE_NAME, value="testrepo.yaml-cpp"),
|
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="testrepo.yaml-cpp"),
|
||||||
Token(TokenType.COMPILER, value="%gcc"),
|
Token(SpecTokens.COMPILER, value="%gcc"),
|
||||||
],
|
],
|
||||||
"yaml-cpp%gcc",
|
"yaml-cpp%gcc",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"builtin.yaml-cpp@0.1.8%gcc@7.2.0 ^boost@3.1.4",
|
r"builtin.yaml-cpp@0.1.8%gcc@7.2.0 ^boost@3.1.4",
|
||||||
[
|
[
|
||||||
Token(TokenType.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
|
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
|
||||||
Token(TokenType.VERSION, value="@0.1.8"),
|
Token(SpecTokens.VERSION, value="@0.1.8"),
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="%gcc@7.2.0"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="%gcc@7.2.0"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="boost"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="boost"),
|
||||||
Token(TokenType.VERSION, value="@3.1.4"),
|
Token(SpecTokens.VERSION, value="@3.1.4"),
|
||||||
],
|
],
|
||||||
"yaml-cpp@0.1.8%gcc@7.2.0 ^boost@3.1.4",
|
"yaml-cpp@0.1.8%gcc@7.2.0 ^boost@3.1.4",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"builtin.yaml-cpp ^testrepo.boost ^zlib",
|
r"builtin.yaml-cpp ^testrepo.boost ^zlib",
|
||||||
[
|
[
|
||||||
Token(TokenType.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
|
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="builtin.yaml-cpp"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.FULLY_QUALIFIED_PACKAGE_NAME, value="testrepo.boost"),
|
Token(SpecTokens.FULLY_QUALIFIED_PACKAGE_NAME, value="testrepo.boost"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
||||||
],
|
],
|
||||||
"yaml-cpp ^boost ^zlib",
|
"yaml-cpp ^boost ^zlib",
|
||||||
),
|
),
|
||||||
@ -273,60 +280,60 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
r"mvapich ^stackwalker ^_openmpi", # Dependencies are reordered
|
r"mvapich ^stackwalker ^_openmpi", # Dependencies are reordered
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="stackwalker"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
],
|
],
|
||||||
"mvapich ^_openmpi ^stackwalker",
|
"mvapich ^_openmpi ^stackwalker",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"y~f+e~d+c~b+a", # Variants are reordered
|
r"y~f+e~d+c~b+a", # Variants are reordered
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~f"),
|
Token(SpecTokens.BOOL_VARIANT, value="~f"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+e"),
|
Token(SpecTokens.BOOL_VARIANT, value="+e"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~d"),
|
Token(SpecTokens.BOOL_VARIANT, value="~d"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+c"),
|
Token(SpecTokens.BOOL_VARIANT, value="+c"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~b"),
|
Token(SpecTokens.BOOL_VARIANT, value="~b"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+a"),
|
Token(SpecTokens.BOOL_VARIANT, value="+a"),
|
||||||
],
|
],
|
||||||
"y+a~b+c~d+e~f",
|
"y+a~b+c~d+e~f",
|
||||||
),
|
),
|
||||||
("@:", [Token(TokenType.VERSION, value="@:")], r""),
|
("@:", [Token(SpecTokens.VERSION, value="@:")], r""),
|
||||||
("@1.6,1.2:1.4", [Token(TokenType.VERSION, value="@1.6,1.2:1.4")], r"@1.2:1.4,1.6"),
|
("@1.6,1.2:1.4", [Token(SpecTokens.VERSION, value="@1.6,1.2:1.4")], r"@1.2:1.4,1.6"),
|
||||||
(
|
(
|
||||||
r"os=fe", # Various translations associated with the architecture
|
r"os=fe", # Various translations associated with the architecture
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="os=fe")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="os=fe")],
|
||||||
"arch=test-redhat6-None",
|
"arch=test-redhat6-None",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"os=default_os",
|
r"os=default_os",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="os=default_os")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="os=default_os")],
|
||||||
"arch=test-debian6-None",
|
"arch=test-debian6-None",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"target=be",
|
r"target=be",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="target=be")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="target=be")],
|
||||||
f"arch=test-None-{spack.platforms.test.Test.default}",
|
f"arch=test-None-{spack.platforms.test.Test.default}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"target=default_target",
|
r"target=default_target",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="target=default_target")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="target=default_target")],
|
||||||
f"arch=test-None-{spack.platforms.test.Test.default}",
|
f"arch=test-None-{spack.platforms.test.Test.default}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"platform=linux",
|
r"platform=linux",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="platform=linux")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="platform=linux")],
|
||||||
r"arch=linux-None-None",
|
r"arch=linux-None-None",
|
||||||
),
|
),
|
||||||
# Version hash pair
|
# Version hash pair
|
||||||
(
|
(
|
||||||
rf"develop-branch-version@{'abc12'*8}=develop",
|
rf"develop-branch-version@{'abc12'*8}=develop",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="develop-branch-version"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="develop-branch-version"),
|
||||||
Token(TokenType.VERSION_HASH_PAIR, value=f"@{'abc12'*8}=develop"),
|
Token(SpecTokens.VERSION_HASH_PAIR, value=f"@{'abc12'*8}=develop"),
|
||||||
],
|
],
|
||||||
rf"develop-branch-version@{'abc12'*8}=develop",
|
rf"develop-branch-version@{'abc12'*8}=develop",
|
||||||
),
|
),
|
||||||
@ -334,40 +341,40 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
r"x ^y@foo ^y@foo",
|
r"x ^y@foo ^y@foo",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="x"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="x"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.VERSION, value="@foo"),
|
Token(SpecTokens.VERSION, value="@foo"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.VERSION, value="@foo"),
|
Token(SpecTokens.VERSION, value="@foo"),
|
||||||
],
|
],
|
||||||
r"x ^y@foo",
|
r"x ^y@foo",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"x ^y@foo ^y+bar",
|
r"x ^y@foo ^y+bar",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="x"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="x"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.VERSION, value="@foo"),
|
Token(SpecTokens.VERSION, value="@foo"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+bar"),
|
Token(SpecTokens.BOOL_VARIANT, value="+bar"),
|
||||||
],
|
],
|
||||||
r"x ^y@foo+bar",
|
r"x ^y@foo+bar",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"x ^y@foo +bar ^y@foo",
|
r"x ^y@foo +bar ^y@foo",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="x"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="x"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.VERSION, value="@foo"),
|
Token(SpecTokens.VERSION, value="@foo"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+bar"),
|
Token(SpecTokens.BOOL_VARIANT, value="+bar"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="y"),
|
||||||
Token(TokenType.VERSION, value="@foo"),
|
Token(SpecTokens.VERSION, value="@foo"),
|
||||||
],
|
],
|
||||||
r"x ^y@foo+bar",
|
r"x ^y@foo+bar",
|
||||||
),
|
),
|
||||||
@ -375,43 +382,43 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
r"_openmpi +debug-qt_4", # Parse as a single bool variant
|
r"_openmpi +debug-qt_4", # Parse as a single bool variant
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+debug-qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="+debug-qt_4"),
|
||||||
],
|
],
|
||||||
r"_openmpi+debug-qt_4",
|
r"_openmpi+debug-qt_4",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"_openmpi +debug -qt_4", # Parse as two variants
|
r"_openmpi +debug -qt_4", # Parse as two variants
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+debug"),
|
Token(SpecTokens.BOOL_VARIANT, value="+debug"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="-qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="-qt_4"),
|
||||||
],
|
],
|
||||||
r"_openmpi+debug~qt_4",
|
r"_openmpi+debug~qt_4",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"_openmpi +debug~qt_4", # Parse as two variants
|
r"_openmpi +debug~qt_4", # Parse as two variants
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="_openmpi"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+debug"),
|
Token(SpecTokens.BOOL_VARIANT, value="+debug"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="~qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="~qt_4"),
|
||||||
],
|
],
|
||||||
r"_openmpi+debug~qt_4",
|
r"_openmpi+debug~qt_4",
|
||||||
),
|
),
|
||||||
# Key value pairs with ":" and "," in the value
|
# Key value pairs with ":" and "," in the value
|
||||||
(
|
(
|
||||||
r"target=:broadwell,icelake",
|
r"target=:broadwell,icelake",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="target=:broadwell,icelake")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="target=:broadwell,icelake")],
|
||||||
r"arch=None-None-:broadwell,icelake",
|
r"arch=None-None-:broadwell,icelake",
|
||||||
),
|
),
|
||||||
# Hash pair version followed by a variant
|
# Hash pair version followed by a variant
|
||||||
(
|
(
|
||||||
f"develop-branch-version@git.{'a' * 40}=develop+var1+var2",
|
f"develop-branch-version@git.{'a' * 40}=develop+var1+var2",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="develop-branch-version"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="develop-branch-version"),
|
||||||
Token(TokenType.VERSION_HASH_PAIR, value=f"@git.{'a' * 40}=develop"),
|
Token(SpecTokens.VERSION_HASH_PAIR, value=f"@git.{'a' * 40}=develop"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+var1"),
|
Token(SpecTokens.BOOL_VARIANT, value="+var1"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+var2"),
|
Token(SpecTokens.BOOL_VARIANT, value="+var2"),
|
||||||
],
|
],
|
||||||
f"develop-branch-version@git.{'a' * 40}=develop+var1+var2",
|
f"develop-branch-version@git.{'a' * 40}=develop+var1+var2",
|
||||||
),
|
),
|
||||||
@ -422,98 +429,101 @@ def _specfile_for(spec_str, filename):
|
|||||||
compiler_with_version_range("%gcc@10.1.0,12.2.1:"),
|
compiler_with_version_range("%gcc@10.1.0,12.2.1:"),
|
||||||
compiler_with_version_range("%gcc@:8.4.3,10.2.1:12.1.0"),
|
compiler_with_version_range("%gcc@:8.4.3,10.2.1:12.1.0"),
|
||||||
# Special key value arguments
|
# Special key value arguments
|
||||||
("dev_path=*", [Token(TokenType.KEY_VALUE_PAIR, value="dev_path=*")], "dev_path='*'"),
|
("dev_path=*", [Token(SpecTokens.KEY_VALUE_PAIR, value="dev_path=*")], "dev_path='*'"),
|
||||||
(
|
(
|
||||||
"dev_path=none",
|
"dev_path=none",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="dev_path=none")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="dev_path=none")],
|
||||||
"dev_path=none",
|
"dev_path=none",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"dev_path=../relpath/work",
|
"dev_path=../relpath/work",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="dev_path=../relpath/work")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="dev_path=../relpath/work")],
|
||||||
"dev_path=../relpath/work",
|
"dev_path=../relpath/work",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"dev_path=/abspath/work",
|
"dev_path=/abspath/work",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="dev_path=/abspath/work")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="dev_path=/abspath/work")],
|
||||||
"dev_path=/abspath/work",
|
"dev_path=/abspath/work",
|
||||||
),
|
),
|
||||||
# One liner for flags like 'a=b=c' that are injected
|
# One liner for flags like 'a=b=c' that are injected
|
||||||
(
|
(
|
||||||
"cflags=a=b=c",
|
"cflags=a=b=c",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="cflags=a=b=c")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=a=b=c")],
|
||||||
"cflags='a=b=c'",
|
"cflags='a=b=c'",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"cflags=a=b=c",
|
"cflags=a=b=c",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="cflags=a=b=c")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=a=b=c")],
|
||||||
"cflags='a=b=c'",
|
"cflags='a=b=c'",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"cflags=a=b=c+~",
|
"cflags=a=b=c+~",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="cflags=a=b=c+~")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=a=b=c+~")],
|
||||||
"cflags='a=b=c+~'",
|
"cflags='a=b=c+~'",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"cflags=-Wl,a,b,c",
|
"cflags=-Wl,a,b,c",
|
||||||
[Token(TokenType.KEY_VALUE_PAIR, value="cflags=-Wl,a,b,c")],
|
[Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=-Wl,a,b,c")],
|
||||||
"cflags=-Wl,a,b,c",
|
"cflags=-Wl,a,b,c",
|
||||||
),
|
),
|
||||||
# Multi quoted
|
# Multi quoted
|
||||||
(
|
(
|
||||||
'cflags=="-O3 -g"',
|
'cflags=="-O3 -g"',
|
||||||
[Token(TokenType.PROPAGATED_KEY_VALUE_PAIR, value='cflags=="-O3 -g"')],
|
[Token(SpecTokens.PROPAGATED_KEY_VALUE_PAIR, value='cflags=="-O3 -g"')],
|
||||||
"cflags=='-O3 -g'",
|
"cflags=='-O3 -g'",
|
||||||
),
|
),
|
||||||
# Whitespace is allowed in version lists
|
# Whitespace is allowed in version lists
|
||||||
("@1.2:1.4 , 1.6 ", [Token(TokenType.VERSION, value="@1.2:1.4 , 1.6")], "@1.2:1.4,1.6"),
|
("@1.2:1.4 , 1.6 ", [Token(SpecTokens.VERSION, value="@1.2:1.4 , 1.6")], "@1.2:1.4,1.6"),
|
||||||
# But not in ranges. `a@1:` and `b` are separate specs, not a single `a@1:b`.
|
# But not in ranges. `a@1:` and `b` are separate specs, not a single `a@1:b`.
|
||||||
(
|
(
|
||||||
"a@1: b",
|
"a@1: b",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="a"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="a"),
|
||||||
Token(TokenType.VERSION, value="@1:"),
|
Token(SpecTokens.VERSION, value="@1:"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="b"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="b"),
|
||||||
],
|
],
|
||||||
"a@1:",
|
"a@1:",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"% intel @ 12.1:12.6 + debug",
|
"% intel @ 12.1:12.6 + debug",
|
||||||
[
|
[
|
||||||
Token(TokenType.COMPILER_AND_VERSION, value="% intel @ 12.1:12.6"),
|
Token(SpecTokens.COMPILER_AND_VERSION, value="% intel @ 12.1:12.6"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+ debug"),
|
Token(SpecTokens.BOOL_VARIANT, value="+ debug"),
|
||||||
],
|
],
|
||||||
"%intel@12.1:12.6+debug",
|
"%intel@12.1:12.6+debug",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"@ 12.1:12.6 + debug - qt_4",
|
"@ 12.1:12.6 + debug - qt_4",
|
||||||
[
|
[
|
||||||
Token(TokenType.VERSION, value="@ 12.1:12.6"),
|
Token(SpecTokens.VERSION, value="@ 12.1:12.6"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+ debug"),
|
Token(SpecTokens.BOOL_VARIANT, value="+ debug"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="- qt_4"),
|
Token(SpecTokens.BOOL_VARIANT, value="- qt_4"),
|
||||||
],
|
],
|
||||||
"@12.1:12.6+debug~qt_4",
|
"@12.1:12.6+debug~qt_4",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"@10.4.0:10,11.3.0:target=aarch64:",
|
"@10.4.0:10,11.3.0:target=aarch64:",
|
||||||
[
|
[
|
||||||
Token(TokenType.VERSION, value="@10.4.0:10,11.3.0:"),
|
Token(SpecTokens.VERSION, value="@10.4.0:10,11.3.0:"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="target=aarch64:"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="target=aarch64:"),
|
||||||
],
|
],
|
||||||
"@10.4.0:10,11.3.0: arch=None-None-aarch64:",
|
"@10.4.0:10,11.3.0: arch=None-None-aarch64:",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"@:0.4 % nvhpc",
|
"@:0.4 % nvhpc",
|
||||||
[Token(TokenType.VERSION, value="@:0.4"), Token(TokenType.COMPILER, value="% nvhpc")],
|
[
|
||||||
|
Token(SpecTokens.VERSION, value="@:0.4"),
|
||||||
|
Token(SpecTokens.COMPILER, value="% nvhpc"),
|
||||||
|
],
|
||||||
"@:0.4%nvhpc",
|
"@:0.4%nvhpc",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"^[virtuals=mpi] openmpi",
|
"^[virtuals=mpi] openmpi",
|
||||||
[
|
[
|
||||||
Token(TokenType.START_EDGE_PROPERTIES, value="^["),
|
Token(SpecTokens.START_EDGE_PROPERTIES, value="^["),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="virtuals=mpi"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=mpi"),
|
||||||
Token(TokenType.END_EDGE_PROPERTIES, value="]"),
|
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
||||||
],
|
],
|
||||||
"^[virtuals=mpi] openmpi",
|
"^[virtuals=mpi] openmpi",
|
||||||
),
|
),
|
||||||
@ -521,48 +531,48 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
"^[virtuals=mpi] openmpi+foo ^[virtuals=lapack] openmpi+bar",
|
"^[virtuals=mpi] openmpi+foo ^[virtuals=lapack] openmpi+bar",
|
||||||
[
|
[
|
||||||
Token(TokenType.START_EDGE_PROPERTIES, value="^["),
|
Token(SpecTokens.START_EDGE_PROPERTIES, value="^["),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="virtuals=mpi"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=mpi"),
|
||||||
Token(TokenType.END_EDGE_PROPERTIES, value="]"),
|
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+foo"),
|
Token(SpecTokens.BOOL_VARIANT, value="+foo"),
|
||||||
Token(TokenType.START_EDGE_PROPERTIES, value="^["),
|
Token(SpecTokens.START_EDGE_PROPERTIES, value="^["),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="virtuals=lapack"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="virtuals=lapack"),
|
||||||
Token(TokenType.END_EDGE_PROPERTIES, value="]"),
|
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="openmpi"),
|
||||||
Token(TokenType.BOOL_VARIANT, value="+bar"),
|
Token(SpecTokens.BOOL_VARIANT, value="+bar"),
|
||||||
],
|
],
|
||||||
"^[virtuals=lapack,mpi] openmpi+bar+foo",
|
"^[virtuals=lapack,mpi] openmpi+bar+foo",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"^[deptypes=link,build] zlib",
|
"^[deptypes=link,build] zlib",
|
||||||
[
|
[
|
||||||
Token(TokenType.START_EDGE_PROPERTIES, value="^["),
|
Token(SpecTokens.START_EDGE_PROPERTIES, value="^["),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="deptypes=link,build"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="deptypes=link,build"),
|
||||||
Token(TokenType.END_EDGE_PROPERTIES, value="]"),
|
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
||||||
],
|
],
|
||||||
"^[deptypes=build,link] zlib",
|
"^[deptypes=build,link] zlib",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"^[deptypes=link] zlib ^[deptypes=build] zlib",
|
"^[deptypes=link] zlib ^[deptypes=build] zlib",
|
||||||
[
|
[
|
||||||
Token(TokenType.START_EDGE_PROPERTIES, value="^["),
|
Token(SpecTokens.START_EDGE_PROPERTIES, value="^["),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="deptypes=link"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="deptypes=link"),
|
||||||
Token(TokenType.END_EDGE_PROPERTIES, value="]"),
|
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
||||||
Token(TokenType.START_EDGE_PROPERTIES, value="^["),
|
Token(SpecTokens.START_EDGE_PROPERTIES, value="^["),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="deptypes=build"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="deptypes=build"),
|
||||||
Token(TokenType.END_EDGE_PROPERTIES, value="]"),
|
Token(SpecTokens.END_EDGE_PROPERTIES, value="]"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="zlib"),
|
||||||
],
|
],
|
||||||
"^[deptypes=link] zlib ^[deptypes=build] zlib",
|
"^[deptypes=link] zlib ^[deptypes=build] zlib",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"git-test@git.foo/bar",
|
"git-test@git.foo/bar",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, "git-test"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "git-test"),
|
||||||
Token(TokenType.GIT_VERSION, "@git.foo/bar"),
|
Token(SpecTokens.GIT_VERSION, "@git.foo/bar"),
|
||||||
],
|
],
|
||||||
"git-test@git.foo/bar",
|
"git-test@git.foo/bar",
|
||||||
),
|
),
|
||||||
@ -570,24 +580,24 @@ def _specfile_for(spec_str, filename):
|
|||||||
(
|
(
|
||||||
"zlib ++foo",
|
"zlib ++foo",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, "zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
|
||||||
Token(TokenType.PROPAGATED_BOOL_VARIANT, "++foo"),
|
Token(SpecTokens.PROPAGATED_BOOL_VARIANT, "++foo"),
|
||||||
],
|
],
|
||||||
"zlib++foo",
|
"zlib++foo",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zlib ~~foo",
|
"zlib ~~foo",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, "zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
|
||||||
Token(TokenType.PROPAGATED_BOOL_VARIANT, "~~foo"),
|
Token(SpecTokens.PROPAGATED_BOOL_VARIANT, "~~foo"),
|
||||||
],
|
],
|
||||||
"zlib~~foo",
|
"zlib~~foo",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zlib foo==bar",
|
"zlib foo==bar",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, "zlib"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, "zlib"),
|
||||||
Token(TokenType.PROPAGATED_KEY_VALUE_PAIR, "foo==bar"),
|
Token(SpecTokens.PROPAGATED_KEY_VALUE_PAIR, "foo==bar"),
|
||||||
],
|
],
|
||||||
"zlib foo==bar",
|
"zlib foo==bar",
|
||||||
),
|
),
|
||||||
@ -605,49 +615,49 @@ def test_parse_single_spec(spec_str, tokens, expected_roundtrip, mock_git_test_p
|
|||||||
(
|
(
|
||||||
"mvapich emacs",
|
"mvapich emacs",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
||||||
],
|
],
|
||||||
["mvapich", "emacs"],
|
["mvapich", "emacs"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"mvapich cppflags='-O3 -fPIC' emacs",
|
"mvapich cppflags='-O3 -fPIC' emacs",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="cppflags='-O3 -fPIC'"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="cppflags='-O3 -fPIC'"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
||||||
],
|
],
|
||||||
["mvapich cppflags='-O3 -fPIC'", "emacs"],
|
["mvapich cppflags='-O3 -fPIC'", "emacs"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"mvapich cppflags=-O3 emacs",
|
"mvapich cppflags=-O3 emacs",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="cppflags=-O3"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="cppflags=-O3"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
||||||
],
|
],
|
||||||
["mvapich cppflags=-O3", "emacs"],
|
["mvapich cppflags=-O3", "emacs"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"mvapich emacs @1.1.1 %intel cflags=-O3",
|
"mvapich emacs @1.1.1 %intel cflags=-O3",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
||||||
Token(TokenType.VERSION, value="@1.1.1"),
|
Token(SpecTokens.VERSION, value="@1.1.1"),
|
||||||
Token(TokenType.COMPILER, value="%intel"),
|
Token(SpecTokens.COMPILER, value="%intel"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value="cflags=-O3"),
|
Token(SpecTokens.KEY_VALUE_PAIR, value="cflags=-O3"),
|
||||||
],
|
],
|
||||||
["mvapich", "emacs @1.1.1 %intel cflags=-O3"],
|
["mvapich", "emacs @1.1.1 %intel cflags=-O3"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'mvapich cflags="-O3 -fPIC" emacs^ncurses%intel',
|
'mvapich cflags="-O3 -fPIC" emacs^ncurses%intel',
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="mvapich"),
|
||||||
Token(TokenType.KEY_VALUE_PAIR, value='cflags="-O3 -fPIC"'),
|
Token(SpecTokens.KEY_VALUE_PAIR, value='cflags="-O3 -fPIC"'),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="emacs"),
|
||||||
Token(TokenType.DEPENDENCY, value="^"),
|
Token(SpecTokens.DEPENDENCY, value="^"),
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="ncurses"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="ncurses"),
|
||||||
Token(TokenType.COMPILER, value="%intel"),
|
Token(SpecTokens.COMPILER, value="%intel"),
|
||||||
],
|
],
|
||||||
['mvapich cflags="-O3 -fPIC"', "emacs ^ncurses%intel"],
|
['mvapich cflags="-O3 -fPIC"', "emacs ^ncurses%intel"],
|
||||||
),
|
),
|
||||||
@ -741,20 +751,20 @@ def test_error_reporting(text, expected_in_error):
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"text,tokens",
|
"text,tokens",
|
||||||
[
|
[
|
||||||
("/abcde", [Token(TokenType.DAG_HASH, value="/abcde")]),
|
("/abcde", [Token(SpecTokens.DAG_HASH, value="/abcde")]),
|
||||||
(
|
(
|
||||||
"foo/abcde",
|
"foo/abcde",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="foo"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="foo"),
|
||||||
Token(TokenType.DAG_HASH, value="/abcde"),
|
Token(SpecTokens.DAG_HASH, value="/abcde"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"foo@1.2.3 /abcde",
|
"foo@1.2.3 /abcde",
|
||||||
[
|
[
|
||||||
Token(TokenType.UNQUALIFIED_PACKAGE_NAME, value="foo"),
|
Token(SpecTokens.UNQUALIFIED_PACKAGE_NAME, value="foo"),
|
||||||
Token(TokenType.VERSION, value="@1.2.3"),
|
Token(SpecTokens.VERSION, value="@1.2.3"),
|
||||||
Token(TokenType.DAG_HASH, value="/abcde"),
|
Token(SpecTokens.DAG_HASH, value="/abcde"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
"""Generic token support."""
|
|
||||||
import enum
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
IS_WINDOWS = sys.platform == "win32"
|
|
||||||
|
|
||||||
#: 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}\
|
|
||||||
WINDOWS_FILENAME = r"(?:\.|[a-zA-Z0-9-_]*\\|[a-zA-Z]:\\)(?:[a-zA-Z0-9-_\.\\]*)(?:\.json|\.yaml)"
|
|
||||||
UNIX_FILENAME = r"(?:\.|\/|[a-zA-Z0-9-_]*\/)(?:[a-zA-Z0-9-_\.\/]*)(?:\.json|\.yaml)"
|
|
||||||
if not IS_WINDOWS:
|
|
||||||
FILENAME = UNIX_FILENAME
|
|
||||||
else:
|
|
||||||
FILENAME = WINDOWS_FILENAME
|
|
||||||
|
|
||||||
#: Values that match this (e.g., variants, flags) can be left unquoted in Spack output
|
|
||||||
NO_QUOTES_NEEDED = re.compile(r"^[a-zA-Z0-9,/_.-]+$")
|
|
||||||
|
|
||||||
#: Regex to strip quotes. Group 2 will be the unquoted string.
|
|
||||||
STRIP_QUOTES = re.compile(r"^(['\"])(.*)\1$")
|
|
||||||
|
|
||||||
|
|
||||||
def strip_quotes_and_unescape(string: str) -> str:
|
|
||||||
"""Remove surrounding single or double quotes from string, if present."""
|
|
||||||
match = STRIP_QUOTES.match(string)
|
|
||||||
if not match:
|
|
||||||
return string
|
|
||||||
|
|
||||||
# replace any escaped quotes with bare quotes
|
|
||||||
quote, result = match.groups()
|
|
||||||
return result.replace(rf"\{quote}", quote)
|
|
||||||
|
|
||||||
|
|
||||||
def quote_if_needed(value: str) -> str:
|
|
||||||
"""Add quotes around the value if it requires quotes.
|
|
||||||
|
|
||||||
This will add quotes around the value unless it matches ``NO_QUOTES_NEEDED``.
|
|
||||||
|
|
||||||
This adds:
|
|
||||||
* single quotes by default
|
|
||||||
* double quotes around any value that contains single quotes
|
|
||||||
|
|
||||||
If double quotes are used, we json-escape the string. That is, we escape ``\\``,
|
|
||||||
``"``, and control codes.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if NO_QUOTES_NEEDED.match(value):
|
|
||||||
return value
|
|
||||||
|
|
||||||
return json.dumps(value) if "'" in value else f"'{value}'"
|
|
||||||
|
|
||||||
|
|
||||||
class TokenBase(enum.Enum):
|
|
||||||
"""Base class for an enum type with a regex value"""
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
value = len(cls.__members__) + 1
|
|
||||||
obj = object.__new__(cls)
|
|
||||||
obj._value_ = value
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __init__(self, regex):
|
|
||||||
self.regex = regex
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self._name_}"
|
|
||||||
|
|
||||||
|
|
||||||
class Token:
|
|
||||||
"""Represents tokens; generated from input by lexer and fed to parse()."""
|
|
||||||
|
|
||||||
__slots__ = "kind", "value", "start", "end"
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, kind: TokenBase, value: str, start: Optional[int] = None, end: Optional[int] = None
|
|
||||||
):
|
|
||||||
self.kind = kind
|
|
||||||
self.value = value
|
|
||||||
self.start = start
|
|
||||||
self.end = end
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"({self.kind}, {self.value})"
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.kind == other.kind) and (self.value == other.value)
|
|
69
lib/spack/spack/tokenize.py
Normal file
69
lib/spack/spack/tokenize.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
"""This module provides building blocks for tokenizing strings. Users can define tokens by
|
||||||
|
inheriting from TokenBase and defining tokens as ordered enum members. The Tokenizer class can then
|
||||||
|
be used to iterate over tokens in a string."""
|
||||||
|
import enum
|
||||||
|
import re
|
||||||
|
from typing import Generator, Match, Optional, Type
|
||||||
|
|
||||||
|
|
||||||
|
class TokenBase(enum.Enum):
|
||||||
|
"""Base class for an enum type with a regex value"""
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
value = len(cls.__members__) + 1
|
||||||
|
obj = object.__new__(cls)
|
||||||
|
obj._value_ = value
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __init__(self, regex):
|
||||||
|
self.regex = regex
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self._name_}"
|
||||||
|
|
||||||
|
|
||||||
|
class Token:
|
||||||
|
"""Represents tokens; generated from input by lexer and fed to parse()."""
|
||||||
|
|
||||||
|
__slots__ = "kind", "value", "start", "end"
|
||||||
|
|
||||||
|
def __init__(self, kind: TokenBase, value: str, start: int = 0, end: int = 0):
|
||||||
|
self.kind = kind
|
||||||
|
self.value = value
|
||||||
|
self.start = start
|
||||||
|
self.end = end
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"({self.kind}, {self.value})"
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (self.kind == other.kind) and (self.value == other.value)
|
||||||
|
|
||||||
|
|
||||||
|
class Tokenizer:
|
||||||
|
def __init__(self, tokens: Type[TokenBase]):
|
||||||
|
self.tokens = tokens
|
||||||
|
self.regex = re.compile("|".join(f"(?P<{token}>{token.regex})" for token in tokens))
|
||||||
|
self.full_match = True
|
||||||
|
|
||||||
|
def tokenize(self, text: str) -> Generator[Token, None, None]:
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
scanner = self.regex.scanner(text) # type: ignore[attr-defined]
|
||||||
|
m: Optional[Match] = None
|
||||||
|
for m in iter(scanner.match, None):
|
||||||
|
# The following two assertions are to help mypy
|
||||||
|
msg = (
|
||||||
|
"unexpected value encountered during parsing. Please submit a bug report "
|
||||||
|
"at https://github.com/spack/spack/issues/new/choose"
|
||||||
|
)
|
||||||
|
assert m is not None, msg
|
||||||
|
assert m.lastgroup is not None, msg
|
||||||
|
yield Token(self.tokens.__members__[m.lastgroup], m.group(), m.start(), m.end())
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import spack.error as error
|
import spack.error as error
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.token
|
import spack.spec_parser
|
||||||
|
|
||||||
#: These are variant names used by Spack internally; packages can't use them
|
#: These are variant names used by Spack internally; packages can't use them
|
||||||
reserved_names = [
|
reserved_names = [
|
||||||
@ -465,7 +465,7 @@ def __repr__(self) -> str:
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
delim = "==" if self.propagate else "="
|
delim = "==" if self.propagate else "="
|
||||||
values = spack.token.quote_if_needed(",".join(str(v) for v in self.value_as_tuple))
|
values = spack.spec_parser.quote_if_needed(",".join(str(v) for v in self.value_as_tuple))
|
||||||
return f"{self.name}{delim}{values}"
|
return f"{self.name}{delim}{values}"
|
||||||
|
|
||||||
|
|
||||||
@ -514,7 +514,7 @@ def __str__(self) -> str:
|
|||||||
values_str = ",".join(str(x) for x in self.value_as_tuple)
|
values_str = ",".join(str(x) for x in self.value_as_tuple)
|
||||||
|
|
||||||
delim = "==" if self.propagate else "="
|
delim = "==" if self.propagate else "="
|
||||||
return f"{self.name}{delim}{spack.token.quote_if_needed(values_str)}"
|
return f"{self.name}{delim}{spack.spec_parser.quote_if_needed(values_str)}"
|
||||||
|
|
||||||
|
|
||||||
class SingleValuedVariant(AbstractVariant):
|
class SingleValuedVariant(AbstractVariant):
|
||||||
@ -571,7 +571,7 @@ def yaml_entry(self) -> Tuple[str, SerializedValueType]:
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
delim = "==" if self.propagate else "="
|
delim = "==" if self.propagate else "="
|
||||||
return f"{self.name}{delim}{spack.token.quote_if_needed(str(self.value))}"
|
return f"{self.name}{delim}{spack.spec_parser.quote_if_needed(str(self.value))}"
|
||||||
|
|
||||||
|
|
||||||
class BoolValuedVariant(SingleValuedVariant):
|
class BoolValuedVariant(SingleValuedVariant):
|
||||||
|
Loading…
Reference in New Issue
Block a user