Files
Pmodules/Pmodules/modulecmd.bash.in
T

2868 lines
80 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!@BASH@ --noprofile
#
unset CDPATH # unset CDPATH, otherwise 'cd' prints the directoy!
unset IFS # use default IFS
shopt -s nullglob
# used in some output messages only
declare -r CMD='module'
declare -r mydir=$(cd $(dirname "$0") && pwd)
declare prefix=$(dirname "${mydir}")
declare -r libdir="${prefix}/lib"
declare -r libexecdir="${prefix}/libexec"
source "${libdir}/libstd.bash"
source "${libdir}/libpmodules.bash"
path="/bin:/usr/bin"
[[ $(uname -s) == 'Darwin' ]] && path+=":${libexecdir}"
std::def_cmds "${path}" \
'awk' 'base64' 'find' 'getopt' 'logger' 'mktemp' \
'rm' 'sort' 'find'
if [[ ${PMODULES_PURETCL} == yes ]]; then
declare -r modulecmd="${libexecdir}/modulecmd.tcl"
else
declare -rx TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules"
declare -r modulecmd="${libexecdir}/modulecmd.bin"
fi
declare -r ol_normal='n'
declare -r ol_hiding='h'
declare -r ol_replacing='r'
declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'}
declare Shell=''
declare -r pmodules_config_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/Pmodules.conf"
# the following settings are used if the Pmodules.conf 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'
export_env() {
case "${Shell}" in
sh | bash | zsh )
local -r fmt="export %s=\"%s\"; "
;;
csh | tcsh )
local -r fmt="setenv %s \"%s\"; "
;;
* )
std::die 1 "Unsupported shell -- ${Shell}"
;;
esac
while (( $# > 0 )); do
printf "${fmt}" "$1" "${!1}"
shift
done
# :FIXME: UsedGroups can be modified in libmodule.tcl using
# append-path/remove-path! But we keep the state in PMODULES_ENV
# so we don't have to export it.
#
case "${Shell}" in
sh | bash | zsh )
echo "unset UsedGroups; "
;;
csh | tcsh )
echo "unsetenv UsedGroups; "
;;
esac
}
#
# Save/cache some variables.
# This function is called on exit via a trap handler.
#
# Args;
# none
#
declare g_env_must_be_saved='no'
save_env() {
[[ $1 == 'no' ]] && return 0
local vars=( Version )
vars+=( UsedReleaseStages UsedFlags UsedGroups )
vars+=( DefaultGroups DefaultReleaseStages )
vars+=( ReleaseStages )
vars+=( GroupDepths )
vars+=( OverlayList )
vars+=( OverlayNames )
vars+=( OverlayDict Dir2OverlayMap)
local s=$(typeset -p ${vars[@]})
declare -g PMODULES_ENV=$( "${base64}" --wrap=0 <<< "$s" )
export_env 'PMODULES_ENV'
}
_exit() {
save_env "${g_env_must_be_saved}"
if [[ -n "${tmpfile}" ]] && [[ -e "${tmpfile}" ]]; then
${rm} -f "${tmpfile}" || :
fi
}
trap '_exit' EXIT
get_overlay_of_moduledir() {
local "$1"
local moduledir="${2//+(\/)/\/}" # replace multpile '/' with one
moduledir="${moduledir/%\/}" # remove trailing slash if exist
if [[ ! -v Dir2OverlayMap[${moduledir}] ]]; then
for overlay in "${OverlayList[@]}" 'other'; do
[[ ${moduledir} == ${overlay}/* ]] && break
done
Dir2OverlayMap[${moduledir}]="${overlay}"
fi
std::upvar $1 "${Dir2OverlayMap[${moduledir}]}"
}
#
# get release stage of module
# Note:
# - the release stage of a module outside ${OverlayDict[@]} is always 'stable'
# - the release stage of a module inside ${OverlayDict[@]} without a
# coresponding file is always 'unstable'
#
# Args:
# $1 upvar for returned release stage
# $2 modulefile directory (element of MODULEPATH)
# $3 module name/version
#
get_release_stage() {
local "$1"
local -r moduledir=$2
local -r name=$3
local -r modulefile="$2/$3"
local overlay
get_overlay_of_moduledir overlay "${moduledir}"
if [[ "${overlay}" == 'other' ]]; then
std::upvar $1 'stable'
return
fi
#
# In an overlay the name of the module-file is something like
# dir/modulefiles/name/version
# the corresponding file is
# dir/modulefiles/name/.release-version
#
local -r rel_stage_file="${modulefile%/*}/.release-${modulefile##*/}"
if [[ -r ${rel_stage_file} ]]; then
# read file, remove empty lines, spaces etc
std::upvar $1 $( < "${rel_stage_file}" )
else
std::upvar $1 'unstable'
fi
}
is_release_stage() {
[[ ${ReleaseStages} =~ :$1: ]]
}
#
# Check whether a given path is in an used overlay.
# If yes, return 0 and the overlay with upvar of first argument
# otherwise return 1
#
# $1 upvar to return overlay
# $2 upvar to return group
# $3 path to check
#
find_overlay () {
local "$1"
local "$2"
local path="${3//+(\/)/\/}" # replace multpile '/' with one
path="${path/%\/}" # remove trailing slash if exist
local overlay="${Dir2OverlayMap[${path}]}"
get_overlay_of_moduledir overlay "${path}"
std::upvar $1 "${overlay}"
[[ "${overlay}" == 'other' ]] && return 1
local group="${path#${overlay}/}"
group=${group%%/*}
std::upvar $2 "${group}"
return 0
}
module_is_loaded() {
[[ :${LOADEDMODULES}: =~ :$1: ]]
}
#
# check shebang
# $1: file name to test
is_modulefile() {
local -r fname="$1"
local shebang
[[ -r ${fname} ]] || return 1
read -n 11 shebang < "${fname}"
[[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]]
}
get_module_prefix() {
local "$1"
local modulefile="$2"
local -r _prefix=$("${modulecmd}" bash show "${modulefile}" 2>&1 | \
${awk} '/_PREFIX |_HOME / {print $3; exit}')
std::upvar $1 "${_prefix}"
}
subcommand_generic0() {
local -r subcommand="$1"
shift
local -a args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${subcommand}"
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift 1
done
if (( ${#args[@]} > 0 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"no arguments allowed"
fi
"${modulecmd}" "${Shell}" "${subcommand}"
}
subcommand_generic1() {
local -r subcommand="$1"
shift
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"
elif (( ${#args[@]} > 1 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"only one argument allowed"
fi
"${modulecmd}" "${Shell}" "${subcommand}" "${args[@]}"
}
subcommand_generic1plus() {
local -r subcommand="$1"
shift
local 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
"${modulecmd}" "${Shell}" "${subcommand}" "${args[@]}"
}
##############################################################################
#
# 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 -r subcommand='load'
local rel_stage='undef'
local current_modulefile=''
local prefix=''
local m=''
#......................................................................
# Test whether a given module is available.
# The passed module-name can be
#
# - an absolute file- or link-name.
# The module can be either in- or outside our hierarchy.
#
# - a relative file- or link-name.
# The module can be either in- or outside out hierarchy.
#
# - specified with name and version (like gcc/5.2.0).
# The module can be either in- or outside our hierarchy.
#
# - specified with name only (without version, like gcc).
# The module can be either in- or outside our hierarchy.
#
# - directory in- or outsite our hierarchy (not supported by
# modulecmd.tcl!)
#
# arguments:
# $1: module name or file
#
# possible return values:
# 0: module is loadable
# 1: either not a modulefile or unsused release stage
#
# The following variables in the enclosing function are set:
# current_modulefile
# prefix
# rel_stage
#
is_available() {
local m=$1
local -a array
#
# the next command assigns the absolute modulefile path
# to ${arry[0]} and - if the module is in our root - the
# prefix of the module to ${array[1]}.
#
# The trick with the first line matching "_PREFIX" is not
# 100% reliable: One of the Pmodules extensions must be
# called before something like
# setenv FOO_PREFIX bar
# can be used.
#
mapfile -t array < <("${modulecmd}" 'bash' show "$m" 2>&1 | \
${awk} 'NR == 2 {print substr($0, 1, length($0)-1)}; /_PREFIX |_HOME / {print $3; exit}')
current_modulefile="${array[0]}"
prefix="${array[1]}"
test -n "${current_modulefile}" || return 1
get_release_stage rel_stage "${current_modulefile}" "${UsedReleaseStages}"
}
#......................................................................
# output load 'hints'
#
# Note:
# The variable 'm' from the parent function will be used
# but not changed.
#
# Args:
# none
get_load_hints() {
local "$1"
local output=''
local rel_stage=''
while read -a line; do
[[ -z ${line} ]] && continue
rel_stage=${line[1]}
if [[ ! ":${UsedReleaseStages}:" =~ "${rel_stage}" ]]; then
output+="module use ${rel_stage}; "
fi
local group=${line[2]}
if [[ ! ":${UsedGroups}:" =~ ":${group}:" ]] && \
(( ${GroupDepths[${group}]} == 0 )); then
output+="module use ${group}; "
fi
local -i n=$(( ${GroupDepths[${group}]}/2 ))
output+="module load ${line[@]:3:$n} ${line[0]}\n"
done < <(set +x; subcommand_search "${m}" -a --no-header 2>&1)
std::upvar $1 "${output}"
}
#......................................................................
module_is_loaded() {
[[ :${LOADEDMODULES}: =~ :$1: ]]
}
#......................................................................
load_dependencies() {
local -r fname="$1"
while read 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 () {
local "$1"
find_an_overlay_providing_group () {
local "$1"
local -r group="$2/${PMODULES_MODULEFILES_DIR}"
local overlay
for overlay in "${OverlayList[@]}"; do
if [[ -d "${overlay}/${group}" ]]; then
std::upvar $1 "${overlay}"
return 0
fi
done
return 1
}
local -r group="$1"
# arg isn't emtpy and group already in cache
[[ -n ${group} ]] && [[ -n ${GroupDepths[${group}]} ]] && return 0
local overlay=''
find_an_overlay_providing_group overlay "${group}" || return 1
local moduledir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}"
compute_group_depth "${moduledir}" || return 1
g_env_must_be_saved='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
if (( ${#args[@]} == 0 )); then
std::die 2 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"No module specified"
fi
IFS=':'
local -a modulepath=(${MODULEPATH})
unset IFS
local m=''
for m in "${args[@]}"; do
if [[ "$m" =~ ":" ]]; then
# extendet module name is either
# - group:name or
# - group:name:rel_stage or
# - rel_stage:name or
# - rel_stage:group:name or
# - name:rel_stage
IFS=':'
local -a toks=($m)
unset IFS
local group=''
local rel_stage=''
if is_group "${toks[0]}"; then
group=${toks[0]}
m=${toks[1]}
rel_stage=${toks[2]}
elif is_release_stage "${toks[0]}"; then
rel_stage=${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]}
rel_stage=${toks[2]}
else
rel_stage=${toks[1]}
group=${toks[2]}
fi
fi
if [[ -n ${group} ]]; then
is_group "${group}" || \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"illegal group name" \
"${group}"
local -i depth=${GroupDepths[${group}]}
(( depth != 0 )) && \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"illegal group name" \
"${group}"
MODULEPATH=""
modulepath=()
group+="${PMODULES_MODULEFILES_DIR}"
for overlay in "${OverlayList[@]}"; do
MODULEPATH="${overlay}/${group}/:${MODULEPATH}"
modulepath=( "${overlay}/${group}/" "${modulepath[@]}" )
done
fi
if [[ -n ${rel_stage} ]]; then
is_release_stage "${rel_stage}" || \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"illegal release stage" \
"${rel_stage}"
std::append_path UsedReleaseStages "${rel_stage}"
g_env_must_be_saved='yes'
fi
fi # handle extended module names
find_module current_modulefile rel_stage "${m}" "${modulepath[@]}"
if [[ -z ${current_modulefile} ]]; then
local text=''
get_load_hints text
if [[ -z "${text}" ]]; then
std::die 3 "%s %s: module does not exist -- %s" \
"${CMD}" 'load' "${m}"
else
std::info "%s %s: module unavailable -- %s" \
"${CMD}" 'load' "${m}"
if [[ ${verbosity_lvl} == 'verbose' ]]; then
std::info ''
std::info "Try with one of the following command(s):"
std::info "${text}"
fi
std::die 3 ""
fi
fi
if [[ ${m} == Pmodules/* ]] && [[ -n ${LOADEDMODULES} ]]; then
std::error "%s %s: %s" \
"${CMD}" "${subcommand}" \
"cannot load a Pmodules module because other modules are already load!"
std::die 3 "%s %s: %s -- %s\n" \
"${CMD}" "${subcommand}" \
"failed" \
"${m}"
fi
if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then
# already loaded
continue
fi
local prefix=''
get_module_prefix prefix "${current_modulefile}"
if [[ -n ${prefix} ]]; then
test -r "${prefix}/.info" && cat "$_" 1>&2
test -r "${prefix}/.dependencies" && load_dependencies "$_"
fi
local 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=$( < "${tmpfile}")
if [[ "${error}" =~ ":ERROR:" ]]; then
local s=${error%%$'\n'*}
local error_txt='failed'
if [[ "$s" =~ ' conflicts ' ]]; then
error_txt="conflicts with already loaded module(s): ${s#*module(s) }"
fi
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"${error_txt}" \
"${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
eval "${output}"
if [[ -n "${error}" ]]; then
echo "${error}" 1>&2
fi
local msg=$(printf "%s %s: %s -- %s" \
"${CMD}" 'load' \
"${rel_stage} module has been loaded" \
"${m}")
if [[ ${verbosity_lvl} != silent ]] && \
[[ ${rel_stage} != stable ]]; then
std::info "%s" "${msg}"
fi
${logger} -t Pmodules "${msg}"
done
# fix LOADEDMODULES
LOADEDMODULES="${_LMFILES_}"
local dir
while read dir; do
[[ "${dir: -1}" == "/" ]] || dir+="/"
LOADEDMODULES="${LOADEDMODULES//${dir}}"
get_overlay_of_moduledir overlay "${dir}"
done <<< "${MODULEPATH//:/$'\n'}"
g_env_must_be_saved='yes'
export_env 'LOADEDMODULES'
}
##############################################################################
#
# 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() {
local -r 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
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} == 0 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"missing argument"
fi
# 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}"
local arg
for arg in "${args[@]}"; do
local output=$("${modulecmd}" "${Shell}" 'unload' "${arg}")
eval "${output}"
case ${Shell} in
sh | bash | zsh )
echo "${output}"
;;
* )
"${modulecmd}" "${Shell}" 'unload' "${arg}"
;;
esac
done
if [[ -z ${PMODULES_HOME} ]]; then
PMODULES_HOME=${saved_home}
export_env 'PMODULES_HOME'
fi
g_env_must_be_saved='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 -r subcommand='swap'
local 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"
elif (( ${#args[@]} > 2 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"too many arguments"
fi
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 -r subcommand='show'
local 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
local arg
for arg in "${args[@]}"; do
"${modulecmd}" "${Shell}" "${subcommand}" "${arg}"
done
}
###############################################################################
# Find all modules in a given modulepath matching a specific string.
# The search can be restricted to certain release stages.
#
# return list like
# modulename_1 rel_stage_1 modulefile_1 ...
#
get_available_modules() {
local var="$1"
local -r module="$2"
local -r used_rel_stages="${3:-${UsedReleaseStages}}"
shift 3 # in the for loop below we use $@ to loop over the directories
local -a mods=()
local rel_stage
local -A dict
local -A modulenames
local dir
for dir in "$@"; do
test -d "${dir}" || continue
{
cd "${dir}"
# there might be no mapping for ${dir}!
# - after loading the parent of a hierarchical group
# - if we do a search
# - if we create a new hierarchical group
local overlay
local group
find_overlay overlay group "${dir}"
# if no modules are installed in ${dir}, '*' expands to
# the empty string! Using '*' in the find command below
# would cause problems with some non-GNU find
# implementations.
local entries=$(echo *)
[[ -n ${entries} ]] || continue
while read mod; do
local add='no'
if [[ -n "${overlay}" ]]; 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
local name="${mod%/*}"
local key="${dir##/${PMODULES_MODULEFILES_DIR}}${name}"
if [[ -z "${modulenames[${key}]}" ]]; then
if [[ "${OverlayDict[$overlay]%%:*}" == "${ol_hiding}" ]]; then
modulenames[${key}]="${overlay}"
else
modulenames[${key}]='0'
fi
add='yes'
elif [[ "${modulenames[${key}]}" == "${overlay}" ]]; then
add='yes'
elif [[ "${modulenames[${key}]}" == '0' ]] \
&& [[ -z ${dict[${mod}]} ]]; then
add='yes'
fi
else
# module is NOT in an overlay
add='yes'
fi
[[ "${add}" == 'yes' ]] || continue
get_release_stage \
rel_stage \
"${dir}" \
"${mod}"
[[ :${used_rel_stages}: =~ :${rel_stage}: ]] || continue
mods+=( "${mod}" ${rel_stage} "${dir}/${mod}" )
dict[${mod}]=1
done < <(${find} -L ${entries} \
\( -type f -o -type l \) \
-not -name ".*" \
-ipath "${module}*" \
| ${sort} --version-sort)
}
done
std::upvar ${var} "${mods[@]}"
} # get_available_modules()
#
# find module(file) to load. Input arguments are
# $1 upvar: return module file
# $2 upvar: return module release stage
# $3 module to load
# $4 a modulepath (usually MODULEPATH)
#
# The module name can be
# name
# name/version
# name/version_flag
#
# Return
# return code 0 and modulefile in first argument
# return code 1 and modulefile in first argument
# if module
# return code 2
# if no modulefile could be found
#
find_module() {
local "$1"
local "$2"
local -r module="$3"
local -a dirs=("${@:4}")
for dir in "${dirs[@]}"; do
test -d "${dir}" || continue
local -i col=$((${#dir} + 2 ))
local -a modules
if [[ ${module} == */* ]]; then
# a version number has been specified. But we still might
# have the same module/version with different use flags.
# The different release stages we ignore in this case.
modules=( $(${find} -L "${dir}" -type f -not -name ".*" \
-ipath "${dir}/${module}*" \
| cut -b${col}-) )
for mod in "${modules[@]}"; do
#
# loop over all used flags. If a module with
# a used flag is available load this module.
local rel_stage
for flag in "${UseFlags[@]/#/_}" ""; do
[[ ${mod} == ${module}${flag} ]] || continue
std::upvar $1 "${dir}/${mod}"
get_release_stage \
rel_stage \
"${dir}" \
"${mod}"
std::upvar $2 "${rel_stage}"
return 0
done
done
else
# no version has been specified. This makes it more
# difficult. We have to load the newest version taking
# the used release stages and flags into account.
(( col += ${#module} + 1 ))
modules=( $(${find} -L "${dir}" -type f -not -name ".*" \
-ipath "${dir}/${module}/*" \
| cut -b${col}- \
| sort -rV ) )
modules=( "${modules[@]/#/${module}/}" )
for mod in "${modules[@]}"; do
#
# loop over all used flags. If a module with
# a used flag is available load this module.
local rel_stage=''
for flag in "${UseFlags[@]/#/_}" ""; do
[[ ${mod} == ${module}/*${flag} ]] || continue
std::upvar $1 "${dir}/${mod}"
get_release_stage \
rel_stage \
"${dir}" \
"${mod}"
std::upvar $2 "${rel_stage}"
[[ :${release}: =~ :${UsedReleaseStages}: ]] && \
return 0
done
done
fi
done
return 1
} # find_module()
##############################################################################
#
# 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() {
local -r subcommand='avail'
# use this variable in the output functions
local -a mods=()
local dir=''
# get number of columns of terminal
cols=$(tput cols)
#......................................................................
output_header() {
local caption="$1"
let 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+=3 )); do
local mod=${mods[i]}
local rel_stage=${mods[i+1]}
case ${rel_stage} in
stable )
out=''
;;
* )
out="${rel_stage}"
;;
esac
printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
done
std::info ""
}
#......................................................................
machine_output() {
for (( i=0; i<${#mods[@]}; i+=3 )); 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+=3 )); do
local mod=${mods[i]}
local rel_stage=${mods[i+1]}
case ${rel_stage} in
stable )
out=''
;;
* )
out=${rel_stage}
;;
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+=3)); do
if [[ ${verbosity_lvl} == 'verbose' ]]; then
local rel_stage=${mods[i+1]}
case ${rel_stage} in
stable )
mod="${mods[i]}"
;;
* )
mod="${mods[i]}(${rel_stage:0:1})"
;;
esac
else
mod=${mods[i]}
fi
local -i n=${#mod}
(( n > max_length )) && (( max_length=n ))
available_modules+=("${mod}")
done
IFS=$'\n' available_modules=($(sort --version-sort <<<"${available_modules[*]}"))
unset IFS
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
fmt="%-${colsize}s"
else
fmt="%-s"
fi
printf "${fmt}" "${mod}" 1>&2
column+=colsize
done
printf -- "\n\n" 1>&2
}
#......................................................................
local pattern=()
local output_function='human_readable_output'
local opt_use_rel_stages="${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_rel_stages="${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
IFS=':'
local -a modulepath=(${MODULEPATH})
unset IFS
local dir
local group=''
local overlay=''
for dir in "${modulepath[@]}"; do
group='other'
find_overlay overlay group "${dir}"
if [[ ! -v modulepath_${group} ]]; then
typeset -a modulepath_${group}
fi
typeset -n path=modulepath_${group}
path+=("${dir}")
done
local p
for string in "${pattern[@]}"; do
for group in ${UsedGroups//:/ } other; do
if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then
continue
fi
[[ -v modulepath_${group} ]] || continue
typeset -n path=modulepath_${group}
get_available_modules \
mods \
"${string}*" \
"${opt_use_rel_stages}" \
"${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 -r subcommand='use'
IFS=':'
local -a modulepath=(${MODULEPATH})
unset IFS
local add2path_func='std::append_path'
#......................................................................
group_is_used() {
[[ :${UsedGroups}: =~ :$1: ]]
}
#......................................................................
print_info() {
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 "\nUsed flags:"
for flag in "${UsedFlags[@]}"; do
std::info "\t${flag}"
done
std::info ''
std::info "Used overlays:"
local ol_dir
for ol_dir in "${OverlayList[@]}"; do
local txt=''
case "${OverlayDict[${ol_dir}]%%:*}" in
"${ol_hiding}" )
txt='(hiding modules with same name)'
;;
"${ol_replacing}" )
txt='(replacing groups)'
;;
esac
local ol_name=${OverlayNames[$ol_dir]}
std::info "\t${ol_name}: ${ol_dir} ${txt}"
done
std::info ''
std::info "Available overlays:"
local -a files
get_ol_config_files files
local -a ol_names=( $(get_ol_names "${files[@]}") )
local -- ol_name ol_type ol_install_dir
for ol_name in "${ol_names[@]}"; do
ol_type=$(get_ol_type \
"${ol_names}" \
"${files[@]}")
ol_install_dir=$(get_ol_install_root \
"${ol_names}" \
"${files[@]}")
ol_modulefiles_root=$(get_ol_modulefiles_root \
"${ol_names}" \
"${files[@]}")
local txt=''
case "${ol_type}" in
"${ol_hiding}" )
txt='(hiding modules with same name)'
;;
"${ol_replacing}" )
txt='()replacing groups)'
;;
esac
std::info "\t${ol_name}: ${ol_dir} ${txt}"
done
std::info ''
std::info "Additonal directories in MODULEPATH:"
let n=0
local group
for (( i=0; i<${#modulepath[@]}; i++)); do
if ! find_overlay overlay group "${modulepath[i]}"; then
std::info "\t${modulepath[i]}"
let n+=1
fi
done
if (( n == 0 )); then
std::info "\tnone"
fi
std::info ""
}
#......................................................................
use () {
use_overlay() {
local ol_name_or_dir="$1"
if [[ -n "${LOADEDMODULES}" ]] && \
[[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]]; then
std::die 3 "%s %s: %s %s" \
"${CMD}" "${subcommand}" \
"overlay cannot be added since some" \
"modules are already loaded!"
fi
local ol_name=''
local ol_type=''
local ol_dir=''
local ol_prefix=''
get_overlay_info \
"${ol_name_or_dir}" \
ol_name \
ol_type \
ol_dir \
ol_prefix \
|| std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"is not an overlay directory" \
"${ol_name_or_dir%:*}"
if [[ -n "${OverlayDict[${ol_dir}]}" ]]; then
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"overlay already in use" \
"${ol_name_or_dir%:*}"
return 0
fi
case ${ol_type} in
${ol_normal} | ${ol_replacing} | ${ol_hiding} )
:
;;
* )
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"invalid type '${ol_type}!" \
"${ol_name_or_dir}"
;;
esac
if [[ "${ol_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="${ol_dir}/"
dir+="${group}/${PMODULES_MODULEFILES_DIR}"
[[ -d "${dir}" ]] || continue # no
dir="/${group}/${PMODULES_MODULEFILES_DIR}"
local -a dirs=( "${OverlayList[@]/%/${dir}}" )
std::remove_path MODULEPATH "${dirs[@]}"
done
fi
for group in ${UsedGroups//:/ }; do
local dir="${ol_dir}/"
dir+="${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
Dir2OverlayMap[${dir}]="${ol_dir}"
fi
done
OverlayDict[${ol_dir}]="${ol_type}:${ol_prefix}"
OverlayList=( "${ol_dir}" "${OverlayList[@]}" )
OverlayNames[${ol_dir}]="${ol_name}"
# FIXME: do we have to export it????
export_env OverlayList
g_env_must_be_saved='yes'
scan_groups "${OverlayList[@]}"
}
#..............................................................
use_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
std::append_path UsedGroups "$1"
local ol_dir
for ol_dir in "${OverlayList[@]}"; do
local dir="${ol_dir}/$1/${PMODULES_MODULEFILES_DIR}"
[[ -d "${dir}" ]] || continue
std::prepend_path MODULEPATH "${dir}"
Dir2OverlayMap[${dir}]=${ol_dir}
[[ "${OverlayDict[${ol_dir}]%%:*}" == "${ol_replacing}" ]] && break
done
}
#..............................................................
local arg=$1
if is_release_stage "${arg}"; then
# argument is release stage
std::append_path UsedReleaseStages "${arg}"
return
fi
if [[ "${arg}" =~ "flag=" ]]; then
# argument is flag
UsedFlags+=( "${arg/flag=}" )
return
fi
if get_overlay_info "${arg}"; then
use_overlay "${arg}"
return 0
fi
if [[ -d ${arg} ]]; then
local dir=$(cd "${arg}" && pwd -L)
${add2path_func} MODULEPATH "${dir}"
fi
if [[ ! -v GroupDepths[${arg}] ]]; then
# this scan is required if a new group has been
# create inside an used overlay
scan_groups "${!OverlayDict[@]}"
g_env_must_be_saved='yes'
fi
if [[ -n ${GroupDepths[${arg}]} ]]; then
use_group "${arg}"
return
fi
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"invalid keyword, group, overlay or directory" \
"${arg}"
} # use ()
#......................................................................
local -a args=()
while (( $# > 0)); do
case "$1" in
-\? | -H | --help )
print_help "${subcommand}"
;;
-a | --append )
add2path_func='std::append_path'
;;
-p | --prepend )
add2path_func='std::prepend_path'
;;
-- )
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
g_env_must_be_saved='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 -r subcommand='unuse'
IFS=':'
local -a modulepath=(${MODULEPATH})
unset IFS
#......................................................................
unuse() {
#..............................................................
unuse_overlay() {
local ol_name_or_dir="$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
local ol_name=''
local ol_type=''
local ol_dir=''
get_overlay_info \
"${ol_name_or_dir}" \
ol_name \
ol_type \
ol_dir \
|| std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"is not an overlay directory" \
"${ol_name_or_dir%:*}"
[[ "${ol_dir}" == "${PMODULES_ROOT}" ]] && \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"cannot remove root overlay" \
"${ol_name_or_dir%:*}"
[[ -z ${OverlayDict[${ol_dir}]} ]] && \
std::die 3 "%s %s: %s -- %s" \
"${CMD}" "${subcommand}" \
"not an used overlay" \
"${ol_name_or_dir%:*}"
# make sure first index is '0' (it should, but you never know)
OverlayList=( "${OverlayList[@]}" )
[[ "${ol_dir}" != "${OverlayList[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_or_dir%:*}"
if [[ "${OverlayDict[${ol_dir}]%%:*}" == "${ol_replacing}" ]]; then
# if this overlay hides groups, we have to re-add
# the modules made available by other overlays
for group in ${UsedGroups//:/ }; do
# is this group in the to be removed overlay?
local dir="${ol_dir}/${group}/${PMODULES_MODULEFILES_DIR}"
[[ -d "${dir}" ]] || continue # no
dir="/${group}/${PMODULES_MODULEFILES_DIR}"
std::prepend_path MODULEPATH "${OverlayList[@]/%/${dir}}"
done
fi
unset "OverlayDict[${ol_dir}]"
OverlayList=( "${OverlayList[@]:1}")
unset OverlayNames[${ol_dir}]
g_env_must_be_saved='yes'
export_env OverlayList
local dir
for dir in "${modulepath[@]}"; do
if [[ "${dir}" =~ "${ol_dir}" ]]; then
std::remove_path MODULEPATH "${dir}"
fi
done
}
#..............................................................
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
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
std::remove_path UsedGroups "${arg}"
local overlay
for overlay in "${OverlayList[@]}"; do
local dir="${overlay}/${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
fi
if [[ "${arg}" =~ "flag=" ]]; then
# argument is flag
local flag="${arg/flag=}"
local i
for i in ${!UsedFlags[@]}; do
[[ ${UsedFlags[i]} == ${flag} ]] && unset UsedFlags[i]
done
return
fi
if get_overlay_info "${arg}"; then
unuse_overlay "${arg}"
return 0
fi
if [[ -d ${arg} ]]; then
local dir=$(cd "${arg} && pwd -L")
std::remove_path MODULEPATH "${dir}"
fi
if [[ -n ${GroupDepths[${arg}]} ]]; then
unuse_group "${arg}"
return
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
g_env_must_be_saved='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 'update' "$@"
}
##############################################################################
#
# 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 'refresh' "$@"
}
#
# help function, used during initialization and for purging all modules
#
reset_modulepath() {
MODULEPATH=''
local group
local overlay
for overlay in "${!OverlayDict[@]}"; do
for group in ${UsedGroups//:/ }; do
local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
std::prepend_path MODULEPATH "${dir}"
Dir2OverlayMap[${dir}]="${overlay}"
fi
done
done
}
reset_used_groups() {
UsedGroups=''
local group
for group in ${DefaultGroups}; do
std::append_path UsedGroups "${group}"
done
g_env_must_be_saved='yes'
}
reset_used_releases() {
declare -g UsedReleaseStages=''
for r in ${DefaultReleaseStages//:/ }; do
std::append_path UsedReleaseStages "${r}"
done
g_env_must_be_saved='yes'
}
init_path() {
std::replace_path PATH "${PMODULES_HOME%/*}/.*"
std::prepend_path PATH "${PMODULES_HOME}/bin"
}
init_manpath() {
std::replace_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 [[ -n ${_manconf} ]]; then
while read name value rest; do
std::append_path MANPATH "${value}"
done < <(grep "^MANPATH\s" "${_manconf}")
unset _manconf
else
std::append_path MANPATH "${PMODULES_HOME}/share/man"
std::append_path MANPATH "/usr/share/man"
fi
}
pmodules_init() {
if [[ -r "${pmodules_config_file}" ]]; then
source "${pmodules_config_file}" || \
std::die 3 "Oops: cannot parse config file -- %s\n" \
"${pmodules_config_file}"
fi
declare -gx LOADEDMODULES=''
declare -gx _LMFILES_=''
declare -gx UsedGroups=''
declare -gx MODULEPATH=''
declare -Ag GroupDepths='()'
declare -ag UsedFlags=()
declare -g Version="${PMODULES_VERSION}"
reset_used_groups
init_overlay_vars
reset_modulepath
reset_used_releases
init_manpath
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 -r subcommand='purge'
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 allowd"
fi
# is a Pmodule module loaded?
# if yes, save name in variable 'pmodule'
local pmodule=''
IFS=':'
local -a lmfiles=($_LMFILES_)
unset IFS
for f in "${lmfiles[@]}"; do
if [[ $f == */${PMODULES_MODULEFILES_DIR}/Pmodules/* ]]; then
pmodule="${f##*/${PMODULES_MODULEFILES_DIR}/}"
break;
fi
done
# run module purge
# since we might have to reload a Pmodules module, we cannot
# just run 'modulecmd ${Shell} purge'
local output=$("${modulecmd}" 'bash' 'purge' 2> "${tmpfile}")
local error=$( < "${tmpfile}")
if [[ "${error}" =~ ":ERROR:" ]]; then
local s=${error%%$'\n'*}
local error_txt='failed'
std::die 3 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"${error_txt}"
fi
if [[ "${Shell}" == "sh" ]]; then
# for sh-like shells just echo
echo "${output}"
else
# re-run with right shell
"${modulecmd}" "${Shell}" 'purge'
fi
eval "${output}"
if [[ -n "${error}" ]]; then
echo "${error}" 1>&2
fi
if [[ -n "${pmodule}" ]]; then
# reload a previously loaded Pmodule module
# stderr is redirected to /dev/null, otherwise
# we may get output like
# 'unstable module has been loaded'
subcommand_load "${pmodule}" 2> /dev/null
fi
reset_modulepath
export_env MODULEPATH PMODULES_HOME
}
##############################################################################
#
# 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() {
local -r subcommand='list'
local opts=()
local args=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help )
print_help "${subcommand}"
;;
-h | --human )
opts+=( '-h' )
;;
-l | --long )
opts+=( '-l' )
;;
-t | --terse )
opts+=( '-t' )
;;
-- )
shift 1
args+=( "$@" )
break
;;
* )
args+=( "$1" )
;;
esac
shift
done
if (( ${#args[@]} > 0 )); then
std::die 3 "%s %s: %s" \
"${CMD}" "${subcommand}" \
"no arguments allowd"
fi
"${modulecmd}" "${Shell}" list "${opts[@]}"
}
##############################################################################
#
# 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 -r 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
export_env LOADEDMODULES MODULEPATH _LMFILES_
}
##############################################################################
#
# search [switches] [STRING...]
#
Subcommands[search]='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'
Help[search]='
USAGE:
module search [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 subcommand='search'
local modules=()
local with_modules='//'
local -ir cols=$(tput cols) # get number of columns of terminal
local -i max_len_modulename=0
local 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_releases=':'
local opt_all_deps='no'
local opt_wrap='no'
local opt_glob='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 %-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" "Requires"
std::info '-%.0s' $(seq 1 ${cols})
}
print_line_default() {
write_line() {
local str="$1"
if (( ${#str} >= cols )); then
str="${str:0:$((cols-1))}>"
fi
std::info "${str}"
}
if [[ "${opt_wrap}" == 'no' ]]; then
local deps="${@:5}"
local str=$(printf "${fmt}" "$1" "$2" "$3" "${deps[@]}")
write_line "${str}"
else
local deps=( "${@:5}" )
local str=$(printf "${fmt}" "$1" "$2" "$3" "${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 %-10s %-12s %-s"
func_print_header='print_header_verbose'
func_print_line='print_line_verbose'
}
print_header_verbose() {
std::info ''
std::info "${fmt}" "Module" "Rel.stage" "Group" "Dependencies/Modulefile"
std::info '-%.0s' $(seq 1 ${cols})
}
print_line_verbose() {
local deps="${@:5}"
std::info "${fmt}" "$1" "$2" "$3" "dependencies: ${deps}"
std::info "${fmt}" "" "" "" "modulefile: $4"
}
# 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() {
std::info "$4"
}
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
${func_print_header}
while read -a toks; do
${func_print_line} "${toks[@]}"
done < <("${sort}" --version-sort -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | \
${awk} "${with_modules}")
}
#.....................................................................
#
# search modules
# Args:
# $1: module name pattern
#
# Variables used from enclosing function
# :FIXME:
#
search () {
if [[ ${opt_glob} == 'yes' ]]; then
local -r module="$1"
else
local -r module="${1}*"
fi
# write results to a temporary file for later processing
local group
# loop over all groups
for group in "${!GroupDepths[@]}"; do
# loop over all directories which can be added to
# MODULEPATH inside current group
local depth=${GroupDepths[${group}]}
local s=''
if (( depth > 0 )); then
s=$(printf '/*%.0s' $(seq 1 ${depth}))
fi
local modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} )
# get and print all available modules in $mpath
# with respect to the requested release stage
# tmpfile: module/version rel_stage group dependencies...
local mods
get_available_modules \
mods \
"${module}" \
"${opt_use_rel_stages}" \
"${modulepath[@]}" \
for (( i=0; i<${#mods[@]}; i+=3 )); do
local name=${mods[i]}
local rel_stage=${mods[i+1]}
local modulefile=${mods[i+2]}
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
deps=($(< "${dependencies_file}"))
else
deps=()
fi
else
# get dependencies encoded in directory name
local deps=()
local -i j
IFS='/' # note: IFS is used to concat in the for loop!
local toks=( ${modulefile} )
for ((j = -depth-2; j < -2; j += 2)); do
deps+=( "${toks[*]: $j:2}" );
done
unset IFS
fi
echo ${name} ${rel_stage} ${group} ${modulefile} \
${deps[@]} >> "${tmpfile}"
done
done
print_result
}
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_rel_stages+="${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_rel_stages+="${ReleaseStages}"
;;
--src | --src=*)
if [[ "$1" == --src ]]; then
local src_prefix="$2"
shift
else
local src_prefix="${1/--src=}"
fi
if [[ ! -e "${src_prefix}" ]]; then
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal value for --src option" \
"${src_prefix} does not exist"
fi
if [[ ! -d "${src_prefix}" ]]; then
std::die 1 "%s %s: %s -- %s" \
"${CMD}" 'search' \
"illegal value for --src option" \
"${src_prefix} 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'
;;
-- )
shift 1
modules+=( "$@" )
break
;;
* )
modules+=( "$1" )
;;
esac
shift
done
if [[ -z "${src_prefix}" ]]; then
local -a src_prefix=( "${OverlayList[@]}" )
fi
if [[ "${opt_use_rel_stages}" == ":" ]]; then
opt_use_rel_stages=":${UsedReleaseStages}:"
fi
if [[ ${#modules[@]} == 0 ]]; then
modules+=( '' )
fi
# :FIXME: do we need this?
if (( ${#GroupDepths[@]} == 0 )) || \
[[ ${src_prefix} != ${PMODULES_ROOT} ]]; then
scan_groups "${src_prefix}"
g_env_must_be_saved='yes'
fi
local module
for module in "${modules[@]}"; do
search "${module}"
done
}
##############################################################################
#
# 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
+ initadd modulefile [modulefile ...]
+ initprepend modulefile [modulefile ...]
+ initrm modulefile [modulefile ...]
+ initswitch modulefile1 modulefile2
+ initlist
+ initclear
'
subcommand_help() {
local -r 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 [[ -n "${Help[${arg}]}" ]] ; then
print_help "${arg}"
else
# :FIXME: print help of newest *available* module
# (respecting UsedReleaseStages)
"${modulecmd}" "${Shell}" "${subcommand}" "${arg}"
fi
done
}
##############################################################################
#
# whatis
#
Subcommands[whatis]='whatis'
Options[whatis]='-o \?H -l help'
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() {
if (( $# == 0 )); then
subcommand_generic0 'whatis'
else
subcommand_generic1plus 'whatis' "$@"
fi
}
##############################################################################
#
# apropos
#
Subcommands[apropos]='apropos'
Subcommands[keyword]='apropos'
Options[apropos]='-o \?H -l help'
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() {
subcommand_generic1 'apropos' "$@"
}
##############################################################################
#
# 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 'initadd' "$@"
}
##############################################################################
#
# 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 'initprepend' "$@"
}
##############################################################################
#
# 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 'initrm' "$@"
}
##############################################################################
#
# 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() {
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 'initlist' "$@"
}
##############################################################################
#
# 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 'initclear' "$@"
}
##############################################################################
#
# main
#
case "$1" in
sh | bash | zsh )
declare Shell="sh"
;;
csh | tcsh )
declare Shell='csh'
;;
* )
std::die 1 "${CMD}: unsupported shell -- $1"
;;
esac
shift
declare -a opts=()
while (( $# > 0 )); do
case $1 in
-\? | -H | --help | -help )
print_help 'help'
;;
-V | --version )
print_help 'version'
;;
--debug )
set -x
;;
'' )
;;
-* )
opts+=( "$1" )
;;
* )
subcommand="$1"
shift
break
;;
esac
shift
done
if [[ -z "${subcommand}" ]]; then
std::die 1 "${CMD}: no sub-command specified."
fi
if [[ -z "${Subcommands[${subcommand}]}" ]]; then
std::die 1 "${CMD}: unknown sub-command -- ${subcommand}"
fi
init_overlay_vars() {
declare -ag OverlayList=( "${PMODULES_ROOT}" )
declare -Ag OverlayNames=( [${PMODULES_ROOT}]='base' )
declare -Ag OverlayDict=([${PMODULES_ROOT}]="0:${PMODULES_ROOT}")
declare -Ag Dir2OverlayMap=()
local overlay
for overlay in "${!OverlayDict[@]}"; do
local group
for group in ${UsedGroups//:/ }; do
local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}"
if [[ -d "${dir}" ]]; then
Dir2OverlayMap[${dir}]=${overlay}
fi
done
done
}
case ${subcommand} in
add )
subcommand='load'
;;
display )
subcommand='show'
;;
keyword )
subcommand='apropos'
;;
rm )
subcommand='unload'
;;
switch )
subcommand='swap'
;;
esac
if [[ -n ${PMODULES_ENV} ]]; then
eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)"
if [[ -z ${Version} ]] || [[ ${Version} != ${PMODULES_VERSION} ]]; then
# the Pmodules version changed!
declare -g Version="${PMODULES_VERSION}"
init_overlay_vars
# renamed in version 1.0.0rc10 and type changed from
# associative array to normal array
if [[ -v UseFlags ]]; then
declare -a UsedFlags=( "${!UseFlags[@]}" )
unset UseFlags
fi
if [[ ! -v UsedFlags ]]; then
declare -a UsedFlags=()
fi
if [[ -v UsedReleases ]]; then
declare -- UsedReleaseStages="${UsedReleases}"
unset UsedReleases
fi
if [[ -v PMODULES_DEFAULT_GROUPS ]]; then
declare -- DefaultGroups="${PMODULES_DEFAULT_GROUPS}"
unset PMODULES_DEFAULT_GROUPS
fi
if [[ -v PMODULES_DEFINED_RELEASES ]]; then
declare -- ReleaseStages="${PMODULES_DEFINED_RELEASES}"
unset PMODULES_DEFINED_RELEASES
fi
if [[ -v PMODULES_DEFAULT_RELEASES ]]; then
declare -- DefaultReleaseStages="${PMODULES_DEFAULT_RELEASES}"
unset PMODULES_DEFAULT_RELEASES
fi
g_env_must_be_saved='yes'
fi
else
pmodules_init
g_env_must_be_saved='yes'
fi
if (( ${#GroupDepths[@]} == 0 )); then
scan_groups "${!OverlayDict[@]}"
g_env_must_be_saved='yes'
fi
case ${subcommand} in
load|purge|search|swap )
declare -r tmpfile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \
|| std::die 1 "Oops: unable to create tmp file!"
;;
* )
declare -r tmpfile=''
;;
esac
tmp=$("${getopt}" --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \
|| print_help "${subcommand}"
eval args=( "$tmp" )
unset tmp
subcommand_${Subcommands[$subcommand]} "${args[@]}"
# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: