Files
Pmodules/Pmodules/modulecmd.bash.in
T

3770 lines
98 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
mydir=$(cd "$(/usr/bin/dirname "$0")" && pwd -L)
prefix=$(/usr/bin/dirname "${mydir}");
path_orig="${PATH}"
PATH="${prefix}/bin:${prefix}/libexec:/bin:/usr/bin:/sbin:/usr/sbin"
source "${prefix}/lib/libstd.bash" || {
echo "Oops: cannot source library -- '$_'" 1>&2; exit 3;
}
source "${prefix}/lib/libpmodules.bash" || \
std::die 3 "Oops: cannot source library -- '$_'"
declare -rx TCL_LIBRARY="${prefix}/lib/tcl@TCL_VERSION@"
declare -x TCLLIBPATH=${TCLLIBPATH:-''}
std::prepend_path TCLLIBPATH "${prefix}/lib/Pmodules"
declare -r Tcl_cmd="${prefix}/libexec/modulecmd.bin"
declare -r Lmod_cmd="${prefix}/libexec/lmod/lmod/libexec/lmod"
declare -- modulecmd="${Tcl_cmd}"
# we have to use the orignal path. Otherwise module load doesn't work.
PATH="${path_orig}"
unset path_orig
unset mydir
unset prefix
##############################################################################
# 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 -- OverlayExcludes=''
declare -a UsedOverlays=()
declare -- PmFiles=''
declare -- ModulePathAppend=''
declare -- ModulePathPrepend=''
##############################################################################
declare -- Verbosity_lvl='verbose'
declare -- Shell=''
TmpFile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \
|| std::die 1 "Oops: unable to create tmp file!"
declare -r TmpFile
declare -A Subcommands=()
declare -A Options=()
declare -A Help=()
# initialize help text of 'module --version'
Help['version']="
Pmodules @PMODULES_VERSION@
using Tcl Environment Modules
VERSION = @MODULES_VERSION@
"
# these variables must exist
if [[ ! -v LOADEDMODULES ]] || [[ ! -v _LMFILES_ ]]; then
LOADEDMODULES=''
_LMFILES_=''
fi
#
# display help text for command given in $1
#
print_help() {
echo -e "${Help[$1]}" 1>&2
std::die 1
}
export_env() {
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 export_functions[${Shell}] ]] || \
std::die 1 "Unsupported shell -- ${Shell}"
${export_functions[${Shell}]} "$@"
}
#
# Save/cache state in the environment variable PMODULES_ENV. The content is
# base64 encoded. This function is called on exit via a trap handler.
#
# Arguments:
# none
#
declare EnvMustBeSaved='no'
save_env() {
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
}
[[ "${EnvMustBeSaved}" == 'no' ]] && return 0
local 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' )
local s=''
s=$(typeset -p "${vars[@]}")
declare -gx PMODULES_ENV=''
PMODULES_ENV=$( encode_base64 "$s" )
export_env 'PMODULES_ENV'
}
#
# function called on exit
#
_exit() {
save_env
if [[ -n "${TmpFile}" ]] && [[ -e "${TmpFile}" ]]; then
${rm} -f "${TmpFile}" || :
fi
}
trap '_exit' EXIT
declare -r CMD='module'
declare SubCommand=''
die_missing_arg(){
std::die 3 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" 'missing argument'
}
die_too_many_args(){
std::die 3 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" 'too many arguments'
}
die_no_args_allowed(){
std::die 3 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" "no arguments allowed"
}
die_wrong_number_of_args(){
std::die 1 "%s %s: %s\n" \
"${CMD}" "${SubCommand}" "wrong number of arguments"
}
die_illegal_opt(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "illegal option" "$1"
}
die_illegal_arg(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid argument" "$1"
}
die_illegal_group(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid group name" "$1"
}
die_illegal_relstage(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid release stage" "$1"
}
die_cannot_remove_grp(){
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "cannot remove group due to loaded modules" "$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_invalid_value(){
std::die 1 "%s %s: %s -- %s\n" \
"${CMD}" "${SubCommand}" "invalid string for $1" "$2"
}
die_conflict(){
std::die 3 "%s %s: %s -- %b\n" \
"${CMD}" "${SubCommand}" \
"module conflicts with already loaded modules" "$1"
}
die_cmd_failed(){
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"failed"
}
die_cannot_use_overlay(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"overlay cannot be added since some modules are already loaded!" "$1"
}
die_overlay_already_in_use(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"overlay already in use" "$1"
}
die_not_a_modulefile(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"not a modulefile" "$1"
}
die_cannot_create_directory(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot create directory" "$1"
}
die_cannot_save_collection(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot save_collection" "$1"
}
die_invalid_collection_name(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"invalid collection name" "$1"
}
die_collection_doesnt_exist(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"collection doesn't exist or isn't readable" "$1"
}
die_removing_collection_failed(){
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot remove collection" "$1"
}
get_module_config(){
: "
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 doesn't exist, get the release
stage from '.release-<version>'. Restrictions to systems or blocking
hosts is not possible.
Note:
- the release stage of a module outside our hierarchy is always
'stable'.
- the release stage of a module inside out hierarchy without a
config file is always 'unstable'.
Arguments:
$1 reference to dictionary to return the configuration
$2 directory containing modulefile
$3 module name (inkl. version and/or sub-dirs)
"
local -n ref_cfg="$1"
local -r dir="$2"
local -r modulefile="${dir}/$3"
ref_cfg['relstage']='unstable'
ref_cfg['systems']=''
ref_cfg['blocklist']=''
if [[ ! -v Dir2OverlayMap[${dir%/"${PMODULES_MODULEFILES_DIR}"*}] ]]; then
# this module is not in an overlay
ref_cfg['relstage']='stable'
return
fi
local -r config_file="${modulefile%/*}/.config-${modulefile##*/}"
local -r relstage_file="${modulefile%/*}/.release-${modulefile##*/}"
if [[ -r ${config_file} ]]; then
local -- yaml=''
yaml=$(${yq} -e '.' < "${config_file}")
debug "module config: ${yaml}"
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
if [[ "${value}" != 'null' ]]; then
ref_cfg[${key,,}]="${value}"
fi
done
return 0
elif [[ -r ${relstage_file} ]]; then
ref_cfg['relstage']=$( < "${relstage_file}" )
else
ref_cfg['relstage']='unstable'
fi
}
is_available(){
: "
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"
local -- os_release=''
os_release=$(std::get_os_release)
check_relstage(){
[[ ":${relstages}:" == *:${ref_cfg['relstage']}:* ]]
}
check_blocklist(){
[[ -z ${ref_cfg['blocklist']} ]] && return 0
local -- s=''
for s in ${ref_cfg['blocklist']}; do
if [[ "${os_release}" =~ $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 [[ "${os_release}" =~ $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}
}
#
# check whether the argument in $1 is a valid release stage.
#
is_release_stage() {
[[ :${ReleaseStages}: =~ :$1: ]]
}
#
# Check whether a given moduledir is in an used overlay.
# If yes, return 0 otherwise return 1
#
# Arguments
# $1 ref.var to return overlay
# $2 ref.var to return group
# $3 moduledir to check
#
find_overlay () {
local -n fo_ol="$1"
local -n fo_group="$2"
local path="${3//+(\/)/\/}" # replace multpile '/' with one
path="${path/%\/}" # remove trailing slash if exist
path="${path%/"${PMODULES_MODULEFILES_DIR}"*}"
# return if not in an overlay
[[ -v Dir2OverlayMap[${path}] ]] || return 1
fo_ol="${Dir2OverlayMap[${path}]}"
fo_group="${path#"${OverlayInfo[${ol}:modulefiles_root]}"/}"
return 0
}
#
# Check whether the module passed in argument $1 is loaded.
#
module_is_loaded() {
[[ :${LOADEDMODULES}: =~ :$1: ]]
}
#
# test whether the given file is a module file.
#
# - for lua: the extension must be .lua
# - for Tcl: check shebang
#
# return interpreter in reference variable
#
# Arguments:
# $1 [out] ref. variable for result
# $2 [in] full qualified file name to test
#
# Return value:
# 0 if file exist, is readable and is either a lua or Tcl module file
# 1 if file exist but is neither a lua or Tcl module file
# 2 if file doesn't exist
#
is_modulefile() {
local -n im_interp="$1"
local -r fname="$2"
# is this a regular, readable file?
[[ -f "${fname}" && -r "${fname}" ]] || return 2
if [[ "${fname##*.}" == 'lua' ]]; then
im_interp="${Lmod_cmd}"
return 0
fi
local -- shebang
read -r -n 11 shebang < "${fname}"
if [[ "${shebang:0:8}" == '#%Module' ]] \
|| [[ "${shebang:0:9}" == '#%Pmodule' ]]; then
im_interp="${Tcl_cmd}"
return 0
fi
return 1
}
#
# Get the value of <module>_PREFIX.
#
# Arguments:
# $1 reference variable to return result
# $2 modulefile
#
get_module_prefix() {
local -n _prefix="$1"
_prefix=$("${modulecmd}" bash show "$2" 2>&1 | \
${awk} '/_PREFIX |_HOME / {print $3; exit}')
}
#
# Generic wrappers of 'modulecmd':
#
# subcommand_generic0:
# no argument allowed
# subcommand_generic1:
# Exact one argument must be passed
# subcommand_generic1plus:
# One or more arguments must be passed
#
# The options to output help are always accepted.
#
subcommand_generic0() {
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_no_args_allowed
"${modulecmd}" "${Shell}" "${SubCommand}"
}
subcommand_generic1() {
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_missing_arg
(( ${#args[@]} > 1 )) && \
die_too_many_args
"${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}"
}
subcommand_generic1plus() {
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_missing_arg
"${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}"
}
##############################################################################
set_lmfiles(){
: '
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 -x _LMFILES_=''
declare -x LOADEDMODULES=''
fi
local -- dir=''
if [[ "${modulecmd}" == "${Lmod_cmd}" ]]; then
local -a dirs=()
IFS=':' read -r -a dirs <<<"${PmFiles}"
for dir in "${dirs[@]}"; do
std::append_path _LMFILES_ "${dir}"
done
fi
# rebuild LOADEDMODULES by setting it to _LMFILES_ and then removing
# all directories given in MODULEPATH from LOADEDMODULES.
LOADEDMODULES="${_LMFILES_}"
while read -r dir; 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 <<< "${MODULEPATH//:/$'\n'}"
}
##############################################################################
#
# load [-fsvw] <module>
#
# $1: module to load
#
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 -- 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" # ref.var to store 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}"
}
#......................................................................
# Check whether argument is a group.
#
# In the following function we test whether the group exist in
# an overlay. It doesn't matter which overlay. If we find an
# overlay providing modules for this group, we compute the
# hierarchical depth of the group and save this value for later
# use.
#
# Args:
# $1: group
#
# Notes:
# This function is used with extended module names.
#
# Multiple overlays may provide modules for the same group. But the
# hierarchical depth of a group must always be the same.
#
is_group () {
find_overlay_with_group() {
local -n _ol="$1"
local -r group="$2/${PMODULES_MODULEFILES_DIR}"
local ol
for ol in "${UsedOverlays[@]}"; do
local install_root="${OverlayInfo[${ol}:install_root]}"
if [[ -d "${install_root}/${group}" ]]; then
_ol="${ol}"
return 0
fi
done
return 1
}
local -r group="$1"
# arg isn't emtpy and group already in cache
[[ -n ${group} ]] && [[ -v GroupDepths[${group}] ]] && return 0
local ol=''
find_overlay_with_group ol "${group}" || return 1
local moduledir="${OverlayInfo[${ol}:modulefiles_root]}/${group}/${PMODULES_MODULEFILES_DIR}"
local -i depth
compute_group_depth depth "${moduledir}" || return 1
GroupDepths[${group}]=${depth}
EnvMustBeSaved='yes'
}
#......................................................................
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_missing_arg
if [[ ! -v LOADEDMODULES ]]; then
LOADEDMODULES=''
_LMFILES_=''
fi
local m=''
for m in "${args[@]}"; do
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
if [[ "$m" == *:* ]]; then
# extendet module name is either
# - group:name or
# - group:name:relstage or
# - relstage:name or
# - relstage:group:name or
# - name:stage
local -a toks=()
IFS=':' read -r -a toks <<< "${m}"
local group=''
local relstage=''
if is_group "${toks[0]}"; then
group=${toks[0]}
m=${toks[1]}
relstage=${toks[2]}
elif is_release_stage "${toks[0]}"; then
relstage=${toks[0]}
if is_group "${toks[1]}"; then
group=${toks[1]}
m=${toks[2]}
else
m=${toks[1]}
group=${toks[2]}
fi
else
m=${toks[0]}
if is_group "${toks[1]}"; then
group=${toks[1]}
relstage=${toks[2]}
else
relstage=${toks[1]}
group=${toks[2]}
fi
fi
if [[ -n ${group} ]]; then
is_group "${group}" || \
die_illegal_group "${group}"
local -i depth=${GroupDepths[${group}]}
(( depth != 0 )) && \
die_illegal_group "${group}"
group+="/${PMODULES_MODULEFILES_DIR}"
for overlay in "${UsedOverlays[@]}"; do
local modulefiles_root="${OverlayInfo[${overlay}:modulefiles_root]}"
modulepath=( "${modulefiles_root}/${group}" "${modulepath[@]}" )
done
fi
if [[ -n ${relstage} ]]; then
is_release_stage "${relstage}" || \
die_illegal_relstage "${relstage}"
std::append_path UsedReleaseStages "${relstage}"
EnvMustBeSaved='yes'
fi
fi # handle extended module names
local moduledir=''
find_modulefile \
current_modulefile \
relstage \
moduledir \
modulecmd \
"${m}" \
"${modulepath[@]}"
if [[ -z ${current_modulefile} ]]; then
local hints=''
get_load_hints hints
if [[ -z "${hints}" ]]; then
die_module_nexist "${m}"
else
die_module_unavail "${m}${hints}"
fi
fi
[[ ${m} == Pmodules/* ]] && [[ -n ${LOADEDMODULES} ]] && \
die_conflict "${m}"
# continue if already loaded
[[ ":${LOADEDMODULES}:" == *:${m}:* ]] && continue
interp[${current_modulefile}]="${modulecmd}"
# show info file if exist
local prefix=''
get_module_prefix prefix "${current_modulefile}"
[[ -n ${prefix} && -r "${prefix}/.info" ]] && cat "${prefix}/.info" 1>&2
# 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 "$_"
[[ ":${LOADEDMODULES}:" == *:${m}:* ]] && continue
# load module
modulecmd="${interp[${current_modulefile}]}"
local output=''
output=$("${modulecmd}" 'bash' "${opts[@]}" 'load' \
"${current_modulefile}" 2> "${TmpFile}")
# we do not want to print the error message we got from
# modulecmd, they are a bit ugly
# :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.
local error=''
error=$( < "${TmpFile}")
if [[ "${error}" == *:ERROR:* ]]; then
local s=${error%%$'\n'*}
[[ "$s" =~ ' conflicts ' ]] && \
die_conflict "${m}"
die_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
local msg=''
if [[ ${Verbosity_lvl} != silent ]] && \
[[ ${relstage} != stable ]]; then
msg=$(printf "%s %s: %s -- %s" \
"${CMD}" 'load' \
"${relstage} module has been loaded" \
"${m}")
std::info "%s" "${msg}"
fi
msg=$(printf "%s: %s %s %s" \
'load' \
"modulefile=${current_modulefile}" \
"rel-stage=${relstage}" \
"user=${USER}")
${logger} -t Pmodules "${msg}"
done
set_lmfiles
EnvMustBeSaved='yes'
export_env 'LOADEDMODULES' '_LMFILES_'
}
##############################################################################
#
# unload
#
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() {
# :FIXME: add dependency tests: don't 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_illegal_opt "$1"
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_missing_arg
# 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_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}"
;;
* )
"${modulecmd}" "${Shell}" 'unload' "${arg}"
;;
esac
if [[ "${modulecmd}" == "${Tcl_cmd}" ]]; then
std::remove_path PmFiles "${lmfile}"
fi
done
if [[ ! -v PMODULES_HOME || -z ${PMODULES_HOME} ]]; then
PMODULES_HOME="${saved_home}"
export_env 'PMODULES_HOME'
fi
if [[ ! -v _LMFILES_ ]]; then
declare -x _LMFILES_=''
declare -x LOADEDMODULES=''
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
export_env 'LOADEDMODULES' '_LMFILES_'
EnvMustBeSaved='yes'
} # subcommand_unload
##############################################################################
#
# swap <module> [<module>]
#
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 args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_missing_arg
(( ${#args[@]} > 2 )) && \
die_too_many_args
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}"
}
##############################################################################
#
# show <module>
#
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 args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} == 0 )) && \
die_missing_arg
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
}
###############################################################################
# Find all modules in a given modulepath matching a specific string.
# The search can be restricted to certain release stages.
#
# Args:
# $1 reference variable to return result
# $2 search pattern
# $3 release stages
# $4... module path (fully qualified directory names)
#
# return list like
# modulename_1 relstage_1 modulefile_1 ...
#
get_available_modules() {
local -n gam_mods="$1"
local -r module="$2"
local -r used_relstages="${3:-${UsedReleaseStages}}"
shift 3 # in the for loop below we use $@ to loop over module path
local relstage
local -A dict
local -A modulenames
local dir
gam_mods=()
# loop over all entries in given module path
for dir in "$@"; do
test -d "${dir}" || continue
cd "${dir}" || std::die 3 "Oops: cannot change to directory '${dir}'"
# find overlay and group for this directory
local ol=''
local group=''
find_overlay ol group "${dir}"
# if directory is empty continue
local -a dir_entries=(*)
(( ${#dir_entries[@]} > 0 )) || continue
local sdirs="${dir##*/modulefiles}"
# loop over all files (and sym-links) in this directory and
# its sub-directories
local mod='' # module_name/module_version
while read -r mod; do
[[ "${mod}" =~ ${OverlayExcludes} ]] && continue
local name="${mod%/*}"
local add='no'
if [[ -n "${ol}" && "${ol}" != '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[${name}] ]]; then
# new entry
if [[ "${OverlayInfo[${ol}:type]}" == "${ol_hiding}" ]]; then
modulenames[${name}]="${ol}"
else
modulenames[${name}]='0'
fi
add='yes'
elif [[ "${modulenames[${name}]}" == "${ol}" ]]; then
add='yes'
elif [[ "${modulenames[${name}]}" == '0' ]] \
&& [[ ! -v dict[${sdirs}/${mod}] ]]; then
add='yes'
fi
else
ol='none'
add='yes' # module is NOT in an overlay
fi
[[ "${add}" == 'no' ]] && continue
local -A cfg=()
get_module_config cfg "${dir}" "${mod}"
is_available cfg "${used_relstages}" || continue
gam_mods+=( "${mod}" "${cfg['relstage']}" "${dir}/${mod}" "${ol}" )
dict[${sdirs}/${mod}]=1
done < <(${find} -L "${dir_entries[@]}" \
\( -type f -o -type l \) \
-not -name ".*" \
-ipath "${module}*" \
| ${sort} --version-sort)
done
} # get_available_modules()
#
# 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
# != else
#
find_modulefile(){
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=()
_match(){
local -- found_modulefile="$1"
if [[ ${found_modulefile} == "${modulename}" ]]; then
return 0
fi
if [[ ${found_modulefile} == "${modulename}.lua" ]]; then
return 0
fi
if [[ ${modulename} != */* ]]; then
if [[ "${found_modulefile}" == "${modulename}"/* ]]; then
return 0
fi
fi
return 1
}
_find_modulefile() {
local -- dir=''
for dir in "${modulepath[@]}"; do
test -d "${dir}" || continue
local -i col=$((${#dir} + 2 ))
local -- mod=''
mapfile -t found_modules \
< <(find -L "${dir}" \
-type f \
-not -name '.*' \
\( -ipath "${dir}/${modulename}" \
-or -ipath "${dir}/${modulename}.lua" \
-or -ipath "${dir}/${modulename}/*" \) \
-printf "%P\n" \
| sort -rV \
)
for mod in "${found_modules[@]}"; do
_match "${mod}" || continue
if [[ -L "${dir}/${mod}" ]]; then
# handle symbolic link
# - resolve link to absolut path
# - the absolut path must be in ${dir}
# - if not: continue
# - else set module name to relativ path
# by removing ${dir}
local lname=''
lname=$( std::get_abspath "${dir}/${mod}" )
[[ "${lname}" == "${dir}/*" ]] || continue
mod="${lname/${dir}\/}"
fi
local -A cfg=()
local -- relstages="${UsedReleaseStages}"
get_module_config cfg "${dir}" "${mod}"
# if the full name is given, we don't care about
# release stages.
if [[ "${mod}" == "${modulename}" || \
"${mod}" == "${modulename}.lua" ]]; then
relstages="${ReleaseStages}"
fi
is_available cfg "${relstages}" || continue
ref_modulefile="${dir}/${mod}"
ref_relstage="${cfg['relstage']}"
ref_moduledir="${dir}"
return 0
done
done
# Nothing found in MODULEPATH!
# The module to be loaded must be either given as relative or absolut
# path.
if [[ -r "${modulename}" ]]; then
ref_modulefile="$(std::get_abspath "${modulename}")"
ref_relstage='stable'
ref_moduledir="$(${dirname} "${ref_modulefile}")"
return 0
fi
return 1
} # _find_modulefile()
if (( $# >= 6 )); then
modulepath=("${@:6}")
else
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
fi
_find_modulefile || die_module_unavail "${modulename}"
is_modulefile modulecmd "${ref_modulefile}" || die_not_a_modulefile "${modulename}"
if [[ "${ref_interp}" == "${Lmod_cmd}" ]]; then
# Lmod doesn't support full qualified path names!
ref_modulefile="${ref_modulefile/${ref_moduledir}\/}"
fi
}
##############################################################################
#
# avail [-hlt] [<module-pattern>...]
#
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() {
# 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 -r caption="$1"
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() {
output_header "$1"
local -i i=0
for (( i=0; i<${#mods[@]}; i+=4 )); do
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+=4 )); 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() {
output_header "$1"
for (( i=0; i<${#mods[@]}; i+=4 )); do
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() {
output_header "$1"
local -a available_modules=()
local mod=''
local -i max_length=1
for ((i=0; i<${#mods[@]}; i+=4)); do
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 n=${#mod}
(( n > max_length )) && (( max_length=n ))
available_modules+=("${mod}")
done
local -i span=$(( max_length / 16 + 1 )) # compute column size
local -i colsize=$(( span * 16 )) # as multiple of 16
local -i column=$cols # force a line-break
for mod in "${available_modules[@]}"; do
local -i len=${#mod}
if (( column+len >= cols )); then
printf -- "\n" 1>&2
column=0
fi
if (( column+colsize < cols )); then
printf "%-${colsize}s" "${mod}" 1>&2
else
printf "%-s" "${mod}" 1>&2
fi
column+=colsize
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
local -- dir=''
local -- group=''
local -- groups=()
local -- ol=''
local -- name=''
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
for dir in "${modulepath[@]}"; do
if find_overlay ol group "${dir}"; then
name="${group}"
else
name="${dir}"
group="${dir//[\/ .-]/_}"
fi
# With overlays we can have multiple directories per group!
#
# Create an ordered list of directories per group.
# Note:
# BASH doesn't support list as values of dictionaries. We use
# reference variables to work around this.
if [[ ! -v modulepath_${group} ]]; then
typeset -a "modulepath_${group}"
fi
typeset -n path="modulepath_${group}"
path+=("${dir}")
# Create ordered list of groups. In the next step we
# loop over this list.
for group in "${groups[@]}"; do
if [[ "${group}" == "${name}" ]]; then
# resume with next dir
continue 2
fi
done
groups+=( "${name}" )
done
local string
for string in "${pattern[@]}"; do
for group in "${groups[@]}"; do
# limit output to certain groups
if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then
continue
fi
local ref="${group//[\/ .-]/_}"
[[ -v modulepath_${ref} ]] || continue
typeset -n path"=modulepath_${ref}"
get_available_modules \
mods \
"${string}*" \
"${opt_use_relstages}" \
"${path[@]}"
[[ ${#mods[@]} == 0 ]] && continue
${output_function} "${group}"
done
done
} # subcommand_avail()
##############################################################################
#
# use [-a|--append|-p|--prepend] [directory|group|release_stage...]
#
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.
"
subcommand_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_overlay() {
local ol_name="$1"
[[ -n "${LOADEDMODULES}" ]] && \
[[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]] && \
die_cannot_use_overlay "${ol_name}"
[[ ${OverlayInfo[${ol_name}:used]} == 'yes' ]] && return 0
# die_overlay_already_in_use "${ol_name}"
if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then
# if this overlay replaces groups, we have
# to remove the modules made available by
# other overlays in these groups
for group in ${UsedGroups//:/ }; do
# is this group in the to be added overlay?
local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/"
dir+="${group}/${PMODULES_MODULEFILES_DIR}"
[[ -d "${dir}" ]] || continue # no
dir="/${group}/${PMODULES_MODULEFILES_DIR}"
local -a dirs=()
for ol in "${UsedOverlays[@]}"; do
dirs+=( "${OverlayInfo[${ol}:modulefiles_root]}${dir}" )
done
std::remove_path MODULEPATH "${dirs[@]}"
done
fi
scan_groups "${ol_name}"
for group in ${UsedGroups//:/ }; do
local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/"
dir+="${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
fi
done
if [[ -v OverlayInfo[${ol_name}:modulepath] && \
-n "${OverlayInfo[${ol_name}:modulepath]}" ]]; then
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${OverlayInfo[${ol_name}:modulepath]}"
local -- dir=''
for dir in "${modulepath[@]}"; do
std::prepend_path MODULEPATH "${dir}"
done
fi
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() {
# die if argument is a hierarchical group
(( ${GroupDepths[${arg}]} > 0 )) && \
die_illegal_group "${arg}"
std::append_path UsedGroups "$1"
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]}/$1/${PMODULES_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
# argument is release stage
std::append_path UsedReleaseStages "${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_invalid_value "use flag, group, overlay or directory" \
"${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
EnvMustBeSaved='yes'
export_env 'MODULEPATH'
}
##############################################################################
#
# unuse directory|group|release_stage|...
#
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 -a modulepath=()
IFS=':' read -r -a modulepath <<<"${MODULEPATH}"
#......................................................................
unuse() {
#..............................................................
unuse_overlay() {
local ol_name="$1"
if [[ -n "${LOADEDMODULES}" ]] && \
[[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]]; then
std::die 3 "%s %s: %s %s" \
"${CMD}" "${SubCommand}" \
"overlay cannot be removed since" \
"some modules are still loaded!"
fi
[[ "${OverlayInfo[${ol_name}:modulefiles_root]}" == "${PMODULES_HOME%%/Tools*}" ]] && \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot remove base overlay" \
"${ol_name}"
[[ "${OverlayInfo[${ol_name}:used]}" != 'yes' ]] && \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"not an used overlay" \
"${ol_name}"
# make sure first index is '0' (it should, but you never know)
UsedOverlays=( "${UsedOverlays[@]}" )
[[ "${ol_name}" != "${UsedOverlays[0]}" ]] && \
std::die 3 "%s %s: %s %s -- %s" \
"${CMD}" "${SubCommand}" \
"overlay cannot be removed since" \
"it not on top of the stack" \
"${ol_name}"
if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then
# if this overlay hides groups, we have to re-add
# the modules made available by other overlays
local modulefiles_root=${OverlayInfo[${ol_name}:modulefiles_root]}
for group in ${UsedGroups//:/ }; do
# is this group in the to be removed overlay?
local dir="${modulefiles_root}/${group}/${PMODULES_MODULEFILES_DIR}"
[[ -d "${dir}" ]] || continue # no
dir="/${group}/${PMODULES_MODULEFILES_DIR}"
std::prepend_path MODULEPATH "${UsedOverlays[@]/%/${dir}}"
done
fi
OverlayInfo[${ol_name}:used]='no'
UsedOverlays=( "${UsedOverlays[@]:1}")
# 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
# remove additional directories added by overlay
if [[ -v OverlayInfo[${ol_name}:modulepath] && \
-n "${OverlayInfo[${ol_name}:modulepath]}" ]]; then
local -a modulepath=()
IFS=':' read -r -a modulepath <<<"${OverlayInfo[${ol_name}:modulepath]}"
local -- dir=''
for dir in "${modulepath[@]}"; do
std::remove_path MODULEPATH "${dir}"
std::remove_path ModulePathAppend "${dir}"
std::remove_path ModulePathPrepend "${dir}"
done
fi
# remove root of overlay
local dir
for dir in "${modulepath[@]}"; do
[[ "${dir}" == "${OverlayInfo[${ol_name}:modulefiles_root]}" ]] && \
std::remove_path MODULEPATH "${dir}"
done
export_env UsedOverlays
EnvMustBeSaved='yes'
}
#..............................................................
unuse_group() {
if (( ${GroupDepths[${arg}]} > 0 )); then
# argument is a hierarchical group in our root
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"illegal group" \
"${arg}"
fi
if [[ -v PMODULES_LOADED_${arg^^} ]]; then
local var="PMODULES_LOADED_${arg^^}"
if [[ -n "${!var}" ]]; then
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"cannot remove group due to loaded modules" \
"${arg}"
fi
fi
std::remove_path UsedGroups "${arg}"
local overlay
for overlay in "${UsedOverlays[@]}"; do
local dir="${OverlayInfo[${overlay}:modulefiles_root]}"
dir+="/${arg}/${PMODULES_MODULEFILES_DIR}"
std::remove_path MODULEPATH "${dir}"
done
}
#..............................................................
local arg=$1
if is_release_stage "${arg}"; then
# argument is release stage
std::remove_path UsedReleaseStages "${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
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${SubCommand}" \
"invalid keyword, group, overlay or directory" \
"${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
if (( ${#args[@]} == 0 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
'missing argument'
fi
for arg in "${args[@]}"; do
unuse "${args[@]}"
done
EnvMustBeSaved='yes'
export_env 'MODULEPATH'
}
##############################################################################
#
# update
#
# :FIXME:
# either compile Modules with --enable-beginenv or remove the
# sub-command
#
Subcommands['update']='update'
Options['update']='-o \?H -l help'
Help['update']='
USAGE:
module update
Attempt to reload all loaded modulefiles.
'
subcommand_update() {
subcommand_generic0 "$@"
}
##############################################################################
#
# refresh
#
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() {
subcommand_generic0 "$@"
}
#
# Helper functions, used during initialization and for purging all modules.
#
# Arguments:
# none
#
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]}/${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
fi
done
done
}
pmodules_init() {
init_used_groups() {
declare -gx UsedGroups=''
local group
for group in ${DefaultGroups//:/ }; do
std::append_path UsedGroups "${group}"
done
EnvMustBeSaved='yes'
}
init_used_releases() {
declare -g UsedReleaseStages=''
for r in ${DefaultReleaseStages//:/ }; do
std::append_path UsedReleaseStages "${r}"
done
EnvMustBeSaved='yes'
}
init_overlay_vars() {
declare -ag UsedOverlays=( 'base' )
OverlayInfo['base:used']='yes'
declare -g OverlayExcludes=''
}
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
}
pm::read_config
declare -gx LOADEDMODULES=''
declare -gx _LMFILES_=''
declare -Ag GroupDepths=()
declare -g Version="${PMODULES_VERSION}"
init_used_groups
init_used_releases
init_overlay_vars
init_modulepath
init_manpath
scan_groups "${UsedOverlays[@]}"
save_env
export_env \
LOADEDMODULES \
_LMFILES_ \
MODULEPATH \
PATH \
MANPATH
}
##############################################################################
#
# purge
#
Subcommands['purge']='purge'
Options['purge']='-o \?H -l help'
Help['purge']='
USAGE:
module purge
Unload all loaded modulefiles.
'
subcommand_purge() {
#
# unload all loaded modules
#
# 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_illegal_opt "$1"
;;
* )
args+=( "$1" )
;;
esac
shift
done
(( ${#args[@]} > 0 )) && die_no_args_allowed
# get list of loaded modules with stripped MODULEPATH
IFS=':' read -r -a modules <<< "${LOADEDMODULES}"
for (( i=${#modules[@]}-1; i>=0; i-- )); do
[[ ${modules[$i]} == Pmodules/* ]] && continue
subcommand_unload "${modules[$i]}"
done
}
##############################################################################
#
# list [-hlt]
#
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() {
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_illegal_opt "$1"
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} > 0 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"no arguments allowed"
fi
"${output_function}"
}
##############################################################################
#
# clear
#
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 -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
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"no arguments allowed"
fi
pmodules_init
}
##############################################################################
#
# search [switches] [STRING...]
#
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 -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 -- search_range='all'
local -a src_prefix=()
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_glob='no'
local opt_newest='no'
#.....................................................................
#
# 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" "$2" "$3" "$5" "${deps[*]}")
write_line "${str}"
else
str=$(printf "${fmt}" "$1" "$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_csv() {
:
}
print_csv() {
fmt=''
func_print_header='print_header_none'
func_print_line='print_line_csv'
}
if [[ "${opt_print_modulefiles}" == 'yes' ]]; then
print_modulefiles
elif [[ "${opt_print_csv}" == 'yes' ]]; then
print_csv
elif [[ "${opt_print_verbose}" == 'yes' ]]; then
print_verbose
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
# Args:
# $1: module name pattern
#
# Variables used from enclosing function
# :FIXME:
#
search () {
local -r module="$1"
local -r group="$2"
shift 2
local -a modulepath=("$@")
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 mods
get_available_modules \
mods \
"${module}" \
"${opt_use_relstages}" \
"${modulepath[@]}"
local i=0
for (( i=0; i<${#mods[@]}; i+=4 )); do
local name=${mods[i]}
local relstage=${mods[i+1]}
local modulefile=${mods[i+2]}
local ol=${mods[i+3]}
local -a deps=()
if (( ${#name} > max_len_modulename)); then
max_len_modulename=${#name}
fi
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
elif [[ "${group}" != 'other' ]]; then
# 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}"
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-csv )
opt_print_csv='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}" || \
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal release stage" \
"${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
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal value for --with option" \
"${arg}"
fi
arg=${arg//:/ }
arg=${arg//,/ }
for module in ${arg}; do
with_modules+=" && / ${module//\//\\/}/"
done
;;
-a | --all-releases-stages )
opt_use_relstages+="${ReleaseStages}"
;;
--src | --src=*)
if [[ "$1" == --src ]]; then
local dir="$2"
shift
else
local dir="${1/--src=}"
fi
if [[ ! -e "${dir}" ]]; then
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal value for --src option" \
"${dir} does not exist"
fi
if [[ ! -d "${dir}" ]]; then
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal value for --src option" \
"${dir} is not a directory"
fi
src_prefix+=( "$(std::get_abspath "${src_prefix}")" )
;;
-v | --verbose )
opt_print_verbose='yes'
;;
--wrap )
opt_wrap='yes'
;;
--glob )
opt_glob='yes'
;;
--newest )
opt_newest='yes'
;;
--group | --group=* )
if [[ $1 == *=* ]]; then
group="${1/--*=}"
else
group="$2"
shift
fi
if [[ -v GroupDepths[${group}] ]]; then
search_range='inside'
elif [[ "${group}" == 'other' ]]; then
search_range='outside'
else
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal value for --group option" \
"${group} is not a valid group!"
fi
groups+=( "${group}" )
;;
-- )
shift 1
modules+=( "$@" )
break
;;
* )
modules+=( "$1" )
;;
esac
shift
done
if (( ${#src_prefix[@]} == 0 )); then
local ol=''
for ol in "${UsedOverlays[@]}"; do
src_prefix+=( "${OverlayInfo[${ol}:modulefiles_root]}" )
done
fi
if [[ "${opt_use_relstages}" == ":" ]]; then
opt_use_relstages=":${UsedReleaseStages}:"
fi
if [[ ${#modules[@]} == 0 ]]; then
modules+=( '' )
fi
local -- module=''
if [[ "${search_range}" == 'all' || "${search_range}" == 'inside' ]]; then
# search inside the Pmodules hierarchy
# search in all groups if search is not limited
if (( ${#groups[@]} == 0 )); then
groups=( "${!GroupDepths[@]}" )
fi
for module in "${modules[@]}"; do
[[ ${opt_glob} == 'no' ]] && module+="*"
for group in "${groups[@]}"; do
# loop over all directories which can be added to
# MODULEPATH inside current group
local -i depth=${GroupDepths[${group}]}
local s=''
if (( depth > 0 )); then
s=$(printf '/*%.0s' $(seq 1 ${depth}))
fi
modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} )
search "${module}" "${group}" "${modulepath[@]}"
done
done
fi
if [[ "${search_range}" == 'all' || "${search_range}" == 'outside' ]]; then
# search outside the Pmodules hierarchy
IFS=':' read -r -a modulepath <<< "${MODULEPATH}"
local -a dirs=()
local -- dir=''
for dir in "${modulepath[@]}"; do
# skip all directories in Pmodules hierarchy
local -- ol_name=''
local -- install_root=''
for ol_name in "${Overlays[@]}"; do
install_root="${OverlayInfo[${ol_name}:install_root]}"
[[ "${dir}" == "${install_root}"/* ]] && continue 2
done
# add moduledir outside Pmodules hierarchy
dirs+=( "${dir}" )
done
for module in "${modules[@]}"; do
[[ ${opt_glob} == 'no' ]] && module+="*"
search "${module}" 'other' "${dirs[@]}"
done
fi
print_result
echo -n '' > ${TmpFile}
}
##############################################################################
#
# help [module|sub-command]
#
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() {
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}"
else
local -- modulefile=''
local -- relstage=''
local -- moduledir=''
find_modulefile \
modulefile \
relstage \
moduledir \
modulecmd \
"${arg}"
"${modulecmd}" 'bash' 'help' "${modulefile}"
fi
done
}
##############################################################################
#
# whatis
#
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 options=()
local args=()
while (( $# > 0 )); do
case $1 in
-\? | --help )
print_help "${SubCommand}"
;;
-a | --all )
options+=( '-a' )
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
local group=''
for group in "${!GroupDepths[@]}"; do
local mod_name=''
local file_name=''
while read -r mod_name file_name; do
[[ -n ${file_name} ]] || continue
if [[ "${file_name##*.}" == 'lua' ]]; then
modulecmd="${Lmod_cmd}"
file_name="${mod_name}"
else
modulecmd="${Tcl_cmd}"
fi
local whatis=''
whatis=$("${modulecmd}" bash \
whatis \
"${file_name}" \
2>&1 1>/dev/null)
printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2
done < <(set +x; subcommand_search \
--group "${group}" \
--print-modulefiles \
--newest \
"${options[@]}" \
"${args[@]}" 2>&1)
done
}
##############################################################################
#
# apropos
#
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 options=()
local args=()
while (( $# > 0 )); do
case $1 in
-\? | --help )
print_help "${SubCommand}"
;;
-a | --all )
options+=( '-a' )
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"no search string specified"
elif (( ${#args[@]} > 1 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"more then one search string specified"
fi
local arg="${args[0]}"
local group=''
for group in "${!GroupDepths[@]}"; do
local mod_name=''
local file_name=''
while read -r mod_name file_name; do
[[ -n ${file_name} ]] || continue
if [[ "${file_name##*.}" == 'lua' ]]; then
modulecmd="${Lmod_cmd}"
file_name="${mod_name}"
else
modulecmd="${Tcl_cmd}"
fi
local whatis=''
whatis=$("${modulecmd}" bash \
whatis \
"${file_name}" \
2>&1 1>/dev/null)
if [[ ${whatis,,} =~ ${arg,,} ]]; then
printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2
fi
done < <(set +x; subcommand_search \
--group "${group}" \
--print-modulefiles \
"${options[@]}" 2>&1)
done
}
##############################################################################
#
# 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:
##
##############################################################################
#
# save [collection]
#
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 -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_too_many_args
[[ ${args[0]:0:1} =~ [0-9a-zA-Z] ]] || \
die_invalid_collection_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_cannot_save_collection "${collection}"
}
##############################################################################
#
# restore [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_collection_doesnt_exist "${_collection}"
}
subcommand_restore() {
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_too_many_args
(( ${#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+=( "${grp}" )
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
save_env
export_env PMODULES_ENV
# load collection
for collection in "${collections[@]}"; do
${cat} "${collection}"
done
EnvMustBeSaved='no'
}
##############################################################################
#
# module savelist [pattern...]
#
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() {
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[@]}"
}
##############################################################################
#
# module saverm [collection...]
#
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 -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_collection_doesnt_exist "${collection}"
${rm} -f "${path}/${collection}" 2>/dev/null || \
die_removing_collection_failed "${collection}"
# remove directories if empty
${rmdir} -p "${path}/${collection%%/*}" 2>/dev/null
done
}
##############################################################################
#
# module saveshow [collection]
#
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 -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_collection_doesnt_exist "${collection}"
std::info "Collection '${collection}':"
cat "${path}/${collection}" 1>&2
done
}
##############################################################################
#
# initadd module...
#
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() {
subcommand_generic1plus "$@"
}
##############################################################################
#
# initprepend module...
#
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() {
subcommand_generic1plus "$@"
}
##############################################################################
#
# initrm module...
#
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() {
subcommand_generic1plus "$@"
}
##############################################################################
#
# initswitch module1 module2
#
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 args=()
while (( $# > 0 )); do
case $1 in
-\? | --help )
print_help "${SubCommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} != 2 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${SubCommand}" \
"two arguments required not less not more"
fi
"${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}"
}
##############################################################################
#
# initlist
#
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() {
subcommand_generic0 "$@"
}
##############################################################################
#
# initclear
#
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() {
subcommand_generic0 "$@"
}
##############################################################################
#
# main
#
# parse arguments
#
(( $# > 0 )) || print_help 'help'
# first argument must be a shell!
case "$1" in
sh | bash | zsh )
declare Shell="sh"
;;
csh | tcsh )
declare Shell='csh'
;;
python )
declare Shell='python'
;;
* )
std::die 1 "${CMD}: unsupported shell -- $1"
;;
esac
shift
debug(){
:
}
# parse agruments till and including the sub-command
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
if [[ -z "${SubCommand}" ]]; then
std::die 1 "${CMD}: no sub-command specified."
fi
if [[ ! -v Subcommands[${SubCommand}] ]]; then
std::die 1 "${CMD}: unknown sub-command -- ${SubCommand}"
fi
# restore variables from last call
if [[ -v PMODULES_ENV ]]; then
eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)"
fi
# Version should now be defined again, if not:
# - PMODULES_ENV was not set
# - Version was not defined the last time the status was saved.
# This is true for older Pmodules versions.
# We (re-)initialise the Pmodules system, if
# - PMODULES_ENV was not set/is empty
# - A new Pmodules version has been loaded
if [[ ! -v Version ]] || \
[[ ${Version} != "${PMODULES_VERSION}" ]] || \
[[ ! -v PMODULES_ENV ]] || \
[[ -z ${PMODULES_ENV} ]]; then
declare _tmp_loaded_modules_="${LOADEDMODULES}"
declare _tmp_lmfiles_="${_LMFILES_}"
pmodules_init
LOADEDMODULES="${_tmp_loaded_modules_}"
_LMFILES_="${_tmp_lmfiles_}"
export_env \
LOADEDMODULES \
_LMFILES_
fi
# re-define these variables. Lmod muught have unset them!
if [[ ! -v _LMFILES_ ]]; then
declare -x _LMFILES_=''
declare -x LOADEDMODULES=''
fi
# we need to handle help text and options for sub-cmd aliases
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: