summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@googlemail.com>2016-09-20 11:26:25 +0200
committerTodd Gamblin <tgamblin@llnl.gov>2016-09-20 02:26:25 -0700
commitea446c0f0ea0dc905ea66ad6a902cbb4713f920a (patch)
tree8c21e7e441bf5877ef234dad7bd68a74bd6d5f66
parentefadc0e2997015545d656f587a5478c9fcc1c2ad (diff)
downloadspack-ea446c0f0ea0dc905ea66ad6a902cbb4713f920a.tar.gz
spack-ea446c0f0ea0dc905ea66ad6a902cbb4713f920a.tar.bz2
spack-ea446c0f0ea0dc905ea66ad6a902cbb4713f920a.tar.xz
spack-ea446c0f0ea0dc905ea66ad6a902cbb4713f920a.zip
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
-rw-r--r--.gitignore1
-rw-r--r--lib/spack/spack/cmd/__init__.py10
-rw-r--r--lib/spack/spack/cmd/module.py2
-rw-r--r--lib/spack/spack/hooks/__init__.py14
-rw-r--r--lib/spack/spack/hooks/lmodmodule.py35
-rw-r--r--lib/spack/spack/modules.py239
-rw-r--r--lib/spack/spack/schema/modules.py15
-rw-r--r--lib/spack/spack/test/database.py14
-rw-r--r--lib/spack/spack/test/modules.py372
-rw-r--r--var/spack/repos/builtin/packages/llvm/package.py7
10 files changed, 560 insertions, 149 deletions
diff --git a/.gitignore b/.gitignore
index b1215f0c7e..072bf30c07 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
/etc/spackconfig
/share/spack/dotkit
/share/spack/modules
+/share/spack/lmod
/TAGS
/htmlcov
.coverage
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)
diff --git a/var/spack/repos/builtin/packages/llvm/package.py b/var/spack/repos/builtin/packages/llvm/package.py
index 61ea8daac4..7582faeb92 100644
--- a/var/spack/repos/builtin/packages/llvm/package.py
+++ b/var/spack/repos/builtin/packages/llvm/package.py
@@ -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 "