Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor generation of required environment variables in module files + deprecate make_module_req_guess method in EasyBlock class #4653

Merged
merged 66 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
734cd99
refactor generation of required environment variables in module files
lexming Sep 23, 2024
df505cf
update test_make_module_req to add a file into MANPATH of test instal…
lexming Sep 23, 2024
5680038
disable non-empty check on search path drectories for fake module files
lexming Sep 23, 2024
5b17f2e
change position of easyblock methods make_module_req_guess and _expan…
lexming Sep 24, 2024
9205c3b
add new class ModuleEnvironmentVariable to hold definitions of enviro…
lexming Sep 30, 2024
29affc9
add new class ModuleLoadEnvironment to hold environment definition fo…
lexming Sep 30, 2024
8d3a051
add LibSymlink enum to easyblock to define possible states of symlink…
lexming Sep 30, 2024
ee37ae7
set library symlink state at the end of post_install_step
lexming Sep 30, 2024
b6e1821
use environment definition from ModuleLoadEnvironment in make_module_…
lexming Sep 30, 2024
544a316
deprecate make_module_req_guess in favor of directly using ModuleLoad…
lexming Sep 30, 2024
dc09eee
consider both possible symplink states between lib and lib64 in expan…
lexming Sep 30, 2024
562c98d
fix code style around ModuleEnvironmentVariable
lexming Sep 30, 2024
17cc73f
add check_install_lib_symlink method to EasyBlock to be able to trigg…
lexming Sep 30, 2024
16e4743
remove unused empty attribute from ModuleEnvironmentVariable
lexming Sep 30, 2024
f68f383
attributes in ModuleLoadEnvironment can only be instances of ModuleEn…
lexming Oct 1, 2024
0b81a21
Merge branch '5.0.x' into cpath-mod
lexming Oct 2, 2024
36d4f92
add test to verify that environment variables don't leak into module …
boegel Oct 7, 2024
fa25ae2
Merge pull request #5 from boegel/cpath-mod
lexming Oct 8, 2024
50b9195
Merge branch '5.0.x' into cpath-mod
lexming Nov 6, 2024
d560ce2
convert ModuleLoadEnvironment to regular class instead of singleton
lexming Nov 6, 2024
9a230cf
enable raising of errors when running toy build in test_toy_multiple_…
boegel Nov 6, 2024
f43ae67
fix test_toy_multiple_ecs_module when using Tcl as module syntax
boegel Nov 7, 2024
0b17167
Merge branch '5.0.x' into cpath-mod
lexming Nov 10, 2024
fad0926
fix deprecation warning about make_module_req_guess
lexming Nov 13, 2024
5f04fc3
make ModuleEnvironmentVariable iterable
lexming Nov 13, 2024
a45783e
ModuleEnvironmentVariable repr corresponds to its list of paths
lexming Nov 19, 2024
b330d84
add update method to ModuleEnvironmentVariable to replace its list of…
lexming Nov 19, 2024
c67afd4
add logging facility to ModuleEnvironmentVariable
lexming Nov 21, 2024
6048010
add remove method to ModuleEnvironmentVariable
lexming Nov 21, 2024
de3fa38
rename ModuleEnvironmentVariable.paths to ModuleEnvironmentVariable.c…
lexming Nov 21, 2024
0274faf
ensure ModuleEnvironmentVariable is a list of unique strings
lexming Nov 21, 2024
5b4372e
make EasyBlock._expand_module_search_path a public method
lexming Nov 21, 2024
f20adb8
fix codestyle issues
lexming Nov 22, 2024
03c6c46
replace make_module_req_guess with module_load_environment in Toy eas…
lexming Nov 22, 2024
d43acae
ExtensionEasyBlock pulls module load environment from its master
lexming Nov 22, 2024
c692e66
automatically enforce top level files for PATH and LD_LIBRARY_PATH in…
lexming Nov 22, 2024
561d89e
replace make_module_req_guess with module_load_environment in test_ma…
lexming Nov 22, 2024
a514a42
allow to customize delimiter in ModuleEnvironmentVariable
lexming Nov 22, 2024
7f96240
rework LibSymlink to make it less confusing
boegel Dec 7, 2024
d3fdd71
fix order of import statement from tools.config in easyblock.py
boegel Dec 11, 2024
084467f
make sure that install_lib_symlink always gets set when calling check…
boegel Dec 11, 2024
ef446e3
rename 'top_level_file' attribute of ModuleEnvironmentVariable to 're…
boegel Dec 11, 2024
f50639d
deprecate EasyBlock.make_module_req_guess method
boegel Dec 11, 2024
b38ff58
use 'continue' rather than 'break' in EasyBlock.expand_module_search_…
boegel Dec 11, 2024
7972f2e
minor style changes
boegel Dec 11, 2024
a34c33f
Merge pull request #6 from boegel/cpath-mod
lexming Dec 12, 2024
9eaed27
add test_expand_module_search_path to easyblock unit tests
lexming Dec 12, 2024
71d36c8
Merge branch '5.0.x' into cpath-mod
lexming Dec 12, 2024
55ae588
fix typo in EasyBlock.check_install_lib_symlink
lexming Dec 12, 2024
f761d34
fix symlink checking in test_expand_module_search_path
lexming Dec 12, 2024
9297094
ModuleLoadEnvironment.environ returns dict with string formatted values
lexming Dec 12, 2024
87afba7
add update and as_dict methods to ModuleLoadEnvironment class
lexming Dec 12, 2024
38774ac
make_module_req updates module_load_environment if a deprecated make_…
lexming Dec 12, 2024
5fb0467
fix codestyle in test.framework.modules
lexming Dec 12, 2024
516d6e6
improve control on ModuleEnvironmentVariable behaviour by adding type…
lexming Dec 12, 2024
de961fe
fix codestyle in easybuild.tools.modules
lexming Dec 12, 2024
d4a9df0
EasyBlock.expand_module_search_path accepts ModEnvVarType as path_typ…
lexming Dec 13, 2024
fcd21b8
require that ModuleLoadEnvironment attributes being set have uppercas…
boegel Jan 8, 2025
2ee964d
fix failures in test_expand_module_search_path by not assuming partic…
boegel Jan 8, 2025
f69f23f
make sure that check_install_lib_symlink updates self.install_lib_sym…
boegel Jan 8, 2025
d27e1cb
always use ModEnvVarType.PATH_WITH_FILES and ModEnvVarType.PATH_WITH_…
boegel Jan 8, 2025
f820358
revert: disable non-empty check on search path drectories for fake mo…
lexming Jan 13, 2025
a9e5267
log at debug level the environment targeted by make_module_req before…
lexming Jan 13, 2025
13f735c
fix passing of arguments in ModuleEnvironmentVariable.remove
lexming Jan 13, 2025
87615f0
Merge pull request #7 from boegel/cpath-mod
lexming Jan 13, 2025
678ce99
Revert "fix passing of arguments in ModuleEnvironmentVariable.remove"
lexming Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 132 additions & 97 deletions easybuild/framework/easyblock.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions easybuild/framework/extensioneasyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def __init__(self, *args, **kwargs):
self.installdir = self.master.installdir
self.modules_tool = self.master.modules_tool
self.module_generator = self.master.module_generator
self.module_load_environment = self.master.module_load_environment
self.robot_path = self.master.robot_path
self.is_extension = True
self.unpack_options = None
Expand Down
5 changes: 4 additions & 1 deletion easybuild/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,13 +168,16 @@
LOCAL_VAR_NAMING_CHECK_WARN = WARN
LOCAL_VAR_NAMING_CHECKS = [LOCAL_VAR_NAMING_CHECK_ERROR, LOCAL_VAR_NAMING_CHECK_LOG, LOCAL_VAR_NAMING_CHECK_WARN]


