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:
Massimiliano Culpo 2016-09-20 11:26:25 +02:00 committed by Todd Gamblin
parent efadc0e299
commit ea446c0f0e
10 changed files with 560 additions and 149 deletions

1
.gitignore vendored
View file

@ -12,6 +12,7 @@
/etc/spackconfig /etc/spackconfig
/share/spack/dotkit /share/spack/dotkit
/share/spack/modules /share/spack/modules
/share/spack/lmod
/TAGS /TAGS
/htmlcov /htmlcov
.coverage .coverage

View file

@ -69,17 +69,17 @@ def get_cmd_function_name(name):
def get_module(name): def get_module(name):
"""Imports the module for a particular command name and returns it.""" """Imports the module for a particular command name and returns it."""
module_name = "%s.%s" % (__name__, name) module_name = "%s.%s" % (__name__, name)
module = __import__( module = __import__(module_name,
module_name, fromlist=[name, SETUP_PARSER, DESCRIPTION], fromlist=[name, SETUP_PARSER, DESCRIPTION],
level=0) level=0)
attr_setdefault(module, SETUP_PARSER, lambda *args: None) # null-op attr_setdefault(module, SETUP_PARSER, lambda *args: None) # null-op
attr_setdefault(module, DESCRIPTION, "") attr_setdefault(module, DESCRIPTION, "")
fn_name = get_cmd_function_name(name) fn_name = get_cmd_function_name(name)
if not hasattr(module, fn_name): if not hasattr(module, fn_name):
tty.die("Command module %s (%s) must define function '%s'." tty.die("Command module %s (%s) must define function '%s'." %
% (module.__name__, module.__file__, fn_name)) (module.__name__, module.__file__, fn_name))
return module return module

View file

@ -29,10 +29,10 @@
import shutil import shutil
import sys import sys
import llnl.util.filesystem as filesystem
import llnl.util.tty as tty import llnl.util.tty as tty
import spack.cmd import spack.cmd
import spack.cmd.common.arguments as arguments import spack.cmd.common.arguments as arguments
import llnl.util.filesystem as filesystem
from spack.modules import module_types from spack.modules import module_types
description = "Manipulate module files" description = "Manipulate module files"

View file

@ -24,7 +24,7 @@
############################################################################## ##############################################################################
"""This package contains modules with hooks for various stages in the """This package contains modules with hooks for various stages in the
Spack install process. You can add modules here and they'll be 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. Each hook is just a function that takes a package as a parameter.
Hooks are not executed in any particular order. Hooks are not executed in any particular order.
@ -41,9 +41,10 @@
features. features.
""" """
import imp import imp
from llnl.util.lang import memoized, list_modules
from llnl.util.filesystem import join_path
import spack import spack
from llnl.util.filesystem import join_path
from llnl.util.lang import memoized, list_modules
@memoized @memoized
@ -70,12 +71,11 @@ def __call__(self, pkg):
if hasattr(hook, '__call__'): if hasattr(hook, '__call__'):
hook(pkg) hook(pkg)
# #
# Define some functions that can be called to fire off hooks. # Define some functions that can be called to fire off hooks.
# #
pre_install = HookRunner('pre_install') pre_install = HookRunner('pre_install')
post_install = HookRunner('post_install') post_install = HookRunner('post_install')
pre_uninstall = HookRunner('pre_uninstall') pre_uninstall = HookRunner('pre_uninstall')
post_uninstall = HookRunner('post_uninstall') post_uninstall = HookRunner('post_uninstall')

View 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()

View file

@ -40,6 +40,7 @@
""" """
import copy import copy
import datetime import datetime
import itertools
import os import os
import os.path import os.path
import re import re
@ -48,6 +49,7 @@
import llnl.util.tty as tty import llnl.util.tty as tty
import spack import spack
import spack.compilers # Needed by LmodModules
import spack.config import spack.config
from llnl.util.filesystem import join_path, mkdirp from llnl.util.filesystem import join_path, mkdirp
from spack.build_environment import parent_class_modules from spack.build_environment import parent_class_modules
@ -56,7 +58,8 @@
__all__ = ['EnvModule', 'Dotkit', 'TclModule'] __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 = {} module_types = {}
CONFIGURATION = spack.config.get_config('modules') CONFIGURATION = spack.config.get_config('modules')
@ -633,3 +636,237 @@ def module_specific_content(self, configuration):
raise SystemExit('Module generation aborted.') raise SystemExit('Module generation aborted.')
line = line.format(**naming_tokens) line = line.format(**naming_tokens)
yield line 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

View file

