#!@PMODULES_HOME@/sbin/bash --noprofile # #set -o nounset # we have to unset CDPATH, otherwise 'cd' prints the directoy! unset CDPATH # used for some output only declare -r CMD='module' declare -r mydir=$(cd $(dirname "$0") && pwd) declare prefix=$(dirname "${mydir}") declare -r sbindir="${prefix}/sbin" declare -r libdir="${prefix}/lib" declare -r libexecdir="${prefix}/libexec" declare -r base64="${sbindir}/base64" declare -r mktemp="${sbindir}/mktemp" declare -r sort="${sbindir}/sort" declare -r getopt="${sbindir}/getopt" source "${libdir}/libstd.bash" source "${libdir}/libpmodules.bash" : ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'} declare -r version='@PMODULES_VERSION@' 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 verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} shopt -s nullglob declare -A GroupDepths='()' declare current_modulefile='' declare g_shell='' declare -A Subcommands declare -A Options declare -A Help Help['version']=" Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@ Copyright GNU GPL v2 " print_help() { echo -e "${Help[$1]}" 1>&2 std::die 1 } export_env() { local -r shell="$1" shift case "${shell}" in bash | zsh ) local -r fmt="export %s=\"%s\"; " ;; csh | tcsh ) local -r fmt="setenv %s \"%s\"; " ;; * ) std::die 1 "Unsupported shell -- ${shell}\n" ;; esac while (( $# > 0 )); do printf "${fmt}" "$1" "${!1}" shift done } # # 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() { [[ ${g_env_must_be_saved} == 'no' ]] && return 0 local vars=( GroupDepths UsedReleases UseFlags UsedGroups ) vars+=( PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES ) vars+=( PMODULES_DEFAULT_RELEASES ) local s=$(typeset -p ${vars[@]}) declare -g PMODULES_ENV=$( "${base64}" --wrap=0 <<< "$s" ) export_env ${g_shell} PMODULES_ENV } trap 'save_env ' EXIT # # get release of module # Note: # - the release of a modulefile outside ${PMODULES_ROOT} is 'stable' # - the release of a modulefile inside ${PMODULES_ROOT} without a # coresponding release file is 'unstable' # # Args: # $1: absolute modulefile name # get_release() { local "$1" local -r modulefile="$2" # is modulefile outside ${PMODULES_ROOT}? if [[ ! ${modulefile} =~ ${PMODULES_ROOT} ]]; then std::upvar $1 'stable' return 0 fi # we are inside ${PMODULES_ROOT} local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}" if [[ -r ${releasefile} ]]; then # read releasefile, remove empty lines, spaces etc local -r data=$( < "${releasefile}" ) std::upvar $1 "${data}" else std::upvar $1 'unstable' fi return 0 } is_release() { [[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]] } # # Check whether argument is a group # # Args: # $1: string # is_group () { local -r group="$1" # arg isn't emtpy and group already in cache [[ -n ${group} ]] && [[ -n ${GroupDepths[${group}]} ]] && return 0 local moduledir="${PMODULES_ROOT}/${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${moduledir}" ]] || return 1 compute_group_depth "${moduledir}" } # # 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\n" \ "${CMD}" "${subcommand}" \ "no arguments allowed" fi "${modulecmd}" "${g_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\n" \ "${CMD}" "${subcommand}" \ "missing argument" elif (( ${#args[@]} > 1 )); then std::die 3 "%s %s: %s\n" \ "${CMD}" "${subcommand}" \ "only one argument allowed" fi "${modulecmd}" "${g_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\n" \ "${CMD}" "${subcommand}" \ "missing argument" fi "${modulecmd}" "${g_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 release='undef' local current_modulefile='' local prefix='' local m='' local saved_IFS="${IFS}"; IFS=':' local -a modulepath=(${MODULEPATH}) IFS=${saved_IFS} # # 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 # # Notes: # The variable 'release' in function 'subcommand_load()' will be set. # 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}" "${shell}" 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 release "${current_modulefile}" "${UsedReleases}" } # # output load 'hints' # # Note: # The variable 'm' from the parent function will be used # but not changed. # # Args: # none output_load_hints() { local output='' local release='' while read -a line; do release=${line[1]} if [[ ! ":${UsedReleases}:" =~ "${release}" ]]; then output+="module use ${release}; " fi output+="module load ${line[@]:3} ${line[0]}\n" done < <(subcommand_search "${m}" -a --no-header 2>&1) if [[ -n "${output}" ]]; then std::info "\nTry with one of the following command(s):\n" std::die 3 "${output}\n" fi } 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 local output=$( subcommand_load --internal "${dep}") echo ${output} eval ${output} done < "${fname}" } local args=() opts=() local shell="${g_shell}" 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' ;; -i | --internal ) shell='bash' ;; -- ) ;; * ) args+=( $1 ) ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 2 "%s %s: %s\n" \ "${CMD}" "${subcommand}" \ "No module specified" fi for m in "${args[@]}"; do if [[ "$m" =~ ":" ]]; then # extendet module name is either # - group:name or # - group:name:release or # - release:name or # - release:group:name or # - name:release local save_ifs=${IFS} IFS=':' local -a toks=($m) IFS=${save_ifs} local group='' local release='' if is_group "${toks[0]}"; then group=${toks[0]} m=${toks[1]} release=${toks[2]} elif is_release "${toks[0]}"; then release=${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]} release=${toks[2]} else release=${toks[1]} group=${toks[2]} fi fi if [[ -n ${group} ]]; then is_group "${group}" || \ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal group name" \ "${group}" local -i depth=${GroupDepths[${group}]} (( depth != 0 )) && \ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal group name" \ "${group}" MODULEPATH="${PMODULES_ROOT}/${group}/" MODULEPATH+="${PMODULES_MODULEFILES_DIR}" modulepath=( ${MODULEPATH} ) fi if [[ -n ${release} ]]; then is_release "${release}" || \ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal release name" "${release}" std::append_path UsedReleases "${release}" g_env_must_be_saved='yes' fi fi local found='' for flag in "${UseFlags[@]/#/_}" ""; do if is_available "${m}${flag}"; then m+="${flag}" found=':' break fi done if [[ ! "${found}" ]]; then std::info "%s %s: module unavailable -- %s\n" \ "${CMD}" 'load' "${m}" [[ ${verbosity_lvl} == 'verbose' ]] && output_load_hints std::die 3 "" fi if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "module conflicts with already loaded module" \ "${m}" fi if [[ ${current_modulefile} =~ ${PMODULES_ROOT} ]]; then # modulefile is in our hierarchy # ${prefix} was set in is_available()! test -r "${prefix}/.info" && cat "$_" 1>&2 test -r "${prefix}/.dependencies" && load_dependencies "$_" fi local tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!\n" local output=$("${modulecmd}" "${shell}" ${opts} 'load' \ "${current_modulefile}" 2> "${tmpfile}") echo "${output}" eval "${output}" # 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 modules' fi std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "${error_txt}" \ "${m}" elif [[ -n ${error} ]]; then echo "${error}" 1>&2 fi if [[ ${verbosity_lvl} != silent ]] && \ [[ ${release} != stable ]]; then std::info "%s %s: %s -- %s\n" \ "${CMD}" 'load' \ "${release} module has been loaded" "${m}" fi done # fix LOADEDMODULES LOADEDMODULES="${_LMFILES_}" local dir while read dir; do [[ "${dir: -1}" == "/" ]] || dir+="/" LOADEDMODULES="${LOADEDMODULES//${dir}}" done <<< "${MODULEPATH//:/$'\n'}" export_env "${g_shell}" 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\n" \ "${CMD}" "${subcommand}" \ "missing argument" fi local arg for arg in "${args[@]}"; do local output=$("${modulecmd}" "${g_shell}" 'unload' "${arg}") echo "${output}" eval "${output}" done } ############################################################################## # # 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 root 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\n" \ "${CMD}" "${subcommand}" \ "missing argument" elif (( ${#args[@]} > 2 )); then std::die 3 "%s %s: %s\n" \ "${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\n" \ "${CMD}" "${subcommand}" \ "missing argument" fi local arg for arg in "${args[@]}"; do "${modulecmd}" "${g_shell}" "${subcommand}" "${arg}" done } # # get all available modules in given directory. # return list like # modulename1 release1 modulename2 release2 ... # get_available_modules() { local -r dir="$1" local -r module="$2" local -r use_releases="${3:-${UsedReleases}}" local -a mods=() local release test -d "${dir}" || return 0 { cd "${dir}" while read mod; do get_release release "${dir}/${mod}" if [[ :${use_releases}: =~ :${release}: ]]; then mods+=( "${mod}" ${release} ) fi done < <(find * \( -type f -o -type l \) -not -name ".*" -ipath "${module}*") } echo "${mods[@]}" } ############################################################################## # # avail [-hlt] [...] # Subcommands[avail]='avail' Options[avail]='-l help -o Hahlmt -l all -l all-releases -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-releases List all available modules independend of the release. -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=${dir/${PMODULES_ROOT}\/} local caption=${caption/\/${PMODULES_MODULEFILES_DIR}/: } local caption=${caption/: \//: } 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 for (( i=0; i<${#mods[@]}; i+=2 )); do local mod=${mods[i]} local release=${mods[i+1]} case $release in stable ) out='' ;; * ) out="${release}" ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 done std::info "\n" } machine_output() { for (( i=0; i<${#mods[@]}; i+=2 )); 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 for (( i=0; i<${#mods[@]}; i+=2 )); do local mod=${mods[i]} local release=${mods[i+1]} case $release in stable ) out='' ;; * ) out=${release} ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 done std::info "\n" } human_readable_output() { output_header local -i column=$cols local -i colsize=16 for ((i=0; i<${#mods[@]}; i+=2)); do if [[ ${verbosity_lvl} == 'verbose' ]]; then local release=${mods[i+1]} case ${mods[i+1]} in stable ) mod=${mods[i]} ;; * ) mod="${mods[i]}(${release:0:1})" ;; esac else mod=${mods[i]} fi local -i len=${#mod} local -i span=$(( len / 16 + 1 )) local -i colsize=$(( span * 16 )) 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_all_groups='no' local opt_use_releases="${UsedReleases}" while (($# > 0)); do case $1 in -H | --help ) print_help "${subcommand}" ;; -a | --all ) opt_all_groups='yes' opt_use_releases="${PMODULES_DEFINED_RELEASES}" ;; --all-releases ) opt_use_releases="${PMODULES_DEFINED_RELEASES}" ;; -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' ;; -- | '' ) ;; * ) pattern+=( "$1" ) ;; esac shift done if [[ "${opt_all_groups}" = 'yes' ]]; then rescan_groups "${PMODULES_ROOT}" fi if (( ${#pattern[@]} == 0 )); then pattern+=( '' ) fi local saved_IFS=${IFS}; IFS=':' local -a modulepath=(${MODULEPATH}) IFS=${saved_IFS} local string for string in "${pattern[@]}"; do for dir in "${modulepath[@]}"; do mods=( $( get_available_modules \ "${dir}" "${string}" \ "${opt_use_releases}" ) ) [[ ${#mods[@]} == 0 ]] && continue ${output_function} done done } # # 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##*/} local -i depth=$(find "${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 root and compute group depth's # # Args: # $1: root of modulefile hierarchy # scan_groups () { local -r root="$1" local moduledir for moduledir in ${root}/*/${PMODULES_MODULEFILES_DIR}; do compute_group_depth "${moduledir}" done } rescan_groups() { local -r root="$1" local moduledir for moduledir in ${root}/*/${PMODULES_MODULEFILES_DIR}; do if [[ -z "${GroupDepths[${group}]}" ]]; then compute_group_depth "${moduledir}" fi done } ############################################################################## # # use [-a|--append|-p|--prepend] [directory|group|release...] # 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...] Without arguments this sub-command displays information about the module search path, used families and releases. You can use this sub-command to get a list of available families and releases. 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' local saved_IFS=${IFS}; IFS=':' local -a modulepath=(${MODULEPATH}) IFS=${saved_IFS} local add2path_func='std::append_path' group_is_used() { [[ :${UsedGroups}: =~ :$1: ]] } release_is_used() { [[ ":${UsedReleases}:" =~ :$1: ]] } print_info() { local f local r std::info "Used groups:\n" for f in ${UsedGroups//:/ }; do std::info "\t${f}\n" done std::info "\nUnused groups:\n" local _group for _group in "${!GroupDepths[@]}"; do local -i depth=${GroupDepths[${_group}]} if ! group_is_used "${_group}" && (( depth == 0 )); then std::info "\t${_group}\n" fi done std::info "\nUsed releases:\n" for r in ${UsedReleases//:/ }; do std::info "\t${r}\n" done std::info "\nUnused releases:\n" for r in ${PMODULES_DEFINED_RELEASES//:/ }; do if ! release_is_used $r; then std::info "\t${r}\n" fi done std::info "\nUsed flags:\n" for flag in "${UseFlags//:/ }"; do std::info "\t${flag}\n" done std::info "\nAdditonal directories in MODULEPATH:\n" let n=0 for (( i=0; i<${#modulepath[@]}; i++)); do if [[ ! ${modulepath[i]} =~ ${PMODULES_ROOT} ]]; then std::info "\t${modulepath[i]}\n" let n+=1 fi done if (( n == 0 )); then std::info "\tnone\n" fi std::info "\n" } use () { local arg=$1 if is_release "${arg}"; then # argument is release std::append_path UsedReleases "${arg}" return fi if [[ "${arg}" =~ "flag=" ]]; then # argument is flag std::append_path UseFlags "${arg/flag=}" return fi if [[ -n ${GroupDepths[${arg}]} ]] && (( ${GroupDepths[${arg}]} == 0 )); then # argument is group in our root with depth 0 std::append_path UsedGroups "${arg}" ${add2path_func} MODULEPATH "${modulefiles_dir}" 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\n" \ "${CMD}" "${subcommand}" \ "illegal group" \ "${arg}" return fi # arg must be a directory! if [[ ! -d ${arg} ]]; then std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal argument" \ "${arg}" return fi dir="$(cd "${arg}" && pwd)" if [[ ${dir} =~ ^${PMODULES_ROOT} ]]; then # argument is somehing in our root std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal argument" \ "${arg}" return fi # argument is a modulepath ${add2path_func} MODULEPATH "$(cd "${arg}" && pwd)" } 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 ${g_shell} 'MODULEPATH' } ############################################################################## # # unuse directory|group|release... # Subcommands[unuse]='unuse' Options[unuse]='-o H -l help' Help[unuse]=' unuse directory|group|release... Remove the given directory, group or release from the search path. ' subcommand_unuse() { local -r subcommand='unuse' unuse() { local arg=$1 if is_release "${arg}"; then # argument is release std::remove_path UsedReleases "${arg}" return fi if [[ "${arg}" =~ "flag=" ]]; then # argument is flag std::remove_path UseFlags "${arg/flag=}" return fi if [[ -n ${GroupDepths[${arg}]} ]] && (( ${GroupDepths[${arg}]} == 0 )); then # argument is group in our root with depth 0 local var="PMODULES_LOADED_${arg^^}" if [[ -n "${!var}" ]]; then std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "cannot remove group due to loaded modules" \ "${arg}" fi std::remove_path UsedGroups "${arg}" std::remove_path MODULEPATH "${modulefiles_dir}" 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\n" \ "${CMD}" "${subcommand}" \ "illegal group" \ "${arg}" return fi # arg must be a directory! if [[ ! -d ${arg} ]]; then std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal argument" \ "${arg}" return fi dir="$(cd "${arg}" && pwd)" if [[ ${dir} =~ ^${PMODULES_ROOT} ]]; then # argument is somehing in our root std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${subcommand}" \ "illegal argument" \ "${arg}" 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\n" \ "${CMD}" "${subcommand}" \ 'missing argument' fi for arg in "${args[@]}"; do unuse "${args[@]}" done g_env_must_be_saved='yes' export_env "${g_shell}" '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' "$@" } reset_modulepath() { MODULEPATH='' local group local root="${PMODULES_ROOT}" for group in ${PMODULES_DEFAULT_GROUPS}; do local dir="${root}/${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] && std::prepend_path MODULEPATH "${dir}" done } reset_used_groups() { UsedGroups='' local group for group in ${PMODULES_DEFAULT_GROUPS}; do std::append_path UsedGroups "${group}" done g_env_must_be_saved='yes' } reset_used_releases() { declare -g UsedReleases='' for r in ${PMODULES_DEFAULT_RELEASES//:/ }; do std::append_path UsedReleases "${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() { declare -gx LOADEDMODULES='' declare -gx _LMFILES_='' declare -gx UsedGroups='' declare -gx MODULEPATH='' declare -Ag GroupDepths='()' declare -g UseFlags=() reset_modulepath reset_used_groups reset_used_releases init_path init_manpath export_env "${g_shell}" \ 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() { 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\n" \ "${CMD}" "${subcommand}" \ "no arguments allowd" fi "${modulecmd}" "${g_shell}" "${subcommand}" reset_modulepath reset_used_groups export_env ${g_shell} MODULEPATH } ############################################################################## # # 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\n" \ "${CMD}" "${subcommand}" \ "no arguments allowd" fi "${modulecmd}" "${g_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\n" \ "${CMD}" "${subcommand}" \ "no arguments allowed" fi pmodules_init export_env ${g_shell} 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: -l with: -l all-releases -l src: -l print-csv' Help[search]=' USAGE: module search [switches] string... Search installed modules. If an argument is given, search for modules whose name match the argument. SWITCHES: --no-header Suppress output of a header. --release=RELEASE Search for modules within this release. You can specify this switch multiple times. Without this switch, the used releases will be searched. -a|--all-releases Search within all releases. --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. ' subcommand_search() { local -r subcommand='search' local modules=() local with_modules='//' local src_prefix='' local opt_print_header='yes' local opt_print_modulefiles='no' local opt_print_csv='no' local opt_use_releases=':' local -r fmt="%-20s %-10s %-12s %-s\n" # no args print_header() { printf '\n' 1>&1 printf "${fmt}" "Module" "Release" "Group" "Requires" 1>&2 printf -- '-%.0s' {1..60} 1>&2 printf '\n' 1>&2 } #..................................................................... # # output result of search # Args: # $1: tmp file # # variables used from enclosing function: # opt_print_header # opt_print_modulefiles # with_modules # print_result() { local -r tmpfile=$1 [[ "${opt_print_header}" == "yes" ]] && print_header if [[ "${opt_print_modulefiles}" == "yes" ]]; then while read -a line; do # 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}\n" done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | \ awk "${with_modules}") elif [[ "${opt_print_csv}" == "yes" ]]; then while read -a toks; do : done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | \ awk "${with_modules}") else "${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | \ awk "${with_modules}" 1>&2 fi } #..................................................................... # # 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 -r tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!\n" 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 mpaths=( $(find \ "${src_prefix}/${group}/modulefiles" \ -type d \ -mindepth ${depth} -maxdepth ${depth} \ 2>/dev/null)) local mpath for mpath in "${mpaths[@]}"; do # get dependencies encoded in directory name local p="${mpath/${src_prefix}}" p=( ${p//\// } ) local deps=() local -i i for ((i=2; i < ${#p[@]}; i+=2)); do deps+=( ${p[i]}/${p[i+1]} ) done local requires=${deps[@]} # get and print all available modules in $mpath # with respect to the requested releases # tmpfile: module/version release group group- # dependencies... local mods=( $( get_available_modules \ "${mpath}" \ "${module}" \ "${opt_use_releases}" ) ) [[ ${#mods[@]} == 0 ]] && continue for (( i=0; i<${#mods[@]}; i+=2 )); do printf "${fmt}" ${mods[i]} "${mods[i+1]}" \ ${group} "${requires}" >> "${tmpfile}" done done done print_result "${tmpfile}" rm -f "${tmpfile}" } while (( $# > 0 )); do case $1 in -H | --help ) print_help "${subcommand}" ;; --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 | --release=* ) if [[ "$1" == "--release" ]]; then local arg=$2 shift else local arg=${1/--release=} fi is_release "${arg}" || \ std::die 1 "%s %s: %s -- %s\n" \ "${CMD}" 'search' \ "illegal release name" \ "${arg}" opt_use_releases+="${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\n" \ "${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_releases="${PMODULES_DEFINED_RELEASES}" ;; --src ) src_prefix=$2 pmodules::check_directories "${src_prefix}" shift ;; -- ) ;; * ) modules+=( "$1" ) ;; esac shift done if [[ -z "${src_prefix}" ]]; then src_prefix="${PMODULES_ROOT}" fi if [[ "${opt_use_releases}" == ":" ]]; then opt_use_releases=":${UsedReleases}:" fi if [[ ${#modules[@]} == 0 ]]; then modules+=( '' ) fi 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 UsedReleases) "${modulecmd}" "${g_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\n" \ "${CMD}" "${subcommand}" \ "two arguments required not less not more" fi "${modulecmd}" "${g_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 bash | zsh ) declare g_shell="$1" ;; csh | tcsh ) declare g_shell='csh' ;; * ) std::die 1 "${CMD}: unsupported shell -- $1\n" ;; 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.\n" fi if [[ -z "${Subcommands[${subcommand}]}" ]]; then std::die 1 "${CMD}: unknown sub-command -- ${subcommand}\n" fi if [[ -n ${PMODULES_ENV} ]]; then eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)" else pmodules_init fi if (( ${#GroupDepths[@]} == 0 )); then scan_groups "${PMODULES_ROOT}" fi 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: