diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/docs/packaging_guide.rst | 22 | ||||
-rw-r--r-- | lib/spack/spack/directives.py | 27 | ||||
-rw-r--r-- | lib/spack/spack/spec.py | 24 | ||||
-rw-r--r-- | lib/spack/spack/test/concretize.py | 27 |
4 files changed, 99 insertions, 1 deletions
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst index 1a64c7db4a..18541179b2 100644 --- a/lib/spack/docs/packaging_guide.rst +++ b/lib/spack/docs/packaging_guide.rst @@ -1560,6 +1560,28 @@ Python's ``setup_dependent_environment`` method also sets up some other variables, creates a directory, and sets up the ``PYTHONPATH`` so that dependent packages can find their dependencies at build time. +.. _packaging_conflicts: + +--------- +Conflicts +--------- + +Sometimes packages have known bugs, or limitations, that would prevent them +to build e.g. against other dependencies or with certain compilers. Spack +makes it possible to express such constraints with the ``conflicts`` directive. + +Adding the following to a package: + +.. code-block:: python + + conflicts('%intel', when='@1.2') + +we express the fact that the current package *cannot be built* with the Intel +compiler when we are trying to install version "1.2". The ``when`` argument can +be omitted, in which case the conflict will always be active. +Conflicts are always evaluated after the concretization step has been performed, +and if any match is found a detailed error message is shown to the user. + .. _packaging_extensions: ---------- diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py index db280cb5fd..e2219d1f49 100644 --- a/lib/spack/spack/directives.py +++ b/lib/spack/spack/directives.py @@ -263,6 +263,33 @@ def _depends_on(pkg, spec, when=None, type=None): conditions[when_spec] = dep_spec +@directive('conflicts') +def conflicts(conflict_spec, when=None): + """Allows a package to define a conflict, i.e. a concretized configuration + that is known to be non-valid. + + For example a package that is known not to be buildable with intel + compilers can declare: + + conflicts('%intel') + + To express the same constraint only when the 'foo' variant is activated: + + conflicts('%intel', when='+foo') + + :param conflict_spec: constraint defining the known conflict + :param when: optional constraint that triggers the conflict + """ + def _execute(pkg): + # If when is not specified the conflict always holds + condition = pkg.name if when is None else when + when_spec = parse_anonymous_spec(condition, pkg.name) + + when_spec_list = pkg.conflicts.setdefault(conflict_spec, []) + when_spec_list.append(when_spec) + return _execute + + @directive(('dependencies', 'dependency_types')) def depends_on(spec, when=None, type=None): """Creates a dict of deps with specs defining when they apply. diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 095b04f837..b6089c7849 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -1707,6 +1707,18 @@ class Spec(object): # Mark everything in the spec as concrete, as well. self._mark_concrete() + # Now that the spec is concrete we should check if + # there are declared conflicts + matches = [] + for x in self.traverse(): + for conflict_spec, when_list in x.package.conflicts.items(): + if x.satisfies(conflict_spec): + for when_spec in when_list: + if x.satisfies(when_spec): + matches.append((x, conflict_spec, when_spec)) + if matches: + raise ConflictsInSpecError(self, matches) + def _mark_concrete(self, value=True): """Mark this spec and its dependencies as concrete. @@ -3336,3 +3348,15 @@ class RedundantSpecError(SpecError): "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)) + + +class ConflictsInSpecError(SpecError, RuntimeError): + def __init__(self, spec, matches): + message = 'Conflicts in concretized spec "{0}"\n'.format( + spec.short_spec + ) + long_message = 'List of matching conflicts:\n\n' + match_fmt = '{0}. "{1}" conflicts with "{2}" in spec "{3}"\n' + for idx, (s, c, w) in enumerate(matches): + long_message += match_fmt.format(idx + 1, c, w, s) + super(ConflictsInSpecError, self).__init__(message, long_message) diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index f4021a89ee..3b383584ce 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -26,7 +26,7 @@ import pytest import spack import spack.architecture from spack.concretize import find_spec -from spack.spec import Spec, CompilerSpec +from spack.spec import Spec, CompilerSpec, ConflictsInSpecError, SpecError from spack.version import ver @@ -82,6 +82,10 @@ def check_concretize(abstract_spec): 'mpileaks ^mpi', 'mpileaks ^mpi@:1.1', 'mpileaks ^mpi@2:', 'mpileaks ^mpi@2.1', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@:1', 'mpileaks ^mpi@1.2:2' + # conflict not triggered + 'conflict', + 'conflict%clang~foo', + 'conflict-parent%gcc' ] ) def spec(request): @@ -89,6 +93,19 @@ def spec(request): return request.param +@pytest.fixture( + params=[ + 'conflict%clang', + 'conflict%clang+foo', + 'conflict-parent%clang', + 'conflict-parent@0.9^conflict~foo' + ] +) +def conflict_spec(request): + """Spec to be concretized""" + return request.param + + @pytest.mark.usefixtures('config', 'builtin_mock') class TestConcretize(object): def test_concretize(self, spec): @@ -372,3 +389,11 @@ class TestConcretize(object): s.concretize() assert s['mpileaks'].satisfies('%clang') assert s['dyninst'].satisfies('%gcc') + + def test_conflicts_in_spec(self, conflict_spec): + # Check that an exception is raised an caught by the appropriate + # exception types. + for exc_type in (ConflictsInSpecError, RuntimeError, SpecError): + s = Spec(conflict_spec) + with pytest.raises(exc_type): + s.concretize() |