stacks: initial implementation of stacks on environments
- stack syntax in env schema - switch environment specs over to SpecList object - add stack functionality to environments - handle definition extensions through stack.yaml and SpecList - implement conditional definitions - tests
This commit is contained in:
parent
5be1ff83d1
commit
d450a2fce2
@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
|
|
||||||
def setup_parser(subparser):
|
def setup_parser(subparser):
|
||||||
|
subparser.add_argument('-l', '--list-name',
|
||||||
|
dest='list_name', default='specs',
|
||||||
|
help="name of the list to add specs to")
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'specs', nargs=argparse.REMAINDER, help="specs of packages to add")
|
'specs', nargs=argparse.REMAINDER, help="specs of packages to add")
|
||||||
|
|
||||||
@ -25,7 +28,7 @@ def add(parser, args):
|
|||||||
env = ev.get_env(args, 'add', required=True)
|
env = ev.get_env(args, 'add', required=True)
|
||||||
|
|
||||||
for spec in spack.cmd.parse_specs(args.specs):
|
for spec in spack.cmd.parse_specs(args.specs):
|
||||||
if not env.add(spec):
|
if not env.add(spec, args.list_name):
|
||||||
tty.msg("Package {0} was already added to {1}"
|
tty.msg("Package {0} was already added to {1}"
|
||||||
.format(spec.name, env.name))
|
.format(spec.name, env.name))
|
||||||
else:
|
else:
|
||||||
|
@ -175,6 +175,7 @@ def find(parser, args):
|
|||||||
tty.msg('No root specs')
|
tty.msg('No root specs')
|
||||||
else:
|
else:
|
||||||
tty.msg('Root specs')
|
tty.msg('Root specs')
|
||||||
|
# TODO: Change this to not print extraneous deps and variants
|
||||||
display_specs(
|
display_specs(
|
||||||
env.user_specs, args,
|
env.user_specs, args,
|
||||||
decorator=lambda s, f: color.colorize('@*{%s}' % f))
|
decorator=lambda s, f: color.colorize('@*{%s}' % f))
|
||||||
|
@ -20,6 +20,9 @@ def setup_parser(subparser):
|
|||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'-a', '--all', action='store_true',
|
'-a', '--all', action='store_true',
|
||||||
help="remove all specs from (clear) the environment")
|
help="remove all specs from (clear) the environment")
|
||||||
|
subparser.add_argument('-l', '--list-name',
|
||||||
|
dest='list_name', default='specs',
|
||||||
|
help="name of the list to remove specs from")
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
'-f', '--force', action='store_true',
|
'-f', '--force', action='store_true',
|
||||||
help="remove concretized spec (if any) immediately")
|
help="remove concretized spec (if any) immediately")
|
||||||
@ -35,5 +38,5 @@ def remove(parser, args):
|
|||||||
else:
|
else:
|
||||||
for spec in spack.cmd.parse_specs(args.specs):
|
for spec in spack.cmd.parse_specs(args.specs):
|
||||||
tty.msg('Removing %s from environment %s' % (spec, env.name))
|
tty.msg('Removing %s from environment %s' % (spec, env.name))
|
||||||
env.remove(spec, force=args.force)
|
env.remove(spec, args.list_name, force=args.force)
|
||||||
env.write()
|
env.write()
|
||||||
|
@ -7,10 +7,13 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
|
import copy
|
||||||
|
|
||||||
import ruamel.yaml
|
import ruamel.yaml
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from ordereddict_backport import OrderedDict
|
||||||
|
|
||||||
import llnl.util.filesystem as fs
|
import llnl.util.filesystem as fs
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.util.tty.color import colorize
|
from llnl.util.tty.color import colorize
|
||||||
@ -21,10 +24,13 @@
|
|||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.util.spack_json as sjson
|
import spack.util.spack_json as sjson
|
||||||
import spack.config
|
import spack.config
|
||||||
from spack.spec import Spec
|
|
||||||
from spack.filesystem_view import YamlFilesystemView
|
from spack.filesystem_view import YamlFilesystemView
|
||||||
|
|
||||||
from spack.util.environment import EnvironmentModifications
|
from spack.util.environment import EnvironmentModifications
|
||||||
|
import spack.architecture as architecture
|
||||||
|
from spack.spec import Spec
|
||||||
|
from spack.spec_list import SpecList, InvalidSpecConstraintError
|
||||||
|
from spack.variant import UnknownVariantError
|
||||||
|
from spack.util.executable import which
|
||||||
|
|
||||||
#: environment variable used to indicate the active environment
|
#: environment variable used to indicate the active environment
|
||||||
spack_env_var = 'SPACK_ENV'
|
spack_env_var = 'SPACK_ENV'
|
||||||
@ -385,6 +391,29 @@ def _write_yaml(data, str_or_file):
|
|||||||
default_flow_style=False)
|
default_flow_style=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _eval_conditional(string):
|
||||||
|
"""Evaluate conditional definitions using restricted variable scope."""
|
||||||
|
arch = architecture.Arch(
|
||||||
|
architecture.platform(), 'default_os', 'default_target')
|
||||||
|
valid_variables = {
|
||||||
|
'target': str(arch.target),
|
||||||
|
'os': str(arch.platform_os),
|
||||||
|
'platform': str(arch.platform),
|
||||||
|
'arch': str(arch),
|
||||||
|
'architecture': str(arch),
|
||||||
|
're': re,
|
||||||
|
'env': os.environ,
|
||||||
|
}
|
||||||
|
hostname_bin = which('hostname')
|
||||||
|
if hostname_bin:
|
||||||
|
hostname = str(hostname_bin(output=str, error=str)).strip()
|
||||||
|
valid_variables['hostname'] = hostname
|
||||||
|
else:
|
||||||
|
tty.warn("Spack was unable to find the executable `hostname`"
|
||||||
|
" hostname will be unavailable in conditionals")
|
||||||
|
return eval(string, valid_variables)
|
||||||
|
|
||||||
|
|
||||||
class Environment(object):
|
class Environment(object):
|
||||||
def __init__(self, path, init_file=None, with_view=None):
|
def __init__(self, path, init_file=None, with_view=None):
|
||||||
"""Create a new environment.
|
"""Create a new environment.
|
||||||
@ -425,6 +454,18 @@ def __init__(self, path, init_file=None, with_view=None):
|
|||||||
if default_manifest:
|
if default_manifest:
|
||||||
self._set_user_specs_from_lockfile()
|
self._set_user_specs_from_lockfile()
|
||||||
|
|
||||||
|
if os.path.exists(self.manifest_path):
|
||||||
|
# read the spack.yaml file, if exists
|
||||||
|
with open(self.manifest_path) as f:
|
||||||
|
self._read_manifest(f)
|
||||||
|
elif self.concretized_user_specs:
|
||||||
|
# if not, take user specs from the lockfile
|
||||||
|
self._set_user_specs_from_lockfile()
|
||||||
|
self.yaml = _read_yaml(default_manifest_yaml)
|
||||||
|
else:
|
||||||
|
# if there's no manifest or lockfile, use the default
|
||||||
|
self._read_manifest(default_manifest_yaml)
|
||||||
|
|
||||||
if with_view is False:
|
if with_view is False:
|
||||||
self._view_path = None
|
self._view_path = None
|
||||||
elif isinstance(with_view, six.string_types):
|
elif isinstance(with_view, six.string_types):
|
||||||
@ -435,9 +476,38 @@ def __init__(self, path, init_file=None, with_view=None):
|
|||||||
def _read_manifest(self, f):
|
def _read_manifest(self, f):
|
||||||
"""Read manifest file and set up user specs."""
|
"""Read manifest file and set up user specs."""
|
||||||
self.yaml = _read_yaml(f)
|
self.yaml = _read_yaml(f)
|
||||||
|
|
||||||
|
self.read_specs = OrderedDict()
|
||||||
|
|
||||||
|
for item in list(self.yaml.values())[0].get('definitions', []):
|
||||||
|
entry = copy.deepcopy(item)
|
||||||
|
when = _eval_conditional(entry.pop('when', 'True'))
|
||||||
|
assert len(entry) == 1
|
||||||
|
if when:
|
||||||
|
name, spec_list = list(entry.items())[0]
|
||||||
|
user_specs = SpecList(name, spec_list, self.read_specs.copy())
|
||||||
|
if name in self.read_specs:
|
||||||
|
self.read_specs[name].extend(user_specs)
|
||||||
|
else:
|
||||||
|
self.read_specs[name] = user_specs
|
||||||
|
|
||||||
spec_list = config_dict(self.yaml).get('specs')
|
spec_list = config_dict(self.yaml).get('specs')
|
||||||
if spec_list:
|
user_specs = SpecList('specs', [s for s in spec_list if s],
|
||||||
self.user_specs = [Spec(s) for s in spec_list if s]
|
self.read_specs.copy())
|
||||||
|
self.read_specs['specs'] = user_specs
|
||||||
|
|
||||||
|
enable_view = config_dict(self.yaml).get('view')
|
||||||
|
# enable_view can be boolean, string, or None
|
||||||
|
if enable_view is True or enable_view is None:
|
||||||
|
self._view_path = self.default_view_path
|
||||||
|
elif isinstance(enable_view, six.string_types):
|
||||||
|
self._view_path = enable_view
|
||||||
|
else:
|
||||||
|
self._view_path = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_specs(self):
|
||||||
|
return self.read_specs['specs']
|
||||||
|
|
||||||
enable_view = config_dict(self.yaml).get('view')
|
enable_view = config_dict(self.yaml).get('view')
|
||||||
# enable_view can be true/false, a string, or None (if the manifest did
|
# enable_view can be true/false, a string, or None (if the manifest did
|
||||||
@ -452,10 +522,14 @@ def _read_manifest(self, f):
|
|||||||
|
|
||||||
def _set_user_specs_from_lockfile(self):
|
def _set_user_specs_from_lockfile(self):
|
||||||
"""Copy user_specs from a read-in lockfile."""
|
"""Copy user_specs from a read-in lockfile."""
|
||||||
self.user_specs = [Spec(s) for s in self.concretized_user_specs]
|
self.read_specs = {
|
||||||
|
'specs': SpecList(
|
||||||
|
'specs', [Spec(s) for s in self.concretized_user_specs]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.user_specs = [] # current user specs
|
self.read_specs = {'specs': SpecList()} # specs read from yaml
|
||||||
self.concretized_user_specs = [] # user specs from last concretize
|
self.concretized_user_specs = [] # user specs from last concretize
|
||||||
self.concretized_order = [] # roots of last concretize, in order
|
self.concretized_order = [] # roots of last concretize, in order
|
||||||
self.specs_by_hash = {} # concretized specs by hash
|
self.specs_by_hash = {} # concretized specs by hash
|
||||||
@ -578,7 +652,7 @@ def destroy(self):
|
|||||||
"""Remove this environment from Spack entirely."""
|
"""Remove this environment from Spack entirely."""
|
||||||
shutil.rmtree(self.path)
|
shutil.rmtree(self.path)
|
||||||
|
|
||||||
def add(self, user_spec):
|
def add(self, user_spec, list_name='specs'):
|
||||||
"""Add a single user_spec (non-concretized) to the Environment
|
"""Add a single user_spec (non-concretized) to the Environment
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -587,47 +661,86 @@ def add(self, user_spec):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
spec = Spec(user_spec)
|
spec = Spec(user_spec)
|
||||||
if not spec.name:
|
|
||||||
raise SpackEnvironmentError(
|
|
||||||
'cannot add anonymous specs to an environment!')
|
|
||||||
elif not spack.repo.path.exists(spec.name):
|
|
||||||
raise SpackEnvironmentError('no such package: %s' % spec.name)
|
|
||||||
|
|
||||||
existing = set(s for s in self.user_specs if s.name == spec.name)
|
if list_name not in self.read_specs:
|
||||||
if not existing:
|
raise SpackEnvironmentError(
|
||||||
self.user_specs.append(spec)
|
'No list %s exists in environment %s' % (list_name, self.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
if list_name == 'specs':
|
||||||
|
if not spec.name:
|
||||||
|
raise SpackEnvironmentError(
|
||||||
|
'cannot add anonymous specs to an environment!')
|
||||||
|
elif not spack.repo.path.exists(spec.name):
|
||||||
|
raise SpackEnvironmentError('no such package: %s' % spec.name)
|
||||||
|
|
||||||
|
added = False
|
||||||
|
existing = False
|
||||||
|
for i, (name, speclist) in enumerate(self.read_specs.items()):
|
||||||
|
# Iterate over all named lists from an OrderedDict()
|
||||||
|
if name == list_name:
|
||||||
|
# We need to modify this list
|
||||||
|
# TODO: Add conditional which reimplements name-level checking
|
||||||
|
existing = str(spec) in speclist.yaml_list
|
||||||
|
if not existing:
|
||||||
|
speclist.add(str(spec))
|
||||||
|
added = True
|
||||||
|
elif added:
|
||||||
|
# We've already modified a list, so all later lists need to
|
||||||
|
# have their references updated.
|
||||||
|
new_reference = dict((n, self.read_specs[n])
|
||||||
|
for n in list(self.read_specs.keys())[:i])
|
||||||
|
speclist.update_reference(new_reference)
|
||||||
return bool(not existing)
|
return bool(not existing)
|
||||||
|
|
||||||
def remove(self, query_spec, force=False):
|
def remove(self, query_spec, list_name='specs', force=False):
|
||||||
"""Remove specs from an environment that match a query_spec"""
|
"""Remove specs from an environment that match a query_spec"""
|
||||||
query_spec = Spec(query_spec)
|
query_spec = Spec(query_spec)
|
||||||
|
|
||||||
# try abstract specs first
|
removed = False
|
||||||
matches = []
|
for i, (name, speclist) in enumerate(self.read_specs.items()):
|
||||||
if not query_spec.concrete:
|
# Iterate over all named lists from an OrderedDict()
|
||||||
matches = [s for s in self.user_specs if s.satisfies(query_spec)]
|
if name == list_name:
|
||||||
|
# We need to modify this list
|
||||||
|
# try abstract specs first
|
||||||
|
matches = []
|
||||||
|
|
||||||
if not matches:
|
if not query_spec.concrete:
|
||||||
# concrete specs match against concrete specs in the env
|
matches = [s for s in speclist if s.satisfies(query_spec)]
|
||||||
specs_hashes = zip(
|
|
||||||
self.concretized_user_specs, self.concretized_order)
|
|
||||||
matches = [
|
|
||||||
s for s, h in specs_hashes if query_spec.dag_hash() == h]
|
|
||||||
|
|
||||||
if not matches:
|
if not matches:
|
||||||
raise SpackEnvironmentError("Not found: {0}".format(query_spec))
|
# concrete specs match against concrete specs in the env
|
||||||
|
specs_hashes = zip(
|
||||||
|
self.concretized_user_specs, self.concretized_order)
|
||||||
|
matches = [
|
||||||
|
s for s, h in specs_hashes
|
||||||
|
if query_spec.dag_hash() == h
|
||||||
|
]
|
||||||
|
|
||||||
for spec in matches:
|
if not matches:
|
||||||
if spec in self.user_specs:
|
raise SpackEnvironmentError(
|
||||||
self.user_specs.remove(spec)
|
"Not found: {0}".format(query_spec))
|
||||||
|
|
||||||
if force and spec in self.concretized_user_specs:
|
for spec in matches:
|
||||||
i = self.concretized_user_specs.index(spec)
|
if spec in speclist:
|
||||||
del self.concretized_user_specs[i]
|
speclist.remove(spec)
|
||||||
|
removed = True
|
||||||
|
|
||||||
dag_hash = self.concretized_order[i]
|
if force and spec in self.concretized_user_specs:
|
||||||
del self.concretized_order[i]
|
i = self.concretized_user_specs.index(spec)
|
||||||
del self.specs_by_hash[dag_hash]
|
del self.concretized_user_specs[i]
|
||||||
|
|
||||||
|
dag_hash = self.concretized_order[i]
|
||||||
|
del self.concretized_order[i]
|
||||||
|
del self.specs_by_hash[dag_hash]
|
||||||
|
removed = True
|
||||||
|
|
||||||
|
elif removed:
|
||||||
|
# We've already modified one list, so all later lists need
|
||||||
|
# their references updated.
|
||||||
|
new_reference = dict((n, self.read_specs[n])
|
||||||
|
for n in list(self.read_specs.keys())[:i])
|
||||||
|
speclist.update_reference(new_reference)
|
||||||
|
|
||||||
def concretize(self, force=False):
|
def concretize(self, force=False):
|
||||||
"""Concretize user_specs in this environment.
|
"""Concretize user_specs in this environment.
|
||||||
@ -663,10 +776,11 @@ def concretize(self, force=False):
|
|||||||
self._add_concrete_spec(s, concrete, new=False)
|
self._add_concrete_spec(s, concrete, new=False)
|
||||||
|
|
||||||
# concretize any new user specs that we haven't concretized yet
|
# concretize any new user specs that we haven't concretized yet
|
||||||
for uspec in self.user_specs:
|
for uspec, uspec_constraints in zip(
|
||||||
|
self.user_specs, self.user_specs.specs_as_constraints):
|
||||||
if uspec not in old_concretized_user_specs:
|
if uspec not in old_concretized_user_specs:
|
||||||
tty.msg('Concretizing %s' % uspec)
|
tty.msg('Concretizing %s' % uspec)
|
||||||
concrete = uspec.concretized()
|
concrete = _concretize_from_constraints(uspec_constraints)
|
||||||
self._add_concrete_spec(uspec, concrete)
|
self._add_concrete_spec(uspec, concrete)
|
||||||
|
|
||||||
# Display concretized spec to the user
|
# Display concretized spec to the user
|
||||||
@ -689,7 +803,8 @@ def install(self, user_spec, concrete_spec=None, **install_args):
|
|||||||
self._add_concrete_spec(spec, concrete)
|
self._add_concrete_spec(spec, concrete)
|
||||||
else:
|
else:
|
||||||
# spec might be in the user_specs, but not installed.
|
# spec might be in the user_specs, but not installed.
|
||||||
spec = next(s for s in self.user_specs if s.name == spec.name)
|
# TODO: Redo name-based comparison for old style envs
|
||||||
|
spec = next(s for s in self.user_specs if s.satisfies(user_spec))
|
||||||
concrete = self.specs_by_hash.get(spec.dag_hash())
|
concrete = self.specs_by_hash.get(spec.dag_hash())
|
||||||
if not concrete:
|
if not concrete:
|
||||||
concrete = spec.concretized()
|
concrete = spec.concretized()
|
||||||
@ -1018,10 +1133,16 @@ def write(self):
|
|||||||
# invalidate _repo cache
|
# invalidate _repo cache
|
||||||
self._repo = None
|
self._repo = None
|
||||||
|
|
||||||
|
# put any changes in the definitions in the YAML
|
||||||
|
named_speclists = list(self.read_specs.items())
|
||||||
|
for i, (name, speclist) in enumerate(named_speclists[:-1]):
|
||||||
|
conf = config_dict(self.yaml)
|
||||||
|
yaml_list = conf.get('definitions', [])[i].setdefault(name, [])
|
||||||
|
yaml_list[:] = speclist.yaml_list
|
||||||
|
|
||||||
# put the new user specs in the YAML
|
# put the new user specs in the YAML
|
||||||
yaml_dict = config_dict(self.yaml)
|
yaml_spec_list = config_dict(self.yaml).setdefault('specs', [])
|
||||||
yaml_spec_list = yaml_dict.setdefault('specs', [])
|
yaml_spec_list[:] = self.user_specs.yaml_list
|
||||||
yaml_spec_list[:] = [str(s) for s in self.user_specs]
|
|
||||||
|
|
||||||
if self._view_path == self.default_view_path:
|
if self._view_path == self.default_view_path:
|
||||||
view = True
|
view = True
|
||||||
@ -1057,6 +1178,48 @@ def __exit__(self, exc_type, exc_val, exc_tb):
|
|||||||
activate(self._previous_active)
|
activate(self._previous_active)
|
||||||
|
|
||||||
|
|
||||||
|
def _concretize_from_constraints(spec_constraints):
|
||||||
|
# Accept only valid constraints from list and concretize spec
|
||||||
|
# Get the named spec even if out of order
|
||||||
|
root_spec = [s for s in spec_constraints if s.name]
|
||||||
|
if len(root_spec) != 1:
|
||||||
|
m = 'The constraints %s are not a valid spec ' % spec_constraints
|
||||||
|
m += 'concretization target. all specs must have a single name '
|
||||||
|
m += 'constraint for concretization.'
|
||||||
|
raise InvalidSpecConstraintError(m)
|
||||||
|
spec_constraints.remove(root_spec[0])
|
||||||
|
|
||||||
|
invalid_constraints = []
|
||||||
|
while True:
|
||||||
|
# Attach all anonymous constraints to one named spec
|
||||||
|
s = root_spec[0].copy()
|
||||||
|
for c in spec_constraints:
|
||||||
|
if c not in invalid_constraints:
|
||||||
|
s.constrain(c)
|
||||||
|
try:
|
||||||
|
return s.concretized()
|
||||||
|
except spack.spec.InvalidDependencyError as e:
|
||||||
|
dep_index = e.message.index('depend on ') + len('depend on ')
|
||||||
|
invalid_msg = e.message[dep_index:]
|
||||||
|
invalid_deps_string = ['^' + d.strip(',')
|
||||||
|
for d in invalid_msg.split()
|
||||||
|
if d != 'or']
|
||||||
|
invalid_deps = [c for c in spec_constraints
|
||||||
|
if any(c.satisfies(invd)
|
||||||
|
for invd in invalid_deps_string)]
|
||||||
|
if len(invalid_deps) != len(invalid_deps_string):
|
||||||
|
raise e
|
||||||
|
invalid_constraints.extend(invalid_deps)
|
||||||
|
except UnknownVariantError as e:
|
||||||
|
invalid_variants = re.findall(r"'(\w+)'", e.message)
|
||||||
|
invalid_deps = [c for c in spec_constraints
|
||||||
|
if any(name in c.variants
|
||||||
|
for name in invalid_variants)]
|
||||||
|
if len(invalid_deps) != len(invalid_variants):
|
||||||
|
raise e
|
||||||
|
invalid_constraints.extend(invalid_deps)
|
||||||
|
|
||||||
|
|
||||||
def make_repo_path(root):
|
def make_repo_path(root):
|
||||||
"""Make a RepoPath from the repo subdirectories in an environment."""
|
"""Make a RepoPath from the repo subdirectories in an environment."""
|
||||||
path = spack.repo.RepoPath()
|
path = spack.repo.RepoPath()
|
||||||
|
@ -11,8 +11,41 @@
|
|||||||
from llnl.util.lang import union_dicts
|
from llnl.util.lang import union_dicts
|
||||||
|
|
||||||
import spack.schema.merged
|
import spack.schema.merged
|
||||||
|
import spack.schema.projections
|
||||||
|
|
||||||
|
|
||||||
|
spec_list_schema = {
|
||||||
|
'type': 'array',
|
||||||
|
'default': [],
|
||||||
|
'items': {
|
||||||
|
'anyOf': [
|
||||||
|
{'type': 'object',
|
||||||
|
'additionalProperties': False,
|
||||||
|
'properties': {
|
||||||
|
'matrix': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'type': 'string',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'exclude': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'type': 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{'type': 'string'},
|
||||||
|
{'type': 'null'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
projections_scheme = spack.schema.projections.properties['projections']
|
||||||
|
|
||||||
schema = {
|
schema = {
|
||||||
'$schema': 'http://json-schema.org/schema#',
|
'$schema': 'http://json-schema.org/schema#',
|
||||||
'title': 'Spack environment file schema',
|
'title': 'Spack environment file schema',
|
||||||
@ -34,22 +67,52 @@
|
|||||||
'type': 'string'
|
'type': 'string'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'specs': {
|
|
||||||
# Specs is a list of specs, which can have
|
|
||||||
# optional additional properties in a sub-dict
|
|
||||||
'type': 'array',
|
|
||||||
'default': [],
|
|
||||||
'additionalProperties': False,
|
|
||||||
'items': {
|
|
||||||
'anyOf': [
|
|
||||||
{'type': 'string'},
|
|
||||||
{'type': 'null'},
|
|
||||||
{'type': 'object'},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'view': {
|
'view': {
|
||||||
'type': ['boolean', 'string']
|
'type': ['boolean', 'string']
|
||||||
|
},
|
||||||
|
'definitions': {
|
||||||
|
'type': 'array',
|
||||||
|
'default': [],
|
||||||
|
'items': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'when': {
|
||||||
|
'type': 'string'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'patternProperties': {
|
||||||
|
'^(?!when$)\w*': spec_list_schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'specs': spec_list_schema,
|
||||||
|
'view': {
|
||||||
|
'anyOf': [
|
||||||
|
{'type': 'boolean'},
|
||||||
|
{'type': 'string'},
|
||||||
|
{'type': 'object',
|
||||||
|
'required': ['root'],
|
||||||
|
'additionalProperties': False,
|
||||||
|
'properties': {
|
||||||
|
'root': {
|
||||||
|
'type': 'string'
|
||||||
|
},
|
||||||
|
'select': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'type': 'string'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'exclude': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {
|
||||||
|
'type': 'string'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'projections': projections_scheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -2494,7 +2494,7 @@ def _constrain_dependencies(self, other):
|
|||||||
"""Apply constraints of other spec's dependencies to this spec."""
|
"""Apply constraints of other spec's dependencies to this spec."""
|
||||||
other = self._autospec(other)
|
other = self._autospec(other)
|
||||||
|
|
||||||
if not self._dependencies or not other._dependencies:
|
if not other._dependencies:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# TODO: might want more detail than this, e.g. specific deps
|
# TODO: might want more detail than this, e.g. specific deps
|
||||||
|
168
lib/spack/spack/spec_list.py
Normal file
168
lib/spack/spack/spec_list.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
import itertools
|
||||||
|
from six import string_types
|
||||||
|
|
||||||
|
from spack.spec import Spec
|
||||||
|
from spack.error import SpackError
|
||||||
|
|
||||||
|
|
||||||
|
def spec_ordering_key(s):
|
||||||
|
if s.startswith('^'):
|
||||||
|
return 5
|
||||||
|
elif s.startswith('/'):
|
||||||
|
return 4
|
||||||
|
elif s.startswith('%'):
|
||||||
|
return 3
|
||||||
|
elif any(s.startswith(c) for c in '~-+@') or '=' in s:
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class SpecList(object):
|
||||||
|
|
||||||
|
def __init__(self, name='specs', yaml_list=[], reference={}):
|
||||||
|
self.name = name
|
||||||
|
self._reference = reference # TODO: Do we need defensive copy here?
|
||||||
|
|
||||||
|
self.yaml_list = yaml_list[:]
|
||||||
|
|
||||||
|
# Expansions can be expensive to compute and difficult to keep updated
|
||||||
|
# We cache results and invalidate when self.yaml_list changes
|
||||||
|
self._expanded_list = None
|
||||||
|
self._constraints = None
|
||||||
|
self._specs = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def specs_as_yaml_list(self):
|
||||||
|
if self._expanded_list is None:
|
||||||
|
self._expanded_list = self._expand_references(self.yaml_list)
|
||||||
|
return self._expanded_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def specs_as_constraints(self):
|
||||||
|
if self._constraints is None:
|
||||||
|
constraints = []
|
||||||
|
for item in self.specs_as_yaml_list:
|
||||||
|
if isinstance(item, dict): # matrix of specs
|
||||||
|
excludes = item.get('exclude', [])
|
||||||
|
for combo in itertools.product(*(item['matrix'])):
|
||||||
|
# Test against the excludes using a single spec
|
||||||
|
ordered_combo = sorted(combo, key=spec_ordering_key)
|
||||||
|
test_spec = Spec(' '.join(ordered_combo))
|
||||||
|
if any(test_spec.satisfies(x) for x in excludes):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add as list of constraints
|
||||||
|
constraints.append([Spec(x) for x in ordered_combo])
|
||||||
|
else: # individual spec
|
||||||
|
constraints.append([Spec(item)])
|
||||||
|
self._constraints = constraints
|
||||||
|
|
||||||
|
return self._constraints
|
||||||
|
|
||||||
|
@property
|
||||||
|
def specs(self):
|
||||||
|
if self._specs is None:
|
||||||
|
specs = []
|
||||||
|
# This could be slightly faster done directly from yaml_list,
|
||||||
|
# but this way is easier to maintain.
|
||||||
|
for constraint_list in self.specs_as_constraints:
|
||||||
|
spec = constraint_list[0].copy()
|
||||||
|
for const in constraint_list[1:]:
|
||||||
|
spec.constrain(const)
|
||||||
|
specs.append(spec)
|
||||||
|
self._specs = specs
|
||||||
|
|
||||||
|
return self._specs
|
||||||
|
|
||||||
|
def add(self, spec):
|
||||||
|
self.yaml_list.append(str(spec))
|
||||||
|
|
||||||
|
# expanded list can be updated without invalidation
|
||||||
|
if self._expanded_list is not None:
|
||||||
|
self._expanded_list.append(str(spec))
|
||||||
|
|
||||||
|
# Invalidate cache variables when we change the list
|
||||||
|
self._constraints = None
|
||||||
|
self._specs = None
|
||||||
|
|
||||||
|
def remove(self, spec):
|
||||||
|
# Get spec to remove from list
|
||||||
|
remove = [s for s in self.yaml_list
|
||||||
|
if (isinstance(s, string_types) and not s.startswith('$'))
|
||||||
|
and Spec(s) == Spec(spec)]
|
||||||
|
if not remove:
|
||||||
|
msg = 'Cannot remove %s from SpecList %s\n' % (spec, self.name)
|
||||||
|
msg += 'Either %s is not in %s or %s is ' % (spec, self.name, spec)
|
||||||
|
msg += 'expanded from a matrix and cannot be removed directly.'
|
||||||
|
raise SpecListError(msg)
|
||||||
|
assert len(remove) == 1
|
||||||
|
self.yaml_list.remove(remove[0])
|
||||||
|
|
||||||
|
# invalidate cache variables when we change the list
|
||||||
|
self._expanded_list = None
|
||||||
|
self._constraints = None
|
||||||
|
self._specs = None
|
||||||
|
|
||||||
|
def extend(self, other, copy_reference=True):
|
||||||
|
self.yaml_list.extend(other.yaml_list)
|
||||||
|
self._expanded_list = None
|
||||||
|
self._constraints = None
|
||||||
|
self._specs = None
|
||||||
|
|
||||||
|
if copy_reference:
|
||||||
|
self._reference = other._reference
|
||||||
|
|
||||||
|
def update_reference(self, reference):
|
||||||
|
self._reference = reference
|
||||||
|
self._expanded_list = None
|
||||||
|
self._constraints = None
|
||||||
|
self._specs = None
|
||||||
|
|
||||||
|
def _expand_references(self, yaml):
|
||||||
|
if isinstance(yaml, list):
|
||||||
|
for idx, item in enumerate(yaml):
|
||||||
|
if isinstance(item, string_types) and item.startswith('$'):
|
||||||
|
name = item[1:]
|
||||||
|
if name in self._reference:
|
||||||
|
ret = [self._expand_references(i) for i in yaml[:idx]]
|
||||||
|
ret += self._reference[name].specs_as_yaml_list
|
||||||
|
ret += [self._expand_references(i)
|
||||||
|
for i in yaml[idx + 1:]]
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
msg = 'SpecList %s refers to ' % self.name
|
||||||
|
msg = 'named list %s ' % name
|
||||||
|
msg += 'which does not appear in its reference dict'
|
||||||
|
raise UndefinedReferenceError(msg)
|
||||||
|
# No references in this
|
||||||
|
return [self._expand_references(item) for item in yaml]
|
||||||
|
elif isinstance(yaml, dict):
|
||||||
|
# There can't be expansions in dicts
|
||||||
|
return dict((name, self._expand_references(val))
|
||||||
|
for (name, val) in yaml.items())
|
||||||
|
else:
|
||||||
|
# Strings are just returned
|
||||||
|
return yaml
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.specs)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.specs[key]
|
||||||
|
|
||||||
|
|
||||||
|
class SpecListError(SpackError):
|
||||||
|
"""Error class for all errors related to SpecList objects."""
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedReferenceError(SpecListError):
|
||||||
|
"""Error class for undefined references in Spack stacks."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidSpecConstraintError(SpecListError):
|
||||||
|
"""Error class for invalid spec constraints at concretize time."""
|
@ -15,6 +15,7 @@
|
|||||||
from spack.cmd.env import _env_create
|
from spack.cmd.env import _env_create
|
||||||
from spack.spec import Spec
|
from spack.spec import Spec
|
||||||
from spack.main import SpackCommand
|
from spack.main import SpackCommand
|
||||||
|
from spack.spec_list import SpecListError
|
||||||
|
|
||||||
|
|
||||||
# everything here uses the mock_env_path
|
# everything here uses the mock_env_path
|
||||||
@ -763,3 +764,271 @@ def test_env_activate_view_fails(
|
|||||||
"""Sanity check on env activate to make sure it requires shell support"""
|
"""Sanity check on env activate to make sure it requires shell support"""
|
||||||
out = env('activate', 'test')
|
out = env('activate', 'test')
|
||||||
assert "To initialize spack's shell commands:" in out
|
assert "To initialize spack's shell commands:" in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_yaml_definitions(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [mpileaks, callpath]
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_yaml_add_to_list(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [mpileaks, callpath]
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
with ev.read('test'):
|
||||||
|
add('-l', 'packages', 'libelf')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('libelf') in test.user_specs
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_yaml_remove_from_list(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [mpileaks, callpath]
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
with ev.read('test'):
|
||||||
|
remove('-l', 'packages', 'mpileaks')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('mpileaks') not in test.user_specs
|
||||||
|
assert Spec('callpath') in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_yaml_attempt_remove_from_matrix(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages:
|
||||||
|
- matrix:
|
||||||
|
- [mpileaks, callpath]
|
||||||
|
- [target=be]
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
with pytest.raises(SpecListError):
|
||||||
|
with ev.read('test'):
|
||||||
|
remove('-l', 'packages', 'mpileaks')
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_concretize_extraneous_deps(tmpdir, config, mock_packages):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- install:
|
||||||
|
- matrix:
|
||||||
|
- [$packages]
|
||||||
|
- ['^zmpi', '^mpich']
|
||||||
|
specs:
|
||||||
|
- $install
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
with ev.read('test'):
|
||||||
|
concretize()
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
for user, concrete in test.concretized_specs():
|
||||||
|
assert concrete.concrete
|
||||||
|
assert not user.concrete
|
||||||
|
if user.name == 'libelf':
|
||||||
|
assert not concrete.satisfies('^mpi', strict=True)
|
||||||
|
elif user.name == 'mpileaks':
|
||||||
|
assert concrete.satisfies('^mpi', strict=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_concretize_extraneous_variants(tmpdir, config, mock_packages):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- install:
|
||||||
|
- matrix:
|
||||||
|
- [$packages]
|
||||||
|
- ['~shared', '+shared']
|
||||||
|
specs:
|
||||||
|
- $install
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
with ev.read('test'):
|
||||||
|
concretize()
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
for user, concrete in test.concretized_specs():
|
||||||
|
assert concrete.concrete
|
||||||
|
assert not user.concrete
|
||||||
|
if user.name == 'libelf':
|
||||||
|
assert 'shared' not in concrete.variants
|
||||||
|
if user.name == 'mpileaks':
|
||||||
|
assert (concrete.variants['shared'].value ==
|
||||||
|
user.variants['shared'].value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_definition_extension(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- packages: [callpath]
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('libelf') in test.user_specs
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_definition_conditional_false(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- packages: [callpath]
|
||||||
|
when: 'False'
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('libelf') in test.user_specs
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') not in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_definition_conditional_true(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- packages: [callpath]
|
||||||
|
when: 'True'
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('libelf') in test.user_specs
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_definition_conditional_with_variable(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- packages: [callpath]
|
||||||
|
when: platform == 'test'
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('libelf') in test.user_specs
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_definition_complex_conditional(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- packages: [callpath]
|
||||||
|
when: re.search(r'foo', hostname) and env['test'] == 'THISSHOULDBEFALSE'
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
|
||||||
|
test = ev.read('test')
|
||||||
|
|
||||||
|
assert Spec('libelf') in test.user_specs
|
||||||
|
assert Spec('mpileaks') in test.user_specs
|
||||||
|
assert Spec('callpath') not in test.user_specs
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_definition_conditional_invalid_variable(tmpdir):
|
||||||
|
filename = str(tmpdir.join('spack.yaml'))
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write("""\
|
||||||
|
env:
|
||||||
|
definitions:
|
||||||
|
- packages: [libelf, mpileaks]
|
||||||
|
- packages: [callpath]
|
||||||
|
when: bad_variable == 'test'
|
||||||
|
specs:
|
||||||
|
- $packages
|
||||||
|
""")
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
with pytest.raises(NameError):
|
||||||
|
env('create', 'test', './spack.yaml')
|
||||||
|
144
lib/spack/spack/test/spec_list.py
Normal file
144
lib/spack/spack/test/spec_list.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
from spack.spec_list import SpecList
|
||||||
|
from spack.spec import Spec
|
||||||
|
|
||||||
|
|
||||||
|
class TestSpecList(object):
|
||||||
|
default_input = ['mpileaks', '$mpis',
|
||||||
|
{'matrix': [['hypre'], ['$gccs', '%clang@3.3']]},
|
||||||
|
'libelf']
|
||||||
|
|
||||||
|
default_reference = {'gccs': SpecList('gccs', ['%gcc@4.5.0']),
|
||||||
|
'mpis': SpecList('mpis', ['zmpi@1.0', 'mpich@3.0'])}
|
||||||
|
|
||||||
|
default_expansion = ['mpileaks', 'zmpi@1.0', 'mpich@3.0',
|
||||||
|
{'matrix': [
|
||||||
|
['hypre'],
|
||||||
|
['%gcc@4.5.0', '%clang@3.3'],
|
||||||
|
]},
|
||||||
|
'libelf']
|
||||||
|
|
||||||
|
default_constraints = [[Spec('mpileaks')],
|
||||||
|
[Spec('zmpi@1.0')],
|
||||||
|
[Spec('mpich@3.0')],
|
||||||
|
[Spec('hypre'), Spec('%gcc@4.5.0')],
|
||||||
|
[Spec('hypre'), Spec('%clang@3.3')],
|
||||||
|
[Spec('libelf')]]
|
||||||
|
|
||||||
|
default_specs = [Spec('mpileaks'), Spec('zmpi@1.0'),
|
||||||
|
Spec('mpich@3.0'), Spec('hypre%gcc@4.5.0'),
|
||||||
|
Spec('hypre%clang@3.3'), Spec('libelf')]
|
||||||
|
|
||||||
|
def test_spec_list_expansions(self):
|
||||||
|
speclist = SpecList('specs', self.default_input,
|
||||||
|
self.default_reference)
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == self.default_expansion
|
||||||
|
assert speclist.specs_as_constraints == self.default_constraints
|
||||||
|
assert speclist.specs == self.default_specs
|
||||||
|
|
||||||
|
def test_spec_list_constraint_ordering(self):
|
||||||
|
specs = [{'matrix': [
|
||||||
|
['^zmpi'],
|
||||||
|
['%gcc@4.5.0'],
|
||||||
|
['hypre', 'libelf'],
|
||||||
|
['~shared'],
|
||||||
|
['cflags=-O3', 'cflags="-g -O0"'],
|
||||||
|
['^foo']
|
||||||
|
]}]
|
||||||
|
|
||||||
|
speclist = SpecList('specs', specs)
|
||||||
|
|
||||||
|
expected_specs = [
|
||||||
|
Spec('hypre cflags=-O3 ~shared %gcc@4.5.0 ^foo ^zmpi'),
|
||||||
|
Spec('hypre cflags="-g -O0" ~shared %gcc@4.5.0 ^foo ^zmpi'),
|
||||||
|
Spec('libelf cflags=-O3 ~shared %gcc@4.5.0 ^foo ^zmpi'),
|
||||||
|
Spec('libelf cflags="-g -O0" ~shared %gcc@4.5.0 ^foo ^zmpi'),
|
||||||
|
]
|
||||||
|
assert speclist.specs == expected_specs
|
||||||
|
|
||||||
|
def test_spec_list_add(self):
|
||||||
|
speclist = SpecList('specs', self.default_input,
|
||||||
|
self.default_reference)
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == self.default_expansion
|
||||||
|
assert speclist.specs_as_constraints == self.default_constraints
|
||||||
|
assert speclist.specs == self.default_specs
|
||||||
|
|
||||||
|
speclist.add('libdwarf')
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == self.default_expansion + [
|
||||||
|
'libdwarf']
|
||||||
|
assert speclist.specs_as_constraints == self.default_constraints + [
|
||||||
|
[Spec('libdwarf')]]
|
||||||
|
assert speclist.specs == self.default_specs + [Spec('libdwarf')]
|
||||||
|
|
||||||
|
def test_spec_list_remove(self):
|
||||||
|
speclist = SpecList('specs', self.default_input,
|
||||||
|
self.default_reference)
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == self.default_expansion
|
||||||
|
assert speclist.specs_as_constraints == self.default_constraints
|
||||||
|
assert speclist.specs == self.default_specs
|
||||||
|
|
||||||
|
speclist.remove('libelf')
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list + [
|
||||||
|
'libelf'
|
||||||
|
] == self.default_expansion
|
||||||
|
|
||||||
|
assert speclist.specs_as_constraints + [
|
||||||
|
[Spec('libelf')]
|
||||||
|
] == self.default_constraints
|
||||||
|
|
||||||
|
assert speclist.specs + [Spec('libelf')] == self.default_specs
|
||||||
|
|
||||||
|
def test_spec_list_update_reference(self):
|
||||||
|
speclist = SpecList('specs', self.default_input,
|
||||||
|
self.default_reference)
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == self.default_expansion
|
||||||
|
assert speclist.specs_as_constraints == self.default_constraints
|
||||||
|
assert speclist.specs == self.default_specs
|
||||||
|
|
||||||
|
new_mpis = SpecList('mpis', self.default_reference['mpis'].yaml_list)
|
||||||
|
new_mpis.add('mpich@3.3')
|
||||||
|
new_reference = self.default_reference.copy()
|
||||||
|
new_reference['mpis'] = new_mpis
|
||||||
|
|
||||||
|
speclist.update_reference(new_reference)
|
||||||
|
|
||||||
|
expansion = list(self.default_expansion)
|
||||||
|
expansion.insert(3, 'mpich@3.3')
|
||||||
|
constraints = list(self.default_constraints)
|
||||||
|
constraints.insert(3, [Spec('mpich@3.3')])
|
||||||
|
specs = list(self.default_specs)
|
||||||
|
specs.insert(3, Spec('mpich@3.3'))
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == expansion
|
||||||
|
assert speclist.specs_as_constraints == constraints
|
||||||
|
assert speclist.specs == specs
|
||||||
|
|
||||||
|
def test_spec_list_extension(self):
|
||||||
|
speclist = SpecList('specs', self.default_input,
|
||||||
|
self.default_reference)
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == self.default_expansion
|
||||||
|
assert speclist.specs_as_constraints == self.default_constraints
|
||||||
|
assert speclist.specs == self.default_specs
|
||||||
|
|
||||||
|
new_ref = self.default_reference.copy()
|
||||||
|
otherlist = SpecList('specs',
|
||||||
|
['zlib', {'matrix': [['callpath'],
|
||||||
|
['%intel@18']]}],
|
||||||
|
new_ref)
|
||||||
|
|
||||||
|
speclist.extend(otherlist)
|
||||||
|
|
||||||
|
assert speclist.specs_as_yaml_list == (self.default_expansion +
|
||||||
|
otherlist.specs_as_yaml_list)
|
||||||
|
assert speclist.specs == self.default_specs + otherlist.specs
|
||||||
|
assert speclist._reference is new_ref
|
Loading…
Reference in New Issue
Block a user