Files
Pmodules/Pmodules/modulecmd.bash.in
gsell c72b203e5f modulecmd: bugfix in get_available_modules()
Due to a bug in this function not all available modules were found.
2025-05-28 14:51:49 +02:00

4042 lines
113 KiB
Plaintext

#!@BASH@ --noprofile
#
# shellcheck -x -e SC1008,SC2239,SC2317,SC2034,SC2128,SC2059,SC2178
#
declare -r VERSION='@PMODULES_VERSION@'
unset CDPATH # unset CDPATH, otherwise 'cd' prints the directoy!
unset IFS # use default IFS
#set -o errexit
set -o pipefail
set -o nounset
#shopt -s nocaseglob
#shopt -s extglob
shopt -s nullglob
# get absolute path of script
declare -- mydir=$(cd "$(/usr/bin/dirname -- "${BASH_SOURCE[0]}")" && pwd -L)
declare -- PMODULES_HOME=$(/usr/bin/dirname -- "${mydir}");
declare -- path_orig="${PATH}"
PATH="${PMODULES_HOME}/bin:${PMODULES_HOME}/libexec:/bin:/usr/bin:/sbin:/usr/sbin"
source "${PMODULES_HOME}/lib/libstd.bash" || {
echo "Oops: cannot source library -- '$_'" 1>&2; exit 3;
}
source "${PMODULES_HOME}/lib/libpmodules.bash" || \
std::die 3 "Oops: cannot source library -- '$_'"
declare -rx TCL_LIBRARY="${PMODULES_HOME}/lib/tcl@TCL_VERSION@"
declare -x TCLLIBPATH=${TCLLIBPATH:-''}
std::prepend_path TCLLIBPATH "${PMODULES_HOME}/lib/Pmodules"
declare -r Tcl_cmd="${PMODULES_HOME}/libexec/modulecmd.bin"
declare -r Lmod_cmd="${PMODULES_HOME}/libexec/lmod/lmod/libexec/lmod"
declare -r Spider_cmd="${PMODULES_HOME}/libexec/lmod/lmod/libexec/spider"
declare -- modulecmd="${Tcl_cmd}"
# we have to use the original path. Otherwise module load doesn't work.
PATH="${path_orig}"
unset path_orig
unset mydir
##############################################################################
# the following settings are used if the config file doesn't exist
# set groups which should be available after initialization
declare -- DefaultGroups='Tools Programming'
# define available release stages
declare -- ReleaseStages=':unstable:stable:deprecated:'
# set releases which should be available after initialization
declare -- DefaultReleaseStages='stable'
declare -A Dir2OverlayMap=()
declare -A GroupDepths=()
declare -- ModulePathAppend=''
declare -- ModulePathPrepend=''
declare -a Overlays=()
declare -- OverlayExcludes=''
declare -A OverlayInfo=()
declare -- PmFiles=''
declare -g UsedGroups=''
declare -g UsedReleaseStages=''
declare -a UsedOverlays=()
declare -- Version=''
declare -x OSRelease=$(std::get_os_release)
declare -x SystemCPU=$(uname -p)
declare -A MaskedGroups=()
##############################################################################
declare -- Verbosity_lvl='verbose'
declare -- Shell=''
TmpFile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) || \
std::die 1 "Oops: unable to create tmp file!"
declare -r TmpFile
declare -r CacheDir="${HOME}/.cache/Pmodules"
${mkdir} -p "${CacheDir}" || \
std::die 1 "Oops: unable to create cache directoy '${CacheDir}"
declare -- SpiderCache=''
HostName=$(${hostname} -f)
declare -r HostName
CurTime=$( ${date} --date=now +%s )
declare -r CurTime
declare -r CMD='module'
declare -- SubCommand=''
declare -A Subcommands=()
declare -A Options=()
declare -A Help=()
# these variables must exist. Under some conditions Lmod unset them.
if [[ ! -v LOADEDMODULES ]] || [[ ! -v _LMFILES_ ]]; then
LOADEDMODULES=''
_LMFILES_=''
fi
[[ -v MODULEPATH ]] || MODULEPATH=''
[[ -v PMODULES_ENV ]] || PMODULES_ENV=''
[[ -v MANPATH ]] || MANPATH=''
# initialize help text of 'module --version'
Help['version']="
Pmodules @PMODULES_VERSION@
using Tcl Environment Modules
VERSION = @MODULES_VERSION@
"
#..............................................................................
print_help() {
local -r __doc__='Display help text for command given in $1.'
echo -e "${Help[$1]}" 1>&2
std::die 1
}
#..............................................................................
save_env() {
local -r __doc__='
Save/cache state in the environment variable PMODULES_ENV.
The content is base64 encoded. This function is called on exit
via a trap handler.'
encode_base64(){
local -- os_name=''
os_name=$(${uname} -s)
case "${os_name}" in
Linux )
"${base64}" --wrap=0 <<< "$1"
;;
Darwin )
# does not wrap if running in a script
"${base64}" <<< "$1"
;;
* )
std::die 255 "Oops: Unsupported OS"
;;
esac
}
local vars=()
vars+=( 'Version' )
vars+=( 'UsedReleaseStages' 'UsedGroups' )
vars+=( 'DefaultGroups' 'DefaultReleaseStages' )
vars+=( 'ReleaseStages' )
vars+=( 'GroupDepths' )
vars+=( 'Overlays' )
vars+=( 'UsedOverlays' )
vars+=( 'OverlayExcludes' )
vars+=( 'OverlayInfo' 'Dir2OverlayMap')
vars+=( 'PmFiles' )
vars+=( 'ModulePathAppend' )
vars+=( 'ModulePathPrepend' )
vars+=( 'MaskedGroups' )
local s=''
s=$(typeset -p "${vars[@]}")
declare -gx PMODULES_ENV=$( encode_base64 "$s" )
}
#..............................................................................
declare -A vars_to_be_exported=()
export_env() {
local -r __doc__='Export required environment variables.'
local -A export_functions=()
export_functions['sh']='export_env_sh'
export_functions['bash']='export_env_sh'
export_functions['zsh']='export_env_sh'
export_functions['csh']='export_env_csh'
export_functions['tcsh']='export_env_csh'
export_functions['python']='export_env_python'
export_env_sh(){
while (( $# > 0 )); do
printf "export %s=\"%s\"; " "$1" "${!1}"
shift
done
}
export_env_csh(){
while (( $# > 0 )); do
printf "setenv %s \"%s\"; " "$1" "${!1}"
shift
done
}
export_env_python(){
while (( $# > 0 )); do
printf "os.environ['%s'] = '%s'\n" "$1" "${!1}"
shift
done
}
[[ -v vars_to_be_exported['PMODULES_ENV'] ]] && save_env
[[ -v export_functions[${Shell}] ]] || die_args_unsupported_shell "${Shell}"
${export_functions[${Shell}]} "${!vars_to_be_exported[@]}"
}
#..............................................................................
_exit() {
local -r __doc__='Function called on exit via trap.'
export_env
if [[ -n "${TmpFile}" ]] && [[ -e "${TmpFile}" ]]; then
${rm} -f "${TmpFile}" || :
fi
}
trap '_exit' EXIT
#..............................................................................
# Error messages
die_args_unsupported_shell(){
std::die 1 "${CMD}: unsupported shell -- $1"
}
die_args_subcmd_missing(){
std::die 1 "${CMD}: no sub-command specified."
}
die_args_invalid_subcmd(){
std::die 1 "${CMD}: unknown sub-command -- $1"
}
die_args_missing(){
std::die 3 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" 'missing argument'
}
die_args_too_many(){
std::die 3 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" 'too many arguments'
}
die_args_not_allowed(){
std::die 3 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" "no arguments allowed"
}
die_args_wrong_number(){
"${CMD}" "${SubCommand}" "invalid option" "$1"
}
die_args_invalid_for_subcmd_use(){
std::die 1 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid argument" "$1"
}
die_args_invalid_value(){
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"invalid value for option '$1'" \
"$2"
}
die_grp_invalid(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid group" "$1"
}
die_grp_cannot_be_removed(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "cannot remove group due to loaded modules" "$1"
}
die_relstage_invalid(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid release stage" "$1"
}
die_module_unavail(){
std::die 3 "%s %s: %s -- %b\n" \
"${CMD}" "${SubCommand}" "not available in the current MODULEPATH" "$1"
}
die_module_nexist(){
std::die 3 "%s %s: module does not exist -- %s" \
"${CMD}" "${SubCommand}" "$1"
}
die_module_conflict(){
std::die 3 "%s %s: %s -- %b\n" \
"${CMD}" "${SubCommand}" \
"module conflicts with already loaded modules" "$1"
}
die_module_cmd_failed(){
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"failed"
}
die_module_not_a_modulefile(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"not a modulefile" "$1"
}
die_col_cannot_be_saved(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot save_collection" "$1"
}
die_col_invalid_name(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"invalid collection name" "$1"
}
die_col_doesnt_exist(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"collection doesn't exist or isn't readable" "$1"
}
die_col_cannot_be_removed(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot remove collection" "$1"
}
die_ol_cannot_be_added(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"overlay cannot be added since some modules are already loaded!" "$1"
}
die_ol_conflict(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"Overlay '$1' conflicts with" "$2"
}
die_ol_cannot_be_removed(){
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"overlay cannot be removed since some modules are still loaded!"
}
die_ol_cannot_remove_base(){
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"cannot remove base overlay!"
}
die_ol_not_used(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"not an used overlay" \
"$1"
}
die_ol_not_on_top_of_stack(){
std::die 3 "%s %s: %s %s -- %s" \
"${CMD}" "${SubCommand}" \
"overlay cannot be removed since" \
"it is not on top of the stack" \
"$1"
}
print_deprecated_msg(){
local -- msg=''
printf -v msg "
#############################################
# DEPRECATED MODULE - IMPORTANT INFORMATION #
#############################################
This module is deprecated and should no longer be used.
Please always use modules from the stable repository. Modules
from the unstable repository may also be used, but please
note that they are subject to change and may not be fully
tested.
Deprecated modules are no longer maintained, and support
requests related to them will not be addressed.
Thank you for your understanding.
"
std::info "%s" "${msg}"
}
print_module_load_msg(){
local -r relstage="$1"
local -- msg=''
[[ "${relstage}" == 'stable' ]] && return 0
printf -v msg "%s %s: %s -- %s" \
"${CMD}" 'load' \
"${relstage} module has been loaded" \
"${m}"
std::info "%s" "${msg}"
}
log_module_load_msg(){
local -r modulefile="$1"
local -r relstage="$2"
printf -v msg "%s: %s %s %s" \
'load' \
"modulefile=${modulefile}" \
"rel-stage=${relstage}" \
"user=${USER}"
${logger} -t Pmodules "${msg}"
}
#..............................................................................
get_module_config(){
local -r __doc__='
Read module configuration.
If a file ".config-<version>" exists, read configuration from this
file. The file must be in YAML format. The following keys are
supported:
RelStage: <relstage>
Systems: <list-of-systems>
Blocklist: <list-of-host-names>
If the above configuration file does not exist, get the release
stage from ".release-<version>". Restrictions to systems or
blocking hosts is not possible.
Note:
- the release stage of a module inside a Pmodules hierarchy
without a config file is always "unstable".
- the release stage of a module inside a Lmod hierarchy created
by Spack depends on the used release stages:
- if "unstable" is used, the release stage is "unstable"
- otherwise it is "stable".
- the release stage of other modules without a config file is
always "stable".
'
local -n ref_cfg="$1" # [out] reference to a dictionary to
# return the configuration
local -r dir="$2" # [in] directory containing modulefile
local -r modulefile="${dir}/$3" # [in] module name (inkl. version
# and/or sub-dirs)
ref_cfg['relstage']='unstable'
ref_cfg['systems']=''
ref_cfg['blocklist']=''
local -r config_file="${modulefile%/*}/.config-${modulefile##*/}"
local -r relstage_file="${modulefile%/*}/.release-${modulefile##*/}"
if [[ ! -r ${config_file} ]]; then
local -- ol_name=''
local -- group=''
find_overlay ol_name group "${modulefile}"
if [[ "${OverlayInfo[${ol_name}:layout]}" == 'Pmodules' ]]; then
[[ -r ${relstage_file} ]] && \
ref_cfg['relstage']=$( < "${relstage_file}" )
else
ref_cfg['relstage']='stable'
fi
return 0
fi
local -- yaml=''
yaml=$(${yq} -e '.' < "${config_file}")
local -- key=''
for key in "${!ref_cfg[@]}"; do
case "${key,,}" in
systems | blocklist )
value=$( ${yq} -e ".${key}[]" \
2>/dev/null <<<"${yaml}") ||
value='null'
;;
* )
value=$( ${yq} -e ".${key}" \
2>/dev/null <<<"${yaml}") ||
value='null'
;;
esac
[[ "${value}" != 'null' ]] && ref_cfg[${key,,}]="${value}"
done
return 0
}
#..............................................................................
is_available(){
local -r __doc__='
Module is available if
- release stage is used
- the systems list is empty or the system is in the list
- the blocklist is empty or the hostname is NOT in the list
'
local -n ref_cfg="$1"
local -- relstages="$2"
check_relstage(){
[[ ":${relstages}:" == *:${ref_cfg['relstage']}:* ]]
}
check_blocklist(){
[[ -z ${ref_cfg['blocklist']} ]] && return 0
local -- s=''
for s in ${ref_cfg['blocklist']}; do
if [[ "${OSRelease}" =~ $s ]] || [[ "${HostName}" =~ $s ]]; then
return 0
fi
done
return 1
}
check_systems(){
[[ -z ${ref_cfg['systems']} ]] && return 0
local -- s=''
for s in ${ref_cfg['systems']}; do
if [[ "${OSRelease}" =~ $s ]] || [[ "${HostName}" =~ $s ]]; then
return 0
fi
done
return 1
}
set -o noglob
check_relstage && check_blocklist && check_systems
local -i ec=$?
set +o noglob
return ${ec}
}
#..............................................................................
is_release_stage() {
local -r __doc__='
check whether the argument in $1 is a valid release stage.
'
[[ :${ReleaseStages}: =~ :$1: ]]
}
#..............................................................................
find_overlay () {
local -r __doc__='
Check whether a given moduledir is in an used overlay.
If yes, return 0 otherwise return 1
'
local -n ref_ol="$1" # [out] ref.var to return overlay name
local -n ref_group="$2" # [out] ref.var to return group
local -- path="$3" # [in] moduledir to check
path="${path%/"${__MODULEFILES_DIR__}"*}"
local -- ol=''
for ol in "${UsedOverlays[@]}"; do
local modulefiles_root="${OverlayInfo[${ol}:modulefiles_root]}"
if [[ "${path}" == ${modulefiles_root}/* ]]; then
ref_ol="${ol}"
if [[ "${OverlayInfo[${ref_ol}:layout]}" == 'Pmodules' ]]; then
ref_group="${path#"${OverlayInfo[${ol}:modulefiles_root]}"/}"
else
ref_group='none'
fi
return 0
fi
done
ref_ol='none'
ref_group='none'
return 1
}
#..............................................................................
is_modulefile() {
local -r __doc__='
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
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 does not exist
'
local -n ref_intrp="$1" # [out] ref. variable for result
local -r fname="$2" # [in] absolute file name to test
# is this a regular, readable file?
[[ -f "${fname}" && -r "${fname}" ]] || return 2
if [[ "${fname##*.}" == 'lua' ]]; then
ref_intrp="${Lmod_cmd}"
return 0
fi
local -- shebang
read -r -n 11 shebang < "${fname}"
if [[ "${shebang:0:8}" == '#%Module' ]] \
|| [[ "${shebang:0:9}" == '#%Pmodule' ]]; then
ref_intrp="${Tcl_cmd}"
return 0
fi
return 1
}
#..............................................................................
get_module_prefix() {
local -r __doc__='
Get the value of <module>_PREFIX.
'
local -n ref_prefix="$1" # [out] ref. variable to return result
local -- modulefile="$2" # [in] modulefile
ref_prefix=$("${modulecmd}" bash show "${modulefile}" 2>&1 | \
${awk} '/_PREFIX |_HOME / {print $3; exit}')
}
###############################################################################
# Generic wrappers for the modulecmd (Tcl and Lmod):
#
# The options to output help are supported.
#
#..............................................................................
subcommand_generic0() {
local -r __doc__='
Call modulecmd without arguments.
'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift 1
done
(( ${#args[@]} == 0 )) || \
die_args_not_allowed
"${modulecmd}" "${Shell}" "${SubCommand}"
}
#..............................................................................
subcommand_generic1() {
local -r __doc__='
Call modulecmd with exactly one argument.
'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
(( ${#args[@]} > 1 )) && \
die_args_too_many
"${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}"
}
#..............................................................................
subcommand_generic1plus() {
local -r __doc__='
Call modulecmd with one or more arguments.
'
local args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
"${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}"
}
#..............................................................................
set_lmfiles(){
local -r __doc__='
Set/fix the environment variable _LMFILES_ and LOADEDMODULES.
Why do we have to do this?
- Lmod removes files from _LMFILE_ which have been loaded
by the Tcl modulecmd. Even worse Lmod might unset _LMFILE_.
If the modulecmd was Lmod, the Tcl module files have to
be added again to _LMFILE_.
- After fixing _LMFILE_ we rebuild LOADEDMODULES from
_LMFILE_.
'
if [[ ! -v _LMFILES_ ]]; then
declare -gx _LMFILES_=''
declare -gx LOADEDMODULES=''
fi
local -- dir=''
local -a dirs=()
if [[ "${modulecmd}" == "${Lmod_cmd}" ]]; then
IFS=':' read -r -a dirs <<<"${PmFiles}"
for dir in "${dirs[@]}"; do
std::append_path _LMFILES_ "${dir}"
done
fi
if [[ -z "${_LMFILES_}" ]]; then
return 0
fi
# rebuild LOADEDMODULES by setting it to _LMFILES_ and then removing
# all directories given in MODULEPATH from LOADEDMODULES.
LOADEDMODULES="${_LMFILES_}"
IFS=':' read -r -a dirs <<<"${MODULEPATH}"
for dir in "${dirs[@]}"; do
# If the first or last character of MODULEPATH is ':',
# we get an empty string. This shouldn't happen, but
# with this test we are on the save side.
[[ -z ${dir} ]] && continue
# Skip relative directories in MODULEPATH.
# Relative directories in MODULEPATH doesn't make make much
# sense. Or? For now we just ignore them.
[[ "${dir:0:1}" == '/' ]] || continue
# The directory string we want to remove must end with a
# slash, otherwise we have entries in LOADEDMODULES
# beginning with a slash.
[[ "${dir: -1}" == "/" ]] || dir+="/"
# Remove this directory from all entries in LOADEDMODULES
LOADEDMODULES="${LOADEDMODULES//${dir}}"
done
}
##############################################################################
Subcommands['add']='load'
Subcommands['load']='load'
Options['load']='-l help -o \?Hfsvw -l force -l silent -l verbose -l warn'
Help['load']='
USAGE:
module add modulefile...
module load modulefile...
Load modulefile(s) into the shell environment. Loading a
"group-head" will extend the MODULEPATH. E.g.: loading a
compiler makes additional modules like openmpi and libraries
compiled with this compiler available.
'
#..............................................................................
subcommand_load() {
local -r __doc__='
Load a module. The following formats are supported for the
name of the module to be loaded:
- name
- name/version
- group:name
- group:name/version
- group:name:relstage
- group:name/version:relstage
- name:relstage
'
local -- relstage='undef'
local -- current_modulefile=''
local -- prefix=''
local -- m=''
local -A interp
#......................................................................
# output load 'hints'
#
# Note:
# The variable 'm' from the parent function will be used
# but not changed.
#
get_load_hints() {
local -n output="$1" # [out] ref.var to return result
local relstage=''
output=''
while read -r -a line; do
(( ${#line[@]} == 0 )) && continue
relstage=${line[1]}
if [[ ! ":${UsedReleaseStages}:" == *:${relstage}:* ]]; then
output+="module use ${relstage}; "
fi
local group=${line[2]}
[[ "${group}" != 'none' ]] || continue
if [[ ! ":${UsedGroups}:" == *:${group}:* ]] && \
(( ${GroupDepths[${group}]} == 0 )); then
output+="module use ${group}; "
fi
local -i n=$(( ${GroupDepths[${group}]}/2 ))
output+="module load ${line[*]:4:$n} ${line[0]}\n"
done < <(set +x; subcommand_search "${m}" -a --no-header 2>&1)
if [[ -n ${output} ]]; then
output="\n\nTry with one of the following command(s):\n${output}"
fi
}
#......................................................................
module_is_loaded() {
[[ :${LOADEDMODULES}: =~ :$1: ]]
}
#......................................................................
load_dependencies() {
local -r fname="$1"
local -- dep=''
while read -r dep; do
[[ -z ${dep} ]] && continue
[[ ${dep:0:1} == \# ]] && continue
module_is_loaded "${dep}" && continue
subcommand_load "${dep}"
done < "${fname}"
}
#......................................................................
local args=()
local opts=()
local overlay
while (($# > 0)); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-f | --force )
opts+=(' -f')
;;
-s | --silent )
Verbosity_lvl='silent'
;;
-v | --verbose )
Verbosity_lvl='verbose'
;;
-w | --warn )
Verbosity_lvl='warn'
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
if [[ ! -v LOADEDMODULES ]]; then
LOADEDMODULES=''
_LMFILES_=''
fi
local m=''
for m in "${args[@]}"; do
if [[ "$m" == *:* ]]; then
local -a toks=()
IFS=':' read -r -a toks <<< "${m}"
local -- group=''
local -- relstage=''
if (( ${#toks[@]} > 3 )); then
: # invalid name
elif (( ${#toks[@]} == 3 )); then
group="${toks[0]}"
[[ -v GroupDepths[${group}] ]] || \
die_grp_invalid
m="${toks[1]}"
relstage="${toks[2]}"
else
if [[ -v GroupDepths[${toks[0]}] ]]; then
group="${toks[0]}"
m="${toks[1]}"
else
m="${toks[0]}"
relstage="${toks[1]}"
fi
fi
if [[ -n ${group} ]]; then
local -i depth=${GroupDepths[${group}]}
(( depth != 0 )) && \
die_grp_invalid "${group}"
subcommand_use "${group}"
fi
if [[ -n ${relstage} ]]; then
is_release_stage "${relstage}" || \
die_relstage_invalid "${relstage}"
subcommand_use "${relstage}"
fi
fi # handle extended module names
find_modulefile current_modulefile relstage moduledir modulecmd "${m}" || {
local hints=''
get_load_hints hints
if [[ -z "${hints}" ]]; then
die_module_nexist "${m}"
else
die_module_unavail "${m}${hints}"
fi
}
# If the user wants to load/switch to another Pmodules version:
# This is possible if
# - no other module is loaded
# - the loaded version of Pmodules is >= 1.1.22
# - the to be loaded version of Pmodules is >= 1.1.22
if [[ ${m} == Pmodules/* ]]; then
local -r new_version="${m##*/}"
[[ -v Version ]] || Version='0.0.0'
if [[ -n ${LOADEDMODULES} ]]; then
if std::version_lt "${new_version}" '1.1.22' || \
std::version_lt "${Version}" '1.1.22'; then
die_module_conflict "${m}"
fi
Version="${new_version}"
fi
fi
# nothing to do if already loaded, continue with
# next module to be loaded
[[ ":${LOADEDMODULES}:" == *:${m}:* ]] && continue
# show info file
local prefix=''
get_module_prefix prefix "${current_modulefile}"
[[ -n ${prefix} && -r "${prefix}/.info" ]] && cat "${prefix}/.info" 1>&2
# loading the dependencies overwrites the interpreter for the
# currend modulefile, so we have to save it for later use.
interp[${current_modulefile}]="${modulecmd}"
# load dependencies
local -- deps_file="${current_modulefile%/*}/.deps-${current_modulefile##*/}"
if [[ ! -r "${deps_file}" && -n "${prefix}" ]]; then
deps_file="${prefix}/.dependencies"
fi
test -r "${deps_file}" && load_dependencies "$_"
# load module
modulecmd="${interp[${current_modulefile}]}"
local output=''
output=$("${modulecmd}" 'bash' "${opts[@]}" 'load' \
"${current_modulefile}" 2> "${TmpFile}")
# we don't print the error message we got from modulecmd, due to
# readability.
# :FIXME:
# Not sure whether this is now correct!
# The idea is to supress the error messages from the Tcl
# modulecmd, but not the output to stderr coded in a
# modulefile.
# :FIXME:
# Handle errors from Lmod.
# :FIXME:
# In some cases the error message is unclear.
local error=''
error=$( < "${TmpFile}")
if [[ "${error}" == *:ERROR:* ]]; then
local s=${error%%$'\n'*}
[[ "$s" =~ ' conflicts ' ]] && \
die_module_conflict "${m}"
die_module_cmd_failed "${m}"
fi
if [[ "${Shell}" == "sh" ]]; then
# for sh-like shells just echo
echo "${output}"
else
# re-run with right shell
"${modulecmd}" "${Shell}" "${opts[@]}" 'load' \
"${current_modulefile}"
fi
output=$( ${sed} -e 's/source [^;]*;//g' <<<"${output}" )
eval "${output}"
if [[ -n "${error}" ]]; then
echo "${error}" 1>&2
fi
if [[ "${modulecmd}" == "${Tcl_cmd}" ]]; then
std::append_path PmFiles "${current_modulefile}"
fi
if [[ ${Verbosity_lvl} != silent ]]; then
[[ "${relstage}" == 'deprecated' ]] && print_deprecated_msg
print_module_load_msg "${relstage}"
fi
log_module_load_msg "${current_modulefile}" "${relstage}"
done
set_lmfiles
vars_to_be_exported['PMODULES_ENV']=1
vars_to_be_exported['LOADEDMODULES']=1
vars_to_be_exported[]'_LMFILES_']=1
}
##############################################################################
Subcommands['rm']='unload'
Subcommands['unload']='unload'
Options['unload']='-o \?H -l help'
Help['unload']='
USAGE:
module rm modulefile...
module unload modulefile...
Remove modulefile(s) from the shell environment. Removing
a "group-head" will also unload all modules belonging to
this group.
'
#..............................................................................
subcommand_unload() {
local -r __doc__='
Unload a module.
:FIXME:
add dependency tests: do not unload if module is required
be another module. For the time being the modules requiring
this module will be unloaded too.
'
local args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
-* )
die_args_invalid_opt "$1"
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
# The module() function uses PMODULES_HOME to call modulecmd.
# If a Pmodules module is unloaded this evnvironment variable
# will be unset. In consequence the module() function would
# fail. Instead of comparing the name of the module to 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}"
if [[ ! -v _LMFILES_ ]]; then
declare -x _LMFILES_=''
declare -x LOADEDMODULES=''
fi
IFS=':' read -r -a _lmfiles_ <<< "${_LMFILES_}"
local arg
local lmfile
for arg in "${args[@]}"; do
# is the module loaded?
for lmfile in "${_lmfiles_[@]}" '_zzzz_'; do
if [[ $lmfile =~ ${arg} ]]; then
break
fi
done
if [[ "${lmfile}" == '_zzzz_' ]]; then
continue
fi
# yes, module has been loaded
is_modulefile modulecmd "${lmfile}" || die_module_not_a_modulefile "${arg}"
local output=''
output=$("${modulecmd}" 'bash' 'unload' "${arg}")
if [[ -n "${output}" ]]; then
eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')"
fi
case ${Shell} in
sh | bash | zsh )
echo "${output}"
echo ""
;;
* )
"${modulecmd}" "${Shell}" 'unload' "${arg}"
echo ""
;;
esac
if [[ "${modulecmd}" == "${Tcl_cmd}" ]]; then
std::remove_path PmFiles "${lmfile}"
fi
done
if [[ ! -v PMODULES_HOME || -z ${PMODULES_HOME} ]]; then
declare -gx PMODULES_HOME="${saved_home}"
vars_to_be_exported['PMODULES_HOME']=1
fi
set_lmfiles
# maybe Lmod removed some directories from MODULEPATH?
local -- dir=''
local -a dirs=()
IFS=':' read -r -a dirs <<<"${ModulePathAppend}"
for dir in "${dirs[@]}"; do
std::append_path MODULEPATH "${dir}"
done
IFS=':' read -r -a dirs <<<"${ModulePathPrepend}"
for dir in "${dirs[@]}"; do
std::prepend_path MODULEPATH "${dir}"
done
if [[ -n "${LOADEDMODULES}" ]]; then
vars_to_be_exported['LOADEDMODULES']=1
vars_to_be_exported['_LMFILES_']=1
fi
vars_to_be_exported['PMODULES_ENV']=1
} # subcommand_unload
##############################################################################
Subcommands['switch']='swap'
Subcommands['swap']='swap'
Options['swap']='-o \?H -l help'
Help['swap']='
USAGE:
module switch [modulefile1] modulefile2
module swap [modulefile1] modulefile2
Switch loaded modulefile1 with modulefile2. If modulefile1
is not specified, then it is assumed to be the currently
loaded module with the same base name as modulefile2.
'
#..............................................................................
subcommand_swap() {
local -r __doc__='
Swap two modules.
'
local args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
(( ${#args[@]} > 2 )) && \
die_args_too_many
if (( ${#args[@]} == 1 )); then
local -r module_to_load=${args[0]}
local -r module_to_unload=${module_to_load%/*}
else
local -r module_to_unload=${args[0]}
local -r module_to_load=${args[1]}
fi
subcommand_unload "${module_to_unload}"
subcommand_load "${module_to_load}"
}
##############################################################################
Subcommands['display']='show'
Subcommands['show']='show'
Options['show']='-o \?H -l help'
Help['show']='
USAGE:
module display modulefile...
module show modulefile...
Display information about one or more modulefiles. The
display sub-command will list the full path of the
modulefile(s) and all (or most) of the environment changes
the modulefile(s) will make if loaded. It will not display
any environment changes found within conditional statements.
'
#..............................................................................
subcommand_show() {
local -r __doc__='Show module info.'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
local arg
for arg in "${args[@]}"; do
local -- modulefile=''
local -- relstage=''
local -- moduledir=''
find_modulefile \
modulefile \
relstage \
moduledir \
modulecmd \
"${arg}"
"${modulecmd}" 'bash' 'show' "${modulefile}"
done
}
#..............................................................................
get_available_modules() {
local -r __doc__='
Find all modules in a given modulepath matching a specific
string. The search can be restricted to certain release stages.
Return list like
(
module_name_1
release_stage_1
directory_1
relative_modulefile_1
overlay_1
group_1
module_name_2
...
)'
local -- mode="$1"
local -n result="$2" # [out] reference variable to return result
local -r pattern="$3" # [in] search pattern
local -r relstages="$4" # [in] excepted release stages
shift 4
local -a dirs=("$@") # [in] module path (absolute directory names)
local -A modules=()
local -A modulenames=()
local -- fpattern="${pattern}" # pattern used in find(1)
if [[ ${pattern} == */* ]]; then
# if pattern contains a slash, match against
# the part before the last slash.
# Example:
# If pattern is gcc/14, we have to match the string 'gcc'
# followed by a slash and a string not containing a slash.
fpattern=".*/${pattern%/*}/[^/]+"
else
# otherwise match max one slash
fpattern=".*/${pattern}[^/]*/*[^/]+"
fi
local -- opt_regex='-regex'
[[ "${mode}" != 'load' ]] && opt_regex='-iregex'
local -- opt_sort='-r'
[[ -z ${pattern} ]] && opt_sort=''
local -- dir=''
# loop over all entries in given module path
for dir in "${dirs[@]}"; do
test -d "${dir}" || continue
# find overlay and group for this directory
local -- ol_name=''
local -- group=''
find_overlay ol_name group "${dir}"
# loop over all files (and sym-links) in this directory and
# its sub-directories
local -- short_module_name='' # module name
local -- long_module_name='' # module name & version
while read -r rel_modulefile; do
IFS='/' read -a toks <<<"${rel_modulefile}"
if (( ${#toks[@]} == 0 )); then
continue
elif (( ${#toks[@]} == 1 )); then
short_module_name="${mod}"
long_module_name="${toks[0]}"
else
short_module_name="${toks[-2]}"
long_module_name="${toks[-2]}/${toks[-1]}"
fi
[[ -n ${OverlayExcludes} \
&& "${mod}" =~ ${OverlayExcludes} ]] && continue
local add='no'
if [[ -n "${ol_name}" && "${ol_name,,}" != 'none' ]]; then
# module is in an overlay
#
# add to list of available modules, if
# - first time found by name only
# - in same overlay as first found
# - new version and not hidden by overlay
if [[ ! -v modulenames["${short_module_name}"] ]]; then
# new entry
if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_hiding}" ]]; then
modulenames[${short_module_name}]="${ol_name}"
else
modulenames[${short_module_name}]='0'
fi
add='yes'
elif [[ "${modulenames[${short_module_name}]}" == "${ol_name}" ]]; then
add='yes'
elif [[ "${modulenames[${short_module_name}]}" == '0' ]] \
&& [[ ! -v modules[${rel_modulefile}] ]]; then
add='yes'
fi
else
# :FIXME:
add='yes' # module is NOT in an overlay
fi
[[ "${add}" == 'no' ]] && continue
if [[ "${mode}" == 'search' ]]; then
[[ "${long_module_name,,}" =~ ${pattern,,} ]] || continue
else
if [[ "${pattern}" == */* ]]; then
[[ "${long_module_name}" == ${pattern} || \
"${long_module_name}" == ${pattern}.lua ]] || \
continue
else
[[ "${long_module_name%/*}" == ${pattern} ]] || continue
fi
fi
local -A cfg=()
local -- relstage='stable'
if [[ "${OverlayInfo[${ol_name}:layout]}" == 'Pmodules' ]]; then
get_module_config cfg "${dir}" "${rel_modulefile}"
is_available cfg "${relstages}" || continue
relstage="${cfg['relstage']}"
elif [[ "${OverlayInfo[${ol_name}:layout]}" == 'Spack' ]]; then
if [[ ":${UsedReleaseStages}:" =~ :unstable: ]]; then
relstage='unstable'
fi
else
get_module_config cfg "${dir}" "${rel_modulefile}"
is_available cfg "${ReleaseStages}" || continue
fi
modules[${rel_modulefile}]=1
result+=(
"${long_module_name}"
"${relstage}"
"${dir}"
"${rel_modulefile}"
"${ol_name}"
"${group}" )
# return after first match, e.g. for loading a module
[[ "${mode}" != 'search' ]] && return 0
done < <(${find} -L "${dir}" \
-not -name ".*" \
\( "${opt_regex}" "${fpattern}" \
-regextype posix-basic \) \
\( -type f -o -type l \) \
-printf "%P\n" \
| ${sort} ${opt_sort} --version-sort)
done
} # get_available_modules()
#..............................................................................
find_modulefile(){
local -r __doc__='
find module(file) to load. The module name can be
name
name/version
relative path
absolute path
Return
0 if a modulefile has been found
1 else'
local -n ref_modulefile="$1"
local -n ref_relstage="$2"
local -n ref_moduledir="$3"
local -n ref_interp="$4"
local -r modulename="$5"
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
local -a mods=()
local -- relstages="${UsedReleaseStages}"
[[ "${modulename}" = */* ]] && relstages="${ReleaseStages}"
get_available_modules \
'load' \
mods \
"${modulename}" \
"${relstages}" \
"${modulepath[@]}"
(( ${#mods[@]} == 0 )) && return 1
ref_relstage="${mods[1]}"
ref_moduledir="${mods[2]}"
ref_modulefile="${mods[2]}/${mods[3]}"
is_modulefile ref_interp "${ref_modulefile}" || \
die_module_not_a_modulefile "${modulename}"
if [[ "${modulecmd}" == "${Lmod_cmd}" ]]; then
# Lmod doesn't support full qualified path names!
ref_modulefile="${ref_modulefile/${ref_moduledir}\/}"
fi
return 0
}
##############################################################################
Subcommands['avail']='avail'
Options['avail']='-l help -o \?Hahlmtg: -l all -l all-release-stages -l group: '
Options['avail']+='-l human -l long -l machine -l terse'
Help['avail']="
USAGE:
module avail [switches] string
List all available modulefiles in the current MODULEPATH. If
an argument is given, then each directory in the MODULEPATH
is searched for modulefiles whose pathname match the argument.
This command does *not* display all installed modules on the
system. Only *loadable* modules are listed. The list of
available modules may change either by loading other modules,
e.g. a compiler, or with the sub-command 'use'.
SWITCHES:
-a|--all||--all-release-stages
List all available modules independend of the release stage.
-t|--terse
Output in short format.
-l|--long
Output in long format.
-g|--group=<GROUP>
Output modules available in <GROUP>
-h|--human
Output in human readable format.
-m|--machine
Output in machine readable format
"
#..............................................................................
subcommand_avail() {
local -r __doc__='List available modules.'
# use this variable in the output functions
local -a mods=()
local -- dir=''
# get number of columns of terminal, set to a default if not running
# in a terminal.
local -i cols=80
[[ -t 1 && -t 2 ]] && cols=$(tput cols)
#......................................................................
output_header() {
local -i i=$1
# use group name, overlay name or directory
local -- caption="${mods[i+5]}" # group name
if [[ "${caption,,}" == 'none' ]]; then
caption="${mods[i+4]}" # overlay name
if [[ "${caption,,}" == 'none' ]]; then
caption="${mods[i+2]}" # directory
fi
fi
(( i != 0 )) && printf -- "\n\n" 1>&2
local -i i=0
(( i=(cols-${#caption})/2-2 ))
printf -- "%0.s-" $(seq 1 "$i") 1>&2
printf -- " %s " "${caption}" 1>&2
printf -- "%0.s-" $(seq 1 "$i") 1>&2
printf -- "\n" 1>&2
}
#......................................................................
terse_output() {
local -- cur_group=''
local -- cur_dir=''
local -i i=0
for (( i=0; i<${#mods[@]}; i+=6 )); do
if [[ "${cur_group}" != "${mods[i+5]}" ]] || \
[[ "${cur_group}" == 'none' && \
"${cur_dir}" != "${mods[i+2]}" ]]; then
output_header "$i"
cur_group="${mods[i+5]}"
cur_dir="${mods[i+2]}"
fi
local mod=${mods[i]%.lua}
local relstage=${mods[i+1]}
case ${relstage} in
stable )
out=''
;;
* )
out="${relstage}"
;;
esac
printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
done
std::info ""
}
#......................................................................
machine_output() {
for (( i=0; i<${#mods[@]}; i+=6 )); do
printf "%-20s\t%s\n" "${mods[i]}" "${mods[i+1]}" 1>&2
done
}
#......................................................................
# :FIXME: for the time being, this is the same as terse_output!
long_output() {
local -- cur_group=''
local -- cur_dir=''
local -i i=0
for (( i=0; i<${#mods[@]}; i+=6 )); do
if [[ "${cur_group}" != "${mods[i+5]}" ]] || \
[[ "${cur_group}" == 'none' && \
"${cur_dir}" != "${mods[i+2]}" ]]; then
output_header "$i"
cur_group="${mods[i+5]}"
cur_dir="${mods[i+2]}"
fi
local mod=${mods[i]%.lua}
local relstage=${mods[i+1]}
case ${relstage} in
stable )
out=''
;;
* )
out=${relstage}
;;
esac
printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
done
std::info ""
}
#......................................................................
human_readable_output() {
local -- cur_group=''
local -- cur_dir=''
local mod=''
local -i colsize=16
local -i column=$cols # force a line-break
for ((i=0; i<${#mods[@]}; i+=6)); do
# print header if
# - module is in another group or overlay
# - group == none && overlay == none and module is in another dir
if [[ "${cur_group}" != "${mods[i+5]}" ]] || \
[[ "${cur_group}" == 'none' && \
"${cur_dir}" != "${mods[i+2]}" ]]; then
output_header "$i"
cur_group="${mods[i+5]}"
cur_dir="${mods[i+2]}"
fi
if [[ ${Verbosity_lvl} == 'verbose' ]]; then
local relstage=${mods[i+1]}
case ${relstage} in
stable )
mod="${mods[i]%.lua}"
;;
* )
mod="${mods[i]%.lua}(${relstage:0:1})"
;;
esac
else
mod=${mods[i]}
fi
local -i len=${#mod}
if (( column+len >= cols )); then
printf -- "\n" 1>&2
column=0
fi
local -i size=0
(( size=((len)/colsize+1)*colsize ))
if (( column+size < cols )); then
printf "%-${size}s" "${mod}" 1>&2
else
printf "%-s" "${mod}" 1>&2
fi
column+=size
done
printf -- "\n\n" 1>&2
}
#......................................................................
local pattern=()
local output_function='human_readable_output'
local opt_use_relstages="${UsedReleaseStages}"
local -A opt_groups=()
local val=''
while (($# > 0)); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-a | --all | --all-release-stages )
opt_use_relstages="${ReleaseStages}"
;;
-h | --human )
output_function='human_readable_output'
;;
-l | --long )
output_function='long_output'
;;
-t | --terse )
output_function='terse_output'
;;
-m | --machine )
output_function='machine_output'
;;
-g | --group | --group=* )
if [[ $1 == --group=* ]]; then
val="${1#--*=}"
else
val="$2"
shift
fi
opt_groups[${val}]=1
;;
-- )
shift 1
pattern+=( "$@" )
break
;;
* )
pattern+=( "$1" )
;;
esac
shift
done
if (( ${#pattern[@]} == 0 )); then
pattern+=( '' )
fi
# With overlays we can have multiple directories per group!
# To find the modules in a given group, we have to loop over
# these directories. In the for loop below, we create a
# 'modulepath' per group and a list of groups. We loop over
# this list of groups in the second for-loop.
local -- dir=''
local -- group=''
local -- groups=''
local -- ol=''
local -A modulepath_of_group=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
for dir in "${modulepath[@]}"; do
find_overlay ol group "${dir}" || group="${dir}"
[[ -v modulepath_of_group[${group}] ]] || modulepath_of_group[${group}]=''
typeset -n tmp="modulepath_of_group[${group}]"
std::append_path tmp "${dir}"
if (( ${#opt_groups[@]} > 0 )); then
# add only groups specified on the command line
[[ -v opt_groups[${group}] ]] || continue
fi
std::append_path groups "${group}"
done
local -a modulepath=()
for group in ${groups//:/ }; do
modulepath+="${modulepath_of_group[${group}]}:"
done
local -a path=()
IFS=':' read -r -a path <<<"${modulepath%:}"
for string in "${pattern[@]}"; do
get_available_modules \
'search' \
mods \
"${string}" \
"${opt_use_relstages}" \
"${path[@]}"
${output_function}
done
} # subcommand_avail()
##############################################################################
Subcommands['use']='use'
Options['use']='-l help -o \?Hap -l append -l prepend'
Help['use']="
USAGE:
module use [-a|--append|-p|--prepend] [directory|group|release_stage|...]
Without arguments this sub-command displays information about
the module search path, used groups and release stages. You can
use this sub-command to get a list of available groups and
releases stages.
With a directory as argument, this directory will either be
prepended or appended to the module search path. The default
is to prepend the directory.
With a group as argument, the modules in this group will
be made available.
With a release as argument, this modules with this release
will be made available.
SWITCHES:
-a | --append -p | --prepend )
Append/prepend agrument to module search path or list of to be
searched releases.
"
#..............................................................................
set_ol_modulepaths(){
local -r __doc__='
Prepend overlay specific directories to MODULEPATH.
If unstable is used:
prepend modulepath_unstable if not empty
otherwise prepend modulepath
else if only stable is used
prepend modulepath_stable if not empty
otherwise prepend modulepath
else if deprecated modules are available
prepend modulepath_deprecated if not empty
otherwise prepend modulepath.'
local -r ol_name="$1" # [in] name of overlay
local -- path=''
if [[ ":${UsedReleaseStages}:" == *:unstable:* ]]; then
path="${OverlayInfo[${ol_name}:modulepath_unstable]}"
elif [[ ":${UsedReleaseStages}:" == *:deprecated:* ]]; then
path="${OverlayInfo[${ol_name}:modulepath_deprecated]}"
else
path="${OverlayInfo[${ol_name}:modulepath_stable]}"
fi
[[ -z "${path}" ]] && path="${OverlayInfo[${ol_name}:modulepath]}"
[[ -z "${path}" ]] && return 0
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${path}"
local -- dir=''
for dir in "${modulepath[@]}"; do
std::prepend_path MODULEPATH "${dir}"
std::prepend_path ModulePathAppend "${dir}"
std::prepend_path ModulePathPrepend "${dir}"
done
}
#..............................................................................
unset_ol_modulepaths(){
local -r __doc__='Remove additional directories added by overlay'
local -r ol_name="$1" # [in] name of overlay
local -- path=''
if [[ -n "${OverlayInfo[${ol_name}:modulepath]}" ]]; then
path+="${OverlayInfo[${ol_name}:modulepath]}:"
fi
if [[ -n "${OverlayInfo[${ol_name}:modulepath_unstable]}" ]]; then
path+="${OverlayInfo[${ol_name}:modulepath_unstable]}:"
fi
if [[ -n "${OverlayInfo[${ol_name}:modulepath_stable]}" ]]; then
path+="${OverlayInfo[${ol_name}:modulepath_stable]}:"
fi
if [[ -n "${OverlayInfo[${ol_name}:modulepath_deprecated]}" ]]; then
path+="${OverlayInfo[${ol_name}:modulepath_deprecated]}:"
fi
[[ -z "${path}" ]] && return 0
path="${path%:}"
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${path}"
local -- dir=''
for dir in "${modulepath[@]}"; do
std::remove_path MODULEPATH "${dir}"
std::remove_path ModulePathAppend "${dir}"
std::remove_path ModulePathPrepend "${dir}"
done
}
#..............................................................................
subcommand_use() {
local -r __doc__='Implementation of the sub-command use.'
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
local add2path_func='std::append_path'
#......................................................................
group_is_used() {
[[ :${UsedGroups}: =~ :$1: ]]
}
#......................................................................
print_info() {
print_ol_info(){
local used="$1" # print used or unused overlays
local ol=''
for ol in "${Overlays[@]}"; do
[[ ${OverlayInfo[${ol}:used]} == "${used}" ]] || continue
local install_root="${OverlayInfo[${ol}:install_root]}"
local modulefiles_root="${OverlayInfo[${ol}:modulefiles_root]}"
local txt="\t${ol}"
if [[ ${install_root} == "${modulefiles_root}" ]]; then
txt+="\n\t\t${install_root}"
else
txt+="\n\t\t${install_root} (install root)"
txt+="\n\t\t${modulefiles_root} (modulefiles root)"
fi
case "${OverlayInfo[${ol}:type]}" in
"${ol_hiding}" )
txt+='\n\t\t(hiding modules with same name)'
;;
"${ol_replacing}" )
txt+='\n\t\t(replacing groups)'
;;
esac
std::info "${txt}"
done
}
local f
local r
std::info "Used groups:"
for f in ${UsedGroups//:/ }; do
std::info "\t${f}"
done
std::info ''
std::info "Unused groups:"
local _group
for _group in "${!GroupDepths[@]}"; do
local -i depth=${GroupDepths[${_group}]}
if ! group_is_used "${_group}" && (( depth == 0 )); then
std::info "\t${_group}"
fi
done
std::info "\nUsed releases stages:"
for r in ${UsedReleaseStages//:/ }; do
std::info "\t${r}"
done
std::info "\nUnused release stages:"
for r in ${ReleaseStages//:/ }; do
[[ ! ":${UsedReleaseStages}:" =~ :$r: ]] && std::info "\t${r}"
done
std::info ''
std::info "Used overlays:"
print_ol_info 'yes'
std::info ''
std::info "Unused overlays:"
print_ol_info 'no'
std::info ''
std::info "Additonal directories in MODULEPATH:"
local -i i=0
local -i n=0
local group
for (( i=0; i<${#modulepath[@]}; i++)); do
if ! find_overlay ol group "${modulepath[i]}"; then
std::info "\t${modulepath[i]}"
(( n+=1 ))
fi
done
(( n == 0 )) && std::info "\tnone"
std::info ""
} # print_info
#......................................................................
use () {
#..............................................................
use_relstage(){
local -r relstage="$1"
[[ ":${UsedReleaseStages}:" =~ :${relstage}: ]] && return 0
std::append_path UsedReleaseStages "${relstage}" || rc=$?
local -- ol_name=''
for ol_name in "${UsedOverlays[@]}"; do
[[ "${OverlayInfo[${ol_name}:has_additional_modulepaths]}" == 'false' ]] && \
continue
unset_ol_modulepaths "${ol_name}"
set_ol_modulepaths "${ol_name}"
done
}
#..............................................................
use_overlay() {
local ol_name="$1"
[[ -n "${LOADEDMODULES}" ]] && \
[[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]] && \
die_ol_cannot_be_added "${ol_name}"
[[ ${OverlayInfo[${ol_name}:used]} == 'yes' ]] && return 0
local -a conflicts=( "${OverlayInfo[${ol_name}:conflicts]//:/ }" )
for ol in "${UsedOverlays[@]}"; do
for conflict in "${conflicts[@]}"; do
[[ "${ol}" == ${conflict} ]] && \
die_ol_conflict "${ol_name}" "${ol}"
done
done
if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then
# this overlay is masking groups. So, we have to remove
# the corresponding directories of the already used
# overlays from MODULEPATH.
for group in ${UsedGroups//:/ }; do
MaskedGroups[${group}]=''
local -- dir=''
# if this overlay has groups, we have to check whether
# ${group} exists in this overlay. If not, we have to
# do nothing.
# if this overlay doesn't have groups, we remove all
# groups.
if [[ "${OverlayInfo[${ol_name}:layout]}" == 'Pmodules' ]]; then
dir="${OverlayInfo[${ol_name}:modulefiles_root]}/"
dir+="${group}/${__MODULEFILES_DIR__}"
# is this group in this overlay?
[[ -d "${dir}" ]] || continue # no
fi
dir="/${group}/${__MODULEFILES_DIR__}"
local -a dirs=()
for ol in "${UsedOverlays[@]}"; do
dirs+=( "${OverlayInfo[${ol}:modulefiles_root]}${dir}" )
done
std::remove_path MODULEPATH "${dirs[@]}"
MaskedGroups[${group}]="${ol_name}:${MaskedGroups[${group}]}"
done
fi
if [[ -n "${OverlayInfo[${ol_name}:groups]}" ]]; then
local -- grp_changes=':'
local -a groups=()
IFS=':' read -r -a groups <<< "${OverlayInfo[${ol_name}:groups]}"
local -- group=''
for group in "${groups[@]}"; do
if [[ "${group:0:1}" == '~' ]]; then
if [[ ":${UsedGroups}:" == *:${group:1}:* ]]; then
subcommand_unuse "${group:1}"
grp_changes+="${group}:"
fi
else
if [[ ":${UsedGroups}:" != *:${group}:* ]]; then
use_group "${group}"
grp_changes+="${group}:"
fi
fi
done
OverlayInfo[${ol_name}:grp_changes]="${grp_changes:0: -1}"
fi
scan_groups "${ol_name}"
for group in ${UsedGroups//:/ }; do
local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/"
dir+="${group}/${__MODULEFILES_DIR__}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
fi
done
set_ol_modulepaths "${ol_name}"
UsedOverlays=( "${ol_name}" "${UsedOverlays[@]}" )
OverlayInfo[${ol_name}:used]='yes'
local -a excludes=()
IFS=':' read -r -a excludes <<< "${OverlayInfo[${ol_name}:excludes]}"
local -- item=''
for item in "${excludes[@]}"; do
OverlayExcludes+="${item}|"
done
if [[ -n "${OverlayExcludes}" ]]; then
OverlayExcludes="${OverlayExcludes:0: -1}"
fi
scan_groups "${UsedOverlays[@]}"
}
#..............................................................
use_group() {
local -- grp="$1"
# die if argument is a hierarchical group
(( ${GroupDepths[${grp}]} > 0 )) && \
die_grp_invalid "${grp}"
std::append_path UsedGroups "${grp}"
local -- ol_name
local -i i=0
local -i n="${#UsedOverlays[@]}"
for ((i=n-1; i>=0; i--)); do
ol_name="${UsedOverlays[i]}"
local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/${grp}/${__MODULEFILES_DIR__}"
[[ -d "${dir}" ]] || continue
std::prepend_path MODULEPATH "${dir}"
[[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]] && break
done
}
#..............................................................
local -- arg="$1"
local -i rc=0
if is_release_stage "${arg}"; then
use_relstage "${arg}" || rc=$?
return ${rc}
fi
if [[ -v OverlayInfo[${arg}:type] ]]; then
use_overlay "${arg}" || rc=$?
return ${rc}
fi
if [[ ! -v GroupDepths[${arg}] ]]; then
# this scan is required if a new group has been
# create inside an used overlay
scan_groups "${UsedOverlays[@]}"
fi
if [[ -v GroupDepths[${arg}] ]]; then
use_group "${arg}" || rc=$?
return ${rc}
fi
if [[ -d ${arg} ]]; then
local dir=''
dir=$(std::get_abspath "${arg}")
if [[ "${opt_append}" == 'yes' ]]; then
std::append_path MODULEPATH "${dir}" || rc=$?
std::append_path ModulePathAppend "${dir}" || rc=$?
else
std::prepend_path MODULEPATH "${dir}" || rc=$?
std::prepend_path ModulePathPrepend "${dir}" || rc=$?
fi
return ${rc}
fi
die_args_invalid_for_subcmd_use "${arg}"
} # use ()
#......................................................................
local -a args=()
opt_append='yes'
while (( $# > 0)); do
case "$1" in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-a | --append )
opt_append='yes'
;;
-p | --prepend )
opt_append='no'
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
print_info
return
fi
for arg in "${args[@]}"; do
use "${arg}"
done
vars_to_be_exported['PMODULES_ENV']=1
vars_to_be_exported['MODULEPATH']=1
}
##############################################################################
Subcommands['unuse']='unuse'
Options['unuse']='-o \?H -l help'
Help['unuse']='
unuse directory|group|release...
Remove the given modulefiles directory, group, release stage,
flag from the search path.
'
#..............................................................................
subcommand_unuse() {
local -r __doc__='Implementation of the sub-command unuse.'
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
#......................................................................
unuse() {
unuse_relstage() {
local -r relstage="$1"
[[ ! ":${UsedReleaseStages}:" =~ :${relstage}: ]] && return 0
std::remove_path UsedReleaseStages "${relstage}"
local -- ol_name=''
for ol_name in "${UsedOverlays[@]}"; do
[[ "${OverlayInfo[${ol_name}:has_additional_modulepaths]}" == 'false' ]] && \
continue
unset_ol_modulepaths "${ol_name}"
set_ol_modulepaths "${ol_name}"
done
}
#..............................................................
unuse_overlay() {
local ol_name="$1"
if [[ -n "${LOADEDMODULES}" ]] && \
[[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]]; then
die_ol_cannot_be_removed
fi
[[ "${ol_name}" == 'base' ]] && \
die_ol_cannot_remove_base
[[ "${OverlayInfo[${ol_name}:used]}" != 'yes' ]] && \
die_ol_not_used "${ol_name}"
# make sure first index is '0' (it should, but you never know)
UsedOverlays=( "${UsedOverlays[@]}" )
[[ "${ol_name}" != "${UsedOverlays[0]}" ]] && \
die_ol_not_on_top_of_stack "${ol_name}"
OverlayInfo[${ol_name}:used]='no'
UsedOverlays=( "${UsedOverlays[@]:1}")
if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then
# if this overlay masks groups, we have to re-add
# some directories to MODULEPATH.
for group in ${UsedGroups//:/ }; do
local -a overlays=( ${MaskedGroups[${group}]//:/ } )
(( ${#overlays[@]} == 0)) && continue
[[ "${ol_name}" != "${overlays[0]}" ]] && continue
# remove overlay "${ol_name}"
local -- s
printf -v s "%s:" "${overlays[@]:1}"
MaskedGroups[${group}]="$s"
local -- ol=''
local -- dir=''
if (( ${#overlays[@]} == 1 )); then
# Only overlay ${ol_name} is masking this group.
# Add the group directories for all overlays.
for ol in "${UsedOverlays[@]}"; do
dir="${OverlayInfo[${ol}:modulefiles_root]}/"
dir+="${group}/${__MODULEFILES_DIR__}"
[[ -d "${dir}" ]] || continue
std::prepend_path MODULEPATH "${dir}"
done
continue
fi
# There is at least one more overlay masking this group.
# If this overlay has no groups, we have to do nothing.
ol="${overlays[1]}"
[[ "${OverlayInfo[${ol}:layout]}" != 'Pmodules' ]] && continue
# add group directory for this overlay, if exists
dir="${OverlayInfo[${ol}:modulefiles_root]}/"
dir+="${group}/${__MODULEFILES_DIR__}"
[[ -d "${dir}" ]] || continue
std::prepend_path MODULEPATH "${dir}"
done
fi
if [[ -n "${OverlayInfo[${ol_name}:groups]}" ]]; then
local -a groups=()
IFS=':' read -r -a groups <<< "${OverlayInfo[${ol_name}:grp_changes]}"
local -- group=''
for group in "${groups[@]}"; do
if [[ "${group:0:1}" == '~' ]]; then
if [[ ":${UsedGroups}:" != *:${group:1}:* ]]; then
subcommand_use "${group:1}"
fi
else
if [[ ":${UsedGroups}:" == *:${group}:* ]]; then
unuse_group "${group}"
fi
fi
done
OverlayInfo[${ol_name}:grp_changes]=':'
fi
# rebuild exclude list.
# Note:
# A module might be excluded in multiple overlays. So, we cannot
# just remove the excludes from the overlay to unuse.
OverlayExcludes=''
local -- ol=''
local -a excludes=()
local -- item=''
for ol in "${UsedOverlays[@]}"; do
IFS=':' read -r -a excludes <<< "${OverlayInfo[${ol}:excludes]}"
for item in "${excludes[@]}"; do
OverlayExcludes+="${item}|"
done
done
if [[ -n "${OverlayExcludes}" ]]; then
OverlayExcludes="${OverlayExcludes:0: -1}"
fi
unset_ol_modulepaths "${ol_name}"
# remove root of overlay
local dir
for dir in "${modulepath[@]}"; do
[[ "${dir}" == "${OverlayInfo[${ol_name}:modulefiles_root]}" ]] && \
std::remove_path MODULEPATH "${dir}"
done
vars_to_be_exported['PMODULES_ENV']=1
}
#..............................................................
unuse_group() {
local -- grp="$1"
(( ${GroupDepths[${grp}]} > 0 )) && \
die_grp_invalid "${grp}"
if [[ -v PMODULES_LOADED_${grp^^} ]]; then
local var="PMODULES_LOADED_${grp^^}"
[[ -n "${!var}" ]] && \
die_grp_cannot_be_removed "${grp}"
fi
std::remove_path UsedGroups "${grp}"
local overlay
for overlay in "${UsedOverlays[@]}"; do
local dir="${OverlayInfo[${overlay}:modulefiles_root]}"
dir+="/${grp}/${__MODULEFILES_DIR__}"
std::remove_path MODULEPATH "${dir}"
done
}
#..............................................................
local arg=$1
if is_release_stage "${arg}"; then
unuse_relstage "${arg}"
return 0
fi
if [[ -v OverlayInfo[${arg}:type] ]]; then
unuse_overlay "${arg}"
return 0
fi
if [[ -d ${arg} ]]; then
local dir=''
dir=$(std::get_abspath "${arg}")
std::remove_path MODULEPATH "${dir}"
std::remove_path ModulePathAppend "${dir}"
std::remove_path ModulePathPrepend "${dir}"
return 0
fi
if [[ -v GroupDepths[${arg}] ]]; then
unuse_group "${arg}"
return 0
fi
die_args_invalid_for_subcmd_use "${arg}"
} # unuse()
#......................................................................
local -a args=()
while (( $# > 0)); do
case "$1" in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && die_args_missing
for arg in "${args[@]}"; do
unuse "${args[@]}"
done
vars_to_be_exported['PMODULES_ENV']=1
vars_to_be_exported['MODULEPATH']=1
}
##############################################################################
Subcommands['update']='update'
Options['update']='-o \?H -l help'
Help['update']='
USAGE:
module update
Attempt to reload all loaded modulefiles.
'
#..............................................................................
subcommand_update() {
local -r __doc__'
Implementation of the sub-command update
:FIXME:
Either compile Modules with --enable-beginenv or remove the
sub-command'
subcommand_generic0 "$@"
}
##############################################################################
Subcommands['refresh']='refresh'
Options[refresh]='-o \?H -l help'
Help[refresh]='
USAGE:
module refresh
Force a refresh of all non-persistent components of currently
loaded modules. This should be used on derived shells where
aliases need to be reinitialized but the environment variables
have already been set by the currently loaded modules.
'
#..............................................................................
subcommand_refresh() {
local -r __doc__='Implementation of the sub-command refresh.'
subcommand_generic0 "$@"
}
#..............................................................................
pmodules_setup() {
local -r __doc__='Setup/initialize Pmodules environment.'
local -r mode="${1:-check}"
init_used_groups() {
declare -gx UsedGroups=''
local group
for group in ${DefaultGroups//:/ }; do
std::append_path UsedGroups "${group}"
done
vars_to_be_exported['PMODULES_ENV']=1
}
init_used_releases() {
declare -g UsedReleaseStages=''
for r in ${DefaultReleaseStages//:/ }; do
std::append_path UsedReleaseStages "${r}"
done
vars_to_be_exported['PMODULES_ENV']=1
}
init_overlay_vars() {
declare -agx UsedOverlays=( 'base' )
OverlayInfo['base:used']='yes'
declare -g OverlayExcludes=''
scan_groups "${UsedOverlays[@]}"
vars_to_be_exported['PMODULES_ENV']=1
}
init_modulepath() {
declare -gx MODULEPATH=''
local group
local ol
for ol in "${UsedOverlays[@]}"; do
for group in ${UsedGroups//:/ }; do
local dir="${OverlayInfo[${ol}:modulefiles_root]}"
dir+="/${group}/${__MODULEFILES_DIR__}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
fi
done
done
vars_to_be_exported['MODULEPATH']=1
}
init_manpath() {
: "
Initialize MANPATH. Use systen wide configuration file
if exist or set some defaults.
"
std::remove_path MANPATH "${PMODULES_HOME%/*}/.*"
if [[ -r /etc/man.config ]]; then
declare _manconf='/etc/man.config'
elif [[ -r /etc/man.conf ]]; then
declare _manconf='/etc/man.conf'
fi
if [[ -v _manconf ]]; then
# initialize from system configuration file
while read -r name value rest; do
std::append_path MANPATH "${value}"
done < <(grep "^MANPATH\s" "${_manconf}")
unset _manconf
else
# set defaults
std::append_path MANPATH "${PMODULES_HOME}/share/man"
std::append_path MANPATH "/usr/share/man"
fi
vars_to_be_exported['MANPATH']=1
}
init_load_modules(){
declare -gx LOADEDMODULES=''
declare -gx _LMFILES_=''
}
# We (re-)initialise the Pmodules system, if
# PMODULES_ENV was not set/is empty
[[ -v PMODULES_ENV ]] || PMODULES_ENV=''
if [[ -z ${PMODULES_ENV} || ${mode} = 'init' ]]; then
pm::read_config
init_load_modules
init_used_groups
init_used_releases
init_overlay_vars
scan_groups "${UsedOverlays[@]}"
init_modulepath
init_manpath
else
# restore variables from last call
eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)"
if (( ${#UsedOverlays[@]} == 0 )); then
pm::read_config
fi
[[ -z "${LOADEDMODULES}" || -z ${_LMFILES_} ]] && init_load_modules
[[ -z "${UsedGroups}" ]] && init_used_groups
[[ -z "${UsedReleaseStages}" ]] && init_used_releases
(( ${#UsedOverlays[@]} == 0 )) && init_overlay_vars
(( ${#GroupDepths[@]} == 0 )) && scan_groups "${UsedOverlays[@]}"
[[ -z "${MODULEPATH}" ]] && init_modulepath
[[ -z "${MANPATH}" ]] && init_manpath
fi
[[ -z Version ]] || Version='0.0.0'
if [[ "${Version}" != "${PMODULES_VERSION}" ]]; then
Version="${PMODULES_VERSION}"
vars_to_be_exported[PMODULES_HOME]=1
fi
save_env
}
##############################################################################
Subcommands['purge']='purge'
Options['purge']='-o \?H -l help'
Help['purge']='
USAGE:
module purge
Unload all loaded modulefiles.
'
#..............................................................................
subcommand_purge() {
local -r __doc__='
Implementation of the sub-command purge.
Note:
If a Pmodule module is loaded, it will *not* be
unloaded!'
local -a args=()
while (( $# > 0)); do
case "$1" in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
-* )
die_args_invalid_opt "$1"
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} > 0 )) && die_args_not_allowed
# unload all modules (except Pmodules itself)
IFS=':' read -r -a modules <<< "${LOADEDMODULES}"
for (( i=${#modules[@]}-1; i>=0; i-- )); do
[[ ${modules[$i]} == Pmodules/* ]] && continue
subcommand_unload "${modules[$i]}"
done
# re-initialize Pmodules
# - save
# - used release stages
local -- saved_relstages="${UsedReleaseStages}"
# - used groups
local -- saved_groups="${UsedGroups}"
# - used overlays
local -- saved_overlays=''
printf -v saved_overlays "%s:" "${UsedOverlays[@]}"
saved_overlays="${saved_overlays%:}"
# - additional directories in MODULEPATH
local -- item=''
local -- group=''
local -a items=()
local -- saved_modulepath=''
IFS=':' read -r -a items <<<"${MODULEPATH}"
for item in "${items[@]}"; do
find_overlay ol group "${item}" && continue
saved_modulepath+="${item}:"
done
# - re-initialize PMODULES_ENV
pmodules_setup init
# - restore
IFS=':' read -r -a items <<<"${saved_relstages}"
for item in "${items[@]}"; do
subcommand_use "${item}"
done
IFS=':' read -r -a items <<<"${saved_overlays}"
for item in "${items[@]}"; do
[[ -v OverlayInfo[${item}:used] ]] || continue
subcommand_use "${item}"
done
IFS=':' read -r -a items <<<"${saved_groups}"
for item in "${items[@]}"; do
[[ -v GroupDepths[${item}] ]] || continue
subcommand_use "${item}"
done
IFS=':' read -r -a items <<<"${saved_modulepath}"
for item in "${items[@]}"; do
[[ -d "${item}" ]] || continue
subcommand_use "${item}"
done
}
##############################################################################
Subcommands['list']='list'
Options['list']='-l help -o \?Hhlt -l human -l long -l terse'
Help['list']='
USAGE:
module list
List loaded modules.
'
#..............................................................................
subcommand_list() {
local -r __doc__='Implementation of the sub-command list.'
human_readable_output(){
# get list of loaded modules with stripped MODULEPATH
IFS=':' read -r -a modules \
< <( ${sed} "s;${MODULEPATH//:/\/\\\|}/;;g" <<< "${_LMFILES_}" )
local -- strs=()
local -i n=1 # enumeration of loaded modules
local -i colsize=0
local -- s=''
for module in "${modules[@]%.lua}"; do
s=$(printf "%2d) %-s" "$n" "${module}")
strs+=( "$s" )
(( ${#s} > colsize )) && (( colsize=${#s} ))
(( n+=1 ))
done
(( colsize+=2 ))
local -i cols=80
[[ -t 1 && -t 2 ]] && cols=$(${tput} cols)
local -i column=0
printf "Currently Loaded Modules:\n" 1>&2
for s in "${strs[@]}"; do
local -i len=${#s}
if (( column+len >= cols )); then
printf -- "\n" 1>&2
column=0
fi
if (( column+colsize < cols )); then
printf "%-${colsize}s" "$s" 1>&2
else
printf "%-s" "$s" 1>&2 # last column
fi
(( column+=colsize ))
done
printf -- "\n\n" 1>&2
}
long_output(){
IFS=':' read -r -a modules \
< <( ${sed} "s;${MODULEPATH//:/\/\\\|}/;;g" <<< "${_LMFILES_}" )
IFS=':' read -r -a lmfiles <<< "${_LMFILES_}"
printf "Currently Loaded Modules:\n" 1>&2
local -i fmt_field_width=0
local -i length=0
local module
for module in "${modules[@]}"; do
length=${#module}
(( length > fmt_field_width )) && fmt_field_width=length
done
for (( i=0; i<${#lmfiles[@]}; i++ )); do
mtime=$(date -r "${lmfiles[i]}" +"%Y-%m-%d %H:%M:%S")
printf "%-${fmt_field_width}s\t%s\n" "${modules[i]}" "${mtime}" 1>&2
done
}
terse_output(){
IFS=':' read -r -a modules \
< <( ${sed} "s;${MODULEPATH//:/\/\\\|}/;;g" <<< "${_LMFILES_}" )
printf "Currently Loaded Modules:\n" 1>&2
for module in "${modules[@]%.lua}"; do
printf "%s\n" "${module}" 1>&2
done
printf -- "\n\n" 1>&2
}
local args=()
local output_function='human_readable_output'
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-h | --human )
output_function='human_readable_output'
;;
-l | --long )
output_function='long_output'
;;
-t | --terse )
output_function='terse_output'
;;
-- )
shift 1
args+=( "$@" )
break
;;
-* )
die_args_invalid_opt "$1"
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} > 0 )) && \
die_args_not_allowed
"${output_function}"
}
##############################################################################
Subcommands['clear']='clear'
Options['clear']='-o \?H -l help'
Help['clear']='
USAGE:
module clear
Force the Modules package to believe that no modules are
currently loaded.
'
#..............................................................................
subcommand_clear() {
local -r __doc__='Implementation of the sub-command clear.'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} > 0 )) && die_args_not_allowed
pmodules_setup 'init'
}
##############################################################################
Subcommands['search']='search'
Subcommands['find']='search'
Subcommands['spider']='search'
Options['search']='-o a\?H -l help -l no-header -l print-modulefiles '
Options['search']+='-l release-stage: -l with: -l all-release-stages -l src: -l print-csv '
Options['search']+='-l verbose '
Options['search']+='-l all-deps -l wrap '
Options['search']+='-l glob '
Options['search']+='-l newest '
Options['search']+='-l group:'
Help['search']='
USAGE:
module find|search|spider [switches] STRING...
Search installed modules. If an argument is given, search
for modules whose name match the argument.
SWITCHES:
-a|--all-release-stages
Search within all releases.
--all-deps
Show all dependecies
--glob
Interpret STRING as shell pattern.
--no-header
Suppress output of a header.
--release-stage=RELEASE_STAGE
Search for modules within this release stage. You can specify
this switch multiple times. Without this switch, the release
stages in use will be searched.
--verbose
vebose output
--with=STRING
Search for modules compiled with modules matching string. The
command
module search --with=gcc/4.8.3
lists all modules in the hierarchy compiled with gcc 4.8.3.
--wrap
wrap output
'
#..............................................................................
subcommand_search() {
local -r __doc__='Implementation of the sub-command search.'
local -a modules=()
local -a groups=()
local -- group=''
local -- with_modules='//'
local -i cols=80
[[ -t 1 && -t 2 ]] && cols=$(${tput} cols) # get number of columns of terminal
local -i max_len_modulename=0
local opt_print_header='yes'
local opt_print_modulefiles='no'
local opt_print_csv='no'
local opt_print_verbose='no'
local opt_use_relstages=':'
local opt_all_deps='no'
local opt_wrap='no'
local opt_newest='no'
local -- opt_print_raw='no'
local -A spider_keys=()
local -- spider_output=''
#.....................................................................
#
# output result of search
# Args:
# $1: tmp file
#
# variables used from enclosing function:
# opt_print_header
# opt_print_modulefiles
# with_modules
#
print_result() {
local func_print_header=''
local func_print_line=''
local fmt=''
print_default() {
fmt="%-${max_len_modulename}s %-10s %-12s %-12s %-s"
if [[ ${opt_print_header} == 'yes' ]]; then
func_print_header='print_header_default'
else
func_print_header='print_header_none'
fi
func_print_line='print_line_default'
}
print_header_default() {
std::info ''
std::info "${fmt}" "Module" "Rel.stage" "Group" "Overlay" "Requires"
std::info '-%.0s' $(seq 1 "${cols}")
}
print_line_default() {
write_line() {
local str="$1"
if [[ -t 1 && -t 2 ]] && (( ${#str} >= cols )); then
str="${str:0:$((cols-1))}>"
fi
std::info "${str}"
}
local -a deps=( "${@:6}" )
(( ${#deps[@]} == 0 )) && deps[0]=''
local -- str=''
if [[ "${opt_wrap}" == 'no' ]]; then
str=$(printf "${fmt}" "${1/.lua}" "$2" "$3" "$5" "${deps[*]}")
write_line "${str}"
else
str=$(printf "${fmt}" "${1/.lua}" "$2" "$3" "$5" "${deps[0]}")
for (( i = 1; i < ${#deps[@]}; i++ )); do
if (( ${#str} + ${#deps[i]} + 1 <= cols )); then
str+=" ${deps[i]}"
else
write_line "${str}"
str=$(printf "${fmt}" "" "" "" "> ${deps[i]}")
fi
done
write_line "${str}"
fi
}
print_verbose() {
fmt="%-${max_len_modulename}s %-12s %-14s %-s"
func_print_header='print_header_verbose'
func_print_line='print_line_verbose'
}
print_header_verbose() {
:
}
print_line_verbose() {
local deps="${*:6}"
[[ -z ${deps} ]] && deps="(none)"
std::info "$1:"
std::info " release stage: $2"
std::info " group: $3"
std::info " overlay: $5"
std::info " modulefile: $4"
std::info " dependencies: ${deps}"
}
# print full modulefile names only
print_modulefiles() {
fmt=''
func_print_header='print_header_none'
func_print_line='print_line_modulefile'
}
print_header_none() {
:
}
print_line_modulefile() {
if (( $# >= 4 )) && [[ -n $4 ]]; then
std::info "$1 $4"
fi
}
print_line_raw() {
echo "$@"
}
print_raw() {
fmt=''
func_print_header='print_header_none'
func_print_line='print_line_raw'
}
if [[ "${opt_print_modulefiles}" == 'yes' ]]; then
print_modulefiles
elif [[ "${opt_print_csv}" == 'yes' ]]; then
print_csv
elif [[ "${opt_print_verbose}" == 'yes' ]]; then
print_verbose
elif [[ "${opt_print_raw}" == 'yes' ]]; then
print_raw
else
print_default
fi
local _script=''
if [[ ${opt_newest} == 'yes' ]]; then
_script='{} END{print}'
fi
${func_print_header}
local -a toks=()
while read -r -a toks; do
${func_print_line} "${toks[@]}"
done < <("${sort}" --version-sort -k 1,1 -k 6,6 -k 7,7 "${TmpFile}" | \
${awk} "${with_modules} ${_script}")
}
#.....................................................................
#
# search modules
#
search () {
local -r module="$1" # name/pattern to search
shift 1
local -a modulepath=("$@") # directories to search
local -i depth=0
# get and print all available modules in $mpath
# with respect to the requested release stage
# TmpFile: module/version relstage group dependencies...
local -a mods=()
get_available_modules \
'search' \
mods \
"${module}" \
"${opt_use_relstages}" \
"${modulepath[@]}"
local i=0
for (( i=0; i<${#mods[@]}; i+=6 )); do
local -- name="${mods[i]}"
local -- relstage="${mods[i+1]}"
local -- moduledir="${mods[i+2]}"
local -- rel_modulefile="${mods[i+3]}"
local -- modulefile="${moduledir}/${rel_modulefile}"
local -- ol="${mods[i+4]}"
local -- group="${mods[i+5]}"
local -a deps=()
if (( ${#name} > max_len_modulename)); then
max_len_modulename=${#name}
fi
if [[ "${OverlayInfo[${ol}:layout]}" == 'Pmodules' ]]; then
if [[ "${opt_print_verbose}" == 'yes' ]] || \
[[ "${opt_all_deps}" == 'yes' ]]; then
local prefix=''
get_module_prefix prefix "${modulefile}"
local dependencies_file="${prefix}/.dependencies"
if [[ -n ${prefix} ]] && [[ -r "${dependencies_file}" ]]; then
mapfile -t deps < "${dependencies_file}"
fi
else
# get dependencies encoded in directory name
local -i j
local -a toks
IFS='/'
read -r -a toks <<< "${modulefile}"
local -i depth=${GroupDepths[${group}]}
for ((j = -depth-2; j < -2; j += 2)); do
deps+=( "${toks[*]: $j:2}" );
done
unset IFS
fi
echo "${name}" "${relstage}" "${group}" "${modulefile}" \
"${ol}" \
"${deps[@]}" >> "${TmpFile}"
continue
fi
if [[ "${modulefile}" == *.lua ]] && \
[[ -v spider_keys[${modulefile}] ]]; then
local -i j=0
local -i n=0
local -- tmp_str=''
n=$( ${yq} ".\"${modulefile}\".parentAA|length" <<<"${spider_output}" )
for (( j=0; j<n; j++ )); do
tmp_str="$(${yq} ".\"${modulefile}\".parentAA[$j][]" <<<"${spider_output}" )"
readarray -t deps <<<"${tmp_str}"
echo "${name}" "${relstage}" "${group}" "${modulefile}" \
"${ol}" \
"${deps[@]}" >> "${TmpFile}"
done
continue
fi
echo "${name}" "${relstage}" "${group}" "${modulefile}" \
"${ol}" \
"${deps[@]}" >> "${TmpFile}"
done
}
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
--all-deps )
opt_all_deps='yes'
;;
--no-header )
opt_print_header='no'
;;
--print-modulefiles )
opt_print_modulefiles='yes'
opt_print_header='no'
;;
--print-raw )
opt_print_raw='yes'
opt_print_header='no'
;;
--release-stage | --release-stage=* )
if [[ "$1" == "--release" ]]; then
local arg=$2
shift
else
local arg=${1/--release=}
fi
is_release_stage "${arg}" || \
die_relstage_invalid "${arg}"
opt_use_relstages+="${arg}:"
;;
--with | --with=* )
if [[ "$1" == --with ]]; then
local arg=$2
shift
else
local arg=${1/--with=}
fi
if [[ -z ${arg} ]] || [[ "${arg}" == -* ]]; then
die_args_invalid_value '--with' "${arg}"
fi
arg=${arg//:/ }
arg=${arg//,/ }
for module in ${arg}; do
with_modules+=" && / ${module//\//\\/}/"
done
;;
-a | --all-releases-stages )
opt_use_relstages+="${ReleaseStages}"
;;
-v | --verbose )
opt_print_verbose='yes'
;;
--wrap )
opt_wrap='yes'
;;
--newest )
opt_newest='yes'
;;
-- )
shift 1
modules+=( "$@" )
break
;;
* )
modules+=( "$1" )
;;
esac
shift
done
[[ "${opt_use_relstages}" == ":" ]] && opt_use_relstages=":${UsedReleaseStages}:"
[[ ${#modules[@]} == 0 ]] && modules+=( '' )
#......................................................................
# collect directories containing modulefiles -> modulepath
local -a groups=( "${!GroupDepths[@]}" )
local -a modulepath_spack=()
local -a modulepath_pmodules=()
local -a modulepath_other=()
# search in overlays with layout 'Spack'
for ol_name in "${UsedOverlays[@]}"; do
[[ "${OverlayInfo[${ol_name}:layout]}" != 'Spack' ]] && break
[[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]] && \
groups=()
local -- path=''
if [[ ":${UsedReleaseStages}:" == *:unstable:* ]]; then
path="${OverlayInfo[${ol_name}:modulepath_unstable]}"
elif [[ ":${UsedReleaseStages}:" == *:deprecated:* ]]; then
path="${OverlayInfo[${ol_name}:modulepath_deprecated]}"
else
path="${OverlayInfo[${ol_name}:modulepath_stable]}"
fi
[[ -z "${path}" ]] && path="${OverlayInfo[${ol_name}:modulepath]}"
[[ -z "${path}" ]] && continue
IFS=':' read -r -a modulepath_spack <<<"${path}"
done
# search in overlays with layout 'Pmodules'
for group in "${groups[@]}"; do
for ol_name in "${UsedOverlays[@]}"; do
[[ "${OverlayInfo[$ol_name:layout]}" != 'Pmodules' ]] && continue
local -- dir="${OverlayInfo[${ol_name}:modulefiles_root]}"
dir+="/${group}/${__MODULEFILES_DIR__}"
[[ -r "${dir}" ]] && modulepath_pmodules+=( "${dir}" )
done
done
# add directories in modulpath outside overlays
local -a dirs
IFS=':' read -r -a dirs <<<"${MODULEPATH}"
for dir in "${dirs[@]}"; do
local -- ol=''
local -- grp=''
find_overlay ol grp "${dir}" && continue
[[ -r "${dir}" ]] && modulepath_other+=( "${dir}" )
done
local -a modulepath=()
printf -v modulepath "%s:" \
"${modulepath_spack[@]}" \
"${modulepath_pmodules[@]}" \
"${modulepath_other[@]}"
modulepath="${modulepath%:}"
local hash=$(echo "${modulepath}" | md5sum)
SpiderCache="${CacheDir}/spider-${hash:0:16}.yaml"
local -- update_cache='yes'
if [[ -r "${SpiderCache}" ]]; then
local -i mtime=0
mtime=$( ${stat} --format %Y "${SpiderCache}" )
# cache expiration time is 4h
(( CurTime - mtime <= 14400 )) && update_cache='no'
fi
if [[ "${update_cache}" == 'yes' ]]; then
std::info "(Re-)building the Pmodules cache. Please be patient ..."
spider_output="$( ${Spider_cmd} -o spider-json "${modulepath}" | \
${yq} -p j -o y '.*' |
${yq} '(.*.parentAA|select(.)) as $i ireduce({}; setpath($i | path; $i))')"
echo "${spider_output}" > "${SpiderCache}"
std::info "Done ..."
else
spider_output=$(< "${SpiderCache}")
fi
local -a keys=()
mapfile -t keys < <( ${yq} '.|keys[]' "${SpiderCache}" )
local -- key=''
for key in "${keys[@]}"; do
spider_keys[${key}]=1
done
#......................................................................
local -- module=''
for module in "${modules[@]}"; do
search "${module}" \
"${modulepath_spack[@]%/Core}" \
"${modulepath_pmodules[@]}" \
"${modulepath_other[@]}"
done
print_result
# empty tmp file
echo -n '' > ${TmpFile}
}
##############################################################################
find_matching_modules(){
local -r __doc__='
Find modules matching the specified arguments.
Return "table" with the columns:
name-name release-stage group modulefile overlay deps
'
local -n result="$1"
shift
local -a opts=('--print-raw')
local -a patterns=()
while (( $# > 0 )); do
case $1 in
-* )
opts+=( "$1" )
;;
* )
patterns+=( "$1")
;;
esac
shift
done
result=$(set +x; subcommand_search "${patterns[@]}" "${opts[@]}")
}
##############################################################################
create_module_tab(){
local -n result="$1"
local -- modules="$2"
local -- modulename=''
local -- relstage=''
local -- grp=''
local -- modulefile=''
local -- ol_name=''
local -- deps=''
local -- rest=''
local -i size_of_first_col=0
local -i num_rows=0
while read modulename rest; do
[[ -z "${modulename}" ]] && continue
(( ${#modulename} > size_of_first_col )) && size_of_first_col=${#modulename}
(( num_rows++ ))
done <<<"${modules}"
result=''
(( num_rows == 0 )) && return 1
printf -v result "%-${size_of_first_col}s %-10s %s\n" \
"Module" "Group" "Dependencies"
while read modulename relstage grp modulefile ol_name deps; do
[[ -z "${modulename}" ]] && continue
local -- str=''
printf -v str "%-${size_of_first_col}s " "${modulename}"
result+="${str}"
if [[ "${grp}" == 'none' ]]; then
grp='-'
elif [[ ":${UsedGroups}:" != *:${grp}:* ]]; then
grp="(${grp})"
fi
printf -v str "%-10s " "${grp}"
result+="${str}"
result+="${deps}"
result+='\n'
done <<<"${modules}"
return 0
}
##############################################################################
Subcommands['help']='help'
Options['help']='-o hHV\? -l version -l help'
Help['help']='
USAGE:
module [ switches ] [ subcommand ] [subcommand-args ]
SWITCHES:
-h|-H|-?|--help this usage info
-V|--version modules version & configuration options
--debug enable debug output
SUBCOMMANDS:
+ add|load [switches] modulefile [modulefile ...]
+ rm|unload modulefile [modulefile ...]
+ switch|swap [modulefile1] modulefile2
+ display|show modulefile [modulefile ...]
+ avail [switches] [modulefile [modulefile ...]]
+ search [switches] [args]
+ use [switches] [dir|group|release ...]
+ unuse dir|group|release [dir|group|release ...]
+ refresh
+ purge
+ list [switches]
+ clear
+ help [modulefile|subcommand]
+ whatis [modulefile [modulefile ...]]
+ apropos|keyword string
+ save collection
+ restore collection
+ savelist
+ saverm collection
+ saveshow collection
+ initadd modulefile [modulefile ...]
+ initprepend modulefile [modulefile ...]
+ initrm modulefile [modulefile ...]
+ initswitch modulefile1 modulefile2
+ initlist
+ initclear
DOCUMENTATION:
Full documentation is available at
http://pmodules.gitpages.psi.ch
'
#..............................................................................
subcommand_help() {
print_help_hints(){
local -- pattern="$1"
local -- modules=''
find_matching_modules modules "${pattern}" -a
std::info "A module with the name '${pattern}' is not available in the current module path!\n"
local -- output=''
create_module_tab output "${modules}" || return 0
std::info "The following modules are matching the given name:\n"
std::info "${output}"
std::info "To get help for a specific module, the runtime dependencies must be loaded first!"
}
local -r __doc__='Implementation of the sub-command help.'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -h | -H | --help )
print_help "${SubCommand}"
;;
-V | --version )
print_help 'version'
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
print_help 'help'
fi
local arg
for arg in "${args[@]}"; do
if [[ -v Help[${arg}] ]] ; then
print_help "${arg}"
continue
fi
local -- modulefile=''
local -- relstage=''
local -- moduledir=''
if ! find_modulefile \
modulefile \
relstage \
moduledir \
modulecmd \
"${arg}"; then
print_help_hints "${arg}"
continue
fi
"${modulecmd}" 'bash' 'help' "${modulefile}"
done
}
##############################################################################
Subcommands['whatis']='whatis'
Options['whatis']='-o \?Ha -l help -l all'
Help['whatis']='
USAGE:
module whatis [modulefile...]
Display the information set up by the module-whatis commands
inside the specified modulefile(s). If no modulefile is
specified, all whatis lines will be shown.
'
#..............................................................................
subcommand_whatis() {
local -r __doc__='Implementation of the sub-command whatis.'
local -- opts=('--newest')
local -- args=()
while (( $# > 0 )); do
case $1 in
-\? | --help )
print_help "${SubCommand}"
;;
-a | --all )
opts+=( '-a' )
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
local -- modules=''
find_matching_modules modules \
"${opts[@]}" \
"${args[@]}"
local -- modulename=''
local -- relstage=''
local -- grp=''
local -- modulefile=''
local -- ol_name=''
local -- deps=''
while read modulename relstage grp modulefile ol_name deps; do
[[ -n ${modulefile} ]] || continue
if [[ "${modulefile##*.}" == 'lua' ]]; then
modulecmd="${Lmod_cmd}"
modulefile="${modulename}"
else
modulecmd="${Tcl_cmd}"
fi
local whatis=''
whatis=$("${modulecmd}" bash \
whatis \
"${modulename}" \
2>&1 1>/dev/null)
[[ "${whatis}" == *:ERROR:* ]] && continue
whatis="${whatis/*:}" # remove modulefile name
printf "%-25s: %s\n" "${modulename}" "${whatis}" 1>&2
done <<<"${modules}"
}
##############################################################################
Subcommands['apropos']='apropos'
Subcommands['keyword']='apropos'
Options['apropos']='-o \?Ha -l help -l all'
Help['apropos']='
USAGE:
module apropos string
module keyword string Seeks through the whatis informations of
all modulefiles for the specified string. All module-whatis
informations matching the string will be displayed.
'
#..............................................................................
subcommand_apropos() {
local -r __doc__='Implementation of the sub-command apropos|keyword'
local opts=()
local args=()
while (( $# > 0 )); do
case $1 in
-\? | --help )
print_help "${SubCommand}"
;;
-a | --all )
opts+=( '-a' )
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_args_missing
(( ${#args[@]} > 1 )) && \
die_args_too_many
local arg="${args[0],,}"
local -- modules=''
find_matching_modules modules '' "${opts[@]}"
local -- modulename=''
local -- relstage=''
local -- grp=''
local -- modulefiles=''
local -- ol_name=''
local -- deps=''
while read modulename relstage grp modulefile ol_name deps; do
[[ -z "${modulename}" ]] && continue
if [[ "${modulename##*.}" == 'lua' ]]; then
modulecmd="${Lmod_cmd}"
modulefile="${modulename}"
else
modulecmd="${Tcl_cmd}"
fi
local whatis=''
whatis=$("${modulecmd}" bash \
whatis \
"${modulefile}" \
2>&1 1>/dev/null)
[[ "${whatis}" == *:ERROR:* ]] && continue
whatis="${whatis/*:}"
[[ ${whatis,,} == *${arg}* ]] && \
printf "%-25s: %s\n" "${modulename}" "${whatis}" 1>&2
done <<<"${modules}"
}
##############################################################################
#
# Collections
#
# Collections describe a sequence of module use then module load commands
# that are interpreted by Modules to set the user environment as described
# by this sequence.
#
# User collections:
#
# System collections:
##
##############################################################################
Subcommands['save']='save'
Options['save']='-o \?H -l help'
Help['save']="
USAGE:
module save [collection]
Record the currently set MODULEPATH directory list and the
currently loaded modulefiles in a collection file under the
user's collection directory \$HOME/.Pmodules. If collection
name is not specified, then it is assumed to be the default
collection. If collection is a fully qualified path, it is
saved at this location rather than under the user's collection
directory.
"
#..............................................................................
declare -r UsrCollectionsDir="${HOME}/.Pmodules/collections"
subcommand_save(){
local -r __doc__='
Implementation of the save collection sub-command save.'
local -a args=()
local -- opt_system='no'
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
--system )
opt_system='yes'
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} > 1 )) && die_args_too_many
[[ ${args[0]:0:1} =~ [0-9a-zA-Z] ]] || \
die_col_invalid_name "${args[0]}"
local -- basedir="${UsrCollectionsDir}"
if [[ "${opt_system}" == 'yes' ]]; then
basedir="${UsedOverlays[0]}/collections"
fi
local collection=''
if (( ${#args[@]} == 0 )); then
collection="${basedir}/default"
else
collection="${basedir}/${args[0]}"
fi
mkdir -p "${collection%/*}" || \
die_cannot create_directory "$_"
# save used release stages, groups and overlays
local -- item=''
local -a items=()
local -a tmp=()
local -- grp=''
IFS=':' read -r -a tmp <<< "${UsedReleaseStages}"
items+=( "${tmp[@]}")
IFS=':' read -r -a tmp <<< "${UsedGroups}"
for grp in "${tmp[@]}"; do
# skip hierarchical groups
(( ${GroupDepths[${tmp}]} > 0 )) && continue
items+=( "${grp}")
done
IFS=':' read -r -a tmp <<< "${UsedOverlays}"
items+=( "${tmp[@]}")
for item in "${items[@]}"; do
[[ "${item}" == 'base' ]] && continue
s+="module use ${item};\n"
done
# save additional module directories
local -a modulepath=()
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
local dir=''
local ol=''
local grp=''
for dir in "${modulepath[@]}"; do
find_overlay ol grp "${dir}" && continue
s+="module use \"${dir}\";\n"
done
# save loaded modules
local -a modules=()
IFS=':' read -r -a modules <<< "${LOADEDMODULES}"
local -- m=''
for m in "${modules[@]}"; do
[[ $m == Pmodules/* ]] && continue
s+="module load $m;\n"
done
# save collection
echo -e "$s" > "${collection}" || \
die_col_cannot_be_saved "${collection}"
}
##############################################################################
Subcommands['restore']='restore'
Options['restore']='-o \?H -l help'
Help['restore']="
USAGE:
module restore [collection]
Restore the environment state as defined in collection.
If collection name is not specified, then it is assumed
to be the default collection.
"
#..............................................................................
search_collection(){
local -n _path="$1"
local -- _collection="$2"
if [[ -r "${UsrCollectionsDir}/${_collection}" ]]; then
_path="${UsrCollectionsDir}"
return 0
fi
local -- _ol
for _ol in "${UsedOverlays[@]}"; do
if [[ -r "${OverlayInfo[${_ol}:install_root]}/collections/${_collection}" ]]; then
_path="${OverlayInfo[${_ol}:install_root]}/collections"
return 0
fi
done
die_col_doesnt_exist "${_collection}"
}
#..............................................................................
subcommand_restore() {
local -r __doc__='
Implementation of the restore collection sub-command.'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} > 1 )) && die_args_too_many
(( ${#args[@]} == 0 )) && args=( 'default' )
local -- path=''
local -- collection=''
local -a collections=()
for collection in "${args[@]}"; do
search_collection path "${collection}"
collections+=( "${path}/${collection}" )
done
# reset/unload everything (except Pmodules/<version>)
IFS=':' read -r -a modules <<< "${LOADEDMODULES}"
local -- m=''
for ((i=${#modules[@]}-1; i>=0; i--)); do
[[ ${modules[$i]} == Pmodules/* ]] && continue
subcommand_unload "${modules[$i]}"
done
local -a items=()
local -a tmp=()
local -- item=''
local -a default_grps=()
IFS=':' read -r -a tmp <<< "${UsedReleaseStages}"
items+=( "${tmp[@]}")
# remove all groups with the exception of the default groups
IFS=':' read -r -a tmp <<< "${UsedGroups}"
IFS=':' read -r -a default_grps <<< "${DefaultGroups}"
for item in "${tmp[@]}"; do
(( ${GroupDepths["${item}"]} > 0 )) && continue
std::is_member_of_array "${item}" default_grps && continue
items+=( "${item}" )
done
IFS=':' read -r -a tmp <<< "${UsedOverlays}"
for item in "${tmp[@]}"; do
[[ "${item}" == 'base' ]] && continue
items+=( "${tmp[@]}")
done
local -- item=''
for item in "${items[@]}"; do
[[ -z "${item}" ]] && continue
subcommand_unuse "${item}"
done
# load collection
for collection in "${collections[@]}"; do
${cat} "${collection}"
done
vars_to_be_exported['PMODULES_ENV']=1
}
##############################################################################
Subcommands['savelist']='savelist'
Options['savelist']='-o \?H -l help'
Help['savelist']="
USAGE:
module savelist [pattern...]
List collections that are currently saved under the user's
collection directory.
If a pattern is given, then the collections are filtered to
only list those whose name matches this pattern. It may
contain wildcard characters. pattern is matched in a case
insensitive manner by default. If multiple patterns are
given, collection has to match at least one of them to be
listed.
"
#..............................................................................
subcommand_savelist() {
local -r __doc__='
Implementation of the savelist collection sub-command.'
get_collections() {
local -n _result="$1"
local -n gc_dirs="$2"
shift 2
_result=()
local _pattern
local _coll
for _pattern in "$@"; do
while read -r _coll; do
_result+=( "${_coll}" )
done < <(${find} "${gc_dirs[@]}" \
-type f \
-ipath "*/${_pattern}" \
-printf "%P\n" 2>/dev/null)
done
}
print_collections() {
local -r prt_header="$1"
local -n prt_dirs="$2"
shift 2
local -a prt_collections=()
get_collections prt_collections prt_dirs "$@"
(( ${#prt_collections[@]} == 0 )) && return 0
std::info "${prt_header}"
for prt_col in "${prt_collections[@]}"; do
std::info "\t${prt_col}"
done
}
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
args[0]='*'
fi
local -a _dirs=( "${UsrCollectionsDir[@]}" )
print_collections "User collections:" _dirs "${args[@]}"
_dirs=()
local _ol
for _ol in "${UsedOverlays[@]}"; do
_dirs+=( "${OverlayInfo[${_ol}:install_root]}/collections" )
done
print_collections "\nSystem collections:" _dirs "${args[@]}"
}
##############################################################################
Subcommands['saverm']='saverm'
Options['saverm']='-o \?H -l help'
Help['saverm']="
USAGE:
module saverm [collection]
Delete the collection file under the user's collection
directory. If collection name is not specified, then it
is assumed to be the default collection.
"
#..............................................................................
subcommand_saverm() {
local -r __doc__='
Implementation of the saverm collection sub-command.'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
args[0]='default'
fi
local -- path=''
local -- collection=''
for collection in "${args[@]}"; do
search_collection path "${collection}"
test -e "${path}/${collection}" || \
die_col_doesnt_exist "${collection}"
${rm} -f "${path}/${collection}" 2>/dev/null || \
die_col_cannot_be_removed "${collection}"
# remove directories if empty
${rmdir} -p "${path}/${collection%%/*}" 2>/dev/null
done
}
##############################################################################
Subcommands['saveshow']='saveshow'
Options['saveshow']='-o \?H -l help'
Help['saveshow']="
USAGE:
module saveshow [collection...]
Display the content of collection. If collection name is not
specified, then it is assumed to be the default collection,
"
#..............................................................................
subcommand_saveshow() {
local -r __doc__='
Implementation of the saveshow collection sub-command.'
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
args[0]='default'
fi
local -- collection=''
local -- path=''
for collection in "${args[@]}"; do
search_collection path "${collection}"
test -e "${path}/${collection}" || \
die_col_doesnt_exist "${collection}"
std::info "Collection '${collection}':"
cat "${path}/${collection}" 1>&2
done
}
##############################################################################
#
# sub-commands to manipulate the shell profile.
#
##
##############################################################################
Subcommands['initadd']='initadd'
Options['initadd']='-o \?H -l help'
Help['initadd']="
USAGE:
module initadd modulefile...
Add modulefile(s) to the shell's initialization file in the
user's home directory. The startup files checked (in order)
are:
csh - .modules, .cshrc(.ext), .csh_variables, and
.login(.ext)
tcsh - .modules, .tcshrc, .cshrc(.ext), .csh_variables,
and .login(.ext)
(k)sh - .modules, .profile(.ext), and .kshenv(.ext)
bash - .modules, .bash_profile, .bash_login,
.profile(.ext) and .bashrc(.ext)
zsh - .modules, .zcshrc(.ext), .zshenv(.ext), and
.zlogin(.ext)
If a 'module load' line is found in any of these files, the
modulefile(s) is(are) appended to any existing list of
modulefiles. The 'module load' line must be located in at
least one of the files listed above for any of the 'init'
sub-commands to work properly. If the 'module load' line
line is found in multiple shell initialization files, all
of the lines are changed.
"
#..............................................................................
subcommand_initadd() {
local -r __doc__='
Implementation of the initadd sub-command.'
subcommand_generic1plus "$@"
}
##############################################################################
Subcommands['initprepend']='initprepend'
Options['initprepend']='-o \?H -l help'
Help['initprepend']="
USAGE:
module initprepend modulefile...
Does the same as initadd but prepends the given modules to
the beginning of the list.
"
#..............................................................................
subcommand_initprepend() {
local -r __doc__='
Implementation of the initprepend sub-command.'
subcommand_generic1plus "$@"
}
##############################################################################
Subcommands['initrm']='initrm'
Options['initrm']='-o \?H -l help'
Help['initrm']="
USAGE:
module initrm modulefile...
Remove modulefile(s) from the shell's initialization files.
"
#..............................................................................
subcommand_initrm() {
local -r __doc__='
Implementation of the initrm sub-command.'
subcommand_generic1plus "$@"
}
##############################################################################
Subcommands['initswitch']='initswitch'
Options['initswitch']='-o \?H -l help'
Help['initswitch']="
USAGE:
module initswitch modulefile1 modulefile2
Switch modulefile1 with modulefile2 in the shell's
initialization files.
"
#..............................................................................
subcommand_initswitch() {
local -r __doc__='
Implementation of the initswitch sub-command.'
local args=()
while (( $# > 0 )); do
case $1 in
-\? | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} != 2 )) && \
die_args_wrong_number
"${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}"
}
##############################################################################
Subcommands['initlist']='initlist'
Options['initlist']='-o \?H -l help'
Help['initlist']="
USAGE:
module initlist
List all of the modulefiles loaded from the shell's
initialization file.
"
#..............................................................................
subcommand_initlist() {
local -r __doc__='
Implementation of the initlist sub-command.'
subcommand_generic0 "$@"
}
##############################################################################
Subcommands['initclear']='initclear'
Options['initclear']='-o \?H -l help'
Help['initclear']="
USAGE:
module initclear
Clear all of the modulefiles from the shell's
initialization files.
"
#..............................................................................
subcommand_initclear() {
local -r __doc__='
Implementation of the initclear sub-command.'
subcommand_generic0 "$@"
}
##############################################################################
#
# main
#
(( $# > 0 )) || print_help 'help'
# the debug() function will be re-defined if modulecmd is called with the
# option '--verbose'. See below.
debug(){
:
}
# first argument must be a shell!
case "$1" in
sh | bash | zsh )
declare Shell="sh"
;;
csh | tcsh )
declare Shell='csh'
;;
python )
declare Shell='python'
;;
* )
die_args_unsupported_shell "$1"
;;
esac
shift
# Parse agruments till and including the sub-command.
# The arguments for the sub-command are parsed later.
declare -a opts=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help | -help )
print_help 'help'
;;
-V | --version )
print_help 'version'
;;
--debug )
set -x
;;
--verbose )
debug(){
echo "INFO: " "$@" 1>&2
}
;;
'' )
;;
-* )
opts+=( "$1" )
;;
* )
SubCommand="$1"
shift
break
;;
esac
shift
done
[[ -z "${SubCommand}" ]] && die_args_subcmd_missing
[[ -v Subcommands[${SubCommand}] ]] || die_args_invalid_subcmd "${SubCommand}"
# We (re-)initialise the Pmodules system, if
# PMODULES_ENV or MODULEPATH was not set/is empty
if [[ -z ${PMODULES_ENV} ]] || [[ -z "${MODULEPATH}" ]]; then
pmodules_setup 'init'
else
# restore variables from last call
eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)"
fi
# re-read config if Pmodules has been changed
if [[ "${Version}" != "${PMODULES_VERSION}" ]]; then
pm::read_config
for ol_name in "${UsedOverlays[@]}"; do
OverlayInfo[${ol_name}:used]='yes'
done
Version="${PMODULES_VERSION}"
vars_to_be_exported['PMODULES_ENV']=1
fi
# make sure that these variables are set
if [[ -z "${UsedGroups}" ]] || \
[[ -z "${UsedReleaseStages}" ]] || \
(( ${#UsedOverlays[@]} == 0 )); then
pmodules_setup
fi
# repace alias with the real "name"
SubCommand=${Subcommands[${SubCommand}]}
# parse arguments of the sub-command and call it
temp=$("${getopt}" --name="${CMD}" ${Options[${SubCommand}]} -- "${opts[@]}" "$@" ) \
|| print_help "${SubCommand}"
eval set -- "${temp}"
unset temp
"subcommand_${SubCommand}" "$@"
# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: