new directive: conflicts() (#3125)
* Add conflicts(<spec>) directive * openblas: added conflicts for intel@16 refs #3119 * added brief docs and unit tests
This commit is contained in:
parent
0e785e0500
commit
ffef681377
@ -1560,6 +1560,28 @@ Python's ``setup_dependent_environment`` method also sets up some
|
|||||||
other variables, creates a directory, and sets up the ``PYTHONPATH``
|
other variables, creates a directory, and sets up the ``PYTHONPATH``
|
||||||
so that dependent packages can find their dependencies at build time.
|
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:
|
.. _packaging_extensions:
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
@ -263,6 +263,33 @@ def _depends_on(pkg, spec, when=None, type=None):
|
|||||||
conditions[when_spec] = dep_spec
|
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'))
|
@directive(('dependencies', 'dependency_types'))
|
||||||
def depends_on(spec, when=None, type=None):
|
def depends_on(spec, when=None, type=None):
|
||||||
"""Creates a dict of deps with specs defining when they apply.
|
"""Creates a dict of deps with specs defining when they apply.
|
||||||
|
@ -1707,6 +1707,18 @@ def concretize(self):
|
|||||||
# Mark everything in the spec as concrete, as well.
|
# Mark everything in the spec as concrete, as well.
|
||||||
self._mark_concrete()
|
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):
|
def _mark_concrete(self, value=True):
|
||||||
"""Mark this spec and its dependencies as concrete.
|
"""Mark this spec and its dependencies as concrete.
|
||||||
|
|
||||||
@ -3336,3 +3348,15 @@ def __init__(self, spec, addition):
|
|||||||
"Attempting to add %s to spec %s which is already concrete."
|
"Attempting to add %s to spec %s which is already concrete."
|
||||||
" This is likely the result of adding to a spec specified by hash."
|
" This is likely the result of adding to a spec specified by hash."
|
||||||
% (addition, spec))
|
% (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)
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
import spack
|
import spack
|
||||||
import spack.architecture
|
import spack.architecture
|
||||||
from spack.concretize import find_spec
|
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
|
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', 'mpileaks ^mpi@:1.1', 'mpileaks ^mpi@2:',
|
||||||
'mpileaks ^mpi@2.1', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@2.2',
|
'mpileaks ^mpi@2.1', 'mpileaks ^mpi@2.2', 'mpileaks ^mpi@2.2',
|
||||||
'mpileaks ^mpi@:1', 'mpileaks ^mpi@1.2:2'
|
'mpileaks ^mpi@:1', 'mpileaks ^mpi@1.2:2'
|
||||||
|
# conflict not triggered
|
||||||
|
'conflict',
|
||||||
|
'conflict%clang~foo',
|
||||||
|
'conflict-parent%gcc'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def spec(request):
|
def spec(request):
|
||||||
@ -89,6 +93,19 @@ def spec(request):
|
|||||||
return request.param
|
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')
|
@pytest.mark.usefixtures('config', 'builtin_mock')
|
||||||
class TestConcretize(object):
|
class TestConcretize(object):
|
||||||
def test_concretize(self, spec):
|
def test_concretize(self, spec):
|
||||||
@ -372,3 +389,11 @@ def test_compiler_child(self):
|
|||||||
s.concretize()
|
s.concretize()
|
||||||
assert s['mpileaks'].satisfies('%clang')
|
assert s['mpileaks'].satisfies('%clang')
|
||||||
assert s['dyninst'].satisfies('%gcc')
|
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()
|
||||||
|
@ -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)
|
46
var/spack/repos/builtin.mock/packages/conflict/package.py
Normal file
46
var/spack/repos/builtin.mock/packages/conflict/package.py
Normal file
@ -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)
|
@ -59,6 +59,8 @@ class Openblas(MakefilePackage):
|
|||||||
|
|
||||||
parallel = False
|
parallel = False
|
||||||
|
|
||||||
|
conflicts('%intel@16', when='@0.2.15:0.2.19')
|
||||||
|
|
||||||
@run_before('edit')
|
@run_before('edit')
|
||||||
def check_compilers(self):
|
def check_compilers(self):
|
||||||
# As of 06/2016 there is no mechanism to specify that packages which
|
# As of 06/2016 there is no mechanism to specify that packages which
|
||||||
|
Loading…
Reference in New Issue
Block a user