Fixes to Handling Multiple Architectures (#2261)

* Added some notes about how multiarch detection could be fixed.

* Implemented a preliminary version of the "spack.spec.ArchSpec" class.

* Updated the "spack.spec.Spec" class to use "ArchSpec" instead of "Arch".

* Fixed a number of small bugs in the "spack.spec.ArchSpec" class.

* Fixed the 'Concretizer.concretize_architecture' method so that it uses the new architecture specs.

* Updated the package class to properly use arch specs.
Removed a number of unused architecture functions.

* Fixed up a number of bugs that were causing the regression tests to fail.
Added a couple of additional regression tests related to architecture parsing/specification.
Fixed a few bugs with setting reserved os/target values on "ArchSpec" objects.
Removed a number of unnecessary functions in the "spack.architecture" and "spack.concretize" modules.

* Fixed a few bugs with reading architecture information from specs.
Updated the tests to use a uniform architecture to improve reliability.
Fixed a few minor style issues.

* Adapted the compiler component of Spack to use arch specs.

* Implemented more test cases for the extended architecture spec features.
Improved error detection for multiple arch components in a spec.

* Fix for backwards compatibility with v0.8 and prior

* Changed os to unknown for compatibility specs

* Use `spack09` instead of `spackcompat` for the platform of old specs.
This commit is contained in:
Joseph Ciurej 2016-12-03 15:38:31 -08:00 committed by Todd Gamblin
parent 2f46613132
commit 552b4eae9e
13 changed files with 471 additions and 270 deletions

View File

@ -126,8 +126,7 @@ def __str__(self):
@key_ordering @key_ordering
class Platform(object): class Platform(object):
""" Abstract class that each type of Platform will subclass. """ Abstract class that each type of Platform will subclass.
Will return a instance of it once it Will return a instance of it once it is returned.
is returned
""" """
priority = None # Subclass sets number. Controls detection order priority = None # Subclass sets number. Controls detection order
@ -139,6 +138,9 @@ class Platform(object):
back_os = None back_os = None
default_os = None default_os = None
reserved_targets = ['default_target', 'frontend', 'fe', 'backend', 'be']
reserved_oss = ['default_os', 'frontend', 'fe', 'backend', 'be']
def __init__(self, name): def __init__(self, name):
self.targets = {} self.targets = {}
self.operating_sys = {} self.operating_sys = {}
@ -149,7 +151,7 @@ def add_target(self, name, target):
Raises an error if the platform specifies a name Raises an error if the platform specifies a name
that is reserved by spack as an alias. that is reserved by spack as an alias.
""" """
if name in ['frontend', 'fe', 'backend', 'be', 'default_target']: if name in Platform.reserved_targets:
raise ValueError( raise ValueError(
"%s is a spack reserved alias " "%s is a spack reserved alias "
"and cannot be the name of a target" % name) "and cannot be the name of a target" % name)
@ -174,7 +176,7 @@ def add_operating_system(self, name, os_class):
""" Add the operating_system class object into the """ Add the operating_system class object into the
platform.operating_sys dictionary platform.operating_sys dictionary
""" """
if name in ['frontend', 'fe', 'backend', 'be', 'default_os']: if name in Platform.reserved_oss:
raise ValueError( raise ValueError(
"%s is a spack reserved alias " "%s is a spack reserved alias "
"and cannot be the name of an OS" % name) "and cannot be the name of an OS" % name)
@ -241,7 +243,7 @@ def __init__(self, name, version):
self.version = version self.version = version
def __str__(self): def __str__(self):
return self.name + self.version return "%s%s" % (self.name, self.version)
def __repr__(self): def __repr__(self):
return self.__str__() return self.__str__()
@ -409,86 +411,52 @@ def _cmp_key(self):
return (platform, platform_os, target) return (platform, platform_os, target)
def to_dict(self): def to_dict(self):
return syaml_dict(( str_or_none = lambda v: str(v) if v else None
('platform', d = syaml_dict([
str(self.platform) if self.platform else None), ('platform', str_or_none(self.platform)),
('platform_os', ('platform_os', str_or_none(self.platform_os)),
str(self.platform_os) if self.platform_os else None), ('target', str_or_none(self.target))])
('target', return syaml_dict([('arch', d)])
str(self.target) if self.target else None)))
@staticmethod
def from_dict(d):
spec = spack.spec.ArchSpec.from_dict(d)
return arch_for_spec(spec)
def _target_from_dict(target_name, plat=None): def get_platform(platform_name):
""" Creates new instance of target and assigns all the attributes of """Returns a platform object that corresponds to the given name."""
that target from the dictionary
"""
if not plat:
plat = platform()
return plat.target(target_name)
def _operating_system_from_dict(os_name, plat=None):
""" uses platform's operating system method to grab the constructed
operating systems that are valid on the platform.
"""
if not plat:
plat = platform()
if isinstance(os_name, dict):
name = os_name['name']
version = os_name['version']
return plat.operating_system(name + version)
else:
return plat.operating_system(os_name)
def _platform_from_dict(platform_name):
""" Constructs a platform from a dictionary. """
platform_list = all_platforms() platform_list = all_platforms()
for p in platform_list: for p in platform_list:
if platform_name.replace("_", "").lower() == p.__name__.lower(): if platform_name.replace("_", "").lower() == p.__name__.lower():
return p() return p()
def arch_from_dict(d): def verify_platform(platform_name):
""" Uses _platform_from_dict, _operating_system_from_dict, _target_from_dict """ Determines whether or not the platform with the given name is supported
helper methods to recreate the arch tuple from the dictionary read from in Spack. For more information, see the 'spack.platforms' submodule.
a yaml file
""" """
arch = Arch() platform_name = platform_name.replace("_", "").lower()
platform_names = [p.__name__.lower() for p in all_platforms()]
if isinstance(d, basestring): if platform_name not in platform_names:
# We have an old spec using a string for the architecture tty.die("%s is not a supported platform; supported platforms are %s" %
arch.platform = Platform('spack_compatibility') (platform_name, platform_names))
arch.platform_os = OperatingSystem('unknown', '')
arch.target = Target(d)
arch.os_string = None
arch.target_string = None
else:
if d is None:
return None
platform_name = d['platform']
os_name = d['platform_os']
target_name = d['target']
if platform_name: def arch_for_spec(arch_spec):
arch.platform = _platform_from_dict(platform_name) """Transforms the given architecture spec into an architecture objct."""
else: arch_spec = spack.spec.ArchSpec(arch_spec)
arch.platform = None assert(arch_spec.concrete)
if target_name:
arch.target = _target_from_dict(target_name, arch.platform)
else:
arch.target = None
if os_name:
arch.platform_os = _operating_system_from_dict(os_name,
arch.platform)
else:
arch.platform_os = None
arch.os_string = None arch_plat = get_platform(arch_spec.platform)
arch.target_string = None if not (arch_plat.operating_system(arch_spec.platform_os) and
arch_plat.target(arch_spec.target)):
raise ValueError(
"Can't recreate arch for spec %s on current arch %s; "
"spec architecture is too different" % (arch_spec, sys_type()))
return arch return Arch(arch_plat, arch_spec.platform_os, arch_spec.target)
@memoized @memoized

