summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/build_environment.py65
-rw-r--r--lib/spack/spack/operating_systems/cnl.py5
-rw-r--r--lib/spack/spack/package_prefs.py2
-rw-r--r--lib/spack/spack/platforms/cray.py4
-rw-r--r--lib/spack/spack/spec.py2
-rw-r--r--lib/spack/spack/test/module_parsing.py143
-rw-r--r--lib/spack/spack/util/module_cmd.py195
7 files changed, 346 insertions, 70 deletions
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index e216d4aa7c..ac44e57c09 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -67,8 +67,8 @@ import spack
import spack.store
from spack.environment import EnvironmentModifications, validate
from spack.util.environment import *
-from spack.util.executable import Executable, which
-
+from spack.util.executable import Executable
+from spack.util.module_cmd import load_module, get_path_from_module
#
# This can be set by the user to globally disable parallel builds.
#
@@ -120,67 +120,6 @@ class MakeExecutable(Executable):
return super(MakeExecutable, self).__call__(*args, **kwargs)
-def load_module(mod):
- """Takes a module name and removes modules until it is possible to
- load that module. It then loads the provided module. Depends on the
- modulecmd implementation of modules used in cray and lmod.
- """
- # Create an executable of the module command that will output python code
- modulecmd = which('modulecmd')
- modulecmd.add_default_arg('python')
-
- # Read the module and remove any conflicting modules
- # We do this without checking that they are already installed
- # for ease of programming because unloading a module that is not
- # loaded does nothing.
- text = modulecmd('show', mod, output=str, error=str).split()
- for i, word in enumerate(text):
- if word == 'conflict':
- exec(compile(modulecmd('unload', text[i + 1], output=str,
- error=str), '<string>', 'exec'))
- # Load the module now that there are no conflicts
- load = modulecmd('load', mod, output=str, error=str)
- exec(compile(load, '<string>', 'exec'))
-
-
-def get_path_from_module(mod):
- """Inspects a TCL module for entries that indicate the absolute path
- at which the library supported by said module can be found.
- """
- # Create a modulecmd executable
- modulecmd = which('modulecmd')
- modulecmd.add_default_arg('python')
-
- # Read the module
- text = modulecmd('show', mod, output=str, error=str).split('\n')
- # If it lists its package directory, return that
- for line in text:
- if line.find(mod.upper() + '_DIR') >= 0:
- words = line.split()
- return words[2]
-
- # If it lists a -rpath instruction, use that
- for line in text:
- rpath = line.find('-rpath/')
- if rpath >= 0:
- return line[rpath + 6:line.find('/lib')]
-
- # If it lists a -L instruction, use that
- for line in text:
- L = line.find('-L/')
- if L >= 0:
- return line[L + 2:line.find('/lib')]
-
- # If it sets the LD_LIBRARY_PATH or CRAY_LD_LIBRARY_PATH, use that
- for line in text:
- if line.find('LD_LIBRARY_PATH') >= 0:
- words = line.split()
- path = words[2]
- return path[:path.find('/lib')]
- # Unable to find module path
- return None
-
-
def set_compiler_environment_variables(pkg, env):
assert(pkg.spec.concrete)
compiler = pkg.compiler
diff --git a/lib/spack/spack/operating_systems/cnl.py b/lib/spack/spack/operating_systems/cnl.py
index b5c759bbcb..95f23a076e 100644
--- a/lib/spack/spack/operating_systems/cnl.py
+++ b/lib/spack/spack/operating_systems/cnl.py
@@ -25,10 +25,10 @@
import re
from spack.architecture import OperatingSystem
-from spack.util.executable import *
import spack.spec
from spack.util.multiproc import parmap
import spack.compilers
+from spack.util.module_cmd import get_module_cmd
class Cnl(OperatingSystem):
@@ -63,8 +63,7 @@ class Cnl(OperatingSystem):
if not cmp_cls.PrgEnv_compiler:
tty.die('Must supply PrgEnv_compiler with PrgEnv')
- modulecmd = which('modulecmd')
- modulecmd.add_default_arg('python')
+ modulecmd = get_module_cmd()
output = modulecmd(
'avail', cmp_cls.PrgEnv_compiler, output=str, error=str)
diff --git a/lib/spack/spack/package_prefs.py b/lib/spack/spack/package_prefs.py
index 8b1fe08154..f9c4ced97e 100644
--- a/lib/spack/spack/package_prefs.py
+++ b/lib/spack/spack/package_prefs.py
@@ -200,7 +200,7 @@ def spec_externals(spec):
"""Return a list of external specs (w/external directory path filled in),
one for each known external installation."""
# break circular import.
- from spack.build_environment import get_path_from_module # noqa: F401
+ from spack.util.module_cmd import get_path_from_module # NOQA: ignore=F401
allpkgs = get_packages_config()
name = spec.name
diff --git a/lib/spack/spack/platforms/cray.py b/lib/spack/spack/platforms/cray.py
index 1cd08e5565..76f7e59674 100644
--- a/lib/spack/spack/platforms/cray.py
+++ b/lib/spack/spack/platforms/cray.py
@@ -31,6 +31,7 @@ from spack.architecture import Platform, Target, NoPlatformError
from spack.operating_systems.linux_distro import LinuxDistro
from spack.operating_systems.cnl import Cnl
from llnl.util.filesystem import join_path
+from spack.util.module_cmd import get_module_cmd
def _get_modules_in_modulecmd_output(output):
@@ -142,8 +143,7 @@ class Cray(Platform):
def _avail_targets(self):
'''Return a list of available CrayPE CPU targets.'''
if getattr(self, '_craype_targets', None) is None:
- module = which('modulecmd', required=True)
- module.add_default_arg('python')
+ module = get_module_cmd()
output = module('avail', '-t', 'craype-', output=str, error=str)
craype_modules = _get_modules_in_modulecmd_output(output)
self._craype_targets = targets = []
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index eed93eccb7..602f2fd878 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -120,7 +120,7 @@ import spack.util.spack_yaml as syaml
from llnl.util.filesystem import find_headers, find_libraries, is_exe
from llnl.util.lang import *
from llnl.util.tty.color import *
-from spack.build_environment import get_path_from_module, load_module
+from spack.util.module_cmd import get_path_from_module, load_module
from spack.error import SpecError, UnsatisfiableSpecError
from spack.provider_index import ProviderIndex
from spack.util.crypto import prefix_bits
diff --git a/lib/spack/spack/test/module_parsing.py b/lib/spack/spack/test/module_parsing.py
new file mode 100644
index 0000000000..6ddb9d2dbf
--- /dev/null
+++ b/lib/spack/spack/test/module_parsing.py
@@ -0,0 +1,143 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 pytest
+import subprocess
+import os
+from spack.util.module_cmd import *
+
+typeset_func = subprocess.Popen('module avail',
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ shell=True)
+typeset_func.wait()
+typeset = typeset_func.stderr.read()
+MODULE_NOT_DEFINED = b'not found' in typeset
+
+
+@pytest.fixture
+def save_env():
+ old_PATH = os.environ.get('PATH', None)
+ old_bash_func = os.environ.get('BASH_FUNC_module()', None)
+
+ yield
+
+ if old_PATH:
+ os.environ['PATH'] = old_PATH
+ if old_bash_func:
+ os.environ['BASH_FUNC_module()'] = old_bash_func
+
+
+def test_get_path_from_module(save_env):
+ lines = ['prepend-path LD_LIBRARY_PATH /path/to/lib',
+ 'setenv MOD_DIR /path/to',
+ 'setenv LDFLAGS -Wl,-rpath/path/to/lib',
+ 'setenv LDFLAGS -L/path/to/lib']
+
+ for line in lines:
+ module_func = '() { eval `echo ' + line + ' bash filler`\n}'
+ os.environ['BASH_FUNC_module()'] = module_func
+ path = get_path_from_module('mod')
+
+ assert path == '/path/to'
+
+ os.environ['BASH_FUNC_module()'] = '() { eval $(echo fill bash $*)\n}'
+ path = get_path_from_module('mod')
+
+ assert path is None
+
+
+def test_get_argument_from_module_line():
+ lines = ['prepend-path LD_LIBRARY_PATH /lib/path',
+ 'prepend-path LD_LIBRARY_PATH /lib/path',
+ "prepend_path('PATH' , '/lib/path')",
+ 'prepend_path( "PATH" , "/lib/path" )',
+ 'prepend_path("PATH",' + "'/lib/path')"]
+
+ bad_lines = ['prepend_path(PATH,/lib/path)',
+ 'prepend-path (LD_LIBRARY_PATH) /lib/path']
+
+ assert all(get_argument_from_module_line(l) == '/lib/path' for l in lines)
+ for bl in bad_lines:
+ with pytest.raises(ValueError):
+ get_argument_from_module_line(bl)
+
+
+@pytest.mark.skipif(MODULE_NOT_DEFINED, reason='Depends on defined module fn')
+def test_get_module_cmd_from_bash_using_modules():
+ module_list_proc = subprocess.Popen(['module list'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ executable='/bin/bash',
+ shell=True)
+ module_list_proc.wait()
+ module_list = module_list_proc.stdout.read()
+
+ module_cmd = get_module_cmd_from_bash()
+ module_cmd_list = module_cmd('list', output=str, error=str)
+
+ # Lmod command reprints some env variables on every invocation.
+ # Test containment to avoid false failures on lmod systems.
+ assert module_list in module_cmd_list
+
+
+def test_get_module_cmd_from_bash_ticks(save_env):
+ os.environ['BASH_FUNC_module()'] = '() { eval `echo bash $*`\n}'
+
+ module_cmd = get_module_cmd()
+ module_cmd_list = module_cmd('list', output=str, error=str)
+
+ assert module_cmd_list == 'python list\n'
+
+
+def test_get_module_cmd_from_bash_parens(save_env):
+ os.environ['BASH_FUNC_module()'] = '() { eval $(echo fill bash $*)\n}'
+
+ module_cmd = get_module_cmd()
+ module_cmd_list = module_cmd('list', output=str, error=str)
+
+ assert module_cmd_list == 'fill python list\n'
+
+
+def test_get_module_cmd_fails(save_env):
+ os.environ.pop('BASH_FUNC_module()')
+ os.environ.pop('PATH')
+ with pytest.raises(ModuleError):
+ module_cmd = get_module_cmd(b'--norc')
+ module_cmd() # Here to avoid Flake F841 on previous line
+
+
+def test_get_module_cmd_from_which(tmpdir, save_env):
+ f = tmpdir.mkdir('bin').join('modulecmd')
+ f.write('#!/bin/bash\n'
+ 'echo $*')
+ f.chmod(0o770)
+
+ os.environ['PATH'] = str(tmpdir.join('bin')) + ':' + os.environ['PATH']
+ os.environ.pop('BASH_FUNC_module()')
+
+ module_cmd = get_module_cmd(b'--norc')
+ module_cmd_list = module_cmd('list', output=str, error=str)
+
+ assert module_cmd_list == 'python list\n'
diff --git a/lib/spack/spack/util/module_cmd.py b/lib/spack/spack/util/module_cmd.py
new file mode 100644
index 0000000000..bdd8463757
--- /dev/null
+++ b/lib/spack/spack/util/module_cmd.py
@@ -0,0 +1,195 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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
+##############################################################################
+"""
+This module contains routines related to the module command for accessing and
+parsing environment modules.
+"""
+import subprocess
+import re
+import os
+import llnl.util.tty as tty
+from spack.util.executable import which
+
+
+def get_module_cmd(bashopts=''):
+ try:
+ return get_module_cmd_from_bash(bashopts)
+ except ModuleError:
+ # Don't catch the exception this time; we have no other way to do it.
+ tty.warn("Could not detect module function from bash."
+ " Trying to detect modulecmd from `which`")
+ try:
+ return get_module_cmd_from_which()
+ except ModuleError:
+ raise ModuleError('Spack requires modulecmd or a defined module'
+ ' fucntion. Make sure modulecmd is in your path'
+ ' or the function "module" is defined in your'
+ ' bash environment.')
+
+
+def get_module_cmd_from_which():
+ module_cmd = which('modulecmd')
+ if not module_cmd:
+ raise ModuleError('`which` did not find any modulecmd executable')
+ module_cmd.add_default_arg('python')
+
+ # Check that the executable works
+ module_cmd('list', output=str, error=str, fail_on_error=False)
+ if module_cmd.returncode != 0:
+ raise ModuleError('get_module_cmd cannot determine the module command')
+
+ return module_cmd
+
+
+def get_module_cmd_from_bash(bashopts=''):
+ # Find how the module function is defined in the environment
+ module_func = os.environ.get('BASH_FUNC_module()', None)
+ if module_func:
+ module_func = os.path.expandvars(module_func)
+ else:
+ module_func_proc = subprocess.Popen(['{0} typeset -f module | '
+ 'envsubst'.format(bashopts)],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ executable='/bin/bash',
+ shell=True)
+ module_func_proc.wait()
+ module_func = module_func_proc.stdout.read()
+
+ # Find the portion of the module function that is evaluated
+ try:
+ find_exec = re.search(r'.*`(.*(:? bash | sh ).*)`.*', module_func)
+ exec_line = find_exec.group(1)
+ except:
+ try:
+ # This will fail with nested parentheses. TODO: expand regex.
+ find_exec = re.search(r'.*\(([^()]*(:? bash | sh )[^()]*)\).*',
+ module_func)
+ exec_line = find_exec.group(1)
+ except:
+ raise ModuleError('get_module_cmd cannot '
+ 'determine the module command from bash')
+
+ # Create an executable
+ args = exec_line.split()
+ module_cmd = which(args[0])
+ if module_cmd:
+ for arg in args[1:]:
+ if arg == 'bash':
+ module_cmd.add_default_arg('python')
+ break
+ else:
+ module_cmd.add_default_arg(arg)
+ else:
+ raise ModuleError('Could not create executable based on module'
+ ' function.')
+
+ # Check that the executable works
+ module_cmd('list', output=str, error=str, fail_on_error=False)
+ if module_cmd.returncode != 0:
+ raise ModuleError('get_module_cmd cannot determine the module command'
+ 'from bash.')
+
+ return module_cmd
+
+
+def load_module(mod):
+ """Takes a module name and removes modules until it is possible to
+ load that module. It then loads the provided module. Depends on the
+ modulecmd implementation of modules used in cray and lmod.
+ """
+ # Create an executable of the module command that will output python code
+ modulecmd = get_module_cmd()
+
+ # Read the module and remove any conflicting modules
+ # We do this without checking that they are already installed
+ # for ease of programming because unloading a module that is not
+ # loaded does nothing.
+ text = modulecmd('show', mod, output=str, error=str).split()
+ for i, word in enumerate(text):
+ if word == 'conflict':
+ exec(compile(modulecmd('unload', text[i + 1], output=str,
+ error=str), '<string>', 'exec'))
+ # Load the module now that there are no conflicts
+ load = modulecmd('load', mod, output=str, error=str)
+ exec(compile(load, '<string>', 'exec'))
+
+
+def get_argument_from_module_line(line):
+ if '(' in line and ')' in line:
+ # Determine which lua quote symbol is being used for the argument
+ comma_index = line.index(',')
+ cline = line[comma_index:]
+ try:
+ quote_index = min(cline.find(q) for q in ['"', "'"] if q in cline)
+ lua_quote = cline[quote_index]
+ except ValueError:
+ # Change error text to describe what is going on.
+ raise ValueError("No lua quote symbol found in lmod module line.")
+ words_and_symbols = line.split(lua_quote)
+ return words_and_symbols[-2]
+ else:
+ return line.split()[2]
+
+
+def get_path_from_module(mod):
+ """Inspects a TCL module for entries that indicate the absolute path
+ at which the library supported by said module can be found.
+ """
+ # Create a modulecmd executable
+ modulecmd = get_module_cmd()
+
+ # Read the module
+ text = modulecmd('show', mod, output=str, error=str).split('\n')
+
+ # If it sets the LD_LIBRARY_PATH or CRAY_LD_LIBRARY_PATH, use that
+ for line in text:
+ if line.find('LD_LIBRARY_PATH') >= 0:
+ path = get_argument_from_module_line(line)
+ return path[:path.find('/lib')]
+
+ # If it lists its package directory, return that
+ for line in text:
+ if line.find(mod.upper() + '_DIR') >= 0:
+ return get_argument_from_module_line(line)
+
+ # If it lists a -rpath instruction, use that
+ for line in text:
+ rpath = line.find('-rpath/')
+ if rpath >= 0:
+ return line[rpath + 6:line.find('/lib')]
+
+ # If it lists a -L instruction, use that
+ for line in text:
+ L = line.find('-L/')
+ if L >= 0:
+ return line[L + 2:line.find('/lib')]
+
+ # Unable to find module path
+ return None
+
+
+class ModuleError(Exception):
+ """Raised the the module_cmd utility to indicate errors."""