summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/packaging_guide.rst22
-rw-r--r--lib/spack/spack/directives.py27
-rw-r--r--lib/spack/spack/spec.py24
-rw-r--r--lib/spack/spack/test/concretize.py27
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()