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:
parent
1e2b3b0768
commit
22fbb3dba7
@ -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')
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user