allow multiple compatible deps from CLI (#21262)

Currently, Spack can fail for a valid spec if the spec is constructed from overlapping, but not conflicting, concrete specs via the hash.

For example, if abcdef and ghijkl are the hashes of specs that both depend on zlib/mnopqr, then foo ^/abcdef ^/ghijkl will fail to construct a spec, with the error message "Cannot depend on zlib... twice".

This PR changes this behavior to check whether the specs are compatible before failing.

With this PR, foo ^/abcdef ^/ghijkl will concretize.

As a side-effect, so will foo ^zlib ^zlib and other specs that are redundant on their dependencies.
This commit is contained in:
Greg Becker 2022-11-06 11:30:37 -08:00 committed by GitHub
parent 8b4b26fcbd
commit d4b45605c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 24 additions and 2 deletions

View File

@ -1559,8 +1559,24 @@ def _set_compiler(self, compiler):
def _add_dependency(self, spec, deptypes): def _add_dependency(self, spec, deptypes):
"""Called by the parser to add another spec as a dependency.""" """Called by the parser to add another spec as a dependency."""
if spec.name in self._dependencies: if spec.name in self._dependencies:
# allow redundant compatible dependency specifications
# depspec equality checks by name, so we need to check components
# separately to test whether the specs are identical
orig = self._dependencies[spec.name]
for dspec in orig:
if deptypes == dspec.deptypes:
try:
dspec.spec.constrain(spec)
return
except spack.error.UnsatisfiableSpecError:
raise DuplicateDependencyError(
"Cannot depend on incompatible specs '%s' and '%s'"
% (dspec.spec, spec)
)
else:
raise DuplicateDependencyError("Cannot depend on '%s' twice" % spec) raise DuplicateDependencyError("Cannot depend on '%s' twice" % spec)
# create an edge and add to parent and child
self.add_dependency_edge(spec, deptypes) self.add_dependency_edge(spec, deptypes)
def add_dependency_edge(self, dependency_spec, deptype): def add_dependency_edge(self, dependency_spec, deptype):

View File

@ -293,6 +293,12 @@ def test_canonicalize(self):
self.check_parse("x ^y", "x@: ^y@:") self.check_parse("x ^y", "x@: ^y@:")
def test_parse_redundant_deps(self):
self.check_parse("x ^y@foo", "x ^y@foo ^y@foo")
self.check_parse("x ^y@foo+bar", "x ^y@foo ^y+bar")
self.check_parse("x ^y@foo+bar", "x ^y@foo+bar ^y")
self.check_parse("x ^y@foo+bar", "x ^y ^y@foo+bar")
def test_parse_errors(self): def test_parse_errors(self):
errors = ["x@@1.2", "x ^y@@1.2", "x@1.2::", "x::"] errors = ["x@@1.2", "x ^y@@1.2", "x@1.2::", "x::"]
self._check_raises(SpecParseError, errors) self._check_raises(SpecParseError, errors)
@ -481,7 +487,7 @@ def test_multiple_versions(self):
self._check_raises(MultipleVersionError, multiples) self._check_raises(MultipleVersionError, multiples)
def test_duplicate_dependency(self): def test_duplicate_dependency(self):
self._check_raises(DuplicateDependencyError, ["x ^y ^y"]) self._check_raises(DuplicateDependencyError, ["x ^y@1 ^y@2"])
def test_duplicate_compiler(self): def test_duplicate_compiler(self):
duplicates = [ duplicates = [