modulecmd: support for Lmod module files added

This commit is contained in:
2023-05-12 16:59:22 +02:00
parent 8453c0aeb3
commit 249e552b63
+167 -158
View File
@@ -37,7 +37,10 @@ declare -rx TCL_LIBRARY="${PMODULES_HOME}/lib/tcl@TCL_VERSION@"
declare -x TCLLIBPATH
std::prepend_path TCLLIBPATH "${PMODULES_HOME}/lib/Pmodules"
declare -r modulecmd="${libexecdir}/modulecmd.bin"
declare -r tcl_cmd="${libexecdir}/modulecmd.bin"
#declare -r lmod_cmd="${PMODULES_HOME}/lmod/lmod/libexec/lmod"
declare -r lmod_cmd="/usr/share/lmod/lmod/libexec/lmod"
declare -- modulecmd="${tcl_cmd}"
declare -- verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'}
@@ -83,14 +86,14 @@ export_env() {
# append-path/remove-path! But we keep the state in PMODULES_ENV
# so we don't have to export it.
#
case "${Shell}" in
sh | bash | zsh )
echo "unset UsedGroups; "
;;
csh | tcsh )
echo "unsetenv UsedGroups; "
;;
esac
#case "${Shell}" in
#sh | bash | zsh )
# echo "unset UsedGroups; "
# ;;
#csh | tcsh )
# echo "unsetenv UsedGroups; "
# ;;
#esac
}
#
@@ -121,7 +124,7 @@ save_env() {
[[ $1 == 'no' ]] && return 0
local vars=( Version )
vars+=( UsedReleaseStages UsedFlags UsedGroups )
vars+=( UsedReleaseStages UsedGroups )
vars+=( DefaultGroups DefaultReleaseStages )
vars+=( ReleaseStages )
vars+=( GroupDepths )
@@ -165,6 +168,11 @@ die_wrong_number_of_args(){
"${CMD}" "${subcommand}" "wrong number of arguments"
}
die_illegal_opt(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${subcommand}" "illegal option" "$1"
}
die_illegal_arg(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${subcommand}" "invalid argument" "$1"
@@ -225,6 +233,12 @@ die_overlay_already_in_use(){
"overlay already in use" "$1"
}
die_not_a_modulefile(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"not a modulefile" "$1"
}
#
# get release stage of module
# Note:
@@ -238,12 +252,12 @@ die_overlay_already_in_use(){
# $3 module name/version
#
get_release_stage() {
local "$1"
local -n grs_rel_stage="$1"
local -r dir="$2"
local -r modulefile="${dir}/$3"
if [[ ! -v Dir2OverlayMap[${dir%/${PMODULES_MODULEFILES_DIR}*}] ]]; then
std::upvar $1 'stable'
grs_rel_stage='stable'
return
fi
#
@@ -255,9 +269,9 @@ get_release_stage() {
local -r rel_stage_file="${modulefile%/*}/.release-${modulefile##*/}"
if [[ -r ${rel_stage_file} ]]; then
# read file, remove empty lines, spaces etc
std::upvar $1 $( < "${rel_stage_file}" )
grs_rel_stage=$( < "${rel_stage_file}" )
else
std::upvar $1 'unstable'
grs_rel_stage='unstable'
fi
}
@@ -270,28 +284,25 @@ is_release_stage() {
#
# Check whether a given moduledir is in an used overlay.
# If yes, return 0 and the overlay with upvar of first argument
# otherwise return 1
# If yes, return 0 otherwise return 1
#
# Arguments
# $1 upvar to return overlay
# $2 upvar to return group
# $1 ref.var to return overlay
# $2 ref.var to return group
# $3 moduledir to check
#
find_overlay () {
local "$1"
local "$2"
local -n fo_ol="$1"
local -n fo_group="$2"
local path="${3//+(\/)/\/}" # replace multpile '/' with one
path="${path/%\/}" # remove trailing slash if exist
path="${path%/${PMODULES_MODULEFILES_DIR}*}"
# return if not in an overlay
[[ -v Dir2OverlayMap[${path}] ]] || return 1
local ol="${Dir2OverlayMap[${path}]}"
std::upvar $1 "${ol}"
local group="${path#${OverlayInfo[${ol}:mod_root]}/}"
std::upvar $2 "${group}"
fo_ol="${Dir2OverlayMap[${path}]}"
fo_group="${path#${OverlayInfo[${ol}:mod_root]}/}"
return 0
}
@@ -303,17 +314,41 @@ module_is_loaded() {
}
#
# Check shebang.
# test whether the given file is a module file.
#
# - for lua: the extension must be .lua
# - for Tcl: check shebang
#
# return interpreter in reference variable
#
# Arguments:
# $1 file name to test
# $1 [out] ref. variable for result
# $2 [in] full qualified file name to test
#
# Return value:
# 0 if file exist, is readable and is either a lua or Tcl module file
# 1 if file exist but is neither a lua or Tcl module file
# 2 if file doesn't exist
#
is_modulefile() {
local -r fname="$1"
local -n im_interp="$1"
local -r fname="$2"
# does file exist and is readable?
[[ -r ${fname} ]] || return 2
if [[ "${fname##*.}" == 'lua' ]]; then
im_interp='lmod'
return 0
fi
local -- shebang
[[ -r ${fname} ]] || return 1
read -n 11 shebang < "${fname}"
[[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]]
if [[ "${shebang:0:8}" == '#%Module' ]] \
|| [[ "${shebang:0:9}" == '#%Pmodule' ]]; then
im_interp='tcl'
return 0
fi
return 1
}
#
@@ -437,59 +472,6 @@ subcommand_load() {
local prefix=''
local m=''
#......................................................................
# Test whether a given module is available.
# The passed module-name can be
#
# - an absolute file- or link-name.
# The module can be either in- or outside our hierarchy.
#
# - a relative file- or link-name.
# The module can be either in- or outside out hierarchy.
#
# - specified with name and version (like gcc/5.2.0).
# The module can be either in- or outside our hierarchy.
#
# - specified with name only (without version, like gcc).
# The module can be either in- or outside our hierarchy.
#
# - directory in- or outsite our hierarchy (not supported by
# modulecmd.tcl!)
#
# arguments:
# $1: module name or file
#
# possible return values:
# 0: module is loadable
# 1: either not a modulefile or unsused release stage
#
# The following variables in the enclosing function are set:
# current_modulefile
# prefix
# rel_stage
#
is_available() {
local m=$1
local -a array
#
# the next command assigns the absolute modulefile path
# to ${arry[0]} and - if the module is in our root - the
# prefix of the module to ${array[1]}.
#
# The trick with the first line matching "_PREFIX" is not
# 100% reliable: One of the Pmodules extensions must be
# called before something like
# setenv FOO_PREFIX bar
# can be used.
#
mapfile -t array < <("${modulecmd}" 'bash' show "$m" 2>&1 | \
${awk} 'NR == 2 {print substr($0, 1, length($0)-1)}; /_PREFIX |_HOME / {print $3; exit}')
current_modulefile="${array[0]}"
prefix="${array[1]}"
test -n "${current_modulefile}" || return 1
get_release_stage rel_stage "${current_modulefile}" "${UsedReleaseStages}"
}
#......................................................................
# output load 'hints'
#
@@ -618,9 +600,7 @@ subcommand_load() {
(( ${#args[@]} == 0 )) && \
die_missing_arg
IFS=':'
local -a modulepath=(${MODULEPATH})
unset IFS
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
local m=''
for m in "${args[@]}"; do
@@ -633,6 +613,7 @@ subcommand_load() {
# - rel_stage:group:name or
# - name:rel_stage
IFS=':'
local -a toks=($m)
unset IFS
@@ -667,7 +648,6 @@ subcommand_load() {
local -i depth=${GroupDepths[${group}]}
(( depth != 0 )) && \
die_illegal_group "${group}"
modulepath=()
group+="/${PMODULES_MODULEFILES_DIR}"
for overlay in "${UsedOverlays[@]}"; do
local mod_root="${OverlayInfo[${overlay}:mod_root]}"
@@ -681,7 +661,8 @@ subcommand_load() {
g_env_must_be_saved='yes'
fi
fi # handle extended module names
find_module current_modulefile rel_stage "${m}" "${modulepath[@]}"
local moduledir=''
find_modulefile current_modulefile rel_stage moduledir "${m}" "${modulepath[@]}"
if [[ -z ${current_modulefile} ]]; then
local hints=''
get_load_hints hints
@@ -696,6 +677,9 @@ subcommand_load() {
# continue if already loaded
[[ ":${LOADEDMODULES}:" =~ ":${m}:" ]] && continue
local interp=''
is_modulefile interp "${current_modulefile}" || \
die_not_a_modulefile "${m}"
local prefix=''
get_module_prefix prefix "${current_modulefile}"
if [[ -n ${prefix} ]]; then
@@ -703,6 +687,12 @@ subcommand_load() {
test -r "${prefix}/.dependencies" && load_dependencies "$_"
fi
if [[ "${interp}" == 'lmod' ]]; then
current_modulefile="${current_modulefile/${moduledir}\/}"
modulecmd="${lmod_cmd}"
else
modulecmd="${tcl_cmd}"
fi
local output=$("${modulecmd}" 'bash' ${opts} 'load' \
"${current_modulefile}" 2> "${tmpfile}")
@@ -794,6 +784,10 @@ subcommand_unload() {
args+=( "$@" )
break
;;
-* )
die_illegal_opt "$1"
;;
* )
args+=( "$1" )
;;
@@ -810,9 +804,28 @@ subcommand_unload() {
# with 'Pmodules', we save the value and set it at the end of
# the loop again, if it has been unset.
local saved_home="${PMODULES_HOME}"
IFS=: read -r -a _lmfiles_ <<< "${_LMFILES_}"
local arg
local lmfile
for arg in "${args[@]}"; do
for lmfile in "${_lmfiles_[@]}" '_zzzz_'; do
if [[ $lmfile =~ ${arg} ]]; then
break
fi
done
if [[ "${lmfile}" == '_zzzz_' ]]; then
continue
fi
local interp
is_modulefile interp "${lmfile}" || die_not_a_modulefile "${m}"
if [[ "${interp}" == 'lmod' ]]; then
current_modulefile="${current_modulefile/${moduledir}\/}"
modulecmd="${lmod_cmd}"
else
modulecmd="${tcl_cmd}"
fi
local output=$("${modulecmd}" "${Shell}" 'unload' "${arg}")
eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')"
case ${Shell} in
@@ -825,7 +838,7 @@ subcommand_unload() {
esac
done
if [[ -z ${PMODULES_HOME} ]]; then
PMODULES_HOME=${saved_home}
PMODULES_HOME="${saved_home}"
export_env 'PMODULES_HOME'
fi
g_env_must_be_saved='yes'
@@ -1017,10 +1030,10 @@ get_available_modules() {
#
# find module(file) to load. Input arguments are
# $1 upvar: return module file
# $2 upvar: return module release stage
# $3 module to load
# $4 a modulepath (usually MODULEPATH)
# $1 [out] ref. variable to return module file
# $2 [out] ref. variable to return release stage
# $3 |in] module to load
# $4 [in] module search path (usually splitted MODULEPATH)
#
# The module name can be
# name
@@ -1028,18 +1041,17 @@ get_available_modules() {
# name/version_flag
#
# Return
# return code 0 and modulefile in first argument
# return code 1 and modulefile in first argument
# if module
# return code 2
# if no modulefile could be found
# 0 if a modulefile has been found
# != else
#
find_module() {
local "$1"
local "$2"
local -r module="$3"
local -a dirs=("${@:4}")
find_modulefile() {
local -n fm_modulefile="$1"
local -n fm_rel_stage="$2"
local -n fm_dir="$3"
local -r module="$4"
local -a dirs=("${@:5}")
local dir
for dir in "${dirs[@]}"; do
test -d "${dir}" || continue
local -i col=$((${#dir} + 2 ))
@@ -1052,20 +1064,21 @@ find_module() {
-ipath "${dir}/${module}*" \
| cut -b${col}-) )
for mod in "${modules[@]}"; do
#
# loop over all used flags. If a module with
# a used flag is available load this module.
local rel_stage
for flag in "${UseFlags[@]/#/_}" ""; do
[[ ${mod} == ${module}${flag} ]] || continue
std::upvar $1 "${dir}/${mod}"
get_release_stage \
rel_stage \
"${dir}" \
"${mod}"
std::upvar $2 "${rel_stage}"
return 0
done
if [[ ${mod} == ${module} ]]; then
:
elif [[ ${mod} == ${module}.lua ]]; then
mod="${module}.lua"
else
continue
fi
get_release_stage \
fm_rel_stage \
"${dir}" \
"${mod}"
fm_modulefile="${dir}/${mod}"
fm_dir="${dir}"
return 0
done
else
# no version has been specified. This makes it more
@@ -1084,26 +1097,26 @@ find_module() {
# now modules contains a reverse sorted list of
# available modules in the form name/version
for mod in "${modules[@]}"; do
#
# loop over all used flags. If a module with
# a used flag is available load this module.
local rel_stage=''
for flag in "${UseFlags[@]/#/_}" ""; do
[[ ${mod} == ${module}/*${flag} ]] || continue
std::upvar $1 "${dir}/${mod}"
get_release_stage \
rel_stage \
"${dir}" \
"${mod}"
std::upvar $2 "${rel_stage}"
[[ :${rel_stage}: =~ :${UsedReleaseStages}: ]] && \
return 0
done
if [[ ${mod} == ${module}/* ]]; then
:
else
continue
fi
get_release_stage \
fm_rel_stage \
"${dir}" \
"${mod}"
# don't load modules with unused release stages
if [[ :${fm_rel_stage}: =~ :${UsedReleaseStages}: ]]; then
fm_modulefile="${dir}/${mod}"
fm_dir="${dir}"
return 0
fi
done
fi
done
return 1
} # find_module()
} # find_modulefile()
##############################################################################
#
@@ -1310,24 +1323,40 @@ subcommand_avail() {
unset IFS
local dir
local group=''
local groups=()
local ol=''
local name=''
for dir in "${modulepath[@]}"; do
group='other'
find_overlay ol group "${dir}"
if (( $? == 0 )); then
name="${group}"
else
name="${dir}"
group="${dir//[\/ .-]/_}"
fi
if [[ ! -v modulepath_${group} ]]; then
typeset -a modulepath_${group}
fi
# with overlays we can have multiple directories per group!
typeset -n path=modulepath_${group}
path+=("${dir}")
local -i found=0
for group in "${groups[@]}"; do
if [[ "${group}" == ${name} ]]; then
found=1
fi
done
(( found == 0 )) && groups+=( "${name}" )
done
local p
for string in "${pattern[@]}"; do
for group in ${UsedGroups//:/ } other; do
for group in "${groups[@]}"; do
if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then
continue
fi
[[ -v modulepath_${group} ]] || continue
typeset -n path=modulepath_${group}
local ref="${group//[\/ .-]/_}"
[[ -v modulepath_${ref} ]] || continue
typeset -n path=modulepath_${ref}
get_available_modules \
mods \
"${string}*" \
@@ -1434,11 +1463,6 @@ subcommand_use() {
[[ ! ":${UsedReleaseStages}:" =~ :$r: ]] && std::info "\t${r}"
done
std::info "\nUsed flags:"
for flag in "${UsedFlags[@]}"; do
std::info "\t${flag}"
done
std::info ''
std::info "Used overlays:"
print_ol_info 'yes'
@@ -1534,11 +1558,6 @@ subcommand_use() {
std::append_path UsedReleaseStages "${arg}"
return $?
fi
if [[ "${arg}" =~ "flag=" ]]; then
# argument is flag
UsedFlags+=( "${arg/flag=}" )
return $?
fi
if [[ -v OverlayInfo[${arg}:type] ]]; then
use_overlay "${arg}"
return $?
@@ -1706,15 +1725,6 @@ subcommand_unuse() {
std::remove_path UsedReleaseStages "${arg}"
return
fi
if [[ "${arg}" =~ "flag=" ]]; then
# argument is flag
local flag="${arg/flag=}"
local i
for i in ${!UsedFlags[@]}; do
[[ ${UsedFlags[i]} == ${flag} ]] && unset UsedFlags[i]
done
return
fi
if [[ -v OverlayInfo[${arg}:type] ]]; then
unuse_overlay "${arg}"
return 0
@@ -1868,7 +1878,6 @@ pmodules_init() {
declare -gx LOADEDMODULES=''
declare -gx _LMFILES_=''
declare -Ag GroupDepths=()
declare -ag UsedFlags=()
declare -g Version="${PMODULES_VERSION}"
init_used_groups