View File

@ -339,7 +339,7 @@ def set_build_environment_variables(pkg, env, dirty=False):
if os.path.isdir(pcdir): if os.path.isdir(pcdir):
env.prepend_path('PKG_CONFIG_PATH', pcdir) env.prepend_path('PKG_CONFIG_PATH', pcdir)
if pkg.spec.architecture.target.module_name: if pkg.architecture.target.module_name:
load_module(pkg.spec.architecture.target.module_name) load_module(pkg.spec.architecture.target.module_name)
return env return env
@ -492,7 +492,7 @@ def setup_package(pkg, dirty=False):
set_compiler_environment_variables(pkg, spack_env) set_compiler_environment_variables(pkg, spack_env)
set_build_environment_variables(pkg, spack_env, dirty) set_build_environment_variables(pkg, spack_env, dirty)
pkg.spec.architecture.platform.setup_platform_environment(pkg, spack_env) pkg.architecture.platform.setup_platform_environment(pkg, spack_env)
load_external_modules(pkg) load_external_modules(pkg)
# traverse in postorder so package can use vars from its dependencies # traverse in postorder so package can use vars from its dependencies
spec = pkg.spec spec = pkg.spec

View File

@ -115,8 +115,8 @@ def fc_rpath_arg(self):
def __init__(self, cspec, operating_system, def __init__(self, cspec, operating_system,
paths, modules=[], alias=None, environment=None, paths, modules=[], alias=None, environment=None,
extra_rpaths=None, **kwargs): extra_rpaths=None, **kwargs):
self.operating_system = operating_system
self.spec = cspec self.spec = cspec
self.operating_system = str(operating_system)
self.modules = modules self.modules = modules
self.alias = alias self.alias = alias

View File

@ -202,20 +202,23 @@ def find(compiler_spec, scope=None):
@_auto_compiler_spec @_auto_compiler_spec
def compilers_for_spec(compiler_spec, scope=None, **kwargs): def compilers_for_spec(compiler_spec, arch_spec=None, scope=None):
"""This gets all compilers that satisfy the supplied CompilerSpec. """This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found. Returns an empty list if none are found.
""" """
platform = kwargs.get('platform', None)
config = all_compilers_config(scope) config = all_compilers_config(scope)
def get_compilers(cspec): def get_compilers(cspec):
compilers = [] compilers = []
for items in config: for items in config:
if items['compiler']['spec'] != str(cspec):
continue
items = items['compiler'] items = items['compiler']
if items['spec'] != str(cspec):
continue
os = items.get('operating_system', None)
if arch_spec and os != arch_spec.platform_os:
continue
if not ('paths' in items and if not ('paths' in items and
all(n in items['paths'] for n in _path_instance_vars)): all(n in items['paths'] for n in _path_instance_vars)):
@ -235,11 +238,6 @@ def get_compilers(cspec):
if mods == 'None': if mods == 'None':
mods = [] mods = []
os = None
if 'operating_system' in items:
os = spack.architecture._operating_system_from_dict(
items['operating_system'], platform)
alias = items.get('alias', None) alias = items.get('alias', None)
compiler_flags = items.get('flags', {}) compiler_flags = items.get('flags', {})
environment = items.get('environment', {}) environment = items.get('environment', {})
@ -259,17 +257,15 @@ def get_compilers(cspec):
@_auto_compiler_spec @_auto_compiler_spec
def compiler_for_spec(compiler_spec, arch): def compiler_for_spec(compiler_spec, arch_spec):
"""Get the compiler that satisfies compiler_spec. compiler_spec must """Get the compiler that satisfies compiler_spec. compiler_spec must
be concrete.""" be concrete."""
operating_system = arch.platform_os
assert(compiler_spec.concrete) assert(compiler_spec.concrete)
assert(arch_spec.concrete)
compilers = [ compilers = compilers_for_spec(compiler_spec, arch_spec=arch_spec)
c for c in compilers_for_spec(compiler_spec, platform=arch.platform)
if c.operating_system == operating_system]
if len(compilers) < 1: if len(compilers) < 1:
raise NoCompilerForSpecError(compiler_spec, operating_system) raise NoCompilerForSpecError(compiler_spec, arch_spec.platform_os)
if len(compilers) > 1: if len(compilers) > 1:
raise CompilerSpecInsufficientlySpecificError(compiler_spec) raise CompilerSpecInsufficientlySpecificError(compiler_spec)
return compilers[0] return compilers[0]

View File

@ -240,47 +240,6 @@ def concretize_version(self, spec):
return True # Things changed return True # Things changed
def _concretize_operating_system(self, spec):
if spec.architecture.platform_os is not None and isinstance(
spec.architecture.platform_os,
spack.architecture.OperatingSystem):
return False
if spec.root.architecture and spec.root.architecture.platform_os:
if isinstance(spec.root.architecture.platform_os,
spack.architecture.OperatingSystem):
spec.architecture.platform_os = \
spec.root.architecture.platform_os
else:
spec.architecture.platform_os = \
spec.architecture.platform.operating_system('default_os')
return True # changed
def _concretize_target(self, spec):
if spec.architecture.target is not None and isinstance(
spec.architecture.target, spack.architecture.Target):
return False
if spec.root.architecture and spec.root.architecture.target:
if isinstance(spec.root.architecture.target,
spack.architecture.Target):
spec.architecture.target = spec.root.architecture.target
else:
spec.architecture.target = spec.architecture.platform.target(
'default_target')
return True # changed
def _concretize_platform(self, spec):
if spec.architecture.platform is not None and isinstance(
spec.architecture.platform, spack.architecture.Platform):
return False
if spec.root.architecture and spec.root.architecture.platform:
if isinstance(spec.root.architecture.platform,
spack.architecture.Platform):
spec.architecture.platform = spec.root.architecture.platform
else:
spec.architecture.platform = spack.architecture.platform()
return True # changed?
def concretize_architecture(self, spec): def concretize_architecture(self, spec):
"""If the spec is empty provide the defaults of the platform. If the """If the spec is empty provide the defaults of the platform. If the
architecture is not a basestring, then check if either the platform, architecture is not a basestring, then check if either the platform,
@ -292,16 +251,25 @@ def concretize_architecture(self, spec):
DAG has an architecture, then use the root otherwise use the defaults DAG has an architecture, then use the root otherwise use the defaults
on the platform. on the platform.
""" """
if spec.architecture is None: root_arch = spec.root.architecture
# Set the architecture to all defaults sys_arch = spack.spec.ArchSpec(spack.architecture.sys_type())
spec.architecture = spack.architecture.Arch() spec_changed = False
return True
# Concretize the operating_system and target based of the spec if spec.architecture is None:
ret = any((self._concretize_platform(spec), spec.architecture = spack.spec.ArchSpec(sys_arch)
self._concretize_operating_system(spec), spec_changed = True
self._concretize_target(spec)))
return ret default_archs = [root_arch, sys_arch]
while not spec.architecture.concrete and default_archs:
arch = default_archs.pop(0)
replacement_fields = [k for k, v in arch.to_cmp_dict().iteritems()
if v and not getattr(spec.architecture, k)]
for field in replacement_fields:
setattr(spec.architecture, field, getattr(arch, field))
spec_changed = True
return spec_changed
def concretize_variants(self, spec): def concretize_variants(self, spec):
"""If the spec already has variants filled in, return. Otherwise, add """If the spec already has variants filled in, return. Otherwise, add
@ -343,13 +311,8 @@ def concretize_compiler(self, spec):
# Takes advantage of the proper logic already existing in # Takes advantage of the proper logic already existing in
# compiler_for_spec Should think whether this can be more # compiler_for_spec Should think whether this can be more
# efficient # efficient
def _proper_compiler_style(cspec, arch): def _proper_compiler_style(cspec, aspec):
platform = arch.platform return spack.compilers.compilers_for_spec(cspec, arch_spec=aspec)
compilers = spack.compilers.compilers_for_spec(cspec,
platform=platform)
return filter(lambda c: c.operating_system ==
arch.platform_os, compilers)
# return compilers
all_compilers = spack.compilers.all_compilers() all_compilers = spack.compilers.all_compilers()

View File

@ -898,7 +898,14 @@ def prefix(self):
return self.spec.prefix return self.spec.prefix
@property @property
# TODO: Change this to architecture def architecture(self):
"""Get the spack.architecture.Arch object that represents the
environment in which this package will be built."""
if not self.spec.concrete:
raise ValueError("Can only get the arch for concrete package.")
return spack.architecture.arch_for_spec(self.spec.architecture)
@property
def compiler(self): def compiler(self):
"""Get the spack.compiler.Compiler object used to build this package""" """Get the spack.compiler.Compiler object used to build this package"""
if not self.spec.concrete: if not self.spec.concrete:

View File

@ -23,8 +23,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
############################################################################## ##############################################################################
from spack.architecture import Platform, Target from spack.architecture import Platform, Target
from spack.operating_systems.linux_distro import LinuxDistro from spack.architecture import OperatingSystem as OS
from spack.operating_systems.cnl import Cnl
class Test(Platform): class Test(Platform):
@ -33,18 +32,17 @@ class Test(Platform):
back_end = 'x86_64' back_end = 'x86_64'
default = 'x86_64' default = 'x86_64'
back_os = 'CNL10' front_os = 'redhat6'
default_os = 'CNL10' back_os = 'debian6'
default_os = 'debian6'
def __init__(self): def __init__(self):
super(Test, self).__init__('test') super(Test, self).__init__('test')
self.add_target(self.default, Target(self.default)) self.add_target(self.default, Target(self.default))
self.add_target(self.front_end, Target(self.front_end)) self.add_target(self.front_end, Target(self.front_end))
self.add_operating_system(self.default_os, Cnl()) self.add_operating_system(self.default_os, OS('debian', 6))
linux_dist = LinuxDistro() self.add_operating_system(self.front_os, OS('redhat', 6))
self.front_os = linux_dist.name
self.add_operating_system(self.front_os, linux_dist)
@classmethod @classmethod
def detect(self): def detect(self):

View File

@ -97,7 +97,6 @@
""" """
import base64 import base64
import hashlib import hashlib
import imp
import ctypes import ctypes
from StringIO import StringIO from StringIO import StringIO
from operator import attrgetter from operator import attrgetter
@ -105,7 +104,6 @@
from yaml.error import MarkedYAMLError from yaml.error import MarkedYAMLError
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.filesystem import join_path
from llnl.util.lang import * from llnl.util.lang import *
from llnl.util.tty.color import * from llnl.util.tty.color import *
@ -116,7 +114,6 @@
import spack.error import spack.error
import spack.parse import spack.parse
from spack.build_environment import get_path_from_module, load_module 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.prefix import Prefix
from spack.util.string import * from spack.util.string import *
import spack.util.spack_yaml as syaml import spack.util.spack_yaml as syaml
@ -255,6 +252,204 @@ def __call__(self, match):
return colorize(re.sub(_separators, insert_color(), str(spec)) + '@.') return colorize(re.sub(_separators, insert_color(), str(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 @key_ordering
class CompilerSpec(object): class CompilerSpec(object):
"""The CompilerSpec field represents the compiler or range of compiler """The CompilerSpec field represents the compiler or range of compiler
@ -664,38 +859,42 @@ def _add_flag(self, name, value):
""" """
valid_flags = FlagMap.valid_compiler_flags() valid_flags = FlagMap.valid_compiler_flags()
if name == 'arch' or name == 'architecture': if name == 'arch' or name == 'architecture':
parts = value.split('-') parts = tuple(value.split('-'))
if len(parts) == 3: plat, os, tgt = parts if len(parts) == 3 else (None, None, value)
platform, op_sys, target = parts self._set_architecture(platform=plat, platform_os=os, target=tgt)
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)
elif name == 'platform': elif name == 'platform':
self._set_platform(value) self._set_architecture(platform=value)
elif name == 'os' or name == 'operating_system': elif name == 'os' or name == 'operating_system':
if self.architecture.platform: self._set_architecture(platform_os=value)
self._set_os(value)
else:
self.architecture.os_string = value
elif name == 'target': elif name == 'target':
if self.architecture.platform: self._set_architecture(target=value)
self._set_target(value)
else:
self.architecture.target_string = value
elif name in valid_flags: elif name in valid_flags:
assert(self.compiler_flags is not None) assert(self.compiler_flags is not None)
self.compiler_flags[name] = value.split() self.compiler_flags[name] = value.split()
else: else:
self._add_variant(name, value) 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): def _set_compiler(self, compiler):
"""Called by the parser to set the compiler.""" """Called by the parser to set the compiler."""
if self.compiler: if self.compiler:
@ -703,53 +902,6 @@ def _set_compiler(self, compiler):
"Spec for '%s' cannot have two compilers." % self.name) "Spec for '%s' cannot have two compilers." % self.name)
self.compiler = compiler 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): def _add_dependency(self, spec, deptypes):
"""Called by the parser to add another spec as a dependency.""" """Called by the parser to add another spec as a dependency."""
if spec.name in self._dependencies: if spec.name in self._dependencies:
@ -990,6 +1142,9 @@ def to_node_dict(self):
if self.versions: if self.versions:
d.update(self.versions.to_dict()) d.update(self.versions.to_dict())
if self.architecture:
d.update(self.architecture.to_dict())
if self.compiler: if self.compiler:
d.update(self.compiler.to_dict()) d.update(self.compiler.to_dict())
@ -1002,9 +1157,6 @@ def to_node_dict(self):
if params: if params:
d['parameters'] = params d['parameters'] = params
if self.architecture:
d['arch'] = self.architecture.to_dict()
# TODO: restore build dependencies here once we have less picky # TODO: restore build dependencies here once we have less picky
# TODO: concretization. # TODO: concretization.
deps = self.dependencies_dict(deptype=('link', 'run')) deps = self.dependencies_dict(deptype=('link', 'run'))
@ -1042,7 +1194,7 @@ def from_node_dict(node):
spec.versions = VersionList.from_dict(node) spec.versions = VersionList.from_dict(node)
if 'arch' in node: if 'arch' in node:
spec.architecture = spack.architecture.arch_from_dict(node['arch']) spec.architecture = ArchSpec.from_dict(node)
if 'compiler' in node: if 'compiler' in node:
spec.compiler = CompilerSpec.from_dict(node) spec.compiler = CompilerSpec.from_dict(node)
@ -1861,25 +2013,10 @@ def satisfies(self, other, deps=True, strict=False):
# Architecture satisfaction is currently just string equality. # Architecture satisfaction is currently just string equality.
# If not strict, None means unconstrained. # If not strict, None means unconstrained.
sarch, oarch = self.architecture, other.architecture if self.architecture and other.architecture:
if sarch and oarch: if not self.architecture.satisfies(other.architecture, strict):
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)):
return False return False
elif strict and (other.architecture and not self.architecture):
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)):
return False return False
if not self.compiler_flags.satisfies( if not self.compiler_flags.satisfies(
@ -1975,7 +2112,8 @@ def _dup(self, other, deps=True, cleardeps=True):
# Local node attributes get copied first. # Local node attributes get copied first.
self.name = other.name self.name = other.name
self.versions = other.versions.copy() 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 self.compiler = other.compiler.copy() if other.compiler else None
if cleardeps: if cleardeps:
self._dependents = DependencyMap() self._dependents = DependencyMap()
@ -2540,10 +2678,12 @@ def do_parse(self):
# If the spec has an os or a target and no platform, give it # If the spec has an os or a target and no platform, give it
# the default platform # the default platform
platform_default = spack.architecture.platform().name
for spec in specs: for spec in specs:
for s in spec.traverse(): for s in spec.traverse():
if s.architecture.os_string or s.architecture.target_string: if s.architecture and not s.architecture.platform and \
s._set_platform(spack.architecture.platform()) (s.architecture.platform_os or s.architecture.target):
s._set_architecture(platform=platform_default)
return specs return specs
def parse_compiler(self, text): def parse_compiler(self, text):
@ -2585,7 +2725,7 @@ def spec(self, name, check_valid_token=False):
spec.name = spec_name spec.name = spec_name
spec.versions = VersionList() spec.versions = VersionList()
spec.variants = VariantMap(spec) spec.variants = VariantMap(spec)
spec.architecture = spack.architecture.Arch() spec.architecture = None
spec.compiler = None spec.compiler = None
spec.external = None spec.external = None
spec.external_module = None spec.external_module = None

View File

@ -28,9 +28,11 @@
import llnl.util.tty as tty import llnl.util.tty as tty
import nose import nose
import spack import spack
import spack.architecture
from llnl.util.filesystem import join_path from llnl.util.filesystem import join_path
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
from spack.test.tally_plugin import Tally from spack.test.tally_plugin import Tally
from spack.platforms.test import Test as TestPlatform
"""Names of tests to be included in Spack's test suite""" """Names of tests to be included in Spack's test suite"""
# All the tests Spack knows about. # All the tests Spack knows about.
@ -84,6 +86,13 @@
] ]
def setup_tests():
"""Prepare the environment for the Spack tests to be run."""
test_platform = TestPlatform()
spack.architecture.real_platform = spack.architecture.platform
spack.architecture.platform = lambda: test_platform
def list_tests(): def list_tests():
"""Return names of all tests that can be run for Spack.""" """Return names of all tests that can be run for Spack."""
return test_names return test_names
@ -117,6 +126,8 @@ def run(names, outputDir, verbose=False):
runOpts += ["--with-xunit", runOpts += ["--with-xunit",
"--xunit-file={0}".format(xmlOutputPath)] "--xunit-file={0}".format(xmlOutputPath)]
argv = [""] + runOpts + modules argv = [""] + runOpts + modules
setup_tests()
nose.run(argv=argv, addplugins=[tally]) nose.run(argv=argv, addplugins=[tally])
succeeded = not tally.failCount and not tally.errorCount succeeded = not tally.failCount and not tally.errorCount

View File

@ -54,10 +54,7 @@ def test_dict_functions_for_architecture(self):
arch.platform_os = arch.platform.operating_system('default_os') arch.platform_os = arch.platform.operating_system('default_os')
arch.target = arch.platform.target('default_target') arch.target = arch.platform.target('default_target')
d = arch.to_dict() new_arch = spack.architecture.Arch.from_dict(arch.to_dict())
new_arch = spack.architecture.arch_from_dict(d)
self.assertEqual(arch, new_arch) self.assertEqual(arch, new_arch)
self.assertTrue(isinstance(arch, spack.architecture.Arch)) self.assertTrue(isinstance(arch, spack.architecture.Arch))
@ -75,7 +72,7 @@ def test_dict_functions_for_architecture(self):
spack.architecture.Target)) spack.architecture.Target))
def test_platform(self): def test_platform(self):
output_platform_class = spack.architecture.platform() output_platform_class = spack.architecture.real_platform()
if os.path.exists('/opt/cray/craype'): if os.path.exists('/opt/cray/craype'):
my_platform_class = Cray() my_platform_class = Cray()
elif os.path.exists('/bgsys'): elif os.path.exists('/bgsys'):
@ -114,10 +111,12 @@ def test_user_front_end_input(self):
"""Test when user inputs just frontend that both the frontend target """Test when user inputs just frontend that both the frontend target
and frontend operating system match and frontend operating system match
""" """
frontend_os = self.platform.operating_system("frontend") frontend_os = str(self.platform.operating_system("frontend"))
frontend_target = self.platform.target("frontend") frontend_target = str(self.platform.target("frontend"))
frontend_spec = Spec("libelf os=frontend target=frontend") frontend_spec = Spec("libelf os=frontend target=frontend")
frontend_spec.concretize() frontend_spec.concretize()
self.assertEqual(frontend_os, frontend_spec.architecture.platform_os) self.assertEqual(frontend_os, frontend_spec.architecture.platform_os)
self.assertEqual(frontend_target, frontend_spec.architecture.target) self.assertEqual(frontend_target, frontend_spec.architecture.target)
@ -125,19 +124,22 @@ def test_user_back_end_input(self):
"""Test when user inputs backend that both the backend target and """Test when user inputs backend that both the backend target and
backend operating system match backend operating system match
""" """
backend_os = self.platform.operating_system("backend") backend_os = str(self.platform.operating_system("backend"))
backend_target = self.platform.target("backend") backend_target = str(self.platform.target("backend"))
backend_spec = Spec("libelf os=backend target=backend") backend_spec = Spec("libelf os=backend target=backend")
backend_spec.concretize() backend_spec.concretize()
self.assertEqual(backend_os, backend_spec.architecture.platform_os) self.assertEqual(backend_os, backend_spec.architecture.platform_os)
self.assertEqual(backend_target, backend_spec.architecture.target) self.assertEqual(backend_target, backend_spec.architecture.target)
def test_user_defaults(self): def test_user_defaults(self):
default_os = self.platform.operating_system("default_os") default_os = str(self.platform.operating_system("default_os"))
default_target = self.platform.target("default_target") default_target = str(self.platform.target("default_target"))
default_spec = Spec("libelf") # default is no args default_spec = Spec("libelf") # default is no args
default_spec.concretize() default_spec.concretize()
self.assertEqual(default_os, default_spec.architecture.platform_os) self.assertEqual(default_os, default_spec.architecture.platform_os)
self.assertEqual(default_target, default_spec.architecture.target) self.assertEqual(default_target, default_spec.architecture.target)
@ -156,8 +158,9 @@ def test_user_input_combination(self):
spec = Spec("libelf os=%s target=%s" % (o, t)) spec = Spec("libelf os=%s target=%s" % (o, t))
spec.concretize() spec.concretize()
results.append(spec.architecture.platform_os == results.append(spec.architecture.platform_os ==
self.platform.operating_system(o)) str(self.platform.operating_system(o)))
results.append(spec.architecture.target == self.platform.target(t)) results.append(spec.architecture.target ==
str(self.platform.target(t)))
res = all(results) res = all(results)
self.assertTrue(res) self.assertTrue(res)

View File

@ -250,7 +250,7 @@ def test_external_package(self):
def test_external_package_module(self): def test_external_package_module(self):
# No tcl modules on darwin/linux machines # No tcl modules on darwin/linux machines
# TODO: improved way to check for this. # TODO: improved way to check for this.
platform = spack.architecture.platform().name platform = spack.architecture.real_platform().name
if (platform == 'darwin' or platform == 'linux'): if (platform == 'darwin' or platform == 'linux'):
return return

View File

@ -132,15 +132,60 @@ def test_satisfies_compiler_version(self):
self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3') self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
def test_satisfies_architecture(self): def test_satisfies_architecture(self):
self.check_satisfies(
'foo platform=test',
'platform=test')
self.check_satisfies(
'foo platform=linux',
'platform=linux')
self.check_satisfies(
'foo platform=test',
'platform=test target=frontend')
self.check_satisfies(
'foo platform=test',
'platform=test os=frontend target=frontend')
self.check_satisfies(
'foo platform=test os=frontend target=frontend',
'platform=test')
self.check_unsatisfiable(
'foo platform=linux',
'platform=test os=redhat6 target=x86_32')
self.check_unsatisfiable(
'foo os=redhat6',
'platform=test os=debian6 target=x86_64')
self.check_unsatisfiable(
'foo target=x86_64',
'platform=test os=redhat6 target=x86_32')
self.check_satisfies(
'foo arch=test-None-None',
'platform=test')
self.check_satisfies(
'foo arch=test-None-frontend',
'platform=test target=frontend')
self.check_satisfies(
'foo arch=test-frontend-frontend',
'platform=test os=frontend target=frontend')
self.check_satisfies(
'foo arch=test-frontend-frontend',
'platform=test')
self.check_unsatisfiable(
'foo arch=test-frontend-frontend',
'platform=test os=frontend target=backend')
self.check_satisfies( self.check_satisfies(
'foo platform=test target=frontend os=frontend', 'foo platform=test target=frontend os=frontend',
'platform=test target=frontend os=frontend') 'platform=test target=frontend os=frontend')
self.check_satisfies( self.check_satisfies(
'foo platform=test target=backend os=backend', 'foo platform=test target=backend os=backend',
'platform=test target=backend', 'platform=test os=backend') 'platform=test target=backend os=backend')
self.check_satisfies( self.check_satisfies(
'foo platform=test target=default_target os=default_os', 'foo platform=test target=default_target os=default_os',
'platform=test target=default_target os=default_os') 'platform=test os=default_os')
self.check_unsatisfiable(
'foo platform=test target=x86_32 os=redhat6',
'platform=linux target=x86_32 os=redhat6')
def test_satisfies_dependencies(self): def test_satisfies_dependencies(self):
self.check_satisfies('mpileaks^mpich', '^mpich') self.check_satisfies('mpileaks^mpich', '^mpich')

View File

@ -120,6 +120,10 @@ def test_full_specs(self):
'mvapich_foo' 'mvapich_foo'
'^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags="-O3"+debug~qt_4' '^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags="-O3"+debug~qt_4'
'^stackwalker@8.1_1e') '^stackwalker@8.1_1e')
self.check_parse(
"mvapich_foo"
"^_openmpi@1.2:1.4,1.6%intel@12.1 debug=2~qt_4"
"^stackwalker@8.1_1e arch=test-redhat6-x86_32")
def test_canonicalize(self): def test_canonicalize(self):
self.check_parse( self.check_parse(
@ -144,6 +148,22 @@ def test_canonicalize(self):
"x^y@1,2:3,4%intel@1,2,3,4+a~b+c~d+e~f", "x^y@1,2:3,4%intel@1,2,3,4+a~b+c~d+e~f",
"x ^y~f+e~d+c~b+a@4,2:3,1%intel@4,3,2,1") "x ^y~f+e~d+c~b+a@4,2:3,1%intel@4,3,2,1")
self.check_parse(
"x arch=test-redhat6-None"
"^y arch=test-None-x86_64"
"^z arch=linux-None-None",
"x os=fe"
"^y target=be"
"^z platform=linux")
self.check_parse(
"x arch=test-debian6-x86_64"
"^y arch=test-debian6-x86_64",
"x os=default_os target=default_target"
"^y os=default_os target=default_target")
self.check_parse("x^y", "x@: ^y@:") self.check_parse("x^y", "x@: ^y@:")
def test_parse_errors(self): def test_parse_errors(self):
@ -169,10 +189,12 @@ def test_duplicate_depdendence(self):
def test_duplicate_compiler(self): def test_duplicate_compiler(self):
self.assertRaises(DuplicateCompilerSpecError, self.assertRaises(DuplicateCompilerSpecError,
self.check_parse, "x%intel%intel") self.check_parse, "x%intel%intel")
self.assertRaises(DuplicateCompilerSpecError, self.assertRaises(DuplicateCompilerSpecError,
self.check_parse, "x%intel%gcc") self.check_parse, "x%intel%gcc")
self.assertRaises(DuplicateCompilerSpecError, self.assertRaises(DuplicateCompilerSpecError,
self.check_parse, "x%gcc%intel") self.check_parse, "x%gcc%intel")
self.assertRaises(DuplicateCompilerSpecError, self.assertRaises(DuplicateCompilerSpecError,
self.check_parse, "x ^y%intel%intel") self.check_parse, "x ^y%intel%intel")
self.assertRaises(DuplicateCompilerSpecError, self.assertRaises(DuplicateCompilerSpecError,
@ -180,6 +202,54 @@ def test_duplicate_compiler(self):
self.assertRaises(DuplicateCompilerSpecError, self.assertRaises(DuplicateCompilerSpecError,
self.check_parse, "x ^y%gcc%intel") self.check_parse, "x ^y%gcc%intel")
def test_duplicate_architecture(self):
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x arch=linux-rhel7-ppc64le arch=linux-rhel7-x86_64")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le")
def test_duplicate_architecture_component(self):
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x os=fe os=fe")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x os=fe os=be")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x target=fe target=fe")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x target=fe target=be")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x platform=test platform=test")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x platform=test platform=test")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x os=fe platform=test target=fe os=fe")
self.assertRaises(
DuplicateArchitectureError, self.check_parse,
"x target=be platform=test os=be os=fe")
# ======================================================================== # ========================================================================
# Lex checks # Lex checks
# ======================================================================== # ========================================================================