Skip to content

Commit 0e469b0

Browse files
authored
Merge pull request #4859 from lexming/modenvvar-expand-paths
move `EasyBlock.expand_module_search_path` into `ModuleEnvironmentVariable.expand_paths`
2 parents 76d4e97 + 584b6f7 commit 0e469b0

File tree

4 files changed

+302
-198
lines changed

4 files changed

+302
-198
lines changed

easybuild/framework/easyblock.py

Lines changed: 9 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,10 @@
9393
from easybuild.tools.filetools import adjust_permissions, apply_patch, back_up_file, change_dir, check_lock
9494
from easybuild.tools.filetools import compute_checksum, convert_name, copy_dir, copy_file, create_lock
9595
from easybuild.tools.filetools import create_non_existing_paths, create_patch_info, derive_alt_pypi_url, diff_files
96-
from easybuild.tools.filetools import dir_contains_files, download_file, encode_class_name, extract_file
97-
from easybuild.tools.filetools import find_backup_name_candidate, get_cwd, get_source_tarball_from_git, is_alt_pypi_url
98-
from easybuild.tools.filetools import is_binary, is_parent_path, is_sha256_checksum, mkdir, move_file, move_logs
99-
from easybuild.tools.filetools import read_file, remove_dir, remove_file, remove_lock, symlink, verify_checksum
100-
from easybuild.tools.filetools import weld_paths, write_file
96+
from easybuild.tools.filetools import download_file, encode_class_name, extract_file, find_backup_name_candidate
97+
from easybuild.tools.filetools import get_cwd, get_source_tarball_from_git, is_alt_pypi_url, is_binary, is_parent_path
98+
from easybuild.tools.filetools import is_sha256_checksum, mkdir, move_file, move_logs, read_file, remove_dir
99+
from easybuild.tools.filetools import remove_file, remove_lock, symlink, verify_checksum, weld_paths, write_file
101100
from easybuild.tools.hooks import (
102101
BUILD_STEP, CLEANUP_STEP, CONFIGURE_STEP, EXTENSIONS_STEP, EXTRACT_STEP, FETCH_STEP, INSTALL_STEP, MODULE_STEP,
103102
MODULE_WRITE, PACKAGE_STEP, PATCH_STEP, PERMISSIONS_STEP, POSTITER_STEP, POSTPROC_STEP, PREPARE_STEP, READY_STEP,
@@ -1709,10 +1708,7 @@ def make_module_req(self):
17091708
mod_req_paths = search_paths
17101709
self.dry_run_msg(f" ${env_var}:{', '.join(mod_req_paths)}")
17111710
else:
1712-
mod_req_paths = [
1713-
expanded_path for unexpanded_path in search_paths
1714-
for expanded_path in self.expand_module_search_path(unexpanded_path, path_type=search_paths.type)
1715-
]
1711+
mod_req_paths = search_paths.expand_paths(self.installdir)
17161712

17171713
if mod_req_paths:
17181714
mod_req_paths = nub(mod_req_paths) # remove duplicates
@@ -1786,64 +1782,10 @@ def inject_module_extra_paths(self):
17861782

17871783
def expand_module_search_path(self, search_path, path_type=ModEnvVarType.PATH_WITH_FILES):
17881784
"""
1789-
Expand given path glob and return list of suitable paths to be used as search paths:
1790-
- Paths must point to existing files/directories
1791-
- Relative paths are relative to installation prefix root and are kept relative after expansion
1792-
- Absolute paths are kept as absolute paths after expansion
1793-
- Follow symlinks and resolve their paths (avoids duplicate paths through symlinks)
1794-
- :path_type: ModEnvVarType that controls requirements for population of directories
1795-
- PATH: no requirements, can be empty
1796-
- PATH_WITH_FILES: must contain at least one file in them (default)
1797-
- PATH_WITH_TOP_FILES: increase stricness to require files in top level directory
1798-
"""
1799-
populated_path_types = (
1800-
ModEnvVarType.PATH_WITH_FILES,
1801-
ModEnvVarType.PATH_WITH_TOP_FILES,
1802-
ModEnvVarType.STRICT_PATH_WITH_FILES,
1803-
)
1804-
1805-
if os.path.isabs(search_path):
1806-
abs_glob = search_path
1807-
else:
1808-
real_installdir = os.path.realpath(self.installdir)
1809-
abs_glob = os.path.join(real_installdir, search_path)
1810-
1811-
exp_search_paths = glob.glob(abs_glob, recursive=True)
1812-
1813-
retained_search_paths = []
1814-
for abs_path in exp_search_paths:
1815-
# avoid going through symlink for strict path types
1816-
if path_type is ModEnvVarType.STRICT_PATH_WITH_FILES and abs_path != os.path.realpath(abs_path):
1817-
self.log.debug(
1818-
f"Discarded strict search path '{search_path} of type '{path_type}' that does not correspond "
1819-
f"to its real path: {abs_path}"
1820-
)
1821-
continue
1822-
1823-
if os.path.isdir(abs_path) and path_type in populated_path_types:
1824-
# only retain paths to directories that contain at least one file
1825-
recursive = path_type in (ModEnvVarType.PATH_WITH_FILES, ModEnvVarType.STRICT_PATH_WITH_FILES)
1826-
if not dir_contains_files(abs_path, recursive=recursive):
1827-
self.log.debug("Discarded search path to empty directory: %s", abs_path)
1828-
continue
1829-
1830-
if os.path.isabs(search_path):
1831-
retain_path = abs_path
1832-
else:
1833-
# recover relative path
1834-
retain_path = os.path.relpath(os.path.realpath(abs_path), start=real_installdir)
1835-
if retain_path == '.':
1836-
retain_path = '' # use empty string to represent root of install dir
1837-
1838-
if retain_path.startswith('..' + os.path.sep):
1839-
raise EasyBuildError(
1840-
f"Expansion of search path glob pattern '{search_path}' resulted in a relative path "
1841-
f"pointing outside of install directory: {retain_path}"
1842-
)
1843-
1844-
retained_search_paths.append(retain_path)
1845-
1846-
return retained_search_paths
1785+
REMOVED in EasyBuild 5.1, use EasyBlock.module_load_environment.expand_paths instead
1786+
"""
1787+
msg = "expand_module_search_path is replaced by EasyBlock.module_load_environment.expand_paths"
1788+
self.log.nosupport(msg, '5.1')
18471789

18481790
def make_module_req_guess(self):
18491791
"""

easybuild/tools/modules.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
from easybuild.tools.config import SEARCH_PATH_BIN_DIRS, SEARCH_PATH_HEADER_DIRS, SEARCH_PATH_LIB_DIRS, UNLOAD, UNSET
5151
from easybuild.tools.config import build_option, get_modules_tool, install_path
5252
from easybuild.tools.environment import ORIG_OS_ENVIRON, restore_env, setvar, unset_env_vars
53-
from easybuild.tools.filetools import convert_name, mkdir, normalize_path, path_matches, read_file, which, write_file
53+
from easybuild.tools.filetools import convert_name, dir_contains_files, mkdir, normalize_path, path_matches, read_file
54+
from easybuild.tools.filetools import which, write_file
5455
from easybuild.tools.module_naming_scheme.mns import DEVEL_MODULE_SUFFIX
5556
from easybuild.tools.run import run_shell_cmd
5657
from easybuild.tools.systemtools import get_shared_lib_ext
@@ -237,6 +238,7 @@ def remove(self, *args):
237238

238239
@property
239240
def is_path(self):
241+
"""Return True for any ModEnvVarType that is a path"""
240242
path_like_types = [
241243
ModEnvVarType.PATH,
242244
ModEnvVarType.PATH_WITH_FILES,
@@ -245,6 +247,81 @@ def is_path(self):
245247
]
246248
return self.type in path_like_types
247249

250+
def expand_paths(self, parent):
251+
"""
252+
Expand path glob into list of unique corresponding real paths.
253+
General behaviour:
254+
- Only expand path-like variables
255+
- Paths must point to existing files/directories
256+
- Resolve paths following symlinks into real paths to avoid duplicate
257+
paths through symlinks
258+
- Relative paths are expanded on given parent folder and are kept
259+
relative after expansion
260+
- Absolute paths are kept as absolute paths after expansion
261+
Follow requirements based on current type (ModEnvVarType):
262+
- PATH: no requirements, must exist but can be empty
263+
- PATH_WITH_FILES: must contain at least one file anywhere in subtree
264+
- PATH_WITH_TOP_FILES: must contain files in top level directory of path
265+
- STRICT_PATH_WITH_FILES: given path must expand into its real path and
266+
contain files anywhere in subtree
267+
"""
268+
if not self.is_path:
269+
return None
270+
271+
populated_path_types = (
272+
ModEnvVarType.PATH_WITH_FILES,
273+
ModEnvVarType.PATH_WITH_TOP_FILES,
274+
ModEnvVarType.STRICT_PATH_WITH_FILES,
275+
)
276+
277+
retained_expanded_paths = []
278+
real_parent = os.path.realpath(parent)
279+
280+
for path_glob in self.contents:
281+
abs_glob = path_glob
282+
if not os.path.isabs(path_glob):
283+
abs_glob = os.path.join(real_parent, path_glob)
284+
285+
expanded_paths = glob.glob(abs_glob, recursive=True)
286+
287+
for exp_path in expanded_paths:
288+
real_path = os.path.realpath(exp_path)
289+
290+
if self.type is ModEnvVarType.STRICT_PATH_WITH_FILES and exp_path != real_path:
291+
# avoid going through symlink for strict path types
292+
self.log.debug(
293+
f"Discarded search path '{exp_path} of type '{self.type}' as it does not correspond "
294+
f"to its real path: {real_path}"
295+
)
296+
continue
297+
298+
if os.path.isdir(exp_path) and self.type in populated_path_types:
299+
# only retain paths to directories that contain at least one file
300+
recursive = self.type in (ModEnvVarType.PATH_WITH_FILES, ModEnvVarType.STRICT_PATH_WITH_FILES)
301+
if not dir_contains_files(exp_path, recursive=recursive):
302+
self.log.debug(f"Discarded search path '{exp_path}' of type '{self.type}' to empty directory.")
303+
continue
304+
305+
retain_path = exp_path # no discards, we got a keeper
306+
307+
if not os.path.isabs(path_glob):
308+
# recover relative path
309+
retain_path = os.path.relpath(real_path, start=real_parent)
310+
# modules use empty string to represent root of install dir
311+
if retain_path == '.':
312+
retain_path = ''
313+
314+
if retain_path.startswith('..' + os.path.sep):
315+
raise EasyBuildError(
316+
f"Expansion of search path glob pattern '{path_glob}' resulted in a relative path "
317+
f"pointing outside of parent directory: {retain_path}"
318+
)
319+
320+
if retain_path not in retained_expanded_paths:
321+
retained_expanded_paths.append(retain_path)
322+
323+
return retained_expanded_paths
324+
248325

249326
class ModuleLoadEnvironment:
250327
"""

0 commit comments

Comments
 (0)