OUTPUT_STYLE_AUTO = 'auto'
OUTPUT_STYLE_BASIC = 'basic'
OUTPUT_STYLE_NO_COLOR = 'no_color'
OUTPUT_STYLE_RICH = 'rich'
OUTPUT_STYLES = (OUTPUT_STYLE_AUTO, OUTPUT_STYLE_BASIC, OUTPUT_STYLE_NO_COLOR, OUTPUT_STYLE_RICH)

SEARCH_PATH_BIN_DIRS = ['bin']
SEARCH_PATH_HEADER_DIRS = ['include']
SEARCH_PATH_LIB_DIRS = ['lib', 'lib64']

PYTHONPATH = 'PYTHONPATH'
EBPYTHONPREFIXES = 'EBPYTHONPREFIXES'
PYTHON_SEARCH_PATH_TYPES = [PYTHONPATH, EBPYTHONPREFIXES]
Expand Down
199 changes: 197 additions & 2 deletions easybuild/tools/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@
import os
import re
import shlex
from enum import Enum

from easybuild.base import fancylogger
from easybuild.tools import LooseVersion
from easybuild.tools.build_log import EasyBuildError, EasyBuildExit, print_warning
from easybuild.tools.config import ERROR, IGNORE, PURGE, UNLOAD, UNSET
from easybuild.tools.config import EBROOT_ENV_VAR_ACTIONS, LOADED_MODULES_ACTIONS
from easybuild.tools.config import ERROR, EBROOT_ENV_VAR_ACTIONS, IGNORE, LOADED_MODULES_ACTIONS, PURGE
from easybuild.tools.config import SEARCH_PATH_BIN_DIRS, SEARCH_PATH_HEADER_DIRS, SEARCH_PATH_LIB_DIRS, UNLOAD, UNSET
from easybuild.tools.config import build_option, get_modules_tool, install_path
from easybuild.tools.environment import ORIG_OS_ENVIRON, restore_env, setvar, unset_env_vars
from easybuild.tools.filetools import convert_name, mkdir, normalize_path, path_matches, read_file, which, write_file
Expand All @@ -55,6 +56,7 @@
from easybuild.tools.systemtools import get_shared_lib_ext
from easybuild.tools.utilities import get_subclasses, nub


