specs: emit better parsing errors for specs. (#24860)

Parse error information is kept for specs, but it doesn't seem like we propagate it
to the user when we encounter an error.  This fixes that.

e.g., for this error in a package:

```python
    depends_on("python@:3.8", when="0.900:")
```

Before, with no context and no clue that it's even from a particular spec:

```
==> Error: Unexpected token: ':'
```

With this PR:

```
==> Error: Unexpected token: ':'
  Encountered when parsing spec:
    0.900:
         ^
```
This commit is contained in:
Todd Gamblin 2022-05-23 23:33:43 -04:00 committed by GitHub
parent 63402c512b
commit 306bed48d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 33 additions and 27 deletions

View file

@ -155,31 +155,17 @@ def parse_specs(args, **kwargs):
normalize = kwargs.get('normalize', False)
tests = kwargs.get('tests', False)
try:
sargs = args
if not isinstance(args, six.string_types):
sargs = ' '.join(spack.util.string.quote(args))
specs = spack.spec.parse(sargs)
for spec in specs:
if concretize:
spec.concretize(tests=tests) # implies normalize
elif normalize:
spec.normalize(tests=tests)
sargs = args
if not isinstance(args, six.string_types):
sargs = ' '.join(spack.util.string.quote(args))
specs = spack.spec.parse(sargs)
for spec in specs:
if concretize:
spec.concretize(tests=tests) # implies normalize
elif normalize:
spec.normalize(tests=tests)
return specs
except spack.spec.SpecParseError as e:
msg = e.message + "\n" + str(e.string) + "\n"
msg += (e.pos + 2) * " " + "^"
raise spack.error.SpackError(msg)
except spack.error.SpecError as e:
msg = e.message
if e.long_message:
msg += e.long_message
raise spack.error.SpackError(msg)
return specs
def matching_spec_from_env(spec):

View file

@ -123,11 +123,11 @@ def accept(self, id):
def next_token_error(self, message):
"""Raise an error about the next token in the stream."""
raise ParseError(message, self.text, self.token.end)
raise ParseError(message, self.text[0], self.token.end)
def last_token_error(self, message):
"""Raise an error about the previous token in the stream."""
raise ParseError(message, self.text, self.token.start)
raise ParseError(message, self.text[0], self.token.start)
def unexpected_token(self):
self.next_token_error("Unexpected token: '%s'" % self.next.value)

View file

@ -5386,6 +5386,16 @@ def __init__(self, parse_error):
self.string = parse_error.string
self.pos = parse_error.pos
@property
def long_message(self):
return "\n".join(
[
" Encountered when parsing spec:",
" %s" % self.string,
" %s^" % (" " * self.pos),
]
)
class DuplicateDependencyError(spack.error.SpecError):
"""Raised when the same dependency occurs in a spec twice."""

View file

@ -124,6 +124,17 @@ def test_spec_returncode():
assert spec.returncode == 1
def test_spec_parse_error():
with pytest.raises(spack.spec.SpecParseError) as e:
spec("1.15:")
# make sure the error is formatted properly
error_msg = """\
1.15:
^"""
assert error_msg in e.value.long_message
def test_env_aware_spec(mutable_mock_env_path):
env = ev.create('test')
env.add('mpileaks')

View file

@ -125,7 +125,6 @@ def check_lex(self, tokens, spec):
def _check_raises(self, exc_type, items):
for item in items:
with pytest.raises(exc_type):
print("CHECKING: ", item, "=======================")
Spec(item)
# ========================================================================