summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--var/spack/repos/builtin.mock/packages/conflict-parent/package.py46
-rw-r--r--var/spack/repos/builtin.mock/packages/conflict/package.py46
-rw-r--r--var/spack/repos/builtin/packages/openblas/package.py2
7 files changed, 193 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()
diff --git a/var/spack/repos/builtin.mock/packages/conflict-parent/package.py b/var/spack/repos/builtin.mock/packages/conflict-parent/package.py
new file mode 100644
index 0000000000..37805537a2
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/conflict-parent/package.py
@@ -0,0 +1,46 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack import *
+
+
+class ConflictParent(Package):
+ homepage = 'https://github.com/tgamblin/callpath'
+ url = 'http://github.com/tgamblin/callpath-1.0.tar.gz'
+
+ version(0.8, 'foobarbaz')
+ version(0.9, 'foobarbaz')
+ version(1.0, 'foobarbaz')
+
+ depends_on('conflict')
+
+ conflicts('^conflict~foo', when='@0.9')
+
+ def install(self, spec, prefix):
+ configure("--prefix=%s" % prefix)
+ make()
+ make("install")
+
+ def setup_environment(self, senv, renv):
+ renv.set('FOOBAR', self.name)
diff --git a/var/spack/repos/builtin.mock/packages/conflict/package.py b/var/spack/repos/builtin.mock/packages/conflict/package.py
new file mode 100644
index 0000000000..a6ba4b5c58
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/conflict/package.py
@@ -0,0 +1,46 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack import *
+
+
+class Conflict(Package):
+ homepage = 'https://github.com/tgamblin/callpath'
+ url = 'http://github.com/tgamblin/callpath-1.0.tar.gz'
+
+ version(0.8, 'foobarbaz')
+ version(0.9, 'foobarbaz')
+ version(1.0, 'foobarbaz')
+
+ variant('foo', default=True, description='')
+
+ conflicts('%clang', when='+foo')
+
+ def install(self, spec, prefix):
+ configure("--prefix=%s" % prefix)
+ make()
+ make("install")
+
+ def setup_environment(self, senv, renv):
+ renv.set('FOOBAR', self.name)
diff --git a/var/spack/repos/builtin/packages/openblas/package.py b/var/spack/repos/builtin/packages/openblas/package.py
index bfa5a16b42..6e16174563 100644
--- a/var/spack/repos/builtin/packages/openblas/package.py
+++ b/var/spack/repos/builtin/packages/openblas/package.py
@@ -59,6 +59,8 @@ class Openblas(MakefilePackage):
parallel = False
+ conflicts('%intel@16', when='@0.2.15:0.2.19')
+
@run_before('edit')
def check_compilers(self):
# As of 06/2016 there is no mechanism to specify that packages which