SPACK-56: fix Variant concretization.
- Variant concretization is tricky: - During concretization, a spec without variants (e.g., mpich) means "don't care". So, Spec('mpich').satisfies('mpich+debug') is true because it *could* still be built that way. - After concretization, a spec without a particular variant means "don't know", as that wasn't part of the spec, so the opposite relationship is true. Assume 'spec' is already installed: spec.satisfies('mpich+debug') this is false beacuse the `debug` variant didn't exist when spec was built, so we can't satisfy the explicit request for +debug.
This commit is contained in:
parent
3b1898b8e4
commit
535c1fac87
@ -101,6 +101,16 @@ def concretize_architecture(self, spec):
|
|||||||
spec.architecture = spack.architecture.sys_type()
|
spec.architecture = spack.architecture.sys_type()
|
||||||
|
|
||||||
|
|
||||||
|
def concretize_variants(self, spec):
|
||||||
|
"""If the spec already has variants filled in, return. Otherwise, add
|
||||||
|
the default variants from the package specification.
|
||||||
|
"""
|
||||||
|
for name, variant in spec.package.variants.items():
|
||||||
|
if name not in spec.variants:
|
||||||
|
spec.variants[name] = spack.spec.VariantSpec(
|
||||||
|
name, variant.default)
|
||||||
|
|
||||||
|
|
||||||
def concretize_compiler(self, spec):
|
def concretize_compiler(self, spec):
|
||||||
"""If the spec already has a compiler, we're done. If not, then take
|
"""If the spec already has a compiler, we're done. If not, then take
|
||||||
the compiler used for the nearest ancestor with a compiler
|
the compiler used for the nearest ancestor with a compiler
|
||||||
|
@ -270,7 +270,7 @@ def __repr__(self):
|
|||||||
|
|
||||||
|
|
||||||
@key_ordering
|
@key_ordering
|
||||||
class Variant(object):
|
class VariantSpec(object):
|
||||||
"""Variants are named, build-time options for a package. Names depend
|
"""Variants are named, build-time options for a package. Names depend
|
||||||
on the particular package being built, and each named variant can
|
on the particular package being built, and each named variant can
|
||||||
be enabled or disabled.
|
be enabled or disabled.
|
||||||
@ -285,7 +285,7 @@ def _cmp_key(self):
|
|||||||
|
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Variant(self.name, self.enabled)
|
return VariantSpec(self.name, self.enabled)
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -294,9 +294,27 @@ def __str__(self):
|
|||||||
|
|
||||||
|
|
||||||
class VariantMap(HashableMap):
|
class VariantMap(HashableMap):
|
||||||
def satisfies(self, other):
|
def satisfies(self, other, self_is_concrete):
|
||||||
return all(self[key].enabled == other[key].enabled
|
if self_is_concrete:
|
||||||
for key in other if key in self)
|
return all(k in self and self[k].enabled == other[k].enabled
|
||||||
|
for k in other)
|
||||||
|
else:
|
||||||
|
return all(self[k].enabled == other[k].enabled
|
||||||
|
for k in other if k in self)
|
||||||
|
|
||||||
|
|
||||||
|
def constrain(self, other, other_is_concrete):
|
||||||
|
if other_is_concrete:
|
||||||
|
for k in self:
|
||||||
|
if k not in other:
|
||||||
|
raise UnsatisfiableVariantSpecError(self[k], '<absent>')
|
||||||
|
|
||||||
|
for k in other:
|
||||||
|
if k in self:
|
||||||
|
if self[k].enabled != other[k].enabled:
|
||||||
|
raise UnsatisfiableVariantSpecError(self[k], other[k])
|
||||||
|
else:
|
||||||
|
self[k] = other[k].copy()
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -375,7 +393,7 @@ def _add_variant(self, name, enabled):
|
|||||||
"""Called by the parser to add a variant."""
|
"""Called by the parser to add a variant."""
|
||||||
if name in self.variants: raise DuplicateVariantError(
|
if name in self.variants: raise DuplicateVariantError(
|
||||||
"Cannot specify variant '%s' twice" % name)
|
"Cannot specify variant '%s' twice" % name)
|
||||||
self.variants[name] = Variant(name, enabled)
|
self.variants[name] = VariantSpec(name, enabled)
|
||||||
|
|
||||||
|
|
||||||
def _set_compiler(self, compiler):
|
def _set_compiler(self, compiler):
|
||||||
@ -607,6 +625,7 @@ def _concretize_helper(self, presets=None, visited=None):
|
|||||||
spack.concretizer.concretize_architecture(self)
|
spack.concretizer.concretize_architecture(self)
|
||||||
spack.concretizer.concretize_compiler(self)
|
spack.concretizer.concretize_compiler(self)
|
||||||
spack.concretizer.concretize_version(self)
|
spack.concretizer.concretize_version(self)
|
||||||
|
spack.concretizer.concretize_variants(self)
|
||||||
presets[self.name] = self
|
presets[self.name] = self
|
||||||
|
|
||||||
visited.add(self.name)
|
visited.add(self.name)
|
||||||
@ -789,8 +808,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
|
|||||||
else:
|
else:
|
||||||
required = index.providers_for(vspec.name)
|
required = index.providers_for(vspec.name)
|
||||||
if required:
|
if required:
|
||||||
raise UnsatisfiableProviderSpecError(
|
raise UnsatisfiableProviderSpecError(required[0], pkg_dep)
|
||||||
required[0], pkg_dep)
|
|
||||||
provider_index.update(pkg_dep)
|
provider_index.update(pkg_dep)
|
||||||
|
|
||||||
if name not in spec_deps:
|
if name not in spec_deps:
|
||||||
@ -929,7 +947,7 @@ def constrain(self, other, **kwargs):
|
|||||||
self.compiler = other.compiler
|
self.compiler = other.compiler
|
||||||
|
|
||||||
self.versions.intersect(other.versions)
|
self.versions.intersect(other.versions)
|
||||||
self.variants.update(other.variants)
|
self.variants.constrain(other.variants, other._concrete)
|
||||||
self.architecture = self.architecture or other.architecture
|
self.architecture = self.architecture or other.architecture
|
||||||
|
|
||||||
if constrain_deps:
|
if constrain_deps:
|
||||||
@ -998,11 +1016,13 @@ def satisfies(self, other, **kwargs):
|
|||||||
# All these attrs have satisfies criteria of their own,
|
# All these attrs have satisfies criteria of their own,
|
||||||
# but can be None to indicate no constraints.
|
# but can be None to indicate no constraints.
|
||||||
for s, o in ((self.versions, other.versions),
|
for s, o in ((self.versions, other.versions),
|
||||||
(self.variants, other.variants),
|
|
||||||
(self.compiler, other.compiler)):
|
(self.compiler, other.compiler)):
|
||||||
if s and o and not s.satisfies(o):
|
if s and o and not s.satisfies(o):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not self.variants.satisfies(other.variants, self._concrete):
|
||||||
|
return False
|
||||||
|
|
||||||
# Architecture satisfaction is currently just string equality.
|
# Architecture satisfaction is currently just string equality.
|
||||||
# Can be None for unconstrained, though.
|
# Can be None for unconstrained, though.
|
||||||
if (self.architecture and other.architecture and
|
if (self.architecture and other.architecture and
|
||||||
|
@ -242,12 +242,6 @@ def test_unsatisfiable_compiler_version(self):
|
|||||||
self.assertRaises(spack.spec.UnsatisfiableCompilerSpecError, spec.normalize)
|
self.assertRaises(spack.spec.UnsatisfiableCompilerSpecError, spec.normalize)
|
||||||
|
|
||||||
|
|
||||||
def test_unsatisfiable_variant(self):
|
|
||||||
set_pkg_dep('mpileaks', 'mpich+debug')
|
|
||||||
spec = Spec('mpileaks ^mpich~debug ^callpath ^dyninst ^libelf ^libdwarf')
|
|
||||||
self.assertRaises(spack.spec.UnsatisfiableVariantSpecError, spec.normalize)
|
|
||||||
|
|
||||||
|
|
||||||
def test_unsatisfiable_architecture(self):
|
def test_unsatisfiable_architecture(self):
|
||||||
set_pkg_dep('mpileaks', 'mpich=bgqos_0')
|
set_pkg_dep('mpileaks', 'mpich=bgqos_0')
|
||||||
spec = Spec('mpileaks ^mpich=sles_10_ppc64 ^callpath ^dyninst ^libelf ^libdwarf')
|
spec = Spec('mpileaks ^mpich=sles_10_ppc64 ^callpath ^dyninst ^libelf ^libdwarf')
|
||||||
|
@ -33,8 +33,8 @@ class SpecSematicsTest(MockPackagesTest):
|
|||||||
# ================================================================================
|
# ================================================================================
|
||||||
# Utility functions to set everything up.
|
# Utility functions to set everything up.
|
||||||
# ================================================================================
|
# ================================================================================
|
||||||
def check_satisfies(self, spec, anon_spec):
|
def check_satisfies(self, spec, anon_spec, concrete=False):
|
||||||
left = Spec(spec)
|
left = Spec(spec, concrete=concrete)
|
||||||
right = parse_anonymous_spec(anon_spec, left.name)
|
right = parse_anonymous_spec(anon_spec, left.name)
|
||||||
|
|
||||||
# Satisfies is one-directional.
|
# Satisfies is one-directional.
|
||||||
@ -46,8 +46,8 @@ def check_satisfies(self, spec, anon_spec):
|
|||||||
right.copy().constrain(left)
|
right.copy().constrain(left)
|
||||||
|
|
||||||
|
|
||||||
def check_unsatisfiable(self, spec, anon_spec):
|
def check_unsatisfiable(self, spec, anon_spec, concrete=False):
|
||||||
left = Spec(spec)
|
left = Spec(spec, concrete=concrete)
|
||||||
right = parse_anonymous_spec(anon_spec, left.name)
|
right = parse_anonymous_spec(anon_spec, left.name)
|
||||||
|
|
||||||
self.assertFalse(left.satisfies(right))
|
self.assertFalse(left.satisfies(right))
|
||||||
@ -150,9 +150,33 @@ def test_satisfies_virtual_dependency_versions(self):
|
|||||||
self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0')
|
self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0')
|
||||||
|
|
||||||
|
|
||||||
def test_satisfies_variant(self):
|
def test_satisfies_matching_variant(self):
|
||||||
self.check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
|
self.check_satisfies('mpich+foo', 'mpich+foo')
|
||||||
self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
|
self.check_satisfies('mpich~foo', 'mpich~foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_satisfies_unconstrained_variant(self):
|
||||||
|
# only asked for mpich, no constraints. Either will do.
|
||||||
|
self.check_satisfies('mpich+foo', 'mpich')
|
||||||
|
self.check_satisfies('mpich~foo', 'mpich')
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsatisfiable_variants(self):
|
||||||
|
# This case is different depending on whether the specs are concrete.
|
||||||
|
|
||||||
|
# 'mpich' is not concrete:
|
||||||
|
self.check_satisfies('mpich', 'mpich+foo', False)
|
||||||
|
self.check_satisfies('mpich', 'mpich~foo', False)
|
||||||
|
|
||||||
|
# 'mpich' is concrete:
|
||||||
|
self.check_unsatisfiable('mpich', 'mpich+foo', True)
|
||||||
|
self.check_unsatisfiable('mpich', 'mpich~foo', True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsatisfiable_variant_mismatch(self):
|
||||||
|
# No matchi in specs
|
||||||
|
self.check_unsatisfiable('mpich~foo', 'mpich+foo')
|
||||||
|
self.check_unsatisfiable('mpich+foo', 'mpich~foo')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
36
lib/spack/spack/variant.py
Normal file
36
lib/spack/spack/variant.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
|
||||||
|
# Produced at the Lawrence Livermore National Laboratory.
|
||||||
|
#
|
||||||
|
# This file is part of Spack.
|
||||||
|
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||||
|
# LLNL-CODE-647188
|
||||||
|
#
|
||||||
|
# For details, see https://scalability-llnl.github.io/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 General Public License (as published by
|
||||||
|
# the Free Software Foundation) version 2.1 dated 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 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
|
||||||
|
##############################################################################
|
||||||
|
"""Variant is a class describing flags on builds, or "variants".
|
||||||
|
|
||||||
|
Could be generalized later to describe aribitrary parameters, but
|
||||||
|
currently variants are just flags.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Variant(object):
|
||||||
|
"""Represents a variant on a build. Can be either on or off."""
|
||||||
|
def __init__(self, default, description):
|
||||||
|
self.default = bool(default)
|
||||||
|
self.description = str(description)
|
Loading…
Reference in New Issue
Block a user