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.py338
1 files changed, 239 insertions, 99 deletions
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 951e9ae652..b95f43e901 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -97,7 +97,6 @@ expansion when it is the first character in an id typed on the command line.
"""
import base64
import hashlib
-import imp
import ctypes
from StringIO import StringIO
from operator import attrgetter
@@ -105,7 +104,6 @@ from operator import attrgetter
from yaml.error import MarkedYAMLError
import llnl.util.tty as tty
-from llnl.util.filesystem import join_path
from llnl.util.lang import *
from llnl.util.tty.color import *
@@ -116,7 +114,6 @@ import spack.compilers as compilers
import spack.error
import spack.parse
from spack.build_environment import get_path_from_module, load_module
-from spack.util.naming import mod_to_class
from spack.util.prefix import Prefix
from spack.util.string import *
import spack.util.spack_yaml as syaml
@@ -256,6 +253,204 @@ 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).
+ """
+
+ # 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
+
+ self.platform, self.platform_os, self.target = (None, None, None)
+
+ if len(args) == 1:
+ spec_like = args[0]
+ if isinstance(spec_like, ArchSpec):
+ self._dup(spec_like)
+ elif isinstance(spec_like, basestring):
+ spec_fields = spec_like.split("-")
+
+ if len(spec_fields) == 3:
+ self.platform, self.platform_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.platform_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)
+
+ def _autospec(self, spec_like):
+ if isinstance(spec_like, ArchSpec):
+ return spec_like
+ return ArchSpec(spec_like)
+
+ def _cmp_key(self):
+ return (self.platform, self.platform_os, self.target)
+
+ def _dup(self, other):
+ self.platform = other.platform
+ self.platform_os = other.platform_os
+ self.target = other.target
+
+ @property
+ def platform(self):
+ 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.
+ """
+ value = str(value) if value is not None else None
+ self._platform = value
+
+ @property
+ def platform_os(self):
+ return self._platform_os
+
+ @platform_os.setter
+ def platform_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.
+ """
+ value = str(value) if value is not None else None
+
+ if value in spack.architecture.Platform.reserved_oss:
+ curr_platform = str(spack.architecture.platform())
+ self.platform = self.platform or curr_platform
+
+ if self.platform != curr_platform:
+ raise ValueError(
+ "Can't set arch spec OS to reserved value '%s' when the "
+ "arch platform (%s) isn't the current platform (%s)" %
+ (value, self.platform, curr_platform))
+
+ spec_platform = spack.architecture.get_platform(self.platform)
+ value = str(spec_platform.operating_system(value))
+
+ self._platform_os = value
+
+ @property
+ def target(self):
+ 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
+
+ if value in spack.architecture.Platform.reserved_targets:
+ curr_platform = str(spack.architecture.platform())
+ self.platform = self.platform or curr_platform
+
+ if self.platform != curr_platform:
+ raise ValueError(
+ "Can't set arch spec target to reserved value '%s' when "
+ "the arch platform (%s) isn't the current platform (%s)" %
+ (value, self.platform, curr_platform))
+
+ spec_platform = spack.architecture.get_platform(self.platform)
+ value = str(spec_platform.target(value))
+
+ self._target = value
+
+ def satisfies(self, other, strict=False):
+ 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])
+
+ 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.
+ """
+ other = self._autospec(other)
+
+ if not self.satisfies(other):
+ raise UnsatisfiableArchitectureSpecError(self, other)
+
+ constrained = False
+ for attr, svalue in self.to_cmp_dict().iteritems():
+ ovalue = getattr(other, attr)
+ if svalue is None and ovalue is not None:
+ setattr(self, attr, ovalue)
+ constrained = True
+
+ return constrained
+
+ def copy(self):
+ clone = ArchSpec.__new__(ArchSpec)
+ clone._dup(self)
+ return clone
+
+ @property
+ def concrete(self):
+ return all(v for k, v in self.to_cmp_dict().iteritems())
+
+ def to_cmp_dict(self):
+ """Returns a dictionary that can be used for field comparison."""
+ return dict([
+ ('platform', self.platform),
+ ('platform_os', self.platform_os),
+ ('target', self.target)])
+
+ def to_dict(self):
+ d = syaml_dict([
+ ('platform', self.platform),
+ ('platform_os', self.platform_os),
+ ('target', self.target)])
+ return syaml_dict([('arch', d)])
+
+ @staticmethod
+ def from_dict(d):
+ """Import an ArchSpec from raw YAML/JSON data.
+
+ This routine implements a measure of compatibility with older
+ versions of Spack. Spack releases before 0.10 used a single
+ string with no OS or platform identifiers. We import old Spack
+ architectures with platform ``spack09``, OS ``unknown``, and the
+ old arch string as the target.
+
+ Specs from `0.10` or later have a more fleshed out architecture
+ descriptor with a platform, an OS, and a target.
+
+ """
+ if not isinstance(d['arch'], dict):
+ return ArchSpec('spack09', 'unknown', d['arch'])
+
+ d = d['arch']
+ return ArchSpec(d['platform'], d['platform_os'], d['target'])
+
+ def __str__(self):
+ return "%s-%s-%s" % (self.platform, self.platform_os, self.target)
+
+ def __repr__(self):
+ return str(self)
+
+
+@key_ordering
class CompilerSpec(object):
"""The CompilerSpec field represents the compiler or range of compiler
versions that a package should be built with. CompilerSpecs have a
@@ -664,38 +859,42 @@ class Spec(object):
"""
valid_flags = FlagMap.valid_compiler_flags()
if name == 'arch' or name == 'architecture':
- parts = value.split('-')
- if len(parts) == 3:
- platform, op_sys, target = parts
- else:
- platform, op_sys, target = None, None, value
-
- assert(self.architecture.platform is None)
- assert(self.architecture.platform_os is None)
- assert(self.architecture.target is None)
- assert(self.architecture.os_string is None)
- assert(self.architecture.target_string is None)
- self._set_platform(platform)
- self._set_os(op_sys)
- self._set_target(target)
+ parts = tuple(value.split('-'))
+ plat, os, tgt = parts if len(parts) == 3 else (None, None, value)
+ self._set_architecture(platform=plat, platform_os=os, target=tgt)
elif name == 'platform':
- self._set_platform(value)
+ self._set_architecture(platform=value)
elif name == 'os' or name == 'operating_system':
- if self.architecture.platform:
- self._set_os(value)
- else:
- self.architecture.os_string = value
+ self._set_architecture(platform_os=value)
elif name == 'target':
- if self.architecture.platform:
- self._set_target(value)
- else:
- self.architecture.target_string = value
+ self._set_architecture(target=value)
elif name in valid_flags:
assert(self.compiler_flags is not None)
self.compiler_flags[name] = value.split()
else:
self._add_variant(name, value)
+ def _set_architecture(self, **kwargs):
+ """Called by the parser to set the architecture."""
+ arch_attrs = ['platform', 'platform_os', 'target']
+ if self.architecture and self.architecture.concrete:
+ raise DuplicateArchitectureError(
+ "Spec for '%s' cannot have two architectures." % self.name)
+
+ if not self.architecture:
+ new_vals = tuple(kwargs.get(arg, None) for arg in arch_attrs)
+ self.architecture = ArchSpec(*new_vals)
+ else:
+ new_attrvals = [(a, v) for a, v in kwargs.iteritems()
+ if a in arch_attrs]
+ for new_attr, new_value in new_attrvals:
+ if getattr(self.architecture, new_attr):
+ raise DuplicateArchitectureError(
+ "Spec for '%s' cannot have two '%s' specified "
+ "for its architecture" % (self.name, new_attr))
+ else:
+ setattr(self.architecture, new_attr, new_value)
+
def _set_compiler(self, compiler):
"""Called by the parser to set the compiler."""
if self.compiler:
@@ -703,53 +902,6 @@ class Spec(object):
"Spec for '%s' cannot have two compilers." % self.name)
self.compiler = compiler
- def _set_platform(self, value):
- """Called by the parser to set the architecture platform"""
- if isinstance(value, basestring):
- mod_path = spack.platform_path
- mod_string = 'spack.platformss'
- names = list_modules(mod_path)
- if value in names:
- # Create a platform object from the name
- mod_name = mod_string + value
- path = join_path(mod_path, value) + '.py'
- mod = imp.load_source(mod_name, path)
- class_name = mod_to_class(value)
- if not hasattr(mod, class_name):
- tty.die(
- 'No class %s defined in %s' % (class_name, mod_name))
- cls = getattr(mod, class_name)
- if not inspect.isclass(cls):
- tty.die('%s.%s is not a class' % (mod_name, class_name))
- platform = cls()
- else:
- tty.die("No platform class %s defined." % value)
- else:
- # The value is a platform
- platform = value
-
- self.architecture.platform = platform
-
- # Set os and target if we previously got strings for them
- if self.architecture.os_string:
- self._set_os(self.architecture.os_string)
- self.architecture.os_string = None
- if self.architecture.target_string:
- self._set_target(self.architecture.target_string)
- self.architecture.target_string = None
-
- def _set_os(self, value):
- """Called by the parser to set the architecture operating system"""
- arch = self.architecture
- if arch.platform:
- arch.platform_os = arch.platform.operating_system(value)
-
- def _set_target(self, value):
- """Called by the parser to set the architecture target"""
- arch = self.architecture
- if arch.platform:
- arch.target = arch.platform.target(value)
-
def _add_dependency(self, spec, deptypes):
"""Called by the parser to add another spec as a dependency."""
if spec.name in self._dependencies:
@@ -990,6 +1142,9 @@ class Spec(object):
if self.versions:
d.update(self.versions.to_dict())
+ if self.architecture:
+ d.update(self.architecture.to_dict())
+
if self.compiler:
d.update(self.compiler.to_dict())
@@ -1002,9 +1157,6 @@ class Spec(object):
if params:
d['parameters'] = params
- if self.architecture:
- d['arch'] = self.architecture.to_dict()
-
# TODO: restore build dependencies here once we have less picky
# TODO: concretization.
deps = self.dependencies_dict(deptype=('link', 'run'))
@@ -1042,7 +1194,7 @@ class Spec(object):
spec.versions = VersionList.from_dict(node)
if 'arch' in node:
- spec.architecture = spack.architecture.arch_from_dict(node['arch'])
+ spec.architecture = ArchSpec.from_dict(node)
if 'compiler' in node:
spec.compiler = CompilerSpec.from_dict(node)
@@ -1861,25 +2013,10 @@ class Spec(object):
# Architecture satisfaction is currently just string equality.
# If not strict, None means unconstrained.
- sarch, oarch = self.architecture, other.architecture
- if sarch and oarch:
- if ((sarch.platform and
- oarch.platform and
- sarch.platform != oarch.platform) or
-
- (sarch.platform_os and
- oarch.platform_os and
- sarch.platform_os != oarch.platform_os) or
-
- (sarch.target and
- oarch.target and
- sarch.target != oarch.target)):
+ if self.architecture and other.architecture:
+ if not self.architecture.satisfies(other.architecture, strict):
return False
-
- elif strict and ((oarch and not sarch) or
- (oarch.platform and not sarch.platform) or
- (oarch.platform_os and not sarch.platform_os) or
- (oarch.target and not sarch.target)):
+ elif strict and (other.architecture and not self.architecture):
return False
if not self.compiler_flags.satisfies(
@@ -1975,7 +2112,8 @@ class Spec(object):
# Local node attributes get copied first.
self.name = other.name
self.versions = other.versions.copy()
- self.architecture = other.architecture
+ self.architecture = other.architecture.copy() if other.architecture \
+ else None
self.compiler = other.compiler.copy() if other.compiler else None
if cleardeps:
self._dependents = DependencyMap()
@@ -2540,10 +2678,12 @@ class SpecParser(spack.parse.Parser):
# If the spec has an os or a target and no platform, give it
# the default platform
+ platform_default = spack.architecture.platform().name
for spec in specs:
for s in spec.traverse():
- if s.architecture.os_string or s.architecture.target_string:
- s._set_platform(spack.architecture.platform())
+ if s.architecture and not s.architecture.platform and \
+ (s.architecture.platform_os or s.architecture.target):
+ s._set_architecture(platform=platform_default)
return specs
def parse_compiler(self, text):
@@ -2585,7 +2725,7 @@ class SpecParser(spack.parse.Parser):
spec.name = spec_name
spec.versions = VersionList()
spec.variants = VariantMap(spec)
- spec.architecture = spack.architecture.Arch()
+ spec.architecture = None
spec.compiler = None
spec.external = None
spec.external_module = None