Warning
This document is for an old release of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.
Source code for galaxy.tool_util.deps.resolvers.lmod
"""
This is a prototype dependency resolver to be able to use the "LMOD environment modules system" from TACC to solve package requirements
LMOD official website: https://www.tacc.utexas.edu/research-development/tacc-projects/lmod
LMOD @ Github: https://github.com/TACC/Lmod
"""
import logging
from io import StringIO
from os import getenv
from os.path import exists
from subprocess import (
PIPE,
Popen
)
from . import (
Dependency,
DependencyResolver,
MappableDependencyResolver,
NullDependency,
)
log = logging.getLogger(__name__)
DEFAULT_LMOD_PATH = getenv('LMOD_CMD')
DEFAULT_SETTARG_PATH = getenv('LMOD_SETTARG_CMD')
DEFAULT_MODULEPATH = getenv('MODULEPATH')
DEFAULT_MAPPING_FILE = 'config/lmod_modules_mapping.yml'
INVALID_LMOD_PATH_MSG = "The following LMOD executable could not be found: %s. Either your LMOD Dependency Resolver is misconfigured or LMOD is improperly installed on your system !"
EMPTY_MODULEPATH_MSG = "No valid LMOD MODULEPATH defined ! Either your LMOD Dependency Resolver is misconfigured or LMOD is improperly installed on your system !"
[docs]class LmodDependencyResolver(DependencyResolver, MappableDependencyResolver):
"""Dependency resolver based on the LMOD environment modules system"""
dict_collection_visible_keys = DependencyResolver.dict_collection_visible_keys + ['base_path', 'modulepath']
resolver_type = "lmod"
[docs] def __init__(self, dependency_manager, **kwds):
# Mapping file management
self._set_default_mapping_file(kwds)
self._setup_mapping(dependency_manager, **kwds)
# Other attributes
self.versionless = _string_as_bool(kwds.get('versionless', 'false'))
self.lmodexec = kwds.get('lmodexec', DEFAULT_LMOD_PATH)
self.settargexec = kwds.get('settargexec', DEFAULT_SETTARG_PATH)
self.modulepath = kwds.get('modulepath', DEFAULT_MODULEPATH)
self.module_checker = AvailModuleChecker(self, self.modulepath)
def _set_default_mapping_file(self, resolver_attributes):
if 'mapping_files' not in resolver_attributes:
if exists(DEFAULT_MAPPING_FILE):
resolver_attributes['mapping_files'] = DEFAULT_MAPPING_FILE
[docs] def resolve(self, requirement, **kwds):
requirement = self._expand_mappings(requirement)
name, version, type = requirement.name, requirement.version, requirement.type
if type != "package":
return NullDependency(version=version, name=name)
if self.__has_module(name, version):
return LmodDependency(self, name, version, exact=True, dependency_resolver=self)
elif self.versionless and self.__has_module(name, None):
return LmodDependency(self, name, None, exact=False, dependency_resolver=self)
return NullDependency(version=version, name=name)
def __has_module(self, name, version):
return self.module_checker.has_module(name, version)
class AvailModuleChecker:
"""Parses the output of Lmod 'module avail' command to get the list of available modules."""
def __init__(self, lmod_dependency_resolver, modulepath):
self.lmod_dependency_resolver = lmod_dependency_resolver
self.modulepath = modulepath
def has_module(self, module, version):
# When version is None (No specific version required by the wrapper -or- versionless is set to 'true'), we only get the list of default modules
# We get the full list of modules otherwise
if version is None:
available_modules = self.__get_list_of_available_modules(True)
else:
available_modules = self.__get_list_of_available_modules(False)
# Is the required module in the list of avaialable modules ?
for module_name, module_version in available_modules:
names_match = module == module_name
module_match = names_match and (version is None or module_version == version)
if module_match:
return True
return False
def __get_list_of_available_modules(self, default_version_only=False):
# Get the results of the "module avail" command in an easy to parse format
# Note that since "module" is actually a bash function, we are directy executing the underlying executable instead
raw_output = self.__get_module_avail_command_output(default_version_only).decode("utf-8")
# Parse the result
for line in StringIO(raw_output):
# Clean line and discard non-module lines
line = line and line.strip()
if not line or line.startswith("/"):
continue
# Split module lines by / to separate the module name from the module version
# Module without version are discarded
module_parts = line.split('/')
if len(module_parts) == 2:
yield module_parts[0], module_parts[1]
def __get_module_avail_command_output(self, default_version_only=False):
# Check if the LMOD executable is available (ie. if both LMOD and the lmod dependency resolver are both setup properly)
lmodexec = self.lmod_dependency_resolver.lmodexec
if not exists(lmodexec):
raise Exception(INVALID_LMOD_PATH_MSG % lmodexec)
# Check if the MODULEPATH environment
if self.modulepath == "" or self.modulepath is None:
raise Exception(EMPTY_MODULEPATH_MSG)
# Build command line
if default_version_only:
module_avail_command = [lmodexec, '-t', '-d', 'avail']
else:
module_avail_command = [lmodexec, '-t', 'avail']
# The list of avaialable modules is actually printed on stderr and not stdout for module commands
return Popen(module_avail_command, stdout=PIPE, stderr=PIPE, env={'MODULEPATH': self.modulepath}, close_fds=True).communicate()[1]
class LmodDependency(Dependency):
"""Prepare the commands required to solve the dependency and add them to the script used to run a tool in Galaxy."""
dict_collection_visible_keys = Dependency.dict_collection_visible_keys + ['module_name', 'module_version', 'dependency_resolver']
dependency_type = 'lmod'
def __init__(self, lmod_dependency_resolver, module_name, module_version=None, exact=True, dependency_resolver=None):
self.lmod_dependency_resolver = lmod_dependency_resolver
self.module_name = module_name
self.module_version = module_version
self._exact = exact
self.dependency_resolver = dependency_resolver
@property
def name(self):
return self.module_name
@property
def version(self):
return self.module_version
@property
def exact(self):
return self._exact
def shell_commands(self):
# Get the full module name in the form "tool_name/tool_version"
module_to_load = self.module_name
if self.module_version:
module_to_load = f'{self.module_name}/{self.module_version}'
# Build the list of command to add to run script
# Note that since "module" is actually a bash function, we are directy executing the underlying executable instead
# - Set the MODULEPATH environment variable
command = f'MODULEPATH={self.lmod_dependency_resolver.modulepath}; '
command += 'export MODULEPATH; '
# - Execute the "module load" command (or rather the "/path/to/lmod load" command)
command += f'eval `{self.lmod_dependency_resolver.lmodexec} load {module_to_load}` '
# - Execute the "settarg" command in addition if needed
if self.lmod_dependency_resolver.settargexec is not None:
command += f'&& eval `{self.lmod_dependency_resolver.settargexec} -s sh`'
return command
def _string_as_bool(value):
return str(value).lower() == "true"
__all__ = ('LmodDependencyResolver', )