summaryrefslogtreecommitdiff
path: root/lib/spack/spack/spec.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r--lib/spack/spack/spec.py122
1 files changed, 76 insertions, 46 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 6cf80754a1..8f315fdabf 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -152,7 +152,9 @@ __all__ = [
'UnsatisfiableArchitectureSpecError',
'UnsatisfiableProviderSpecError',
'UnsatisfiableDependencySpecError',
- 'AmbiguousHashError']
+ 'AmbiguousHashError',
+ 'InvalidHashError',
+ 'RedundantSpecError']
# Valid pattern for an identifier in Spack
identifier_re = r'\w[\w-]*'
@@ -1993,7 +1995,7 @@ class Spec(object):
except SpecError:
return parse_anonymous_spec(spec_like, self.name)
- def satisfies(self, other, deps=True, strict=False):
+ def satisfies(self, other, deps=True, strict=False, strict_deps=False):
"""Determine if this spec satisfies all constraints of another.
There are two senses for satisfies:
@@ -2067,7 +2069,8 @@ class Spec(object):
# If we need to descend into dependencies, do it, otherwise we're done.
if deps:
deps_strict = strict
- if not (self.name and other.name):
+ if self.concrete and not other.name:
+ # We're dealing with existing specs
deps_strict = True
return self.satisfies_dependencies(other, strict=deps_strict)
else:
@@ -2083,9 +2086,10 @@ class Spec(object):
if other._dependencies and not self._dependencies:
return False
- alldeps = set(d.name for d in self.traverse(root=False))
- if not all(dep.name in alldeps
- for dep in other.traverse(root=False)):
+ selfdeps = self.traverse(root=False)
+ otherdeps = other.traverse(root=False)
+ if not all(any(d.satisfies(dep) for d in selfdeps)
+ for dep in otherdeps):
return False
elif not self._dependencies or not other._dependencies:
@@ -2697,30 +2701,28 @@ class SpecParser(spack.parse.Parser):
specs = []
try:
- while self.next or self.previous:
+ while self.next:
# TODO: clean this parsing up a bit
- if self.previous:
- # We picked up the name of this spec while finishing the
- # previous spec
- specs.append(self.spec(self.previous.value))
- self.previous = None
- elif self.accept(ID):
+ if self.accept(ID):
self.previous = self.token
if self.accept(EQ):
- # We're either parsing an anonymous spec beginning
- # with a key-value pair or adding a key-value pair
- # to the last spec
+ # We're parsing an anonymous spec beginning with a
+ # key-value pair.
if not specs:
specs.append(self.spec(None))
self.expect(VAL)
+ # Raise an error if the previous spec is already
+ # concrete (assigned by hash)
+ if specs[-1]._hash:
+ raise RedundantSpecError(specs[-1],
+ 'key-value pair')
specs[-1]._add_flag(
self.previous.value, self.token.value)
self.previous = None
else:
# We're parsing a new spec by name
- value = self.previous.value
self.previous = None
- specs.append(self.spec(value))
+ specs.append(self.spec(self.token.value))
elif self.accept(HASH):
# We're finding a spec by hash
specs.append(self.spec_by_hash())
@@ -2728,27 +2730,38 @@ class SpecParser(spack.parse.Parser):
elif self.accept(DEP):
if not specs:
# We're parsing an anonymous spec beginning with a
- # dependency
- self.previous = self.token
+ # dependency. Push the token to recover after creating
+ # anonymous spec
+ self.push_tokens([self.token])
specs.append(self.spec(None))
- self.previous = None
- if self.accept(HASH):
- # We're finding a dependency by hash for an anonymous
- # spec
- dep = self.spec_by_hash()
else:
- # We're adding a dependency to the last spec
- self.expect(ID)
- dep = self.spec(self.token.value)
-
- # command line deps get empty deptypes now.
- # Real deptypes are assigned later per packages.
- specs[-1]._add_dependency(dep, ())
+ if self.accept(HASH):
+ # We're finding a dependency by hash for an
+ # anonymous spec
+ dep = self.spec_by_hash()
+ else:
+ # We're adding a dependency to the last spec
+ self.expect(ID)
+ dep = self.spec(self.token.value)
+
+ # Raise an error if the previous spec is already
+ # concrete (assigned by hash)
+ if specs[-1]._hash:
+ raise RedundantSpecError(specs[-1], 'dependency')
+ # command line deps get empty deptypes now.
+ # Real deptypes are assigned later per packages.
+ specs[-1]._add_dependency(dep, ())
else:
# If the next token can be part of a valid anonymous spec,
# create the anonymous spec
if self.next.type in (AT, ON, OFF, PCT):
+ # Raise an error if the previous spec is already
+ # concrete (assigned by hash)
+ if specs and specs[-1]._hash:
+ raise RedundantSpecError(specs[-1],
+ 'compiler, version, '
+ 'or variant')
specs.append(self.spec(None))
else:
self.unexpected_token()
@@ -2783,13 +2796,13 @@ class SpecParser(spack.parse.Parser):
if len(matches) != 1:
raise AmbiguousHashError(
- "Multiple packages specify hash %s." % self.token.value,
- *matches)
+ "Multiple packages specify hash beginning %s."
+ % self.token.value, *matches)
return matches[0]
def spec(self, name):
- """Parse a spec out of the input. If a spec is supplied, then initialize
+ """Parse a spec out of the input. If a spec is supplied, initialize
and return it instead of creating a new one."""
if name:
spec_namespace, dot, spec_name = name.rpartition('.')
@@ -2823,16 +2836,6 @@ class SpecParser(spack.parse.Parser):
# unspecified or not.
added_version = False
- if self.previous and self.previous.value == DEP:
- if self.accept(HASH):
- spec.add_dependency(self.spec_by_hash())
- else:
- self.expect(ID)
- if self.accept(EQ):
- raise SpecParseError(spack.parse.ParseError(
- "", "", "Expected dependency received anonymous spec"))
- spec.add_dependency(self.spec(self.token.value))
-
while self.next:
if self.accept(AT):
vlist = self.version_list()
@@ -2858,13 +2861,25 @@ class SpecParser(spack.parse.Parser):
self.previous = None
else:
# We've found the start of a new spec. Go back to do_parse
+ # and read this token again.
+ self.push_tokens([self.token])
+ self.previous = None
break
+ elif self.accept(HASH):
+ # Get spec by hash and confirm it matches what we already have
+ hash_spec = self.spec_by_hash()
+ if hash_spec.satisfies(spec):
+ spec = hash_spec
+ break
+ else:
+ raise InvalidHashError(spec, hash_spec.dag_hash())
+
else:
break
# If there was no version in the spec, consier it an open range
- if not added_version:
+ if not added_version and not spec._hash:
spec.versions = VersionList(':')
return spec
@@ -3139,3 +3154,18 @@ class AmbiguousHashError(SpecError):
super(AmbiguousHashError, self).__init__(msg)
for spec in specs:
print(' ', spec.format('$.$@$%@+$+$=$#'))
+
+
+class InvalidHashError(SpecError):
+ def __init__(self, spec, hash):
+ super(InvalidHashError, self).__init__(
+ "The spec specified by %s does not match provided spec %s"
+ % (hash, spec))
+
+
+class RedundantSpecError(SpecError):
+ def __init__(self, spec, addition):
+ super(RedundantSpecError, self).__init__(
+ "Attempting to add %s to spec %s which is already concrete."
+ " This is likely the result of adding to a spec specified by hash."
+ % (addition, spec))