# software root/version environment variable name prefixes
ROOT_ENV_VAR_NAME_PREFIX = "EBROOT"
VERSION_ENV_VAR_NAME_PREFIX = "EBVERSION"
Expand Down Expand Up @@ -131,6 +133,199 @@
_log = fancylogger.getLogger('modules', fname=False)


class ModEnvVarType(Enum):
"""
Possible types of ModuleEnvironmentVariable:
- STRING: (list of) strings with no further meaning
- PATH: (list of) of paths to existing directories or files
- PATH_WITH_FILES: (list of) of paths to existing directories containing
one or more files
- PATH_WITH_TOP_FILES: (list of) of paths to existing directories
containing one or more files in its top directory
- """
STRING, PATH, PATH_WITH_FILES, PATH_WITH_TOP_FILES = range(0, 4)


class ModuleEnvironmentVariable:
"""
Environment variable data structure for modules
Contents of environment variable is a list of unique strings
"""

def __init__(self, contents, var_type=None, delim=os.pathsep):
"""
Initialize new environment variable
Actual contents of the environment variable are held in self.contents
By default, environment variable is a (list of) paths with files in them
Existence of paths and their contents are not checked at init
"""
self.contents = contents
self.delim = delim

if var_type is None:
var_type = ModEnvVarType.PATH_WITH_FILES
self.type = var_type

self.log = fancylogger.getLogger(self.__class__.__name__, fname=False)

def __repr__(self):
return repr(self.contents)

def __str__(self):
return self.delim.join(self.contents)

def __iter__(self):
return iter(self.contents)

@property
def contents(self):
return self._contents

@contents.setter
def contents(self, value):
"""Enforce that contents is a list of strings"""
if isinstance(value, str):
value = [value]

try:
str_list = [str(path) for path in value]
except TypeError as err:
raise TypeError("ModuleEnvironmentVariable.contents must be a list of strings") from err

self._contents = nub(str_list) # remove duplicates and keep order

@property
def type(self):
return self._type

@type.setter
def type(self, value):
"""Convert type to VarType"""
if isinstance(value, ModEnvVarType):
self._type = value
else:
try:
self._type = ModEnvVarType[value]
except KeyError as err:
raise EasyBuildError(f"Cannot create ModuleEnvironmentVariable with type {value}") from err

def append(self, item):
"""Shortcut to append to list of contents"""
self.contents += [item]

def extend(self, item):
"""Shortcut to extend list of contents"""
self.contents += item

def prepend(self, item):
"""Shortcut to prepend item to list of contents"""
self.contents = [item] + self.contents

def update(self, item):
"""Shortcut to replace list of contents with item"""
self.contents = item

def remove(self, *args):
"""Shortcut to remove items from list of contents"""
try:
self.contents.remove(*args)
except ValueError:
# item is not in the list, move along
self.log.debug(f"ModuleEnvironmentVariable does not contain item: {' '.join(args)}")

@property
def is_path(self):
path_like_types = [
ModEnvVarType.PATH,
ModEnvVarType.PATH_WITH_FILES,
ModEnvVarType.PATH_WITH_TOP_FILES,
]
return self.type in path_like_types


class ModuleLoadEnvironment:
"""Changes to environment variables that should be made when environment module is loaded"""

def __init__(self):
"""
Initialize default environment definition
Paths are relative to root of installation directory
"""
self.ACLOCAL_PATH = [os.path.join('share', 'aclocal')]
self.CLASSPATH = ['*.jar']
self.CMAKE_LIBRARY_PATH = ['lib64'] # only needed for installations with standalone lib64
self.CMAKE_PREFIX_PATH = ['']
self.CPATH = SEARCH_PATH_HEADER_DIRS
self.GI_TYPELIB_PATH = [os.path.join(x, 'girepository-*') for x in SEARCH_PATH_LIB_DIRS]
self.LD_LIBRARY_PATH = SEARCH_PATH_LIB_DIRS
self.LIBRARY_PATH = SEARCH_PATH_LIB_DIRS
self.MANPATH = ['man', os.path.join('share', 'man')]
self.PATH = SEARCH_PATH_BIN_DIRS + ['sbin']
self.PKG_CONFIG_PATH = [os.path.join(x, 'pkgconfig') for x in SEARCH_PATH_LIB_DIRS + ['share']]
self.XDG_DATA_DIRS = ['share']

def __setattr__(self, name, value):
"""
Specific restrictions for ModuleLoadEnvironment attributes:
- attribute names are uppercase
- attributes are instances of ModuleEnvironmentVariable
"""
if name != name.upper():
raise EasyBuildError(f"Names of ModuleLoadEnvironment attributes must be uppercase, got '{name}'")
try:
(contents, kwargs) = value
except ValueError:
contents, kwargs = value, {}

if not isinstance(kwargs, dict):
contents, kwargs = value, {}

# special variables that require files in their top directories
if name in ('LD_LIBRARY_PATH', 'PATH'):
kwargs.update({'var_type': ModEnvVarType.PATH_WITH_TOP_FILES})

return super().__setattr__(name, ModuleEnvironmentVariable(contents, **kwargs))

def __iter__(self):
"""Make the class iterable"""
yield from self.__dict__

def items(self):
"""
Return key-value pairs for each attribute that is a ModuleEnvironmentVariable
- key = attribute name
- value = its "contents" attribute
"""
for attr in self.__dict__:
yield attr, getattr(self, attr)

def update(self, new_env):
"""Update contents of environment from given dictionary"""
try:
for envar_name, envar_contents in new_env.items():
setattr(self, envar_name, envar_contents)
except AttributeError as err:
raise EasyBuildError("Cannot update ModuleLoadEnvironment from a non-dict variable") from err

@property
def as_dict(self):
"""
Return dict with mapping of ModuleEnvironmentVariables names with their contents
"""
return dict(self.items())

@property
def environ(self):
"""
Return dict with mapping of ModuleEnvironmentVariables names with their contents
Equivalent in shape to os.environ
"""
mapping = {}
for envar_name, envar_contents in self.items():
mapping.update({envar_name: str(envar_contents)})
return mapping


class ModulesTool(object):
"""An abstract interface to a tool that deals with modules."""
# name of this modules tool (used in log/warning/error messages)
Expand Down
Loading
Loading