@ -139,7 +139,20 @@
'default': [], 'default': [],
'items': { 'items': {
'type': 'string', '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': { 'tcl': {
'allOf': [ 'allOf': [
# Base configuration # Base configuration

View file

@ -26,8 +26,8 @@
These tests check the database is functioning properly, These tests check the database is functioning properly,
both in memory and in its file both in memory and in its file
""" """
import os.path
import multiprocessing import multiprocessing
import os.path
import spack import spack
from llnl.util.filesystem import join_path from llnl.util.filesystem import join_path
@ -88,16 +88,16 @@ def test_010_all_install_sanity(self):
# query specs with multiple configurations # query specs with multiple configurations
mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')] mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
callpath_specs = [s for s in all_specs if s.satisfies('callpath')] 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(mpileaks_specs), 3)
self.assertEqual(len(callpath_specs), 3) self.assertEqual(len(callpath_specs), 3)
self.assertEqual(len(mpi_specs), 3) self.assertEqual(len(mpi_specs), 3)
# query specs with single configurations # 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')] 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(dyninst_specs), 1)
self.assertEqual(len(libdwarf_specs), 1) self.assertEqual(len(libdwarf_specs), 1)
@ -163,16 +163,16 @@ def test_050_basic_query(self):
# query specs with multiple configurations # query specs with multiple configurations
mpileaks_specs = self.installed_db.query('mpileaks') mpileaks_specs = self.installed_db.query('mpileaks')
callpath_specs = self.installed_db.query('callpath') 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(mpileaks_specs), 3)
self.assertEqual(len(callpath_specs), 3) self.assertEqual(len(callpath_specs), 3)
self.assertEqual(len(mpi_specs), 3) self.assertEqual(len(mpi_specs), 3)
# query specs with single configurations # 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') 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(dyninst_specs), 1)
self.assertEqual(len(libdwarf_specs), 1) self.assertEqual(len(libdwarf_specs), 1)

View file

@ -49,105 +49,10 @@ def mock_open(filename, mode):
handle.close() handle.close()
configuration_autoload_direct = { # Spec strings that will be used throughout the tests
'enable': ['tcl'], mpich_spec_string = 'mpich@3.0.4'
'tcl': { mpileaks_spec_string = 'mpileaks'
'all': { libdwarf_spec_string = 'libdwarf arch=x64-linux'
'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'
}
}
}
}
class HelperFunctionsTests(MockPackagesTest): class HelperFunctionsTests(MockPackagesTest):
@ -187,44 +92,156 @@ def test_inspect_path(self):
self.assertTrue('CPATH' in names) 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): def setUp(self):
super(TclTests, self).setUp() super(ModuleFileGeneratorTests, self).setUp()
self.configuration_obj = spack.modules.CONFIGURATION self.configuration_instance = spack.modules.CONFIGURATION
self.module_types_instance = spack.modules.module_types
spack.modules.open = mock_open spack.modules.open = mock_open
# Make sure that a non-mocked configuration will trigger an error # Make sure that a non-mocked configuration will trigger an error
spack.modules.CONFIGURATION = None spack.modules.CONFIGURATION = None
spack.modules.module_types = {self.factory.name: self.factory}
def tearDown(self): def tearDown(self):
del spack.modules.open del spack.modules.open
spack.modules.CONFIGURATION = self.configuration_obj spack.modules.module_types = self.module_types_instance
super(TclTests, self).tearDown() spack.modules.CONFIGURATION = self.configuration_instance
super(ModuleFileGeneratorTests, self).tearDown()
def get_modulefile_content(self, spec): def get_modulefile_content(self, spec):
spec.concretize() spec.concretize()
generator = spack.modules.TclModule(spec) generator = self.factory(spec)
generator.write() generator.write()
content = FILE_REGISTRY[generator.file_name].split('\n') content = FILE_REGISTRY[generator.file_name].split('\n')
return content 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): def test_simple_case(self):
spack.modules.CONFIGURATION = configuration_autoload_direct spack.modules.CONFIGURATION = self.configuration_autoload_direct
spec = spack.spec.Spec('mpich@3.0.4') spec = spack.spec.Spec(mpich_spec_string)
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertTrue('module-whatis "mpich @3.0.4"' in content) self.assertTrue('module-whatis "mpich @3.0.4"' in content)
self.assertRaises(TypeError, spack.modules.dependencies, self.assertRaises(TypeError, spack.modules.dependencies,
spec, 'non-existing-tag') spec, 'non-existing-tag')
def test_autoload(self): def test_autoload(self):
spack.modules.CONFIGURATION = configuration_autoload_direct spack.modules.CONFIGURATION = self.configuration_autoload_direct
spec = spack.spec.Spec('mpileaks') spec = spack.spec.Spec(mpileaks_spec_string)
content = self.get_modulefile_content(spec) 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 'is-loaded' in x]), 2)
self.assertEqual(len([x for x in content if 'module load ' in x]), 2) self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
spack.modules.CONFIGURATION = configuration_autoload_all spack.modules.CONFIGURATION = self.configuration_autoload_all
spec = spack.spec.Spec('mpileaks') spec = spack.spec.Spec(mpileaks_spec_string)
content = self.get_modulefile_content(spec) 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 'is-loaded' in x]), 5)
self.assertEqual(len([x for x in content if 'module load ' 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) self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
def test_prerequisites(self): 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') spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'prereq' in x]), 2) 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') spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'prereq' in x]), 5) self.assertEqual(len([x for x in content if 'prereq' in x]), 5)
def test_alter_environment(self): 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') spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertEqual( self.assertEqual(
@ -293,7 +310,7 @@ def test_alter_environment(self):
len([x for x in content if 'setenv LIBDWARF_ROOT' in x]), 1) len([x for x in content if 'setenv LIBDWARF_ROOT' in x]), 1)
def test_blacklist(self): def test_blacklist(self):
spack.modules.CONFIGURATION = configuration_blacklist spack.modules.CONFIGURATION = self.configuration_blacklist
spec = spack.spec.Spec('mpileaks ^zmpi') spec = spack.spec.Spec('mpileaks ^zmpi')
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 1) 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) self.assertEqual(len([x for x in content if 'module load ' in x]), 1)
def test_conflicts(self): def test_conflicts(self):
spack.modules.CONFIGURATION = configuration_conflicts spack.modules.CONFIGURATION = self.configuration_conflicts
spec = spack.spec.Spec('mpileaks') spec = spack.spec.Spec('mpileaks')
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertEqual( self.assertEqual(
@ -317,11 +334,11 @@ def test_conflicts(self):
self.assertEqual( self.assertEqual(
len([x for x in content if x == 'conflict intel/14.0.1']), 1) 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) self.assertRaises(SystemExit, self.get_modulefile_content, spec)
def test_suffixes(self): 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 = spack.spec.Spec('mpileaks+debug arch=x86-linux')
spec.concretize() spec.concretize()
generator = spack.modules.TclModule(spec) generator = spack.modules.TclModule(spec)
@ -333,18 +350,123 @@ def test_suffixes(self):
self.assertTrue('bar' in generator.use_name) self.assertTrue('bar' in generator.use_name)
configuration_dotkit = { class LmodTests(ModuleFileGeneratorTests):
'enable': ['dotkit'], factory = spack.modules.LmodModule
'dotkit': {
'all': { configuration_autoload_direct = {
'prerequisites': '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): class DotkitTests(MockPackagesTest):
configuration_dotkit = {
'enable': ['dotkit'],
'dotkit': {
'all': {
'prerequisites': 'direct'
}
}
}
def setUp(self): def setUp(self):
super(DotkitTests, self).setUp() super(DotkitTests, self).setUp()
self.configuration_obj = spack.modules.CONFIGURATION self.configuration_obj = spack.modules.CONFIGURATION
@ -365,7 +487,7 @@ def get_modulefile_content(self, spec):
return content return content
def test_dotkit(self): def test_dotkit(self):
spack.modules.CONFIGURATION = configuration_dotkit spack.modules.CONFIGURATION = self.configuration_dotkit
spec = spack.spec.Spec('mpileaks arch=x86-linux') spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = self.get_modulefile_content(spec) content = self.get_modulefile_content(spec)
self.assertTrue('#c spack' in content) self.assertTrue('#c spack' in content)

View file

@ -22,9 +22,10 @@
# License along with this program; if not, write to the Free Software # License along with this program; if not, write to the Free Software
# 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 import *
import os import os
from spack import *
class Llvm(Package): class Llvm(Package):
"""The LLVM Project is a collection of modular and reusable compiler and """The LLVM Project is a collection of modular and reusable compiler and
@ -37,9 +38,11 @@ class Llvm(Package):
homepage = 'http://llvm.org/' homepage = 'http://llvm.org/'
url = 'http://llvm.org/releases/3.7.1/llvm-3.7.1.src.tar.xz' 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 # currently required by mesa package
version('3.0', 'a8e5f5f1c1adebae7b4a654c376a6005', 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, variant('debug', default=False,
description="Build a debug version of LLVM, this increases " description="Build a debug version of LLVM, this increases "