"""Environment module modification/installation."""
import os
import shutil
from pathlib import Path
import pathlib
import subprocess
import tarfile
import inspect
from packaging import version
import requests
from venvmod.tools import (get_std_name, get_shell_command, check_raise,
get_shell_name, PACKAGE_NAME)
[docs]def get_module_file_directory(virtual_env: Path) -> Path:
"""Gets the modulefiles directory
Parameters
----------
virtual_env : Path
Path to virtual env
Returns
-------
Path
virtual_env path + "/etc/modulefiles"
"""
return virtual_env.absolute() / "etc" / "modulefiles"
ACTIVATE_HEADER_LINE = (f"# This file is generated from {PACKAGE_NAME}"
" from regular venv or virtualenv file.")
[docs]def test_if_already_init(virtual_env: Path):
"""checks if venv-modulefile is already initialized.
Parameters
----------
virtual_env : Path
Path to virtual env
"""
with (virtual_env / "bin" / "activate").open(mode="r", encoding='utf-8') as src_file:
check_raise(ACTIVATE_HEADER_LINE in src_file.read(), AssertionError,
f"{virtual_env} is already a venv-modulefile environment.")
[docs]class ModuleInstaller: # pylint: disable=too-few-public-methods
"""Class to install Environment Module.
"""
[docs] def __init__(self,
version_or_path: str,
install_prefix: str or Path,
cache_directory: str or Path) -> None:
self._version_or_path: str = version_or_path
self._install_prefix: Path = Path(install_prefix)
self._cache_directory: Path = Path(cache_directory)
[docs] def run(self, verbose: bool = False):
"""run installer
Parameters
----------
verbose : bool, optional
True to enable verbosity, by default False
"""
self._cache_directory.mkdir(parents=True, exist_ok=True)
if Path(self._version_or_path).exists():
build_directory = self._cache_directory / Path(self._version_or_path).name
shutil.copytree(Path(self._version_or_path), build_directory, symlinks=True)
else:
check_raise(condition=version.parse(self._version_or_path) < version.parse("4.6"),
exception_type=ValueError,
message="Version number for Modulefile must be >= 4.6,"
f" found {self._version_or_path}."
)
build_directory = self._cache_directory / f"modules-{self._version_or_path}"
if not build_directory.exists():
cwd = os.getcwd()
os.chdir(self._cache_directory)
try:
tar_file = (Path(__file__).parent.absolute().resolve() /
"modulefiles_src" / f"modules-{self._version_or_path}.tar.gz")
if tar_file.exists():
file = tarfile.open( # pylint: disable=consider-using-with
tar_file,
mode="r|gz")
else:
file = tarfile.open( # pylint: disable=consider-using-with
fileobj=requests.get(
url="https://github.com/cea-hpc/modules/releases/download/"
f"v{self._version_or_path}/"
f"modules-{self._version_or_path}.tar.gz",
stream=True,
timeout=120.0).raw,
mode="r|gz")
try:
if 'filter' in inspect.signature(file.extractall).parameters:
file.extractall(filter='tar')
else:
file.extractall()
finally:
file.close()
finally:
os.chdir(cwd)
pipe = subprocess.PIPE if not verbose else None
for command in [[f"{build_directory}/configure",
f"--prefix={self._install_prefix}",
"--with-python=$(which python3)"],
["make", "clean"],
["make"],
["make", "install"]]:
subprocess.run([get_shell_command(), "-c", " ".join(command)],
stderr=pipe, stdout=pipe, cwd=build_directory, check=True)
[docs]def upgrade_modulefile(virtual_env: Path, module_prefix: Path):
"""Upgrade modulefile in venv
Parameters
----------
virtual_env : Path
Path to virtual env
module_prefix : Path
Modulefile install prefix
"""
activate_src = virtual_env / "bin" / "activate"
check_raise(not activate_src.is_file(), AssertionError,
f'"activate" file {activate_src} not found.')
with open(activate_src, "r", encoding='utf-8') as src_file:
src_lines = src_file.readlines()
tmp_path = virtual_env / "tmp" / PACKAGE_NAME
tmp_path.mkdir(parents=True, exist_ok=True)
activate_tmp = tmp_path / "activate"
init_file_path = module_prefix / "init"
init_file = init_file_path / get_shell_name()
check_raise(not os.path.isfile(init_file), AssertionError,
f'Environment Module "init" file {init_file} not found.')
with open(activate_tmp, "w", encoding='utf-8') as tmp_file:
for line in src_lines:
tmp_file.write(line)
if "you cannot run it directly" in line.lower():
tmp_file.write("\n"f". {init_file_path}{os.sep}$(ps -ocomm= -q $$)\n")
pathlib.Path.replace(activate_tmp, activate_src)
MODULE_TEMPLATES = {"TCL": """#%Module -*- tcl -*-
##
## modulefile for __name__
##
#Global infos
set category __category__
set name __name__
proc ModulesHelp { } {
puts stderr "\tAdds $name to your environment,"
}
module-whatis "adds $name to your environment"
__log_load__
#conflicts, prereq
conflict $category
"""}
[docs]def create_modulefile(virtual_env: Path,
module_name: str = PACKAGE_NAME,
module_category: str = None,
log_load: str = ""):
"""Creates a modulefile.
Parameters
----------
module_name : str
Name of the module to create
module_directory : Path
Module directory
module_category : str, optional
Module category, by default None
log_load : str, optional
Loag edited at load, by default ""
"""
module_directory = get_module_file_directory(virtual_env=virtual_env)
module_directory.mkdir(parents=True, exist_ok=True)
module_file_name = module_directory / module_name
module_file = MODULE_TEMPLATES["TCL"].replace("__name__", module_name)
module_file = module_file.replace("__category__", module_category)
to_replace = ""
if log_load:
log_load = log_load.replace('[', r'\[').replace(']', r'\]')
to_replace = ('if { [ module-info mode load ] } {\n'
f' puts stderr "{log_load}"\n'
'}')
module_file = module_file.replace("__log_load__", to_replace)
with open(module_file_name, "w", encoding='utf-8') as mod_file:
mod_file.write(module_file)
SHELL_TEST_DEACTIVATE_STATUS = """
function _test_deactivate_status(){
module_status=( $module_unuse_status $module_unload_status )
unset module_unuse_status
unset module_unload_status
unset -f _test_deactivate_status
for return_code in $module_status ; do
if (( $return_code > 0 )); then return $return_code; fi
done
}
"""
UNLOAD_MODULES = """
# Unload non-Python dependencies
module unload '{}'
module_unload_status=$?
module unuse '{}'
module_unuse_status=$?
"""
LOAD_MODULES = """
# Load non-Python dependencies
module use '{}'
module_use_status=$?
module load '{}'
module_load_status=$?
"""
SHELL_TEST_ACTIVATE_STATUS = """
function _test_activate_status(){
module_status=( $module_use_status $module_load_status )
unset module_use_status
unset module_load_status
unset -f _test_activate_status
for return_code in $module_status ; do
if (( $return_code > 0 )); then return $return_code; fi
done
}
_test_activate_status
"""
[docs]def upgrade_venv(virtual_env: Path):
"""Ugrade virtual env with modulefile at activate and deactivate.
Parameters
----------
env_prefix : Path
Path to environment.
Raises
------
AssertionError
If the activate script is not found.
AssertionError
If the module is already loaded frome the script but from another modulefile directory.
"""
activate_src = virtual_env / "bin" / "activate"
check_raise(not activate_src.is_file(), AssertionError,
f'{activate_src} file not found.')
modulefile_name = get_std_name(virtual_env.name)
module_directory = get_module_file_directory(virtual_env=virtual_env)
test_if_already_init(virtual_env=virtual_env)
with open(activate_src, "r", encoding='utf-8') as src_file:
src_lines = src_file.readlines()
tmp_path = virtual_env / "tmp" / PACKAGE_NAME
tmp_path.mkdir(exist_ok=True, parents=True)
activate_tmp = tmp_path / "activate"
with activate_tmp.open("w", encoding='utf-8') as tmp_file:
for count, line in enumerate(src_lines):
to_write = ()
if count == 2:
to_write = (0, f"{ACTIVATE_HEADER_LINE}\n")
elif "deactivate () {" in line:
to_write = (0, SHELL_TEST_DEACTIVATE_STATUS)
elif "unset -f deactivate" in line:
to_write = (1, " _test_deactivate_status\n return $?\n")
elif line.split() == ["unset", "VIRTUAL_ENV"]:
to_write = (1, UNLOAD_MODULES.format(modulefile_name, str(module_directory)))
elif "deactivate nondestructive" in line:
to_write = (1, LOAD_MODULES.format(str(module_directory), modulefile_name))
else:
to_write = (0, "")
tmp_file.write(line + to_write[1] if to_write[0] else to_write[1] + line)
tmp_file.write(SHELL_TEST_ACTIVATE_STATUS)
shutil.copyfile(activate_tmp, activate_src)
shutil.rmtree(tmp_path)