#!@BASH@ --noprofile # #set -o nounset # we have to unset CDPATH, otherwise 'cd' prints the directoy! unset CDPATH # 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" base64=$(PATH=/bin:/usr/bin /usr/bin/which base64) declare -r base64 mktemp=$(PATH=/bin:/usr/bin /usr/bin/which mktemp) declare -r mktemp sort=$(PATH=/bin:/usr/bin /usr/bin/which sort) declare -r sort awk=$(PATH=/bin:/usr/bin /usr/bin/which awk) declare -r awk rm=$(PATH=/bin:/usr/bin /usr/bin/which rm) declare -r rm logger=$(PATH=/bin:/usr/bin /usr/bin/which logger) declare -r logger if [[ $(uname -s) == 'Darwin' ]]; then declare -r getopt="${libexecdir}/getopt" declare -r find="${libexecdir}/find" else getopt=$(PATH=/bin:/usr/bin /usr/bin/which getopt) declare -r getopt find=$(PATH=/bin:/usr/bin /usr/bin/which find) declare -r find fi source "${libdir}/libstd.bash" 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'} # use default IFS unset IFS shopt -s nullglob declare -A GroupDepths='()' declare -A Dir2OverlayMap='()' declare Shell='' declare -A Subcommands declare -A Options declare -A Help declare -r pmodules_config_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/Pmodules.conf" # the following settings are used if the Pmodules.conf is not available # 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' # In the dictionary Help we store the help text of each single command # and for displaying the version. # initialize help text of 'module --version' Help['version']=" Pmodules @PMODULES_VERSION@ using Tcl Environment Modules @MODULES_VERSION@ Copyright GNU GPL v2 " # # display help text for command given in $1 # print_help() { echo -e "${Help[$1]}" 1>&2 std::die 1 } 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+=( 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}" ${rm} -f "${tmpfile}" } trap '_exit' EXIT get_overlay_of_moduledir() { local "$1" local -r moduledir=$2 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: ]] } # # compute depth of modulefile directory. # # Args: # $1: absolute path of a modulefile directory # compute_group_depth () { local -r dir=$1 test -d "${dir}" || return 1 local group=${dir%/*} local group=${group##*/} [[ -n "${GroupDepths[${group}]}" ]] && return 0 local -i depth=$(${find} -L "${dir}" -depth \( -type f -o -type l \) \ -printf "%d" -quit 2>/dev/null) (( depth-=2 )) # if a group doesn't contain a modulefile, depth is negativ # :FIXME: better solution? (( depth < 0 )) && (( depth = 0 )) GroupDepths[$group]=${depth} g_env_must_be_saved='yes' } # # (Re-)Scan available groups in given overlays and compute group depth's # # Args: # $1: array of overlays # scan_groups () { local -r overlays=( "$@" ) local overlay for overlay in "${overlays[@]}"; do local moduledir for moduledir in ${overlay}/*/${PMODULES_MODULEFILES_DIR}; do compute_group_depth "${moduledir}" done done } # # 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 -r path=$3 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' ]] } subcommand_generic0() { local -r subcommand="$1" shift local -a args=() while (( $# > 0 )); do case $1 in -H | --help ) print_help "${subcommand}" ;; -- ) : ;; * ) args+=( "$1" ) ;; esac 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}" ;; -- ) : ;; * ) 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}" ;; -- ) : ;; * ) 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] # # $1: module to load # Subcommands[add]='load' Subcommands[load]='load' Options['load']='-l help -o Hfsvwi -l force -l silent -l verbose -l warn -l internal' 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}" [[ -d "${moduledir}" ]] || return 1 compute_group_depth "${moduledir}" } local args=() local opts=() local overlay while (($# > 0)); do case $1 in -H | --help ) print_help "${subcommand_load}" ;; -f | --force ) opts+=(' -f') ;; -s | --silent ) verbosity_lvl='silent' ;; -v | --verbose ) verbosity_lvl='verbose' ;; -w | --warn ) verbosity_lvl='warn' ;; -- ) ;; * ) 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=$("${modulecmd}" 'bash' 'show' "${current_modulefile}" 2>&1 | \ awk '/_PREFIX |_HOME / {print $3; exit}') 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}" ;; -- ) ;; * ) 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' } ############################################################################## # # swap [] # 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}" ;; -- ) ;; * ) 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 # 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}" ;; -- ) ;; * ) 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 modulename_2 rel_stage_2 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 get_release_stage \ rel_stage \ "${dir}" \ "${mod}" [[ :${used_rel_stages}: =~ :${rel_stage}: ]] || continue 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%/*}" if [[ -z "${modulenames[${name}]}" ]]; then if [[ "${OverlayDict[$overlay]}" == "${ol_hiding}" ]]; then modulenames[${name}]="${overlay}" else modulenames[${name}]='0' fi add='yes' elif [[ "${modulenames[${name}]}" == "${overlay}" ]]; then add='yes' elif [[ "${modulenames[${name}]}" == '0' ]] \ && [[ -z ${dict[${mod}]} ]]; then add='yes' fi else # module is NOT in an overlay add='yes' fi if [[ "${add}" == 'yes' ]]; then mods+=( "${mod}" ${rel_stage} "${dir}/${mod}" ) dict[${mod}]=1 fi done < <(${find} -L ${entries} \ \( -type f -o -type l \) \ -not -name ".*" \ -ipath "${module}*" \ | ${sort} --version-sort) } done std::upvar ${var} "${mods[@]}" } # # 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 } ############################################################################## # # avail [-hlt] [...] # Subcommands[avail]='avail' Options[avail]='-l help -o Hahlmt -l all -l all-release-stages -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. -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 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/--group=}" else val="$2" shift fi opt_groups[${val}]=1 ;; -- | '' ) ;; * ) 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 } ############################################################################## # # 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 local overlay std::info '' std::info "Used overlays:" for overlay in "${OverlayList[@]}"; do local hiding='' [[ "${OverlayDict[${overlay}]}" != '0' ]] && hiding=' (hiding)' std::info "\t${overlay}${hiding}" 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() { if [[ -n ${LOADEDMODULES} ]]; then if [[ $LOADEDMODULES == *:* ]] \ || [[ $LOADEDMODULES != Pmodules/* ]]; then std::error "%s %s: %s" \ "${CMD}" "${subcommand}" \ "overlay cannot be added since some modules are already loaded!" std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "overlay failed" \ "${overlay}" fi fi local overlay='' local type="${ol_normal}" if [[ $1 == *:* ]]; then type=${1##*:} overlay=${1%:*} elif [[ -d $1 ]] && [[ -r $1/config/overlay.conf ]]; then overlay=$1 source "${overlay}/config/overlay.conf" else overlay=$1 fi case ${type} in ${ol_normal} | ${ol_replacing} | ${ol_hiding} ) : ;; * ) std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "invalid type '${type}!" \ "${overlay}" ;; esac overlay=${overlay%/} # remove trailing '/' if there is one [[ -d "${overlay}" ]] || \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "is not an overlay directory" \ "${overlay}" if [[ -n "${OverlayDict[${overlay}]}" ]]; then std::info "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "overlay already in use" \ "${overlay}" return 0 fi if [[ "${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 # first test whether this group is in the to be added overlay local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue # no for dir in "${OverlayList[@]}"; do dir+="/${group}/${PMODULES_MODULEFILES_DIR}" std::remove_path MODULEPATH "${dir}" done done fi for group in ${UsedGroups//:/ }; do local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" Dir2OverlayMap[${dir}]=${overlay} fi done OverlayDict[${overlay}]=${type} OverlayList=( "${overlay}" "${OverlayList[@]}" ) export_env OverlayList g_env_must_be_saved='yes' scan_groups "${OverlayList[@]}" local group } use_group() { std::append_path UsedGroups "$1" local overlay group for overlay in "${OverlayList[@]}"; do for group in ${UsedGroups//:/ }; do local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" Dir2OverlayMap[${dir}]=${overlay} fi done 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 [[ "${arg}" =~ "overlay=" ]]; then local overlay="${arg/overlay=}" use_overlay "${overlay}" return fi # check whether the user wants to add an unused group in # an used overlay. if [[ -z ${GroupDepths[${arg}]} ]]; then # this scan is required if a new group has been # create inside an used overlay scan_groups "${overlays}" fi if [[ -n ${GroupDepths[${arg}]} ]] && (( ${GroupDepths[${arg}]} == 0 )); then # argument is group with depth 0 use_group "${arg}" return fi if [[ -n ${GroupDepths[${arg}]} ]] && (( ${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 # arg must be a directory! if [[ ! -d ${arg} ]]; then std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "illegal argument: is neither a valid keyword, group nor directory" \ "${arg}" fi local dir="$(cd "${arg}" && pwd)" if [[ -r ${dir}/config/overlay.conf ]]; then use_overlay "${dir}" return fi # Is ${dir} a used overlay? # Note: the config.file 'overlay.conf is *not* required # in an overlay!' if [[ -v OverlayDict[${dir}] ]]; then std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "illegal argument: is already loaded as overlay" \ "${dir}" fi # argument is a modulepath ${add2path_func} MODULEPATH "${dir}" } 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' ;; -- ) ;; * ) 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 overlay="$1" if [[ -n ${LOADEDMODULES} ]]; then if [[ $LOADEDMODULES == *:* ]] || [[ $LOADEDMODULES != Pmodules/* ]]; then std::error "%s %s: %s" \ "${CMD}" "${subcommand}" \ "overlay cannot be removed since some modules are still loaded!" std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "overlay failed" \ "${overlay}" fi fi overlay=${overlay%:*} # ignore any type overlay=${overlay%/} # remove trailing '/' if there is one [[ -d "${overlay}" ]] || \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "is not an overlay directory" \ "${overlay}" [[ "${overlay}" == "${PMODULES_ROOT}" ]] && \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "cannot remove root overlay" \ "${overlay}" [[ -z ${OverlayDict[${overlay}]} ]] && \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "not an used overlay" \ "${overlay}" if [[ "${OverlayDict[${overlay}]}" == "${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 # first test whether this group is in the to be added overlay local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue # no for dir in "${OverlayList[@]}"; do dir+="/${group}/${PMODULES_MODULEFILES_DIR}" std::remove_path MODULEPATH "${dir}" done done fi unset "OverlayDict[${overlay}]" local i for i in "${!OverlayList[@]}"; do [[ ${OverlayList[i]} == ${overlay} ]] && unset 'OverlayList[i]' done g_env_must_be_saved='yes' export_env OverlayList local dir for dir in "${modulepath[@]}"; do if [[ "${dir}" =~ "${overlay}" ]]; then std::remove_path MODULEPATH "${dir}" fi done } unuse_group() { 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 [[ ${arg} =~ ^overlay= ]]; then local overlay="${arg/overlay=}" unuse_overlay "${overlay}" return fi if [[ -n ${GroupDepths[${arg}]} ]] && (( ${GroupDepths[${arg}]} == 0 )); then # argument is group in our root with depth 0 unuse_group "${arg}" return fi if [[ -n ${GroupDepths[${arg}]} ]] && (( ${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 # user wants to append a directory to MODULEPATH if [[ ! -d ${arg} ]]; then std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "illegal argument" \ "${arg}" fi local dir="$(cd "${arg}" && pwd)" if [[ -r ${dir}/config/overlay.conf ]] || [[ -v OverlayDict[${dir}] ]]; then unuse_overlay "${dir}" return fi # argument is a modulepath std::remove_path MODULEPATH "${dir}" } local -a args=() while (( $# > 0)); do case "$1" in -H | --help ) print_help "${subcommand}" ;; -- ) ;; * ) 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}" declare -Ag OverlayDict=([${PMODULES_ROOT}]="0") declare -ag OverlayList=( "${PMODULES_ROOT}" ) reset_used_groups 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}" ;; -- ) ;; * ) 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' ) ;; -- ) ;; * ) 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}" ;; -- ) : ;; * ) 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 aH -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' 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 --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' #..................................................................... # # 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_header_none' } print_header_none() { : } print_line_modulefile() { local line=( "$@" ) # group first local out="${line[2]}/" # add directory of modulefiles out+="${PMODULES_MODULEFILES_DIR}/" for d in "${line[@]:3}"; do out+="$d/" done out+="${line[0]}" std::info "${out}" } 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}") } 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}" } #..................................................................... # # search modules # Args: # $1: module name pattern # # Variables used from enclosing function # :FIXME: # search () { local -r module=$1 # 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 ) opt_use_rel_stages+="${ReleaseStages}" ;; --src ) # :FIXME: do we have to add some sanity checks here? src_prefix=$2 shift ;; -v | --verbose ) opt_print_verbose='yes' ;; --wrap ) opt_wrap='yes' ;; -- ) ;; * ) 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}" 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 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 -[hH] | --help ) print_help "${subcommand}" ;; -V | --version ) print_help 'version' ;; -- ) : ;; * ) 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 -h | --help ) print_help "${subcommand}" ;; -- ) : ;; * ) 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 OverlayDict=([${PMODULES_ROOT}]="0") 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 save_env 'yes' } 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 fi if (( ${#GroupDepths[@]} == 0 )); then scan_groups "${!OverlayDict[@]}" fi case ${subcommand} in load|purge|search|switch ) declare -r tmpfile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!" ;; * ) declare -r tmpfile='' ;; esac declare options options=$( "${getopt}" ${Options[${subcommand}]} -- -- "${opts[@]}" "$@" ) \ || print_help "${subcommand}" eval set -- ${options} subcommand_${Subcommands[$subcommand]} "$@" # Local Variables: # mode: sh # sh-basic-offset: 8 # tab-width: 8 # End: