summaryrefslogtreecommitdiff
path: root/lib/spack/spack/spec.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r--lib/spack/spack/spec.py281
1 files changed, 183 insertions, 98 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index dca06101b8..99246ebb02 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -85,10 +85,9 @@ import itertools
import os
import re
+import six
+
from operator import attrgetter
-from six import StringIO
-from six import string_types
-from six import iteritems
from llnl.util.filesystem import find_headers, find_libraries, is_exe
from llnl.util.lang import key_ordering, HashableMap, ObjectWrapper, dedupe
@@ -216,37 +215,47 @@ def colorize_spec(spec):
@key_ordering
class ArchSpec(object):
- """ The ArchSpec class represents an abstract architecture specification
- that a package should be built with. At its core, each ArchSpec is
- comprised of three elements: a platform (e.g. Linux), an OS (e.g.
- RHEL6), and a target (e.g. x86_64).
- """
+ def __init__(self, spec_or_platform_tuple=(None, None, None)):
+ """ Architecture specification a package should be built with.
- # TODO: Formalize the specifications for architectures and then use
- # the appropriate parser here to read these specifications.
- def __init__(self, *args):
- to_attr_string = lambda s: str(s) if s and s != "None" else None
+ Each ArchSpec is comprised of three elements: a platform (e.g. Linux),
+ an OS (e.g. RHEL6), and a target (e.g. x86_64).
+
+ Args:
+ spec_or_platform_tuple (ArchSpec or str or tuple): if an ArchSpec
+ is passed it will be duplicated into the new instance.
+ Otherwise information on platform, OS and target should be
+ passed in either as a spec string or as a tuple.
+ """
+ # If another instance of ArchSpec was passed, duplicate it
+ if isinstance(spec_or_platform_tuple, ArchSpec):
+ self._dup(spec_or_platform_tuple)
+ return
- self.platform, self.os, self.target = (None, None, None)
+ # If the argument to __init__ is a spec string, parse it
+ # and construct an ArchSpec
+ def _string_or_none(s):
+ if s and s != 'None':
+ return str(s)
+ return None
- if len(args) == 1:
- spec_like = args[0]
- if isinstance(spec_like, ArchSpec):
- self._dup(spec_like)
- elif isinstance(spec_like, string_types):
- spec_fields = spec_like.split("-")
+ if isinstance(spec_or_platform_tuple, six.string_types):
+ spec_fields = spec_or_platform_tuple.split("-")
+ msg = "invalid arch spec [{0}]"
+ assert len(spec_fields) == 3, msg.format(spec_or_platform_tuple)
- if len(spec_fields) == 3:
- self.platform, self.os, self.target = tuple(
- to_attr_string(f) for f in spec_fields)
- else:
- raise ValueError("%s is an invalid arch spec" % spec_like)
- elif len(args) == 3:
- self.platform = to_attr_string(args[0])
- self.os = to_attr_string(args[1])
- self.target = to_attr_string(args[2])
- elif len(args) != 0:
- raise TypeError("Can't make arch spec from %s" % args)
+ platform, operating_system, target = spec_fields
+ platform_tuple = _string_or_none(platform),\
+ _string_or_none(operating_system), target
+
+ if isinstance(spec_or_platform_tuple, tuple):
+ platform, operating_system, target = spec_or_platform_tuple
+ platform_tuple = _string_or_none(platform), \
+ _string_or_none(operating_system), target
+ msg = "invalid arch spec tuple [{0}]"
+ assert len(platform_tuple) == 3, msg.format(platform_tuple)
+
+ self.platform, self.os, self.target = platform_tuple
def _autospec(self, spec_like):
if isinstance(spec_like, ArchSpec):
@@ -254,7 +263,7 @@ class ArchSpec(object):
return ArchSpec(spec_like)
def _cmp_key(self):
- return (self.platform, self.os, self.target)
+ return self.platform, self.os, self.target
def _dup(self, other):
self.platform = other.platform
@@ -263,29 +272,29 @@ class ArchSpec(object):
@property
def platform(self):
+ """The platform of the architecture."""
return self._platform
@platform.setter
def platform(self, value):
- """ The platform of the architecture spec will be verified as a
- supported Spack platform before it's set to ensure all specs
- refer to valid platforms.
- """
+ # The platform of the architecture spec will be verified as a
+ # supported Spack platform before it's set to ensure all specs
+ # refer to valid platforms.
value = str(value) if value is not None else None
self._platform = value
@property
def os(self):
+ """The OS of this ArchSpec."""
return self._os
@os.setter
def os(self, value):
- """ The OS of the architecture spec will update the platform field
- if the OS is set to one of the reserved OS types so that the
- default OS type can be resolved. Since the reserved OS
- information is only available for the host machine, the platform
- will assumed to be the host machine's platform.
- """
+ # The OS of the architecture spec will update the platform field
+ # if the OS is set to one of the reserved OS types so that the
+ # default OS type can be resolved. Since the reserved OS
+ # information is only available for the host machine, the platform
+ # will assumed to be the host machine's platform.
value = str(value) if value is not None else None
if value in spack.architecture.Platform.reserved_oss:
@@ -305,19 +314,27 @@ class ArchSpec(object):
@property
def target(self):
+ """The target of the architecture."""
return self._target
@target.setter
def target(self, value):
- """ The target of the architecture spec will update the platform field
- if the target is set to one of the reserved target types so that
- the default target type can be resolved. Since the reserved target
- information is only available for the host machine, the platform
- will assumed to be the host machine's platform.
- """
- value = str(value) if value is not None else None
+ # The target of the architecture spec will update the platform field
+ # if the target is set to one of the reserved target types so that
+ # the default target type can be resolved. Since the reserved target
+ # information is only available for the host machine, the platform
+ # will assumed to be the host machine's platform.
+
+ def target_or_none(t):
+ if isinstance(t, spack.architecture.Target):
+ return t
+ if t and t != 'None':
+ return spack.architecture.Target(t)
+ return None
- if value in spack.architecture.Platform.reserved_targets:
+ value = target_or_none(value)
+
+ if str(value) in spack.architecture.Platform.reserved_targets:
curr_platform = str(spack.architecture.platform())
self.platform = self.platform or curr_platform
@@ -328,25 +345,76 @@ class ArchSpec(object):
(value, self.platform, curr_platform))
spec_platform = spack.architecture.get_platform(self.platform)
- value = str(spec_platform.target(value))
+ value = spec_platform.target(value)
self._target = value
def satisfies(self, other, strict=False):
+ """Predicate to check if this spec satisfies a constraint.
+
+ Args:
+ other (ArchSpec or str): constraint on the current instance
+ strict (bool): if ``False`` the function checks if the current
+ instance *might* eventually satisfy the constraint. If
+ ``True`` it check if the constraint is satisfied right now.
+
+ Returns:
+ True if the constraint is satisfied, False otherwise.
+ """
other = self._autospec(other)
- sdict, odict = self.to_cmp_dict(), other.to_cmp_dict()
- if strict or self.concrete:
- return all(getattr(self, attr) == getattr(other, attr)
- for attr in odict if odict[attr])
- else:
- return all(getattr(self, attr) == getattr(other, attr)
- for attr in odict if sdict[attr] and odict[attr])
+ # Check platform and os
+ for attribute in ('platform', 'os'):
+ other_attribute = getattr(other, attribute)
+ self_attribute = getattr(self, attribute)
+ if strict or self.concrete:
+ if other_attribute and self_attribute != other_attribute:
+ return False
+ else:
+ if other_attribute and self_attribute and \
+ self_attribute != other_attribute:
+ return False
+
+ # Check target
+ return self._satisfies_target(other.target, strict=strict)
+
+ def _satisfies_target(self, other_target, strict):
+ self_target = self.target
+
+ need_to_check = bool(other_target) if strict or self.concrete \
+ else bool(other_target and self_target)
+
+ # If there's no need to check we are fine
+ if not need_to_check:
+ return True
+
+ for target_range in str(other_target).split(','):
+ t_min, sep, t_max = target_range.partition(':')
+
+ # Checking against a single specific target
+ if not sep and self_target == t_min:
+ return True
+
+ min_ok = self_target.microarchitecture >= t_min if t_min else True
+ max_ok = self_target.microarchitecture <= t_max if t_max else True
+
+ if min_ok and max_ok:
+ return True
+
+ return False
def constrain(self, other):
- """ Projects all architecture fields that are specified in the given
- spec onto the instance spec if they're missing from the instance
- spec. This will only work if the two specs are compatible.
+ """Projects all architecture fields that are specified in the given
+ spec onto the instance spec if they're missing from the instance
+ spec.
+
+ This will only work if the two specs are compatible.
+
+ Args:
+ other (ArchSpec or str): constraints to be added
+
+ Returns:
+ True if the current instance was constrained, False otherwise.
"""
other = self._autospec(other)
@@ -354,8 +422,8 @@ class ArchSpec(object):
raise UnsatisfiableArchitectureSpecError(self, other)
constrained = False
- for attr, svalue in iteritems(self.to_cmp_dict()):
- ovalue = getattr(other, attr)
+ for attr in ('platform', 'os', 'target'):
+ svalue, ovalue = getattr(self, attr), getattr(other, attr)
if svalue is None and ovalue is not None:
setattr(self, attr, ovalue)
constrained = True
@@ -363,26 +431,22 @@ class ArchSpec(object):
return constrained
def copy(self):
+ """Copy the current instance and returns the clone."""
clone = ArchSpec.__new__(ArchSpec)
clone._dup(self)
return clone
@property
def concrete(self):
- return all(v for k, v in iteritems(self.to_cmp_dict()))
-
- def to_cmp_dict(self):
- """Returns a dictionary that can be used for field comparison."""
- return dict([
- ('platform', self.platform),
- ('os', self.os),
- ('target', self.target)])
+ """True if the spec is concrete, False otherwise"""
+ # return all(v for k, v in six.iteritems(self.to_cmp_dict()))
+ return self.platform and self.os and self.target
def to_dict(self):
d = syaml_dict([
('platform', self.platform),
('platform_os', self.os),
- ('target', self.target)])
+ ('target', self.target.to_dict_or_value())])
return syaml_dict([('arch', d)])
@staticmethod
@@ -400,22 +464,26 @@ class ArchSpec(object):
"""
if not isinstance(d['arch'], dict):
- return ArchSpec('spack09', 'unknown', d['arch'])
+ return ArchSpec(('spack09', 'unknown', d['arch']))
d = d['arch']
- if 'platform_os' in d:
- return ArchSpec(d['platform'], d['platform_os'], d['target'])
- else:
- return ArchSpec(d['platform'], d['os'], d['target'])
+
+ operating_system = d.get('platform_os', None) or d['os']
+ target = spack.architecture.Target.from_dict_or_value(d['target'])
+
+ return ArchSpec((d['platform'], operating_system, target))
def __str__(self):
return "%s-%s-%s" % (self.platform, self.os, self.target)
def __repr__(self):
+ # TODO: this needs to be changed (repr is meant to return valid
+ # TODO: Python code to return an instance equivalent to the current
+ # TODO: one).
return str(self)
def __contains__(self, string):
- return string in str(self)
+ return string in str(self) or string in self.target
@key_ordering
@@ -430,7 +498,7 @@ class CompilerSpec(object):
arg = args[0]
# If there is one argument, it's either another CompilerSpec
# to copy or a string to parse
- if isinstance(arg, string_types):
+ if isinstance(arg, six.string_types):
c = SpecParser().parse_compiler(arg)
self.name = c.name
self.versions = c.versions
@@ -618,7 +686,7 @@ class FlagMap(HashableMap):
return clone
def _cmp_key(self):
- return tuple((k, tuple(v)) for k, v in sorted(iteritems(self)))
+ return tuple((k, tuple(v)) for k, v in sorted(six.iteritems(self)))
def __str__(self):
sorted_keys = [k for k in sorted(self.keys()) if self[k] != []]
@@ -945,7 +1013,7 @@ class Spec(object):
self.external_module = external_module
self._full_hash = full_hash
- if isinstance(spec_like, string_types):
+ if isinstance(spec_like, six.string_types):
spec_list = SpecParser(self).parse(spec_like)
if len(spec_list) > 1:
raise ValueError("More than one spec in string: " + spec_like)
@@ -1032,9 +1100,9 @@ class Spec(object):
if not self.architecture:
new_vals = tuple(kwargs.get(arg, None) for arg in arch_attrs)
- self.architecture = ArchSpec(*new_vals)
+ self.architecture = ArchSpec(new_vals)
else:
- new_attrvals = [(a, v) for a, v in iteritems(kwargs)
+ new_attrvals = [(a, v) for a, v in six.iteritems(kwargs)
if a in arch_attrs]
for new_attr, new_value in new_attrvals:
if getattr(self.architecture, new_attr):
@@ -1186,7 +1254,7 @@ class Spec(object):
# get initial values for kwargs
depth = kwargs.get('depth', False)
key_fun = kwargs.get('key', id)
- if isinstance(key_fun, string_types):
+ if isinstance(key_fun, six.string_types):
key_fun = attrgetter(key_fun)
yield_root = kwargs.get('root', True)
cover = kwargs.get('cover', 'nodes')
@@ -1659,7 +1727,7 @@ class Spec(object):
formats so that reindex will work on old specs/databases.
"""
for dep_name, elt in dependency_dict.items():
- if isinstance(elt, string_types):
+ if isinstance(elt, six.string_types):
# original format, elt is just the dependency hash.
dag_hash, deptypes = elt, ['build', 'link']
elif isinstance(elt, tuple):
@@ -1813,7 +1881,7 @@ class Spec(object):
# Recurse on dependencies
for s, s_dependencies in dep_like.items():
- if isinstance(s, string_types):
+ if isinstance(s, six.string_types):
dag_node, dependency_types = name_and_dependency_types(s)
else:
dag_node, dependency_types = spec_and_dependency_types(s)
@@ -1884,7 +1952,7 @@ class Spec(object):
tty.debug(e)
raise sjson.SpackJSONError("error parsing JSON spec:", str(e))
- def _concretize_helper(self, presets=None, visited=None):
+ def _concretize_helper(self, concretizer, presets=None, visited=None):
"""Recursive helper function for concretize().
This concretizes everything bottom-up. As things are
concretized, they're added to the presets, and ancestors
@@ -1906,8 +1974,9 @@ class Spec(object):
# Concretize deps first -- this is a bottom-up process.
for name in sorted(self._dependencies.keys()):
- changed |= self._dependencies[
- name].spec._concretize_helper(presets, visited)
+ changed |= self._dependencies[name].spec._concretize_helper(
+ concretizer, presets, visited
+ )
if self.name in presets:
changed |= self.constrain(presets[self.name])
@@ -1916,11 +1985,10 @@ class Spec(object):
# to presets below, their constraints will all be merged, but we'll
# still need to select a concrete package later.
if not self.virtual:
- import spack.concretize
- concretizer = spack.concretize.concretizer
changed |= any(
(concretizer.concretize_architecture(self),
concretizer.concretize_compiler(self),
+ concretizer.adjust_target(self),
# flags must be concretized after compiler
concretizer.concretize_compiler_flags(self),
concretizer.concretize_version(self),
@@ -1945,7 +2013,7 @@ class Spec(object):
if concrete.name not in dependent._dependencies:
dependent._add_dependency(concrete, deptypes)
- def _expand_virtual_packages(self):
+ def _expand_virtual_packages(self, concretizer):
"""Find virtual packages in this spec, replace them with providers,
and normalize again to include the provider's (potentially virtual)
dependencies. Repeat until there are no virtual deps.
@@ -1985,8 +2053,6 @@ class Spec(object):
if not replacement:
# Get a list of possible replacements in order of
# preference.
- import spack.concretize
- concretizer = spack.concretize.concretizer
candidates = concretizer.choose_virtual_or_external(spec)
# Try the replacements in order, skipping any that cause
@@ -2075,12 +2141,13 @@ class Spec(object):
force = False
user_spec_deps = self.flat_dependencies(copy=False)
-
+ import spack.concretize
+ concretizer = spack.concretize.Concretizer(self.copy())
while changed:
changes = (self.normalize(force, tests=tests,
user_spec_deps=user_spec_deps),
- self._expand_virtual_packages(),
- self._concretize_helper())
+ self._expand_virtual_packages(concretizer),
+ self._concretize_helper(concretizer))
changed = any(changes)
force = True
@@ -2190,6 +2257,10 @@ class Spec(object):
if matches:
raise ConflictsInSpecError(self, matches)
+ # Check if we can produce an optimized binary (will throw if
+ # there are declared inconsistencies)
+ self.architecture.target.optimization_flags(self.compiler)
+
def _mark_concrete(self, value=True):
"""Mark this spec and its dependencies as concrete.
@@ -3283,7 +3354,7 @@ class Spec(object):
color = kwargs.get('color', False)
transform = kwargs.get('transform', {})
- out = StringIO()
+ out = six.StringIO()
def write(s, c=None):
f = cescape(s)
@@ -3515,7 +3586,7 @@ class Spec(object):
(k.upper(), v) for k, v in kwargs.get('transform', {}).items())
length = len(format_string)
- out = StringIO()
+ out = six.StringIO()
named = escape = compiler = False
named_str = fmt = ''
@@ -3790,6 +3861,20 @@ class Spec(object):
def __repr__(self):
return str(self)
+ @property
+ def platform(self):
+ return self.architecture.platform
+
+ @property
+ def os(self):
+ return self.architecture.os
+
+ @property
+ def target(self):
+ # This property returns the underlying microarchitecture object
+ # to give to the attribute the appropriate comparison semantic
+ return self.architecture.target.microarchitecture
+
class LazySpecCache(collections.defaultdict):
"""Cache for Specs that uses a spec_like as key, and computes lazily