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