lmod : added support for the creation of hierarchical lua module files (#1723)
Includes : - treatment of a generic hierarchy (i.e. lapack + mpi + compiler) - possibility to specify which compilers are to be considered Core - correct treatment of the 'family' directive - unit tests for most new features
This commit is contained in:
parent
efadc0e299
commit
ea446c0f0e
10 changed files with 560 additions and 149 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -12,6 +12,7 @@
|
|||
/etc/spackconfig
|
||||
/share/spack/dotkit
|
||||
/share/spack/modules
|
||||
/share/spack/lmod
|
||||
/TAGS
|
||||
/htmlcov
|
||||
.coverage
|
||||
|
|
|
@ -69,17 +69,17 @@ def get_cmd_function_name(name):
|
|||
def get_module(name):
|
||||
"""Imports the module for a particular command name and returns it."""
|
||||
module_name = "%s.%s" % (__name__, name)
|
||||
module = __import__(
|
||||
module_name, fromlist=[name, SETUP_PARSER, DESCRIPTION],
|
||||
level=0)
|
||||
module = __import__(module_name,
|
||||
fromlist=[name, SETUP_PARSER, DESCRIPTION],
|
||||
level=0)
|
||||
|
||||
attr_setdefault(module, SETUP_PARSER, lambda *args: None) # null-op
|
||||
attr_setdefault(module, DESCRIPTION, "")
|
||||
|
||||
fn_name = get_cmd_function_name(name)
|
||||
if not hasattr(module, fn_name):
|
||||
tty.die("Command module %s (%s) must define function '%s'."
|
||||
% (module.__name__, module.__file__, fn_name))
|
||||
tty.die("Command module %s (%s) must define function '%s'." %
|
||||
(module.__name__, module.__file__, fn_name))
|
||||
|
||||
return module
|
||||
|
||||
|
|
|
@ -29,10 +29,10 @@
|
|||
import shutil
|
||||
import sys
|
||||
|
||||
import llnl.util.filesystem as filesystem
|
||||
import llnl.util.tty as tty
|
||||
import spack.cmd
|
||||
import spack.cmd.common.arguments as arguments
|
||||
import llnl.util.filesystem as filesystem
|
||||
from spack.modules import module_types
|
||||
|
||||
description = "Manipulate module files"
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
##############################################################################
|
||||
"""This package contains modules with hooks for various stages in the
|
||||
Spack install process. You can add modules here and they'll be
|
||||
executaed by package at various times during the package lifecycle.
|
||||
executed by package at various times during the package lifecycle.
|
||||
|
||||
Each hook is just a function that takes a package as a parameter.
|
||||
Hooks are not executed in any particular order.
|
||||
|
@ -41,9 +41,10 @@
|
|||
features.
|
||||
"""
|
||||
import imp
|
||||
from llnl.util.lang import memoized, list_modules
|
||||
from llnl.util.filesystem import join_path
|
||||
|
||||
import spack
|
||||
from llnl.util.filesystem import join_path
|
||||
from llnl.util.lang import memoized, list_modules
|
||||
|
||||
|
||||
@memoized
|
||||
|
@ -70,12 +71,11 @@ def __call__(self, pkg):
|
|||
if hasattr(hook, '__call__'):
|
||||
hook(pkg)
|
||||
|
||||
|
||||
#
|
||||
# Define some functions that can be called to fire off hooks.
|
||||
#
|
||||
pre_install = HookRunner('pre_install')
|
||||
post_install = HookRunner('post_install')
|
||||
pre_install = HookRunner('pre_install')
|
||||
post_install = HookRunner('post_install')
|
||||
|
||||
pre_uninstall = HookRunner('pre_uninstall')
|
||||
pre_uninstall = HookRunner('pre_uninstall')
|
||||
post_uninstall = HookRunner('post_uninstall')
|
||||
|
|
35
lib/spack/spack/hooks/lmodmodule.py
Normal file
35
lib/spack/spack/hooks/lmodmodule.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
##############################################################################
|
||||
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
|
||||
# Produced at the Lawrence Livermore National Laboratory.
|
||||
#
|
||||
# This file is part of Spack.
|
||||
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||
# LLNL-CODE-647188
|
||||
#
|
||||
# For details, see https://scalability-llnl.github.io/spack
|
||||
# Please also see the LICENSE file for our notice and the LGPL.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License (as published by
|
||||
# the Free Software Foundation) version 2.1 dated February 1999.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
|
||||
# conditions of the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
import spack.modules
|
||||
|
||||
|
||||
def post_install(pkg):
|
||||
dk = spack.modules.LmodModule(pkg.spec)
|
||||
dk.write()
|
||||
|
||||
|
||||
def post_uninstall(pkg):
|
||||
dk = spack.modules.LmodModule(pkg.spec)
|
||||
dk.remove()
|
|
@ -40,6 +40,7 @@
|
|||
"""
|
||||
import copy
|
||||
import datetime
|
||||
import itertools
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
|
@ -48,6 +49,7 @@
|
|||
|
||||
import llnl.util.tty as tty
|
||||
import spack
|
||||
import spack.compilers # Needed by LmodModules
|
||||
import spack.config
|
||||
from llnl.util.filesystem import join_path, mkdirp
|
||||
from spack.build_environment import parent_class_modules
|
||||
|
@ -56,7 +58,8 @@
|
|||
|
||||
__all__ = ['EnvModule', 'Dotkit', 'TclModule']
|
||||
|
||||
# Registry of all types of modules. Entries created by EnvModule's metaclass
|
||||
"""Registry of all types of modules. Entries created by EnvModule's
|
||||
metaclass."""
|
||||
module_types = {}
|
||||
|
||||
CONFIGURATION = spack.config.get_config('modules')
|
||||
|
@ -633,3 +636,237 @@ def module_specific_content(self, configuration):
|
|||
raise SystemExit('Module generation aborted.')
|
||||
line = line.format(**naming_tokens)
|
||||
yield line
|
||||
|
||||
# To construct an arbitrary hierarchy of module files:
|
||||
# 1. Parse the configuration file and check that all the items in
|
||||
# hierarchical_scheme are indeed virtual packages
|
||||
# This needs to be done only once at start-up
|
||||
# 2. Order the stack as `hierarchical_scheme + ['mpi, 'compiler']
|
||||
# 3. Check which of the services are provided by the package
|
||||
# -> may be more than one
|
||||
# 4. Check which of the services are needed by the package
|
||||
# -> this determines where to write the module file
|
||||
# 5. For each combination of services in which we have at least one provider
|
||||
# here add the appropriate conditional MODULEPATH modifications
|
||||
|
||||
|
||||
class LmodModule(EnvModule):
|
||||
name = 'lmod'
|
||||
path = join_path(spack.share_path, "lmod")
|
||||
|
||||
environment_modifications_formats = {
|
||||
PrependPath: 'prepend_path("{name}", "{value}")\n',
|
||||
AppendPath: 'append_path("{name}", "{value}")\n',
|
||||
RemovePath: 'remove_path("{name}", "{value}")\n',
|
||||
SetEnv: 'setenv("{name}", "{value}")\n',
|
||||
UnsetEnv: 'unsetenv("{name}")\n'
|
||||
}
|
||||
|
||||
autoload_format = ('if not isloaded("{module_file}") then\n'
|
||||
' LmodMessage("Autoloading {module_file}")\n'
|
||||
' load("{module_file}")\n'
|
||||
'end\n\n')
|
||||
|
||||
prerequisite_format = 'prereq("{module_file}")\n'
|
||||
|
||||
family_format = 'family("{family}")\n'
|
||||
|
||||
path_part_with_hash = join_path('{token.name}', '{token.version}-{token.hash}') # NOQA: ignore=E501
|
||||
path_part_without_hash = join_path('{token.name}', '{token.version}')
|
||||
|
||||
# TODO : Check that extra tokens specified in configuration file
|
||||
# TODO : are actually virtual dependencies
|
||||
configuration = CONFIGURATION.get('lmod', {})
|
||||
hierarchy_tokens = configuration.get('hierarchical_scheme', [])
|
||||
hierarchy_tokens = hierarchy_tokens + ['mpi', 'compiler']
|
||||
|
||||
def __init__(self, spec=None):
|
||||
super(LmodModule, self).__init__(spec)
|
||||
# Sets the root directory for this architecture
|
||||
self.modules_root = join_path(LmodModule.path, self.spec.architecture)
|
||||
# Retrieve core compilers
|
||||
self.core_compilers = self.configuration.get('core_compilers', [])
|
||||
# Keep track of the requirements that this package has in terms
|
||||
# of virtual packages
|
||||
# that participate in the hierarchical structure
|
||||
self.requires = {'compiler': self.spec.compiler}
|
||||
# For each virtual dependency in the hierarchy
|
||||
for x in self.hierarchy_tokens:
|
||||
if x in self.spec and not self.spec.package.provides(
|
||||
x): # if I depend on it
|
||||
self.requires[x] = self.spec[x] # record the actual provider
|
||||
# Check what are the services I need (this will determine where the
|
||||
# module file will be written)
|
||||
self.substitutions = {}
|
||||
self.substitutions.update(self.requires)
|
||||
# TODO : complete substitutions
|
||||
# Check what service I provide to others
|
||||
self.provides = {}
|
||||
# If it is in the list of supported compilers family -> compiler
|
||||
if self.spec.name in spack.compilers.supported_compilers():
|
||||
self.provides['compiler'] = spack.spec.CompilerSpec(str(self.spec))
|
||||
# Special case for llvm
|
||||
if self.spec.name == 'llvm':
|
||||
self.provides['compiler'] = spack.spec.CompilerSpec(str(self.spec))
|
||||
self.provides['compiler'].name = 'clang'
|
||||
|
||||
for x in self.hierarchy_tokens:
|
||||
if self.spec.package.provides(x):
|
||||
self.provides[x] = self.spec[x]
|
||||
|
||||
def _hierarchy_token_combinations(self):
|
||||
"""
|
||||
Yields all the relevant combinations that could appear in the hierarchy
|
||||
"""
|
||||
for ii in range(len(self.hierarchy_tokens) + 1):
|
||||
for item in itertools.combinations(self.hierarchy_tokens, ii):
|
||||
if 'compiler' in item:
|
||||
yield item
|
||||
|
||||
def _hierarchy_to_be_provided(self):
|
||||
"""
|
||||
Filters a list of hierarchy tokens and yields only the one that we
|
||||
need to provide
|
||||
"""
|
||||
for item in self._hierarchy_token_combinations():
|
||||
if any(x in self.provides for x in item):
|
||||
yield item
|
||||
|
||||
def token_to_path(self, name, value):
|
||||
# If we are dealing with a core compiler, return 'Core'
|
||||
if name == 'compiler' and str(value) in self.core_compilers:
|
||||
return 'Core'
|
||||
# CompilerSpec does not have an hash
|
||||
if name == 'compiler':
|
||||
return self.path_part_without_hash.format(token=value)
|
||||
# For virtual providers add a small part of the hash
|
||||
# to distinguish among different variants in a directory hierarchy
|
||||
value.hash = value.dag_hash(length=6)
|
||||
return self.path_part_with_hash.format(token=value)
|
||||
|
||||
@property
|
||||
def file_name(self):
|
||||
parts = [self.token_to_path(x, self.requires[x])
|
||||
for x in self.hierarchy_tokens if x in self.requires]
|
||||
hierarchy_name = join_path(*parts)
|
||||
fullname = join_path(self.modules_root, hierarchy_name,
|
||||
self.use_name + '.lua')
|
||||
return fullname
|
||||
|
||||
@property
|
||||
def use_name(self):
|
||||
return self.token_to_path('', self.spec)
|
||||
|
||||
def modulepath_modifications(self):
|
||||
# What is available is what we require plus what we provide
|
||||
entry = ''
|
||||
available = {}
|
||||
available.update(self.requires)
|
||||
available.update(self.provides)
|
||||
available_parts = [self.token_to_path(x, available[x])
|
||||
for x in self.hierarchy_tokens if x in available]
|
||||
# Missing parts
|
||||
missing = [x for x in self.hierarchy_tokens if x not in available]
|
||||
# Direct path we provide on top of compilers
|
||||
modulepath = join_path(self.modules_root, *available_parts)
|
||||
env = EnvironmentModifications()
|
||||
env.prepend_path('MODULEPATH', modulepath)
|
||||
for line in self.process_environment_command(env):
|
||||
entry += line
|
||||
|
||||
def local_variable(x):
|
||||
lower, upper = x.lower(), x.upper()
|
||||
fmt = 'local {lower}_name = os.getenv("LMOD_{upper}_NAME")\n'
|
||||
fmt += 'local {lower}_version = os.getenv("LMOD_{upper}_VERSION")\n' # NOQA: ignore=501
|
||||
return fmt.format(lower=lower, upper=upper)
|
||||
|
||||
def set_variables_for_service(env, x):
|
||||
upper = x.upper()
|
||||
s = self.provides[x]
|
||||
name, version = os.path.split(self.token_to_path(x, s))
|
||||
|
||||
env.set('LMOD_{upper}_NAME'.format(upper=upper), name)
|
||||
env.set('LMOD_{upper}_VERSION'.format(upper=upper), version)
|
||||
|
||||
def conditional_modulepath_modifications(item):
|
||||
entry = 'if '
|
||||
needed = []
|
||||
for x in self.hierarchy_tokens:
|
||||
if x in missing:
|
||||
needed.append('{x}_name '.format(x=x))
|
||||
entry += 'and '.join(needed) + 'then\n'
|
||||
entry += ' local t = pathJoin("{root}"'.format(
|
||||
root=self.modules_root)
|
||||
for x in item:
|
||||
if x in missing:
|
||||
entry += ', {lower}_name, {lower}_version'.format(
|
||||
lower=x.lower())
|
||||
else:
|
||||
entry += ', "{x}"'.format(
|
||||
x=self.token_to_path(x, available[x]))
|
||||
entry += ')\n'
|
||||
entry += ' prepend_path("MODULEPATH", t)\n'
|
||||
entry += 'end\n\n'
|
||||
return entry
|
||||
|
||||
if 'compiler' not in self.provides:
|
||||
# Retrieve variables
|
||||
entry += '\n'
|
||||
for x in missing:
|
||||
entry += local_variable(x)
|
||||
entry += '\n'
|
||||
# Conditional modifications
|
||||
conditionals = [x
|
||||
for x in self._hierarchy_to_be_provided()
|
||||
if any(t in missing for t in x)]
|
||||
for item in conditionals:
|
||||
entry += conditional_modulepath_modifications(item)
|
||||
|
||||
# Set environment variables for the services we provide
|
||||
env = EnvironmentModifications()
|
||||
for x in self.provides:
|
||||
set_variables_for_service(env, x)
|
||||
for line in self.process_environment_command(env):
|
||||
entry += line
|
||||
|
||||
return entry
|
||||
|
||||
@property
|
||||
def header(self):
|
||||
timestamp = datetime.datetime.now()
|
||||
# Header as in
|
||||
# https://www.tacc.utexas.edu/research-development/tacc-projects/lmod/advanced-user-guide/more-about-writing-module-files
|
||||
header = "-- -*- lua -*-\n"
|
||||
header += '-- Module file created by spack (https://github.com/LLNL/spack) on %s\n' % timestamp # NOQA: ignore=E501
|
||||
header += '--\n'
|
||||
header += '-- %s\n' % self.spec.short_spec
|
||||
header += '--\n'
|
||||
|
||||
# Short description -> whatis()
|
||||
if self.short_description:
|
||||
header += "whatis([[Name : {name}]])\n".format(name=self.spec.name)
|
||||
header += "whatis([[Version : {version}]])\n".format(
|
||||
version=self.spec.version)
|
||||
|
||||
# Long description -> help()
|
||||
if self.long_description:
|
||||
doc = re.sub(r'"', '\"', self.long_description)
|
||||
header += "help([[{documentation}]])\n".format(documentation=doc)
|
||||
|
||||
# Certain things need to be done only if we provide a service
|
||||
if self.provides:
|
||||
# Add family directives
|
||||
header += '\n'
|
||||
for x in self.provides:
|
||||
header += self.family_format.format(family=x)
|
||||
header += '\n'
|
||||
header += '-- MODULEPATH modifications\n'
|
||||
header += '\n'
|
||||
# Modify MODULEPATH
|
||||
header += self.modulepath_modifications()
|
||||
# Set environment variables for services we provide
|
||||
header += '\n'
|
||||
header += '-- END MODULEPATH modifications\n'
|
||||
header += '\n'
|
||||
|
||||
return header
|
||||
|
|
|
@ -139,7 +139,20 @@
|
|||
'default': [],
|
||||
'items': {
|
||||
'type': 'string',
|
||||
'enum': ['tcl', 'dotkit']}},
|
||||
'enum': ['tcl', 'dotkit', 'lmod']}},
|
||||
'lmod': {
|
||||
'allOf': [
|
||||
# Base configuration
|
||||
{'$ref': '#/definitions/module_type_configuration'},
|
||||
{
|
||||
'core_compilers': {
|
||||
'$ref': '#/definitions/array_of_strings'
|
||||
},
|
||||
'hierarchical_scheme': {
|
||||
'$ref': '#/definitions/array_of_strings'
|
||||
}
|
||||
} # Specific lmod extensions
|
||||
]},
|
||||
'tcl': {
|
||||
'allOf': [
|
||||
# Base configuration
|
||||
|
|
|
@ -26,8 +26,8 @@
|
|||
These tests check the database is functioning properly,
|
||||
both in memory and in its file
|
||||
"""
|
||||
import os.path
|
||||
import multiprocessing
|
||||
import os.path
|
||||
|
||||
import spack
|
||||
from llnl.util.filesystem import join_path
|
||||
|
@ -88,16 +88,16 @@ def test_010_all_install_sanity(self):
|
|||
# query specs with multiple configurations
|
||||
mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
|
||||
callpath_specs = [s for s in all_specs if s.satisfies('callpath')]
|
||||
mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
|
||||
mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
|
||||
|
||||
self.assertEqual(len(mpileaks_specs), 3)
|
||||
self.assertEqual(len(callpath_specs), 3)
|
||||
self.assertEqual(len(mpi_specs), 3)
|
||||
|
||||
# query specs with single configurations
|
||||
dyninst_specs = [s for s in all_specs if s.satisfies('dyninst')]
|
||||
dyninst_specs = [s for s in all_specs if s.satisfies('dyninst')]
|
||||
libdwarf_specs = [s for s in all_specs if s.satisfies('libdwarf')]
|
||||
libelf_specs = [s for s in all_specs if s.satisfies('libelf')]
|
||||
libelf_specs = [s for s in all_specs if s.satisfies('libelf')]
|
||||
|
||||
self.assertEqual(len(dyninst_specs), 1)
|
||||
self.assertEqual(len(libdwarf_specs), 1)
|
||||
|
@ -163,16 +163,16 @@ def test_050_basic_query(self):
|
|||
# query specs with multiple configurations
|
||||
mpileaks_specs = self.installed_db.query('mpileaks')
|
||||
callpath_specs = self.installed_db.query('callpath')
|
||||
mpi_specs = self.installed_db.query('mpi')
|
||||
mpi_specs = self.installed_db.query('mpi')
|
||||
|
||||
self.assertEqual(len(mpileaks_specs), 3)
|
||||
self.assertEqual(len(callpath_specs), 3)
|
||||
self.assertEqual(len(mpi_specs), 3)
|
||||
|
||||
# query specs with single configurations
|
||||
dyninst_specs = self.installed_db.query('dyninst')
|
||||
dyninst_specs = self.installed_db.query('dyninst')
|
||||
libdwarf_specs = self.installed_db.query('libdwarf')
|
||||
libelf_specs = self.installed_db.query('libelf')
|
||||
libelf_specs = self.installed_db.query('libelf')
|
||||
|
||||
self.assertEqual(len(dyninst_specs), 1)
|
||||
self.assertEqual(len(libdwarf_specs), 1)
|
||||
|
|
|
@ -49,105 +49,10 @@ def mock_open(filename, mode):
|
|||
handle.close()
|
||||
|
||||
|
||||
configuration_autoload_direct = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'autoload': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_autoload_all = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'autoload': 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_prerequisites_direct = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'prerequisites': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_prerequisites_all = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'prerequisites': 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_alter_environment = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']},
|
||||
'environment': {
|
||||
'set': {'{name}_ROOT': '{prefix}'}
|
||||
}
|
||||
},
|
||||
'platform=test target=x86_64': {
|
||||
'environment': {
|
||||
'set': {'FOO': 'foo'},
|
||||
'unset': ['BAR']
|
||||
}
|
||||
},
|
||||
'platform=test target=x86_32': {
|
||||
'load': ['foo/bar']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_blacklist = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'whitelist': ['zmpi'],
|
||||
'blacklist': ['callpath', 'mpi'],
|
||||
'all': {
|
||||
'autoload': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_conflicts = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'naming_scheme': '{name}/{version}-{compiler.name}',
|
||||
'all': {
|
||||
'conflict': ['{name}', 'intel/14.0.1']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_wrong_conflicts = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'naming_scheme': '{name}/{version}-{compiler.name}',
|
||||
'all': {
|
||||
'conflict': ['{name}/{compiler.name}']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_suffix = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'mpileaks': {
|
||||
'suffixes': {
|
||||
'+debug': 'foo',
|
||||
'~debug': 'bar'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Spec strings that will be used throughout the tests
|
||||
mpich_spec_string = 'mpich@3.0.4'
|
||||
mpileaks_spec_string = 'mpileaks'
|
||||
libdwarf_spec_string = 'libdwarf arch=x64-linux'
|
||||
|
||||
|
||||
class HelperFunctionsTests(MockPackagesTest):
|
||||
|
@ -187,44 +92,156 @@ def test_inspect_path(self):
|
|||
self.assertTrue('CPATH' in names)
|
||||
|
||||
|
||||
class TclTests(MockPackagesTest):
|
||||
class ModuleFileGeneratorTests(MockPackagesTest):
|
||||
"""
|
||||
Base class to test module file generators. Relies on child having defined
|
||||
a 'factory' attribute to create an instance of the generator to be tested.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TclTests, self).setUp()
|
||||
self.configuration_obj = spack.modules.CONFIGURATION
|
||||
super(ModuleFileGeneratorTests, self).setUp()
|
||||
self.configuration_instance = spack.modules.CONFIGURATION
|
||||
self.module_types_instance = spack.modules.module_types
|
||||
spack.modules.open = mock_open
|
||||
# Make sure that a non-mocked configuration will trigger an error
|
||||
spack.modules.CONFIGURATION = None
|
||||
spack.modules.module_types = {self.factory.name: self.factory}
|
||||
|
||||
def tearDown(self):
|
||||
del spack.modules.open
|
||||
spack.modules.CONFIGURATION = self.configuration_obj
|
||||
super(TclTests, self).tearDown()
|
||||
spack.modules.module_types = self.module_types_instance
|
||||
spack.modules.CONFIGURATION = self.configuration_instance
|
||||
super(ModuleFileGeneratorTests, self).tearDown()
|
||||
|
||||
def get_modulefile_content(self, spec):
|
||||
spec.concretize()
|
||||
generator = spack.modules.TclModule(spec)
|
||||
generator = self.factory(spec)
|
||||
generator.write()
|
||||
content = FILE_REGISTRY[generator.file_name].split('\n')
|
||||
return content
|
||||
|
||||
|
||||
class TclTests(ModuleFileGeneratorTests):
|
||||
|
||||
factory = spack.modules.TclModule
|
||||
|
||||
configuration_autoload_direct = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'autoload': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_autoload_all = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'autoload': 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_prerequisites_direct = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'prerequisites': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_prerequisites_all = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'prerequisites': 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_alter_environment = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'all': {
|
||||
'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']},
|
||||
'environment': {
|
||||
'set': {'{name}_ROOT': '{prefix}'}
|
||||
}
|
||||
},
|
||||
'platform=test target=x86_64': {
|
||||
'environment': {
|
||||
'set': {'FOO': 'foo'},
|
||||
'unset': ['BAR']
|
||||
}
|
||||
},
|
||||
'platform=test target=x86_32': {
|
||||
'load': ['foo/bar']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_blacklist = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'whitelist': ['zmpi'],
|
||||
'blacklist': ['callpath', 'mpi'],
|
||||
'all': {
|
||||
'autoload': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_conflicts = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'naming_scheme': '{name}/{version}-{compiler.name}',
|
||||
'all': {
|
||||
'conflict': ['{name}', 'intel/14.0.1']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_wrong_conflicts = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'naming_scheme': '{name}/{version}-{compiler.name}',
|
||||
'all': {
|
||||
'conflict': ['{name}/{compiler.name}']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_suffix = {
|
||||
'enable': ['tcl'],
|
||||
'tcl': {
|
||||
'mpileaks': {
|
||||
'suffixes': {
|
||||
'+debug': 'foo',
|
||||
'~debug': 'bar'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def test_simple_case(self):
|
||||
spack.modules.CONFIGURATION = configuration_autoload_direct
|
||||
spec = spack.spec.Spec('mpich@3.0.4')
|
||||
spack.modules.CONFIGURATION = self.configuration_autoload_direct
|
||||
spec = spack.spec.Spec(mpich_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertTrue('module-whatis "mpich @3.0.4"' in content)
|
||||
self.assertRaises(TypeError, spack.modules.dependencies,
|
||||
spec, 'non-existing-tag')
|
||||
|
||||
def test_autoload(self):
|
||||
spack.modules.CONFIGURATION = configuration_autoload_direct
|
||||
spec = spack.spec.Spec('mpileaks')
|
||||
spack.modules.CONFIGURATION = self.configuration_autoload_direct
|
||||
spec = spack.spec.Spec(mpileaks_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
|
||||
self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
|
||||
|
||||
spack.modules.CONFIGURATION = configuration_autoload_all
|
||||
spec = spack.spec.Spec('mpileaks')
|
||||
spack.modules.CONFIGURATION = self.configuration_autoload_all
|
||||
spec = spack.spec.Spec(mpileaks_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 5)
|
||||
self.assertEqual(len([x for x in content if 'module load ' in x]), 5)
|
||||
|
@ -252,18 +269,18 @@ def test_autoload(self):
|
|||
self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
|
||||
|
||||
def test_prerequisites(self):
|
||||
spack.modules.CONFIGURATION = configuration_prerequisites_direct
|
||||
spack.modules.CONFIGURATION = self.configuration_prerequisites_direct
|
||||
spec = spack.spec.Spec('mpileaks arch=x86-linux')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(len([x for x in content if 'prereq' in x]), 2)
|
||||
|
||||
spack.modules.CONFIGURATION = configuration_prerequisites_all
|
||||
spack.modules.CONFIGURATION = self.configuration_prerequisites_all
|
||||
spec = spack.spec.Spec('mpileaks arch=x86-linux')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(len([x for x in content if 'prereq' in x]), 5)
|
||||
|
||||
def test_alter_environment(self):
|
||||
spack.modules.CONFIGURATION = configuration_alter_environment
|
||||
spack.modules.CONFIGURATION = self.configuration_alter_environment
|
||||
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(
|
||||
|
@ -293,7 +310,7 @@ def test_alter_environment(self):
|
|||
len([x for x in content if 'setenv LIBDWARF_ROOT' in x]), 1)
|
||||
|
||||
def test_blacklist(self):
|
||||
spack.modules.CONFIGURATION = configuration_blacklist
|
||||
spack.modules.CONFIGURATION = self.configuration_blacklist
|
||||
spec = spack.spec.Spec('mpileaks ^zmpi')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 1)
|
||||
|
@ -307,7 +324,7 @@ def test_blacklist(self):
|
|||
self.assertEqual(len([x for x in content if 'module load ' in x]), 1)
|
||||
|
||||
def test_conflicts(self):
|
||||
spack.modules.CONFIGURATION = configuration_conflicts
|
||||
spack.modules.CONFIGURATION = self.configuration_conflicts
|
||||
spec = spack.spec.Spec('mpileaks')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(
|
||||
|
@ -317,11 +334,11 @@ def test_conflicts(self):
|
|||
self.assertEqual(
|
||||
len([x for x in content if x == 'conflict intel/14.0.1']), 1)
|
||||
|
||||
spack.modules.CONFIGURATION = configuration_wrong_conflicts
|
||||
spack.modules.CONFIGURATION = self.configuration_wrong_conflicts
|
||||
self.assertRaises(SystemExit, self.get_modulefile_content, spec)
|
||||
|
||||
def test_suffixes(self):
|
||||
spack.modules.CONFIGURATION = configuration_suffix
|
||||
spack.modules.CONFIGURATION = self.configuration_suffix
|
||||
spec = spack.spec.Spec('mpileaks+debug arch=x86-linux')
|
||||
spec.concretize()
|
||||
generator = spack.modules.TclModule(spec)
|
||||
|
@ -333,18 +350,123 @@ def test_suffixes(self):
|
|||
self.assertTrue('bar' in generator.use_name)
|
||||
|
||||
|
||||
configuration_dotkit = {
|
||||
'enable': ['dotkit'],
|
||||
'dotkit': {
|
||||
'all': {
|
||||
'prerequisites': 'direct'
|
||||
class LmodTests(ModuleFileGeneratorTests):
|
||||
factory = spack.modules.LmodModule
|
||||
|
||||
configuration_autoload_direct = {
|
||||
'enable': ['lmod'],
|
||||
'lmod': {
|
||||
'all': {
|
||||
'autoload': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_autoload_all = {
|
||||
'enable': ['lmod'],
|
||||
'lmod': {
|
||||
'all': {
|
||||
'autoload': 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_alter_environment = {
|
||||
'enable': ['lmod'],
|
||||
'lmod': {
|
||||
'all': {
|
||||
'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']}
|
||||
},
|
||||
'platform=test target=x86_64': {
|
||||
'environment': {
|
||||
'set': {'FOO': 'foo'},
|
||||
'unset': ['BAR']
|
||||
}
|
||||
},
|
||||
'platform=test target=x86_32': {
|
||||
'load': ['foo/bar']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configuration_blacklist = {
|
||||
'enable': ['lmod'],
|
||||
'lmod': {
|
||||
'blacklist': ['callpath'],
|
||||
'all': {
|
||||
'autoload': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def test_simple_case(self):
|
||||
spack.modules.CONFIGURATION = self.configuration_autoload_direct
|
||||
spec = spack.spec.Spec(mpich_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertTrue('-- -*- lua -*-' in content)
|
||||
self.assertTrue('whatis([[Name : mpich]])' in content)
|
||||
self.assertTrue('whatis([[Version : 3.0.4]])' in content)
|
||||
|
||||
def test_autoload(self):
|
||||
spack.modules.CONFIGURATION = self.configuration_autoload_direct
|
||||
spec = spack.spec.Spec(mpileaks_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'if not isloaded(' in x]), 2)
|
||||
self.assertEqual(len([x for x in content if 'load(' in x]), 2)
|
||||
|
||||
spack.modules.CONFIGURATION = self.configuration_autoload_all
|
||||
spec = spack.spec.Spec(mpileaks_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'if not isloaded(' in x]), 5)
|
||||
self.assertEqual(len([x for x in content if 'load(' in x]), 5)
|
||||
|
||||
def test_alter_environment(self):
|
||||
spack.modules.CONFIGURATION = self.configuration_alter_environment
|
||||
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(
|
||||
len([x
|
||||
for x in content
|
||||
if x.startswith('prepend_path("CMAKE_PREFIX_PATH"')]), 0)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'setenv("FOO", "foo")' in x]), 1)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'unsetenv("BAR")' in x]), 1)
|
||||
|
||||
spec = spack.spec.Spec('libdwarf %clang platform=test target=x86_32')
|
||||
content = self.get_modulefile_content(spec)
|
||||
print('\n'.join(content))
|
||||
self.assertEqual(
|
||||
len([x
|
||||
for x in content
|
||||
if x.startswith('prepend-path("CMAKE_PREFIX_PATH"')]), 0)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'setenv("FOO", "foo")' in x]), 0)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'unsetenv("BAR")' in x]), 0)
|
||||
|
||||
def test_blacklist(self):
|
||||
spack.modules.CONFIGURATION = self.configuration_blacklist
|
||||
spec = spack.spec.Spec(mpileaks_spec_string)
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertEqual(
|
||||
len([x for x in content if 'if not isloaded(' in x]), 1)
|
||||
self.assertEqual(len([x for x in content if 'load(' in x]), 1)
|
||||
|
||||
|
||||
class DotkitTests(MockPackagesTest):
|
||||
|
||||
configuration_dotkit = {
|
||||
'enable': ['dotkit'],
|
||||
'dotkit': {
|
||||
'all': {
|
||||
'prerequisites': 'direct'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(DotkitTests, self).setUp()
|
||||
self.configuration_obj = spack.modules.CONFIGURATION
|
||||
|
@ -365,7 +487,7 @@ def get_modulefile_content(self, spec):
|
|||
return content
|
||||
|
||||
def test_dotkit(self):
|
||||
spack.modules.CONFIGURATION = configuration_dotkit
|
||||
spack.modules.CONFIGURATION = self.configuration_dotkit
|
||||
spec = spack.spec.Spec('mpileaks arch=x86-linux')
|
||||
content = self.get_modulefile_content(spec)
|
||||
self.assertTrue('#c spack' in content)
|
||||
|
|
|
@ -22,9 +22,10 @@
|
|||
# License along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
from spack import *
|
||||
import os
|
||||
|
||||
from spack import *
|
||||
|
||||
|
||||
class Llvm(Package):
|
||||
"""The LLVM Project is a collection of modular and reusable compiler and
|
||||
|
@ -37,9 +38,11 @@ class Llvm(Package):
|
|||
homepage = 'http://llvm.org/'
|
||||
url = 'http://llvm.org/releases/3.7.1/llvm-3.7.1.src.tar.xz'
|
||||
|
||||
family = 'compiler' # Used by lmod
|
||||
|
||||
# currently required by mesa package
|
||||
version('3.0', 'a8e5f5f1c1adebae7b4a654c376a6005',
|
||||
url='http://llvm.org/releases/3.0/llvm-3.0.tar.gz')
|
||||
url='http://llvm.org/releases/3.0/llvm-3.0.tar.gz') # currently required by mesa package
|
||||
|
||||
variant('debug', default=False,
|
||||
description="Build a debug version of LLVM, this increases "
|
||||
|
|
Loading…
Reference in a new issue