diff options
Diffstat (limited to 'lib/spack/spack/spec.py')
-rw-r--r-- | lib/spack/spack/spec.py | 338 |
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 |