Bug fix: module file path parsing (#9100)

Improve Spack's parsing of module show to eliminate some false
positives (e.g. accepting MODULEPATH when it is in fact looking for
PATH). This makes the following changes:

* Updates the pattern searching for several paths to avoid the case
  where they are prefixes of unwanted paths
* Adds a warning message when an extracted path doesn't exist (which
  may help catch future module parsing bugs faster)
* Adds a test with the content mentioned in #9083
This commit is contained in:
scheibelp 2018-09-12 18:15:31 -07:00 committed by GitHub
parent 1e2b3b0768
commit 22fbb3dba7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 51 additions and 15 deletions

View File

@ -25,10 +25,13 @@
import pytest import pytest
import subprocess import subprocess
import os import os
from spack.util.module_cmd import get_path_from_module from spack.util.module_cmd import (
from spack.util.module_cmd import get_argument_from_module_line get_path_from_module,
from spack.util.module_cmd import get_module_cmd_from_bash get_path_from_module_contents,
from spack.util.module_cmd import get_module_cmd, ModuleError get_path_arg_from_module_line,
get_module_cmd_from_bash,
get_module_cmd,
ModuleError)
typeset_func = subprocess.Popen('module avail', typeset_func = subprocess.Popen('module avail',
@ -73,6 +76,26 @@ def test_get_path_from_module(save_env):
assert path is None assert path is None
def test_get_path_from_module_contents():
module_show_output = """
os.environ["MODULEPATH"] = "/path/to/modules1:/path/to/modules2";
----------------------------------------------------------------------------
/root/cmake/3.9.2.lua:
----------------------------------------------------------------------------
help([[CMake Version 3.9.2
]])
whatis("Name: CMake")
whatis("Version: 3.9.2")
whatis("Category: Tools")
whatis("URL: https://cmake.org/")
prepend_path("PATH","/path/to/cmake-3.9.2/bin")
prepend_path("MANPATH","/path/to/cmake/cmake-3.9.2/share/man")
"""
module_show_lines = module_show_output.split('\n')
assert (get_path_from_module_contents(module_show_lines, 'cmake-3.9.2') ==
'/path/to/cmake-3.9.2')
def test_get_argument_from_module_line(): def test_get_argument_from_module_line():
lines = ['prepend-path LD_LIBRARY_PATH /lib/path', lines = ['prepend-path LD_LIBRARY_PATH /lib/path',
'prepend-path LD_LIBRARY_PATH /lib/path', 'prepend-path LD_LIBRARY_PATH /lib/path',
@ -83,10 +106,10 @@ def test_get_argument_from_module_line():
bad_lines = ['prepend_path(PATH,/lib/path)', bad_lines = ['prepend_path(PATH,/lib/path)',
'prepend-path (LD_LIBRARY_PATH) /lib/path'] 'prepend-path (LD_LIBRARY_PATH) /lib/path']
assert all(get_argument_from_module_line(l) == '/lib/path' for l in lines) assert all(get_path_arg_from_module_line(l) == '/lib/path' for l in lines)
for bl in bad_lines: for bl in bad_lines:
with pytest.raises(ValueError): with pytest.raises(ValueError):
get_argument_from_module_line(bl) get_path_arg_from_module_line(bl)
@pytest.mark.skipif(MODULE_NOT_DEFINED, reason='Depends on defined module fn') @pytest.mark.skipif(MODULE_NOT_DEFINED, reason='Depends on defined module fn')

View File

@ -148,7 +148,7 @@ def load_module(mod):
exec(compile(load, '<string>', 'exec')) exec(compile(load, '<string>', 'exec'))
def get_argument_from_module_line(line): def get_path_arg_from_module_line(line):
if '(' in line and ')' in line: if '(' in line and ')' in line:
# Determine which lua quote symbol is being used for the argument # Determine which lua quote symbol is being used for the argument
comma_index = line.index(',') comma_index = line.index(',')
@ -160,9 +160,15 @@ def get_argument_from_module_line(line):
# Change error text to describe what is going on. # Change error text to describe what is going on.
raise ValueError("No lua quote symbol found in lmod module line.") raise ValueError("No lua quote symbol found in lmod module line.")
words_and_symbols = line.split(lua_quote) words_and_symbols = line.split(lua_quote)
return words_and_symbols[-2] path_arg = words_and_symbols[-2]
else: else:
return line.split()[2] path_arg = line.split()[2]
if not os.path.exists(path_arg):
tty.warn("Extracted path from module does not exist:"
"\n\tExtracted path: " + path_arg +
"\n\tFull line: " + line)
return path_arg
def get_path_from_module(mod): def get_path_from_module(mod):
@ -175,16 +181,22 @@ def get_path_from_module(mod):
# Read the module # Read the module
text = modulecmd('show', mod, output=str, error=str).split('\n') text = modulecmd('show', mod, output=str, error=str).split('\n')
return get_path_from_module_contents(text, mod)
def get_path_from_module_contents(text, module_name):
# If it sets the LD_LIBRARY_PATH or CRAY_LD_LIBRARY_PATH, use that # If it sets the LD_LIBRARY_PATH or CRAY_LD_LIBRARY_PATH, use that
for line in text: for line in text:
if line.find('LD_LIBRARY_PATH') >= 0: pattern = r'\WLD_LIBRARY_PATH'
path = get_argument_from_module_line(line) if re.search(pattern, line):
path = get_path_arg_from_module_line(line)
return path[:path.find('/lib')] return path[:path.find('/lib')]
# If it lists its package directory, return that # If it lists its package directory, return that
for line in text: for line in text:
if line.find(mod.upper() + '_DIR') >= 0: pattern = r'\W{0}_DIR'.format(module_name.upper())
return get_argument_from_module_line(line) if re.search(pattern, line):
return get_path_arg_from_module_line(line)
# If it lists a -rpath instruction, use that # If it lists a -rpath instruction, use that
for line in text: for line in text:
@ -200,8 +212,9 @@ def get_path_from_module(mod):
# If it sets the PATH, use it # If it sets the PATH, use it
for line in text: for line in text:
if line.find('PATH') >= 0: pattern = r'\WPATH'
path = get_argument_from_module_line(line) if re.search(pattern, line):
path = get_path_arg_from_module_line(line)
return path[:path.find('/bin')] return path[:path.find('/bin')]
# Unable to find module path # Unable to find module path