From ea446c0f0ea0dc905ea66ad6a902cbb4713f920a Mon Sep 17 00:00:00 2001 From: Massimiliano Culpo Date: Tue, 20 Sep 2016 11:26:25 +0200 Subject: 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 --- lib/spack/spack/cmd/__init__.py | 10 +- lib/spack/spack/cmd/module.py | 2 +- lib/spack/spack/hooks/__init__.py | 14 +- lib/spack/spack/hooks/lmodmodule.py | 35 ++++ lib/spack/spack/modules.py | 239 ++++++++++++++++++++++- lib/spack/spack/schema/modules.py | 15 +- lib/spack/spack/test/database.py | 14 +- lib/spack/spack/test/modules.py | 372 ++++++++++++++++++++++++------------ 8 files changed, 554 insertions(+), 147 deletions(-) create mode 100644 lib/spack/spack/hooks/lmodmodule.py (limited to 'lib') diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py index f69f434afd..6b1561b7fc 100644 --- a/lib/spack/spack/cmd/__init__.py +++ b/lib/spack/spack/cmd/__init__.py @@ -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 diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py index 2d0b83fe00..c6fa84109e 100644 --- a/lib/spack/spack/cmd/module.py +++ b/lib/spack/spack/cmd/module.py @@ -29,10 +29,10 @@ import os 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" diff --git a/lib/spack/spack/hooks/__init__.py b/lib/spack/spack/hooks/__init__.py index c7c84defa0..ff4ebc2e57 100644 --- a/lib/spack/spack/hooks/__init__.py +++ b/lib/spack/spack/hooks/__init__.py @@ -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 @@ class HookRunner(object): 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') diff --git a/lib/spack/spack/hooks/lmodmodule.py b/lib/spack/spack/hooks/lmodmodule.py new file mode 100644 index 0000000000..6b4318b1d0 --- /dev/null +++ b/lib/spack/spack/hooks/lmodmodule.py @@ -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() diff --git a/lib/spack/spack/modules.py b/lib/spack/spack/modules.py index 3db08a6e90..d75bfbac45 100644 --- a/lib/spack/spack/modules.py +++ b/lib/spack/spack/modules.py @@ -40,6 +40,7 @@ module file. """ import copy import datetime +import itertools import os import os.path import re @@ -48,6 +49,7 @@ import textwrap 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 @@ from spack.environment import * __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 @@ class TclModule(EnvModule): 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 diff --git a/lib/spack/spack/schema/modules.py b/lib/spack/spack/schema/modules.py index f8066919f1..bdc70c9ef1 100644 --- a/lib/spack/spack/schema/modules.py +++ b/lib/spack/spack/schema/modules.py @@ -139,7 +139,20 @@ schema = { '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 diff --git a/lib/spack/spack/test/database.py b/lib/spack/spack/test/database.py index 22b1f17890..b114bbacb8 100644 --- a/lib/spack/spack/test/database.py +++ b/lib/spack/spack/test/database.py @@ -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 @@ class DatabaseTest(MockDatabase): # 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 @@ class DatabaseTest(MockDatabase): # 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) diff --git a/lib/spack/spack/test/modules.py b/lib/spack/spack/test/modules.py index 55b771fc79..88f67090f1 100644 --- a/lib/spack/spack/test/modules.py +++ b/lib/spack/spack/test/modules.py @@ -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 @@ class HelperFunctionsTests(MockPackagesTest): 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 @@ class TclTests(MockPackagesTest): 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 @@ class TclTests(MockPackagesTest): 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 @@ class TclTests(MockPackagesTest): 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 @@ class TclTests(MockPackagesTest): 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 @@ class TclTests(MockPackagesTest): 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 @@ class DotkitTests(MockPackagesTest): 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) -- cgit v1.2.3-70-g09d2