#!@BASH@ --noprofile # # shellcheck -x -e SC1008,SC2239,SC2317,SC2034,SC2128,SC2059,SC2178 # declare -r VERSION='@PMODULES_VERSION@' unset CDPATH # unset CDPATH, otherwise 'cd' prints the directoy! unset IFS # use default IFS #set -o errexit set -o pipefail set -o nounset #shopt -s nocaseglob #shopt -s extglob shopt -s nullglob # get absolute path of script mydir=$(cd "$(/usr/bin/dirname "$0")" && pwd -L) prefix=$(/usr/bin/dirname "${mydir}"); path_orig="${PATH}" PATH="${prefix}/bin:${prefix}/libexec:/bin:/usr/bin:/sbin:/usr/sbin" source "${prefix}/lib/libstd.bash" || { echo "Oops: cannot source library -- '$_'" 1>&2; exit 3; } source "${prefix}/lib/libpmodules.bash" || \ std::die 3 "Oops: cannot source library -- '$_'" declare -rx TCL_LIBRARY="${prefix}/lib/tcl@TCL_VERSION@" declare -x TCLLIBPATH=${TCLLIBPATH:-''} std::prepend_path TCLLIBPATH "${prefix}/lib/Pmodules" declare -r Tcl_cmd="${prefix}/libexec/modulecmd.bin" declare -r Lmod_cmd="${prefix}/libexec/lmod/lmod/libexec/lmod" declare -- modulecmd="${Tcl_cmd}" # we have to use the orignal path. Otherwise module load doesn't work. PATH="${path_orig}" unset path_orig unset mydir unset prefix ############################################################################## # the following settings are used if the config file doesn't exist # set groups which should be available after initialization declare -- DefaultGroups='Tools Programming' # define available release stages declare -- ReleaseStages=':unstable:stable:deprecated:' # set releases which should be available after initialization declare -- DefaultReleaseStages='stable' declare -A OverlayExcludes=() declare -a UsedOverlays=() ############################################################################## declare -- Verbosity_lvl='verbose' declare -- Shell='' TmpFile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!" declare -r TmpFile declare -A Subcommands=() declare -A Options=() declare -A Help=() # initialize help text of 'module --version' Help['version']=" Pmodules @PMODULES_VERSION@ using Tcl Environment Modules VERSION = @MODULES_VERSION@ " # these variables must exist if [[ ! -v LOADEDMODULES ]] || [[ ! -v _LMFILES_ ]]; then LOADEDMODULES='' _LMFILES_='' fi # # display help text for command given in $1 # print_help() { echo -e "${Help[$1]}" 1>&2 std::die 1 } export_env() { local -A export_functions=() export_functions['sh']='export_env_sh' export_functions['bash']='export_env_sh' export_functions['zsh']='export_env_sh' export_functions['csh']='export_env_csh' export_functions['tcsh']='export_env_csh' export_functions['python']='export_env_python' export_env_sh(){ while (( $# > 0 )); do printf "export %s=\"%s\"; " "$1" "${!1}" shift done } export_env_csh(){ while (( $# > 0 )); do printf "setenv %s \"%s\"; " "$1" "${!1}" shift done } export_env_python(){ while (( $# > 0 )); do printf "os.environ['%s'] = '%s'\n" "$1" "${!1}" shift done } [[ -v export_functions[${Shell}] ]] || \ std::die 1 "Unsupported shell -- ${Shell}" ${export_functions[${Shell}]} "$@" } # # Save/cache state in the environment variable PMODULES_ENV. The content is # base64 encoded. This function is called on exit via a trap handler. # # Arguments: # none # declare EnvMustBeSaved='no' save_env() { encode_base64(){ local -- os_name='' os_name=$(${uname} -s) case "${os_name}" in Linux ) "${base64}" --wrap=0 <<< "$1" ;; Darwin ) # does not wrap if running in a script "${base64}" <<< "$1" ;; * ) std::die 255 "Oops: Unsupported OS" ;; esac } [[ "${EnvMustBeSaved}" == 'no' ]] && return 0 local vars=( Version ) vars+=( UsedReleaseStages UsedGroups ) vars+=( DefaultGroups DefaultReleaseStages ) vars+=( ReleaseStages ) vars+=( GroupDepths ) vars+=( Overlays ) vars+=( UsedOverlays ) vars+=( OverlayExcludes ) vars+=( OverlayInfo Dir2OverlayMap) local s='' s=$(typeset -p "${vars[@]}") declare -gx PMODULES_ENV='' PMODULES_ENV=$( encode_base64 "$s" ) export_env 'PMODULES_ENV' } # # function called on exit # _exit() { save_env if [[ -n "${TmpFile}" ]] && [[ -e "${TmpFile}" ]]; then ${rm} -f "${TmpFile}" || : fi } trap '_exit' EXIT declare -r CMD='module' declare SubCommand='' die_missing_arg(){ std::die 3 "%s %s: %s\n" \ "${CMD}" "${SubCommand}" 'missing argument' } die_too_many_args(){ std::die 3 "%s %s: %s\n" \ "${CMD}" "${SubCommand}" 'too many arguments' } die_no_args_allowed(){ std::die 3 "%s %s: %s\n" \ "${CMD}" "${SubCommand}" "no arguments allowed" } die_wrong_number_of_args(){ std::die 1 "%s %s: %s\n" \ "${CMD}" "${SubCommand}" "wrong number of arguments" } die_illegal_opt(){ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${SubCommand}" "illegal option" "$1" } die_illegal_arg(){ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${SubCommand}" "invalid argument" "$1" } die_illegal_group(){ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${SubCommand}" "invalid group name" "$1" } die_illegal_relstage(){ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${SubCommand}" "invalid release stage" "$1" } die_cannot_remove_grp(){ std::die 3 "%s %s: %s -- %s\n" \ "${CMD}" "${SubCommand}" "cannot remove group due to loaded modules" "$1" } die_module_unavail(){ std::die 3 "%s %s: %s -- %b\n" \ "${CMD}" "${SubCommand}" "not available in the current MODULEPATH" "$1" } die_module_nexist(){ std::die 3 "%s %s: module does not exist -- %s" \ "${CMD}" "${SubCommand}" "$1" } die_invalid_value(){ std::die 1 "%s %s: %s -- %s\n" \ "${CMD}" "${SubCommand}" "invalid string for $1" "$2" } die_conflict(){ std::die 3 "%s %s: %s -- %b\n" \ "${CMD}" "${SubCommand}" \ "module conflicts with already loaded modules" "$1" } die_cmd_failed(){ std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ "failed" } die_cannot_use_overlay(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "overlay cannot be added since some modules are already loaded!" "$1" } die_overlay_already_in_use(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "overlay already in use" "$1" } die_not_a_modulefile(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "not a modulefile" "$1" } die_cannot_create_directory(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "cannot create directory" "$1" } die_cannot_save_collection(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "cannot save_collection" "$1" } die_invalid_collection_name(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "invalid collection name" "$1" } die_collection_doesnt_exist(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "collection doesn't exist or isn't readable" "$1" } die_removing_collection_failed(){ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "cannot remove collection" "$1" } get_module_config(){ : " Read module configuration. If a file '.config-' 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 doesn't exist, get the release stage from '.release-'. Restrictions to systems or blocking hosts is not possible. Note: - the release stage of a module outside our hierarchy is always 'stable'. - the release stage of a module inside out hierarchy without a config file is always 'unstable'. Arguments: $1 reference to dictionary to return the configuration $2 directory containing modulefile $3 module name (inkl. version and/or sub-dirs) " local -n ref_cfg="$1" local -r dir="$2" local -r modulefile="${dir}/$3" ref_cfg['relstage']='unstable' ref_cfg['systems']='' ref_cfg['blocklist']='' if [[ ! -v Dir2OverlayMap[${dir%/"${PMODULES_MODULEFILES_DIR}"*}] ]]; then # this module is not in an overlay ref_cfg['relstage']='stable' return fi local -r config_file="${modulefile%/*}/.config-${modulefile##*/}" local -r relstage_file="${modulefile%/*}/.release-${modulefile##*/}" if [[ -r ${config_file} ]]; then local -- yaml='' yaml=$(${yq} -e '.' < "${config_file}") debug "module config: ${yaml}" local -- key='' for key in "${!ref_cfg[@]}"; do case "${key,,}" in systems | blocklist ) value=$( ${yq} -e ".${key}[]" \ 2>/dev/null <<<"${yaml}") || value='null' ;; * ) value=$( ${yq} -e ".${key}" \ 2>/dev/null <<<"${yaml}") || value='null' ;; esac if [[ "${value}" != 'null' ]]; then ref_cfg[${key,,}]="${value}" fi done return 0 elif [[ -r ${relstage_file} ]]; then ref_cfg['relstage']=$( < "${relstage_file}" ) else ref_cfg['relstage']='unstable' fi } is_available(){ : " Module is available if - release stage is used - the systems list is empty or the system is in the list - the blocklist is empty or the hostname is NOT in the list " local -n ref_cfg="$1" local -- relstages="$2" local -- os_release='' os_release=$(std::get_os_release) check_relstage(){ [[ ":${relstages}:" == *:${ref_cfg['relstage']}:* ]] } check_blocklist(){ [[ -z ${ref_cfg['blocklist']} ]] && return 0 local -- s='' for s in ${ref_cfg['blocklist']}; do if [[ "${os_release}" =~ $s ]] || [[ "${HOSTNAME}" =~ $s ]]; then return 0 fi done return 1 } check_systems(){ [[ -z ${ref_cfg['systems']} ]] && return 0 local -- s='' for s in ${ref_cfg['systems']}; do if [[ "${os_release}" =~ $s ]] || [[ "${HOSTNAME}" =~ $s ]]; then return 0 fi done return 1 } set -o noglob check_relstage && check_blocklist && check_systems local -i ec=$? set +o noglob return ${ec} } # # check whether the argument in $1 is a valid release stage. # is_release_stage() { [[ :${ReleaseStages}: =~ :$1: ]] } # # Check whether a given moduledir is in an used overlay. # If yes, return 0 otherwise return 1 # # Arguments # $1 ref.var to return overlay # $2 ref.var to return group # $3 moduledir to check # find_overlay () { local -n fo_ol="$1" local -n fo_group="$2" local path="${3//+(\/)/\/}" # replace multpile '/' with one path="${path/%\/}" # remove trailing slash if exist path="${path%/"${PMODULES_MODULEFILES_DIR}"*}" # return if not in an overlay [[ -v Dir2OverlayMap[${path}] ]] || return 1 fo_ol="${Dir2OverlayMap[${path}]}" fo_group="${path#"${OverlayInfo[${ol}:modulefiles_root]}"/}" return 0 } # # Check whether the module passed in argument $1 is loaded. # module_is_loaded() { [[ :${LOADEDMODULES}: =~ :$1: ]] } # # test whether the given file is a module file. # # - for lua: the extension must be .lua # - for Tcl: check shebang # # return interpreter in reference variable # # Arguments: # $1 [out] ref. variable for result # $2 [in] full qualified file name to test # # Return value: # 0 if file exist, is readable and is either a lua or Tcl module file # 1 if file exist but is neither a lua or Tcl module file # 2 if file doesn't exist # is_modulefile() { local -n im_interp="$1" local -r fname="$2" # does file exist and is readable? [[ -r ${fname} ]] || return 2 if [[ "${fname##*.}" == 'lua' ]]; then im_interp="${Lmod_cmd}" return 0 fi local -- shebang read -r -n 11 shebang < "${fname}" if [[ "${shebang:0:8}" == '#%Module' ]] \ || [[ "${shebang:0:9}" == '#%Pmodule' ]]; then im_interp="${Tcl_cmd}" return 0 fi return 1 } # # Get the value of _PREFIX. # # Arguments: # $1 reference variable to return result # $2 modulefile # get_module_prefix() { local -n _prefix="$1" _prefix=$("${modulecmd}" bash show "$2" 2>&1 | \ ${awk} '/_PREFIX |_HOME / {print $3; exit}') } # # Generic wrappers of 'modulecmd': # # subcommand_generic0: # no argument allowed # subcommand_generic1: # Exact one argument must be passed # subcommand_generic1plus: # One or more arguments must be passed # # The options to output help are always accepted. # subcommand_generic0() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift 1 done (( ${#args[@]} == 0 )) || \ die_no_args_allowed "${modulecmd}" "${Shell}" "${SubCommand}" } subcommand_generic1() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} == 0 )) && \ die_missing_arg (( ${#args[@]} > 1 )) && \ die_too_many_args "${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}" } subcommand_generic1plus() { local args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} == 0 )) && \ die_missing_arg "${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}" } ############################################################################## # # load [-fsvw] # # $1: module to load # Subcommands['add']='load' Subcommands['load']='load' Options['load']='-l help -o \?Hfsvw -l force -l silent -l verbose -l warn' Help[load]=' USAGE: module add modulefile... module load modulefile... Load modulefile(s) into the shell environment. Loading a 'group-head' will extend the MODULEPATH. E.g.: loading a compiler makes additional modules like openmpi and libraries compiled with this compiler available. ' subcommand_load() { local relstage='undef' local current_modulefile='' local prefix='' local m='' #...................................................................... # output load 'hints' # # Note: # The variable 'm' from the parent function will be used # but not changed. # get_load_hints() { local -n output="$1" # ref.var to store result local relstage='' output='' while read -r -a line; do (( ${#line[@]} == 0 )) && continue relstage=${line[1]} if [[ ! ":${UsedReleaseStages}:" == *:${relstage}:* ]]; then output+="module use ${relstage}; " fi local group=${line[2]} [[ "${group}" != 'none' ]] || continue if [[ ! ":${UsedGroups}:" == *:${group}:* ]] && \ (( ${GroupDepths[${group}]} == 0 )); then output+="module use ${group}; " fi local -i n=$(( ${GroupDepths[${group}]}/2 )) output+="module load ${line[*]:4:$n} ${line[0]}\n" done < <(set +x; subcommand_search "${m}" -a --no-header 2>&1) if [[ -n ${output} ]]; then output="\n\nTry with one of the following command(s):\n${output}" fi } #...................................................................... module_is_loaded() { [[ :${LOADEDMODULES}: =~ :$1: ]] } #...................................................................... load_dependencies() { local -r fname="$1" local -- dep='' while read -r dep; do [[ -z ${dep} ]] && continue [[ ${dep:0:1} == \# ]] && continue module_is_loaded "${dep}" && continue subcommand_load "${dep}" done < "${fname}" } #...................................................................... # Check whether argument is a group. # # In the following function we test whether the group exist in # an overlay. It doesn't matter which overlay. If we find an # overlay providing modules for this group, we compute the # hierarchical depth of the group and save this value for later # use. # # Args: # $1: group # # Notes: # This function is used with extended module names. # # Multiple overlays may provide modules for the same group. But the # hierarchical depth of a group must always be the same. # is_group () { find_overlay_with_group() { local -n _ol="$1" local -r group="$2/${PMODULES_MODULEFILES_DIR}" local ol for ol in "${UsedOverlays[@]}"; do local install_root="${OverlayInfo[${ol}:install_root]}" if [[ -d "${install_root}/${group}" ]]; then _ol="${ol}" return 0 fi done return 1 } local -r group="$1" # arg isn't emtpy and group already in cache [[ -n ${group} ]] && [[ -v GroupDepths[${group}] ]] && return 0 local ol='' find_overlay_with_group ol "${group}" || return 1 local moduledir="${OverlayInfo[${ol}:modulefiles_root]}/${group}/${PMODULES_MODULEFILES_DIR}" local -i depth compute_group_depth depth "${moduledir}" || return 1 GroupDepths[${group}]=${depth} EnvMustBeSaved='yes' } #...................................................................... local args=() local opts=() local overlay while (($# > 0)); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -f | --force ) opts+=(' -f') ;; -s | --silent ) Verbosity_lvl='silent' ;; -v | --verbose ) Verbosity_lvl='verbose' ;; -w | --warn ) Verbosity_lvl='warn' ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} == 0 )) && \ die_missing_arg local m='' for m in "${args[@]}"; do IFS=':' read -r -a modulepath <<< "${MODULEPATH}" if [[ "$m" == *:* ]]; then # extendet module name is either # - group:name or # - group:name:relstage or # - relstage:name or # - relstage:group:name or # - name:stage local -a toks=() IFS=':' read -r -a toks <<< "${m}" local group='' local relstage='' if is_group "${toks[0]}"; then group=${toks[0]} m=${toks[1]} relstage=${toks[2]} elif is_release_stage "${toks[0]}"; then relstage=${toks[0]} if is_group "${toks[1]}"; then group=${toks[1]} m=${toks[2]} else m=${toks[1]} group=${toks[2]} fi else m=${toks[0]} if is_group "${toks[1]}"; then group=${toks[1]} relstage=${toks[2]} else relstage=${toks[1]} group=${toks[2]} fi fi if [[ -n ${group} ]]; then is_group "${group}" || \ die_illegal_group "${group}" local -i depth=${GroupDepths[${group}]} (( depth != 0 )) && \ die_illegal_group "${group}" group+="/${PMODULES_MODULEFILES_DIR}" for overlay in "${UsedOverlays[@]}"; do local modulefiles_root="${OverlayInfo[${overlay}:modulefiles_root]}" modulepath=( "${modulefiles_root}/${group}" "${modulepath[@]}" ) done fi if [[ -n ${relstage} ]]; then is_release_stage "${relstage}" || \ die_illegal_relstage "${relstage}" std::append_path UsedReleaseStages "${relstage}" EnvMustBeSaved='yes' fi fi # handle extended module names local moduledir='' find_modulefile current_modulefile relstage moduledir "${m}" "${modulepath[@]}" if [[ -z ${current_modulefile} ]]; then local hints='' get_load_hints hints if [[ -z "${hints}" ]]; then die_module_nexist "${m}" else die_module_unavail "${m}${hints}" fi fi local interp='' is_modulefile modulecmd "${current_modulefile}" || \ die_not_a_modulefile "${m}" if [[ "${modulecmd}" == "${Lmod_cmd}" ]]; then current_modulefile="${current_modulefile/${moduledir}\/}" fi [[ ${m} == Pmodules/* ]] && [[ -n ${LOADEDMODULES} ]] && \ die_conflict "${m}" # continue if already loaded [[ ":${LOADEDMODULES}:" == *:${m}:* ]] && continue # show info file if exist, load dependencies and the module itself local prefix='' get_module_prefix prefix "${current_modulefile}" if [[ -n ${prefix} ]]; then test -r "${prefix}/.info" && cat "$_" 1>&2 test -r "${prefix}/.dependencies" && load_dependencies "$_" fi local output='' output=$("${modulecmd}" 'bash' "${opts[@]}" 'load' \ "${current_modulefile}" 2> "${TmpFile}") # we do not want to print the error message we got from # modulecmd, they are a bit ugly # :FIXME: Not sure whether this is now correct! # The idea is to supress the error messages from the Tcl # modulecmd, but not the output to stderr coded in a # modulefile. local error='' error=$( < "${TmpFile}") if [[ "${error}" == *:ERROR:* ]]; then local s=${error%%$'\n'*} [[ "$s" =~ ' conflicts ' ]] && \ die_conflict "${m}" die_cmd_failed "${m}" fi if [[ "${Shell}" == "sh" ]]; then # for sh-like shells just echo echo "${output}" else # re-run with right shell "${modulecmd}" "${Shell}" "${opts[@]}" 'load' \ "${current_modulefile}" fi output=$( ${sed} -e 's/source [^;]*;//g' <<<"${output}" ) eval "${output}" if [[ -n "${error}" ]]; then echo "${error}" 1>&2 fi local msg='' if [[ ${Verbosity_lvl} != silent ]] && \ [[ ${relstage} != stable ]]; then msg=$(printf "%s %s: %s -- %s" \ "${CMD}" 'load' \ "${relstage} module has been loaded" \ "${m}") std::info "%s" "${msg}" fi msg=$(printf "%s: %s %s %s" \ 'load' \ "modulefile=${current_modulefile}" \ "rel-stage=${relstage}" \ "user=${USER}") ${logger} -t Pmodules "${msg}" done # fix LOADEDMODULES LOADEDMODULES="${_LMFILES_}" local -- dir='' while read -r dir; do # if the first or last character of MODULEPATH is ':', # we get an empty string. [[ -z ${dir} ]] && continue # skip relative directories in MODULEPATH [[ "${dir:0:1}" == '/' ]] || continue # dir must end with a slash, otherwise we have entries # in LOADEDMODULES beginning with a slash [[ "${dir: -1}" == "/" ]] || dir+="/" # remove dir from all entries in LOADEDMODULES LOADEDMODULES="${LOADEDMODULES//${dir}}" done <<< "${MODULEPATH//:/$'\n'}" EnvMustBeSaved='yes' export_env 'LOADEDMODULES' } ############################################################################## # # unload # Subcommands['rm']='unload' Subcommands['unload']='unload' Options['unload']='-o \?H -l help' Help[unload]=" USAGE: module rm modulefile... module unload modulefile... Remove modulefile(s) from the shell environment. Removing a 'group-head' will also unload all modules belonging to this group. " subcommand_unload() { # :FIXME: add dependency tests: don't unload if module is required # be another module. # For the time being the modules requiring this module will # be unloaded too. local args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; -* ) die_illegal_opt "$1" ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} == 0 )) && \ die_missing_arg # The module() function uses PMODULES_HOME to call modulecmd. # If a Pmodules module is unloaded this evnvironment variable # will be unset. In consequence the module() function would # fail. Instead of comparing the name of the module to unload # with 'Pmodules', we save the value and set it at the end of # the loop again, if it has been unset. local saved_home="${PMODULES_HOME}" IFS=':' read -r -a _lmfiles_ <<< "${_LMFILES_}" local arg local lmfile for arg in "${args[@]}"; do for lmfile in "${_lmfiles_[@]}" '_zzzz_'; do if [[ $lmfile =~ ${arg} ]]; then break fi done if [[ "${lmfile}" == '_zzzz_' ]]; then continue fi # yes, module has been loaded local interp is_modulefile modulecmd "${lmfile}" || die_not_a_modulefile "${arg}" local output='' output=$("${modulecmd}" "${Shell}" 'unload' "${arg}") if [[ -n "${output}" ]]; then eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')" fi case ${Shell} in sh | bash | zsh ) echo "${output}" ;; * ) "${modulecmd}" "${Shell}" 'unload' "${arg}" ;; esac done if [[ -z ${PMODULES_HOME} ]]; then PMODULES_HOME="${saved_home}" export_env 'PMODULES_HOME' fi EnvMustBeSaved='yes' } # subcommand_unload ############################################################################## # # swap [] # Subcommands['switch']='swap' Subcommands['swap']='swap' Options['swap']='-o \?H -l help' Help['swap']=" USAGE: module switch [modulefile1] modulefile2 module swap [modulefile1] modulefile2 Switch loaded modulefile1 with modulefile2. If modulefile1 is not specified, then it is assumed to be the currently loaded module with the same base name as modulefile2. " subcommand_swap() { local args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} == 0 )) && \ die_missing_arg (( ${#args[@]} > 2 )) && \ die_too_many_args if (( ${#args[@]} == 1 )); then local -r module_to_load=${args[0]} local -r module_to_unload=${module_to_load%/*} else local -r module_to_unload=${args[0]} local -r module_to_load=${args[1]} fi subcommand_unload "${module_to_unload}" subcommand_load "${module_to_load}" } ############################################################################## # # show # Subcommands['display']='show' Subcommands['show']='show' Options['show']='-o \?H -l help' Help['show']=' USAGE: module display modulefile... module show modulefile... Display information about one or more modulefiles. The display sub-command will list the full path of the modulefile(s) and all (or most) of the environment changes the modulefile(s) will make if loaded. It will not display any environment changes found within conditional statements. ' subcommand_show() { local args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} == 0 )) && \ die_missing_arg local arg for arg in "${args[@]}"; do local -- modulefile='' find_modulefile_and_interpreter modulefile modulecmd "${arg}" "${modulecmd}" 'bash' 'show' "${modulefile}" done } ############################################################################### # Find all modules in a given modulepath matching a specific string. # The search can be restricted to certain release stages. # # Args: # $1 reference variable to return result # $2 search pattern # $3 release stages # $4... module path (fully qualified directory names) # # return list like # modulename_1 relstage_1 modulefile_1 ... # get_available_modules() { local -n gam_mods="$1" local -r module="$2" local -r used_relstages="${3:-${UsedReleaseStages}}" shift 3 # in the for loop below we use $@ to loop over module path local relstage local -A dict local -A modulenames local dir gam_mods=() # loop over all entries in given module path for dir in "$@"; do test -d "${dir}" || continue cd "${dir}" || std::die 3 "Oops: cannot change to directory '${dir}'" # find overlay and group for this directory local ol='' local group='' find_overlay ol group "${dir}" # if directory is empty continue local -a dir_entries=(*) (( ${#dir_entries[@]} > 0 )) || continue local sdirs="${dir##*/modulefiles}" # loop over all files (and sym-links) in this directory and # its sub-directories local mod='' # module_name/module_version while read -r mod; do local name="${mod%/*}" [[ -v OverlayExcludes[${name}] ]] && continue local add='no' if [[ -n "${ol}" && "${ol}" != 'none' ]]; then # module is in an overlay # # add to list of available modules, if # - first time found by name only # - in same overlay as first found # - new version and not hidden by overlay if [[ ! -v modulenames[${name}] ]]; then # new entry if [[ "${OverlayInfo[${ol}:type]}" == "${ol_hiding}" ]]; then modulenames[${name}]="${ol}" else modulenames[${name}]='0' fi add='yes' elif [[ "${modulenames[${name}]}" == "${ol}" ]]; then add='yes' elif [[ "${modulenames[${name}]}" == '0' ]] \ && [[ ! -v dict[${sdirs}/${mod}] ]]; then add='yes' fi else ol='none' add='yes' # module is NOT in an overlay fi [[ "${add}" == 'no' ]] && continue local -A cfg=() get_module_config cfg "${dir}" "${mod}" is_available cfg "${used_relstages}" || continue gam_mods+=( "${mod}" "${cfg['relstage']}" "${dir}/${mod}" "${ol}" ) dict[${sdirs}/${mod}]=1 done < <(${find} -L "${dir_entries[@]}" \ \( -type f -o -type l \) \ -not -name ".*" \ -ipath "${module}*" \ | ${sort} --version-sort) done } # get_available_modules() # # find module(file) to load. The module name can be # name # name/version # relative path # absolute path # # Return # 0 if a modulefile has been found # != else # find_modulefile() { local -n fm_modulefile="$1" # ref. variable to return module file local -n fm_relstage="$2" # ref. variable to return release stage local -n fm_dir="$3" # ref. variable to return dir in modulepath local -r module="$4" # module to load local -a dirs=("${@:5}") # module search path (MODULEPATH as bash arra) # loop over all dirs in MODULEPATH # if a modulefile is found return from function local dir for dir in "${dirs[@]}"; do test -d "${dir}" || continue local -i col=$((${#dir} + 2 )) local -a modules if [[ ${module} == */* ]]; then # a version number has been specified. The first module # found will be returned, independend from the release # stage. mapfile -t modules < <( ${find} -L "${dir}" -type f -not -name ".*" \ -ipath "${dir}/${module}*" \ | cut -b${col}-) for mod in "${modules[@]}"; do if [[ ${mod} == "${module}" ]]; then : elif [[ ${mod} == "${module}.lua" ]]; then mod="${module}.lua" else continue fi local -A cfg=() get_module_config cfg "${dir}" "${mod}" is_available cfg "${ReleaseStages}" || continue fm_modulefile="${dir}/${mod}" fm_relstage="${cfg['relstage']}" fm_dir="${dir}" return 0 done else # no version has been specified. This makes it more # difficult. We have to load the newest version taking # the used release stages into account. # get list of reverse sorted version numbers (( col += ${#module} + 1 )) mapfile -t modules < <(${find} -L "${dir}" -type f -not -name ".*" \ -ipath "${dir}/${module}/*" \ | cut -b${col}- \ | sort -rV ) # prepend module name modules=( "${modules[@]/#/${module}/}" ) # now modules contains a reverse sorted list of # available modules in the form name/version for mod in "${modules[@]}"; do if [[ ${mod} == ${module}/* ]]; then : else continue fi local -A cfg=() get_module_config cfg "${dir}" "${mod}" is_available cfg "${ReleaseStages}" || continue fm_modulefile="${dir}/${mod}" fm_relstage="${cfg['relstage']}" fm_dir="${dir}" return 0 done fi done # Nothing found in MODULEPATH! # The module to be loaded must be either given as relative or absolut # path. if [[ -r "${module}" ]]; then fm_modulefile="$(std::get_abspath "${module}")" fm_relstage='stable' fm_dir="$(${dirname} "${fm_modulefile}")" return 0 fi return 1 } # find_modulefile() find_modulefile_and_interpreter(){ local -n ref_modulefile="$1" local -n ref_interp="$2" local -r module="$3" local -a modulepath=() IFS=':' read -r -a modulepath <<< "${MODULEPATH}" local -- relstage='' local -- moduledir='' find_modulefile ref_modulefile relstage moduledir "${module}" "${modulepath[@]}" if [[ -z ${modulefile} ]]; then die_module_unavail "${module}" fi is_modulefile modulecmd "${ref_modulefile}" || die_not_a_modulefile "${module}" if [[ "${ref_interp}" == "${Lmod_cmd}" ]]; then ref_modulefile="${ref_modulefile/${moduledir}\/}" fi } ############################################################################## # # avail [-hlt] [...] # 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() { # use this variable in the output functions local -a mods=() local -- dir='' # get number of columns of terminal, set to a default if not running # in a terminal. local -i cols=80 [[ -t 1 && -t 2 ]] && cols=$(tput cols) #...................................................................... output_header() { local -r caption="$1" local -i i=0 (( i=(cols-${#caption})/2-2 )) printf -- "%0.s-" $(seq 1 "$i") 1>&2 printf -- " %s " "${caption}" 1>&2 printf -- "%0.s-" $(seq 1 "$i") 1>&2 printf -- "\n" 1>&2 } #...................................................................... terse_output() { output_header "$1" local -i i=0 for (( i=0; i<${#mods[@]}; i+=4 )); do local mod=${mods[i]%.lua} local relstage=${mods[i+1]} case ${relstage} in stable ) out='' ;; * ) out="${relstage}" ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 done std::info "" } #...................................................................... machine_output() { for (( i=0; i<${#mods[@]}; i+=4 )); do printf "%-20s\t%s\n" "${mods[i]}" "${mods[i+1]}" 1>&2 done } #...................................................................... # :FIXME: for the time being, this is the same as terse_output! long_output() { output_header "$1" for (( i=0; i<${#mods[@]}; i+=4 )); do local mod=${mods[i]%.lua} local relstage=${mods[i+1]} case ${relstage} in stable ) out='' ;; * ) out=${relstage} ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 done std::info "" } #...................................................................... human_readable_output() { output_header "$1" local -a available_modules=() local mod='' local -i max_length=1 for ((i=0; i<${#mods[@]}; i+=4)); do if [[ ${Verbosity_lvl} == 'verbose' ]]; then local relstage=${mods[i+1]} case ${relstage} in stable ) mod="${mods[i]%.lua}" ;; * ) mod="${mods[i]%.lua}(${relstage:0:1})" ;; esac else mod=${mods[i]} fi local -i n=${#mod} (( n > max_length )) && (( max_length=n )) available_modules+=("${mod}") done local -i span=$(( max_length / 16 + 1 )) # compute column size local -i colsize=$(( span * 16 )) # as multiple of 16 local -i column=$cols # force a line-break for mod in "${available_modules[@]}"; do local -i len=${#mod} if (( column+len >= cols )); then printf -- "\n" 1>&2 column=0 fi if (( column+colsize < cols )); then printf "%-${colsize}s" "${mod}" 1>&2 else printf "%-s" "${mod}" 1>&2 fi column+=colsize done printf -- "\n\n" 1>&2 } #...................................................................... local pattern=() local output_function='human_readable_output' local opt_use_relstages="${UsedReleaseStages}" local -A opt_groups=() local val='' while (($# > 0)); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -a | --all | --all-release-stages ) opt_use_relstages="${ReleaseStages}" ;; -h | --human ) output_function='human_readable_output' ;; -l | --long ) output_function='long_output' ;; -t | --terse ) output_function='terse_output' ;; -m | --machine ) output_function='machine_output' ;; -g | --group | --group=* ) if [[ $1 == --group=* ]]; then val="${1#--*=}" else val="$2" shift fi opt_groups[${val}]=1 ;; -- ) shift 1 pattern+=( "$@" ) break ;; * ) pattern+=( "$1" ) ;; esac shift done if (( ${#pattern[@]} == 0 )); then pattern+=( '' ) fi local -- dir='' local -- group='' local -- groups=() local -- ol='' local -- name='' local -a modulepath=() IFS=':' read -r -a modulepath <<<"${MODULEPATH}" for dir in "${modulepath[@]}"; do if find_overlay ol group "${dir}"; then name="${group}" else name="${dir}" group="${dir//[\/ .-]/_}" fi # With overlays we can have multiple directories per group! # # Create an ordered list of directories per group. # Note: # BASH doesn't support list as values of dictionaries. We use # reference variables to work around this. if [[ ! -v modulepath_${group} ]]; then typeset -a "modulepath_${group}" fi typeset -n path="modulepath_${group}" path+=("${dir}") # Create ordered list of groups. In the next step we # loop over this list. for group in "${groups[@]}"; do if [[ "${group}" == "${name}" ]]; then # resume with next dir continue 2 fi done groups+=( "${name}" ) done local string for string in "${pattern[@]}"; do for group in "${groups[@]}"; do # limit output to certain groups if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then continue fi local ref="${group//[\/ .-]/_}" [[ -v modulepath_${ref} ]] || continue typeset -n path"=modulepath_${ref}" get_available_modules \ mods \ "${string}*" \ "${opt_use_relstages}" \ "${path[@]}" [[ ${#mods[@]} == 0 ]] && continue ${output_function} "${group}" done done } # subcommand_avail() ############################################################################## # # use [-a|--append|-p|--prepend] [directory|group|release_stage...] # Subcommands['use']='use' Options['use']='-l help -o \?Hap -l append -l prepend' Help['use']=" USAGE: module use [-a|--append|-p|--prepend] [directory|group|release_stage|...] Without arguments this sub-command displays information about the module search path, used groups and release stages. You can use this sub-command to get a list of available groups and releases stages. With a directory as argument, this directory will either be prepended or appended to the module search path. The default is to prepend the directory. With a group as argument, the modules in this group will be made available. With a release as argument, this modules with this release will be made available. SWITCHES: -a | --append -p | --prepend ) Append/prepend agrument to module search path or list of to be searched releases. " subcommand_use() { local -a modulepath=() IFS=':' read -r -a modulepath <<<"${MODULEPATH}" local add2path_func='std::append_path' #...................................................................... group_is_used() { [[ :${UsedGroups}: =~ :$1: ]] } #...................................................................... print_info() { print_ol_info(){ local used="$1" # print used or unused overlays local ol='' for ol in "${Overlays[@]}"; do [[ ${OverlayInfo[${ol}:used]} == "${used}" ]] || continue local install_root="${OverlayInfo[${ol}:install_root]}" local modulefiles_root="${OverlayInfo[${ol}:modulefiles_root]}" local txt="\t${ol}" if [[ ${install_root} == "${modulefiles_root}" ]]; then txt+="\n\t\t${install_root}" else txt+="\n\t\t${install_root} (install root)" txt+="\n\t\t${modulefiles_root} (modulefiles root)" fi case "${OverlayInfo[${ol}:type]}" in "${ol_hiding}" ) txt+='\n\t\t(hiding modules with same name)' ;; "${ol_replacing}" ) txt+='\n\t\t(replacing groups)' ;; esac std::info "${txt}" done } local f local r std::info "Used groups:" for f in ${UsedGroups//:/ }; do std::info "\t${f}" done std::info '' std::info "Unused groups:" local _group for _group in "${!GroupDepths[@]}"; do local -i depth=${GroupDepths[${_group}]} if ! group_is_used "${_group}" && (( depth == 0 )); then std::info "\t${_group}" fi done std::info "\nUsed releases stages:" for r in ${UsedReleaseStages//:/ }; do std::info "\t${r}" done std::info "\nUnused release stages:" for r in ${ReleaseStages//:/ }; do [[ ! ":${UsedReleaseStages}:" =~ :$r: ]] && std::info "\t${r}" done std::info '' std::info "Used overlays:" print_ol_info 'yes' std::info '' std::info "Unused overlays:" print_ol_info 'no' std::info '' std::info "Additonal directories in MODULEPATH:" local -i i=0 local -i n=0 local group for (( i=0; i<${#modulepath[@]}; i++)); do if ! find_overlay ol group "${modulepath[i]}"; then std::info "\t${modulepath[i]}" (( n+=1 )) fi done (( n == 0 )) && std::info "\tnone" std::info "" } # print_info #...................................................................... use () { use_overlay() { local ol_name="$1" [[ -n "${LOADEDMODULES}" ]] && \ [[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]] && \ die_cannot_use_overlay "${ol_name}" [[ ${OverlayInfo[${ol_name}:used]} == 'yes' ]] && return 0 # die_overlay_already_in_use "${ol_name}" if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then # if this overlay replaces groups, we have # to remove the modules made available by # other overlays in these groups for group in ${UsedGroups//:/ }; do # is this group in the to be added overlay? local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue # no dir="/${group}/${PMODULES_MODULEFILES_DIR}" local -a dirs=() for ol in "${UsedOverlays[@]}"; do dirs+=( "${OverlayInfo[${ol}:modulefiles_root]}${dir}" ) done std::remove_path MODULEPATH "${dirs[@]}" done fi scan_groups "${ol_name}" for group in ${UsedGroups//:/ }; do local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" fi done UsedOverlays=( "${ol_name}" "${UsedOverlays[@]}" ) OverlayInfo[${ol_name}:used]='yes' local excludes=() local item IFS=':' read -r -a excludes <<< "${OverlayInfo[${ol_name}:excludes]}" for item in "${excludes[@]}"; do OverlayExcludes[${item}]=1 done export_env UsedOverlays EnvMustBeSaved='yes' scan_groups "${UsedOverlays[@]}" } #.............................................................. use_group() { # die if argument is a hierarchical group (( ${GroupDepths[${arg}]} > 0 )) && \ die_illegal_group "${arg}" std::append_path UsedGroups "$1" local ol_name for ol_name in "${UsedOverlays[@]}"; do local dir="${OverlayInfo[${ol_name}:modulefiles_root]}/$1/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue std::prepend_path MODULEPATH "${dir}" [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]] && break done } #.............................................................. local -- arg="$1" local -i rc=0 if is_release_stage "${arg}"; then # argument is release stage std::append_path UsedReleaseStages "${arg}" || rc=$? return ${rc} fi if [[ -v OverlayInfo[${arg}:type] ]]; then use_overlay "${arg}" || rc=$? return ${rc} fi if [[ ! -v GroupDepths[${arg}] ]]; then # this scan is required if a new group has been # create inside an used overlay scan_groups "${UsedOverlays[@]}" EnvMustBeSaved='yes' fi if [[ -v GroupDepths[${arg}] ]]; then use_group "${arg}" || rc=$? return ${rc} fi if [[ -d ${arg} ]]; then local dir='' dir=$(std::get_abspath "${arg}") ${add2path_func} MODULEPATH "${dir}" || rc=$? return ${rc} fi die_invalid_value "use flag, group, overlay or directory" \ "${arg}" } # use () #...................................................................... local -a args=() while (( $# > 0)); do case "$1" in -\? | -H | --help ) print_help "${SubCommand}" ;; -a | --append ) add2path_func='std::append_path' ;; -p | --prepend ) add2path_func='std::prepend_path' ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then print_info return fi for arg in "${args[@]}"; do use "${arg}" done EnvMustBeSaved='yes' export_env 'MODULEPATH' } ############################################################################## # # unuse directory|group|release_stage|... # Subcommands['unuse']='unuse' Options['unuse']='-o \?H -l help' Help['unuse']=' unuse directory|group|release... Remove the given modulefiles directory, group, release stage, flag from the search path. ' subcommand_unuse() { local -a modulepath=() IFS=':' read -r -a modulepath <<<"${MODULEPATH}" #...................................................................... unuse() { #.............................................................. unuse_overlay() { local ol_name="$1" if [[ -n "${LOADEDMODULES}" ]] && \ [[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]]; then std::die 3 "%s %s: %s %s" \ "${CMD}" "${SubCommand}" \ "overlay cannot be removed since" \ "some modules are still loaded!" fi [[ "${OverlayInfo[ol_name]:modulefiles_root}" == "${PMODULES_HOME%%/Tools*}" ]] && \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "cannot remove base overlay" \ "${ol_name}" [[ ${OverlayInfo[${ol_name}:used]} != 'yes' ]] && \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "not an used overlay" \ "${ol_name}" # make sure first index is '0' (it should, but you never know) UsedOverlays=( "${UsedOverlays[@]}" ) [[ "${ol_name}" != "${UsedOverlays[0]}" ]] && \ std::die 3 "%s %s: %s %s -- %s" \ "${CMD}" "${SubCommand}" \ "overlay cannot be removed since" \ "it not on top of the stack" \ "${ol_name}" if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then # if this overlay hides groups, we have to re-add # the modules made available by other overlays local modulefiles_root=${OverlayInfo[${ol_name}:modulefiles_root]} for group in ${UsedGroups//:/ }; do # is this group in the to be removed overlay? local dir="${modulefiles_root}/${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue # no dir="/${group}/${PMODULES_MODULEFILES_DIR}" std::prepend_path MODULEPATH "${UsedOverlays[@]/%/${dir}}" done fi OverlayInfo[${ol_name}:used]='no' UsedOverlays=( "${UsedOverlays[@]:1}") # rebuild exclude list. # Note: # A module might be excluded in multiple overlays. So, we cannot # just remove the excludes from the overlay to unuse. local excludes=() OverlayExcludes=() local ol local item for ol in "${UsedOverlays[@]}"; do IFS=':' read -r -a excludes <<< "${OverlayInfo[${ol}:excludes]}" for item in "${excludes[@]}"; do OverlayExcludes[${item}]=1 done done EnvMustBeSaved='yes' export_env UsedOverlays local dir for dir in "${modulepath[@]}"; do [[ "${dir}" == "${OverlayInfo[${ol_name}:modulefiles_root]}" ]] && \ std::remove_path MODULEPATH "${dir}" done } #.............................................................. unuse_group() { if (( ${GroupDepths[${arg}]} > 0 )); then # argument is a hierarchical group in our root std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "illegal group" \ "${arg}" fi local var="PMODULES_LOADED_${arg^^}" if [[ -n "${!var}" ]]; then std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "cannot remove group due to loaded modules" \ "${arg}" fi std::remove_path UsedGroups "${arg}" local overlay for overlay in "${UsedOverlays[@]}"; do local dir="${overlay}/${arg}/${PMODULES_MODULEFILES_DIR}" std::remove_path MODULEPATH "${dir}" done } #.............................................................. local arg=$1 if is_release_stage "${arg}"; then # argument is release stage std::remove_path UsedReleaseStages "${arg}" return 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}" return 0 fi if [[ -v GroupDepths[${arg}] ]]; then unuse_group "${arg}" return 0 fi std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${SubCommand}" \ "invalid keyword, group, overlay or directory" \ "${arg}" } # unuse() #...................................................................... local -a args=() while (( $# > 0)); do case "$1" in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ 'missing argument' fi for arg in "${args[@]}"; do unuse "${args[@]}" done EnvMustBeSaved='yes' export_env 'MODULEPATH' } ############################################################################## # # update # # :FIXME: # either compile Modules with --enable-beginenv or remove the # sub-command # Subcommands['update']='update' Options['update']='-o \?H -l help' Help['update']=' USAGE: module update Attempt to reload all loaded modulefiles. ' subcommand_update() { subcommand_generic0 "$@" } ############################################################################## # # refresh # Subcommands['refresh']='refresh' Options[refresh]='-o \?H -l help' Help[refresh]=' USAGE: module refresh Force a refresh of all non-persistent components of currently loaded modules. This should be used on derived shells where aliases need to be reinitialized but the environment variables have already been set by the currently loaded modules. ' subcommand_refresh() { subcommand_generic0 "$@" } # # Helper functions, used during initialization and for purging all modules. # # Arguments: # none # init_modulepath() { declare -gx MODULEPATH='' local group local ol for ol in "${UsedOverlays[@]}"; do for group in ${UsedGroups//:/ }; do local dir="${OverlayInfo[${ol}:modulefiles_root]}/${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" fi done done } pmodules_init() { init_used_groups() { declare -gx UsedGroups='' local group for group in ${DefaultGroups//:/ }; do std::append_path UsedGroups "${group}" done EnvMustBeSaved='yes' } init_used_releases() { declare -g UsedReleaseStages='' for r in ${DefaultReleaseStages//:/ }; do std::append_path UsedReleaseStages "${r}" done EnvMustBeSaved='yes' } init_overlay_vars() { declare -ag UsedOverlays=( 'base' ) OverlayInfo['base:used']='yes' } init_manpath() { : " Initialize MANPATH. Use systen wide configuration file if exist or set some defaults. " std::remove_path MANPATH "${PMODULES_HOME%/*}/.*" if [[ -r /etc/man.config ]]; then declare _manconf='/etc/man.config' elif [[ -r /etc/man.conf ]]; then declare _manconf='/etc/man.conf' fi if [[ -v _manconf ]]; then # initialize from system configuration file while read -r name value rest; do std::append_path MANPATH "${value}" done < <(grep "^MANPATH\s" "${_manconf}") unset _manconf else # set defaults std::append_path MANPATH "${PMODULES_HOME}/share/man" std::append_path MANPATH "/usr/share/man" fi } pm::read_config declare -gx LOADEDMODULES='' declare -gx _LMFILES_='' declare -Ag GroupDepths=() declare -g Version="${PMODULES_VERSION}" init_used_groups init_used_releases init_overlay_vars init_modulepath init_manpath scan_groups "${UsedOverlays[@]}" save_env export_env \ LOADEDMODULES \ _LMFILES_ \ MODULEPATH \ PATH \ MANPATH } ############################################################################## # # purge # Subcommands['purge']='purge' Options['purge']='-o \?H -l help' Help['purge']=' USAGE: module purge Unload all loaded modulefiles. ' subcommand_purge() { # # unload all loaded modules # # Note: # If a Pmodule module is loaded, it will *not* be # unloaded! # local -a args=() while (( $# > 0)); do case "$1" in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; -* ) die_illegal_opt "$1" ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} > 0 )) && die_no_args_allowed # get list of loaded modules with stripped MODULEPATH IFS=':' read -r -a modules <<< "${LOADEDMODULES}" for (( i=${#modules[@]}-1; i>=0; i-- )); do [[ ${modules[$i]} == Pmodules/* ]] && continue subcommand_unload "${modules[$i]}" done } ############################################################################## # # list [-hlt] # Subcommands['list']='list' Options['list']='-l help -o \?Hhlt -l human -l long -l terse' Help['list']=' USAGE: module list List loaded modules. ' subcommand_list() { human_readable_output(){ # get list of loaded modules with stripped MODULEPATH IFS=':' read -r -a modules \ < <( ${sed} "s;${MODULEPATH//:/\/\\\|}/;;g" <<< "${_LMFILES_}" ) local -- strs=() local -i n=1 # enumeration of loaded modules local -i colsize=0 local -- s='' for module in "${modules[@]%.lua}"; do s=$(printf "%2d) %-s" "$n" "${module}") strs+=( "$s" ) (( ${#s} > colsize )) && (( colsize=${#s} )) (( n+=1 )) done (( colsize+=2 )) local -i cols=80 [[ -t 1 && -t 2 ]] && cols=$(${tput} cols) local -i column=0 printf "Currently Loaded Modules:\n" 1>&2 for s in "${strs[@]}"; do local -i len=${#s} if (( column+len >= cols )); then printf -- "\n" 1>&2 column=0 fi if (( column+colsize < cols )); then printf "%-${colsize}s" "$s" 1>&2 else printf "%-s" "$s" 1>&2 # last column fi (( column+=colsize )) done printf -- "\n\n" 1>&2 } long_output(){ IFS=':' read -r -a modules \ < <( ${sed} "s;${MODULEPATH//:/\/\\\|}/;;g" <<< "${_LMFILES_}" ) IFS=':' read -r -a lmfiles <<< "${_LMFILES_}" printf "Currently Loaded Modules:\n" 1>&2 local -i fmt_field_width=0 local -i length=0 local module for module in "${modules[@]}"; do length=${#module} (( length > fmt_field_width )) && fmt_field_width=length done for (( i=0; i<${#lmfiles[@]}; i++ )); do mtime=$(date -r "${lmfiles[i]}" +"%Y-%m-%d %H:%M:%S") printf "%-${fmt_field_width}s\t%s\n" "${modules[i]}" "${mtime}" 1>&2 done } terse_output(){ IFS=':' read -r -a modules \ < <( ${sed} "s;${MODULEPATH//:/\/\\\|}/;;g" <<< "${_LMFILES_}" ) printf "Currently Loaded Modules:\n" 1>&2 for module in "${modules[@]%.lua}"; do printf "%s\n" "${module}" 1>&2 done printf -- "\n\n" 1>&2 } local args=() local output_function='human_readable_output' while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -h | --human ) output_function='human_readable_output' ;; -l | --long ) output_function='long_output' ;; -t | --terse ) output_function='terse_output' ;; -- ) shift 1 args+=( "$@" ) break ;; -* ) die_illegal_opt "$1" ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} > 0 )); then std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ "no arguments allowed" fi "${output_function}" } ############################################################################## # # clear # Subcommands['clear']='clear' Options['clear']='-o \?H -l help' Help['clear']=' USAGE: module clear Force the Modules package to believe that no modules are currently loaded. ' subcommand_clear() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} > 0 )); then std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ "no arguments allowed" fi pmodules_init } ############################################################################## # # search [switches] [STRING...] # Subcommands['search']='search' Subcommands['find']='search' Subcommands['spider']='search' Options['search']='-o a\?H -l help -l no-header -l print-modulefiles ' Options['search']+='-l release-stage: -l with: -l all-release-stages -l src: -l print-csv ' Options['search']+='-l verbose ' Options['search']+='-l all-deps -l wrap ' Options['search']+='-l glob ' Options['search']+='-l newest ' Options['search']+='-l group:' Help['search']=' USAGE: module find|search|spider [switches] STRING... Search installed modules. If an argument is given, search for modules whose name match the argument. SWITCHES: -a|--all-release-stages Search within all releases. --all-deps Show all dependecies --glob Interpret STRING as shell pattern. --no-header Suppress output of a header. --release-stage=RELEASE_STAGE Search for modules within this release stage. You can specify this switch multiple times. Without this switch, the release stages in use will be searched. --verbose vebose output --with=STRING Search for modules compiled with modules matching string. The command module search --with=gcc/4.8.3 lists all modules in the hierarchy compiled with gcc 4.8.3. --wrap wrap output ' subcommand_search() { local -a modules=() local -a groups=() local -- group='' local -- with_modules='//' local -i cols=80 [[ -t 1 && -t 2 ]] && cols=$(${tput} cols) # get number of columns of terminal local -i max_len_modulename=0 local -- search_range='all' local -a src_prefix=() local opt_print_header='yes' local opt_print_modulefiles='no' local opt_print_csv='no' local opt_print_verbose='no' local opt_use_relstages=':' local opt_all_deps='no' local opt_wrap='no' local opt_glob='no' local opt_newest='no' #..................................................................... # # output result of search # Args: # $1: tmp file # # variables used from enclosing function: # opt_print_header # opt_print_modulefiles # with_modules # print_result() { local func_print_header='' local func_print_line='' local fmt='' print_default() { fmt="%-${max_len_modulename}s %-10s %-12s %-12s %-s" if [[ ${opt_print_header} == 'yes' ]]; then func_print_header='print_header_default' else func_print_header='print_header_none' fi func_print_line='print_line_default' } print_header_default() { std::info '' std::info "${fmt}" "Module" "Rel.stage" "Group" "Overlay" "Requires" std::info '-%.0s' $(seq 1 "${cols}") } print_line_default() { write_line() { local str="$1" if [[ -t 1 && -t 2 ]] && (( ${#str} >= cols )); then str="${str:0:$((cols-1))}>" fi std::info "${str}" } local -a deps=( "${@:6}" ) (( ${#deps[@]} == 0 )) && deps[0]='' local -- str='' if [[ "${opt_wrap}" == 'no' ]]; then str=$(printf "${fmt}" "$1" "$2" "$3" "$5" "${deps[*]}") write_line "${str}" else str=$(printf "${fmt}" "$1" "$2" "$3" "$5" "${deps[0]}") for (( i = 1; i < ${#deps[@]}; i++ )); do if (( ${#str} + ${#deps[i]} + 1 <= cols )); then str+=" ${deps[i]}" else write_line "${str}" str=$(printf "${fmt}" "" "" "" "> ${deps[i]}") fi done write_line "${str}" fi } print_verbose() { fmt="%-${max_len_modulename}s %-12s %-14s %-s" func_print_header='print_header_verbose' func_print_line='print_line_verbose' } print_header_verbose() { : } print_line_verbose() { local deps="${*:6}" [[ -z ${deps} ]] && deps="(none)" std::info "$1:" std::info " release stage: $2" std::info " group: $3" std::info " overlay: $5" std::info " modulefile: $4" std::info " dependencies: ${deps}" } # print full modulefile names only print_modulefiles() { fmt='' func_print_header='print_header_none' func_print_line='print_line_modulefile' } print_header_none() { : } print_line_modulefile() { if (( $# >= 4 )) && [[ -n $4 ]]; then std::info "$1 $4" fi } print_line_csv() { : } print_csv() { fmt='' func_print_header='print_header_none' func_print_line='print_line_csv' } if [[ "${opt_print_modulefiles}" == 'yes' ]]; then print_modulefiles elif [[ "${opt_print_csv}" == 'yes' ]]; then print_csv elif [[ "${opt_print_verbose}" == 'yes' ]]; then print_verbose else print_default fi local _script='' if [[ ${opt_newest} == 'yes' ]]; then _script='{} END{print}' fi ${func_print_header} local -a toks=() while read -r -a toks; do ${func_print_line} "${toks[@]}" done < <("${sort}" --version-sort -k 1,1 -k 6,6 -k 7,7 "${TmpFile}" | \ ${awk} "${with_modules} ${_script}") } #..................................................................... # # search modules # Args: # $1: module name pattern # # Variables used from enclosing function # :FIXME: # search () { local -r module="$1" local -r group="$2" shift 2 local -a modulepath=("$@") local -i depth=0 # get and print all available modules in $mpath # with respect to the requested release stage # TmpFile: module/version relstage group dependencies... local mods get_available_modules \ mods \ "${module}" \ "${opt_use_relstages}" \ "${modulepath[@]}" local i=0 for (( i=0; i<${#mods[@]}; i+=4 )); do local name=${mods[i]} local relstage=${mods[i+1]} local modulefile=${mods[i+2]} local ol=${mods[i+3]} local -a deps=() if (( ${#name} > max_len_modulename)); then max_len_modulename=${#name} fi if [[ "${opt_print_verbose}" == 'yes' ]] || \ [[ "${opt_all_deps}" == 'yes' ]]; then local prefix='' get_module_prefix prefix "${modulefile}" local dependencies_file="${prefix}/.dependencies" if [[ -n ${prefix} ]] && [[ -r "${dependencies_file}" ]]; then mapfile -t deps < "${dependencies_file}" fi elif [[ "${group}" != 'other' ]]; then # get dependencies encoded in directory name local -i j local -a toks IFS='/' read -r -a toks <<< "${modulefile}" local -i depth=${GroupDepths[${group}]} for ((j = -depth-2; j < -2; j += 2)); do deps+=( "${toks[*]: $j:2}" ); done unset IFS fi echo "${name}" "${relstage}" "${group}" "${modulefile}" \ "${ol}" \ "${deps[@]}" >> "${TmpFile}" done } while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; --all-deps ) opt_all_deps='yes' ;; --no-header ) opt_print_header='no' ;; --print-modulefiles ) opt_print_modulefiles='yes' opt_print_header='no' ;; --print-csv ) opt_print_csv='yes' opt_print_header='no' ;; --release-stage | --release-stage=* ) if [[ "$1" == "--release" ]]; then local arg=$2 shift else local arg=${1/--release=} fi is_release_stage "${arg}" || \ std::die 1 "%s %s: %s -- %s" \ "${CMD}" 'search' \ "illegal release stage" \ "${arg}" opt_use_relstages+="${arg}:" ;; --with | --with=* ) if [[ "$1" == --with ]]; then local arg=$2 shift else local arg=${1/--with=} fi if [[ -z ${arg} ]] || [[ "${arg}" == -* ]]; then std::die 1 "%s %s: %s -- %s" \ "${CMD}" 'search' \ "illegal value for --with option" \ "${arg}" fi arg=${arg//:/ } arg=${arg//,/ } for module in ${arg}; do with_modules+=" && / ${module//\//\\/}/" done ;; -a | --all-releases-stages ) opt_use_relstages+="${ReleaseStages}" ;; --src | --src=*) if [[ "$1" == --src ]]; then local dir="$2" shift else local dir="${1/--src=}" fi if [[ ! -e "${dir}" ]]; then std::die 1 "%s %s: %s -- %s" \ "${CMD}" 'search' \ "illegal value for --src option" \ "${dir} does not exist" fi if [[ ! -d "${dir}" ]]; then std::die 1 "%s %s: %s -- %s" \ "${CMD}" 'search' \ "illegal value for --src option" \ "${dir} is not a directory" fi src_prefix+=( "$(std::get_abspath "${src_prefix}")" ) ;; -v | --verbose ) opt_print_verbose='yes' ;; --wrap ) opt_wrap='yes' ;; --glob ) opt_glob='yes' ;; --newest ) opt_newest='yes' ;; --group | --group=* ) if [[ $1 == *=* ]]; then group="${1/--*=}" else group="$2" shift fi if [[ -v GroupDepths[${group}] ]]; then search_range='inside' elif [[ "${group}" == 'other' ]]; then search_range='outside' else std::die 1 "%s %s: %s -- %s" \ "${CMD}" 'search' \ "illegal value for --group option" \ "${group} is not a valid group!" fi groups+=( "${group}" ) ;; -- ) shift 1 modules+=( "$@" ) break ;; * ) modules+=( "$1" ) ;; esac shift done if (( ${#src_prefix[@]} == 0 )); then local ol='' for ol in "${UsedOverlays[@]}"; do src_prefix+=( "${OverlayInfo[${ol}:modulefiles_root]}" ) done fi if [[ "${opt_use_relstages}" == ":" ]]; then opt_use_relstages=":${UsedReleaseStages}:" fi if [[ ${#modules[@]} == 0 ]]; then modules+=( '' ) fi local -- module='' if [[ "${search_range}" == 'all' || "${search_range}" == 'inside' ]]; then # search inside the Pmodules hierarchy # search in all groups if search is not limited if (( ${#groups[@]} == 0 )); then groups=( "${!GroupDepths[@]}" ) fi for module in "${modules[@]}"; do [[ ${opt_glob} == 'no' ]] && module+="*" for group in "${groups[@]}"; do # loop over all directories which can be added to # MODULEPATH inside current group local -i depth=${GroupDepths[${group}]} local s='' if (( depth > 0 )); then s=$(printf '/*%.0s' $(seq 1 ${depth})) fi modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} ) search "${module}" "${group}" "${modulepath[@]}" done done fi if [[ "${search_range}" == 'all' || "${search_range}" == 'outside' ]]; then # search outside the Pmodules hierarchy IFS=':' read -r -a modulepath <<< "${MODULEPATH}" local -a dirs=() local -- dir='' for dir in "${modulepath[@]}"; do # skip all directories in Pmodules hierarchy local -- ol_name='' local -- install_root='' for ol_name in "${Overlays[@]}"; do install_root="${OverlayInfo[${ol_name}:install_root]}" [[ "${dir}" == "${install_root}"/* ]] && continue 2 done # add moduledir outside Pmodules hierarchy dirs+=( "${dir}" ) done for module in "${modules[@]}"; do [[ ${opt_glob} == 'no' ]] && module+="*" search "${module}" 'other' "${dirs[@]}" done fi print_result echo -n '' > ${TmpFile} } ############################################################################## # # help [module|sub-command] # Subcommands['help']='help' Options['help']='-o hHV\? -l version -l help' Help['help']=' USAGE: module [ switches ] [ subcommand ] [subcommand-args ] SWITCHES: -h|-H|-?|--help this usage info -V|--version modules version & configuration options --debug enable debug output SUBCOMMANDS: + add|load [switches] modulefile [modulefile ...] + rm|unload modulefile [modulefile ...] + switch|swap [modulefile1] modulefile2 + display|show modulefile [modulefile ...] + avail [switches] [modulefile [modulefile ...]] + search [switches] [args] + use [switches] [dir|group|release ...] + unuse dir|group|release [dir|group|release ...] + refresh + purge + list [switches] + clear + help [modulefile|subcommand] + whatis [modulefile [modulefile ...]] + apropos|keyword string + save collection + restore collection + savelist + saverm collection + saveshow collection + initadd modulefile [modulefile ...] + initprepend modulefile [modulefile ...] + initrm modulefile [modulefile ...] + initswitch modulefile1 modulefile2 + initlist + initclear DOCUMENTATION: Full documentation is available at http://pmodules.gitpages.psi.ch ' subcommand_help() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -h | -H | --help ) print_help "${SubCommand}" ;; -V | --version ) print_help 'version' ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then print_help 'help' fi local arg for arg in "${args[@]}"; do if [[ -v Help[${arg}] ]] ; then print_help "${arg}" else local -- modulefile='' find_modulefile_and_interpreter modulefile modulecmd "${arg}" "${modulecmd}" 'bash' 'help' "${modulefile}" fi done } ############################################################################## # # whatis # Subcommands['whatis']='whatis' Options['whatis']='-o \?Ha -l help -l all' Help['whatis']=' USAGE: module whatis [modulefile...] Display the information set up by the module-whatis commands inside the specified modulefile(s). If no modulefile is specified, all whatis lines will be shown. ' subcommand_whatis() { local options=() local args=() while (( $# > 0 )); do case $1 in -\? | --help ) print_help "${SubCommand}" ;; -a | --all ) options+=( '-a' ) ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done local group='' for group in "${!GroupDepths[@]}"; do local mod_name='' local file_name='' while read -r mod_name file_name; do [[ -n ${file_name} ]] || continue if [[ "${file_name##*.}" == 'lua' ]]; then modulecmd="${Lmod_cmd}" file_name="${mod_name}" else modulecmd="${Tcl_cmd}" fi local whatis='' whatis=$("${modulecmd}" bash \ whatis \ "${file_name}" \ 2>&1 1>/dev/null) printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2 done < <(set +x; subcommand_search \ --group "${group}" \ --print-modulefiles \ --newest \ "${options[@]}" \ "${args[@]}" 2>&1) done } ############################################################################## # # apropos # Subcommands['apropos']='apropos' Subcommands['keyword']='apropos' Options['apropos']='-o \?Ha -l help -l all' Help['apropos']=' USAGE: module apropos string module keyword string Seeks through the whatis informations of all modulefiles for the specified string. All module-whatis informations matching the string will be displayed. ' subcommand_apropos() { local options=() local args=() while (( $# > 0 )); do case $1 in -\? | --help ) print_help "${SubCommand}" ;; -a | --all ) options+=( '-a' ) ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ "no search string specified" elif (( ${#args[@]} > 1 )); then std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ "more then one search string specified" fi local arg="${args[0]}" local group='' for group in "${!GroupDepths[@]}"; do local mod_name='' local file_name='' while read -r mod_name file_name; do [[ -n ${file_name} ]] || continue if [[ "${file_name##*.}" == 'lua' ]]; then modulecmd="${Lmod_cmd}" file_name="${mod_name}" else modulecmd="${Tcl_cmd}" fi local whatis='' whatis=$("${modulecmd}" bash \ whatis \ "${file_name}" \ 2>&1 1>/dev/null) if [[ ${whatis,,} =~ ${arg,,} ]]; then printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2 fi done < <(set +x; subcommand_search \ --group "${group}" \ --print-modulefiles \ "${options[@]}" 2>&1) done } ############################################################################## # # Collections # # Collections describe a sequence of module use then module load commands # that are interpreted by Modules to set the user environment as described # by this sequence. # # User collections: # # System collections: ## ############################################################################## # # save [collection] # Subcommands['save']='save' Options['save']='-o \?H -l help' Help['save']=" USAGE: module save [collection] Record the currently set MODULEPATH directory list and the currently loaded modulefiles in a collection file under the user's collection directory \$HOME/.Pmodules. If collection name is not specified, then it is assumed to be the default collection. If collection is a fully qualified path, it is saved at this location rather than under the user's collection directory. " declare -r UsrCollectionsDir="${HOME}/.Pmodules/collections" subcommand_save() { local -a args=() local -- opt_system='no' while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; --system ) opt_system='yes' ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} > 1 )) && die_too_many_args [[ ${args[0]:0:1} =~ [0-9a-zA-Z] ]] || \ die_invalid_collection_name "${args[0]}" local -- basedir="${UsrCollectionsDir}" if [[ "${opt_system}" == 'yes' ]]; then basedir="${UsedOverlays[0]}/collections" fi local collection='' if (( ${#args[@]} == 0 )); then collection="${basedir}/default" else collection="${basedir}/${args[0]}" fi mkdir -p "${collection%/*}" || \ die_cannot create_directory "$_" # save used release stages, groups and overlays local -- item='' local -a items=() local -a tmp=() local -- grp='' IFS=':' read -r -a tmp <<< "${UsedReleaseStages}" items+=( "${tmp[@]}") IFS=':' read -r -a tmp <<< "${UsedGroups}" for grp in "${tmp[@]}"; do # skip hierarchical groups (( ${GroupDepths[${tmp}]} > 0 )) && continue items+=( "${grp}") done IFS=':' read -r -a tmp <<< "${UsedOverlays}" items+=( "${tmp[@]}") for item in "${items[@]}"; do [[ "${item}" == 'base' ]] && continue s+="module use ${item};\n" done # save additional module directories local -a modulepath=() IFS=':' read -r -a modulepath <<< "${MODULEPATH}" local dir='' local ol='' local grp='' for dir in "${modulepath[@]}"; do find_overlay ol grp "${dir}" && continue s+="module use \"${dir}\";\n" done # save loaded modules local -a modules=() IFS=':' read -r -a modules <<< "${LOADEDMODULES}" local -- m='' for m in "${modules[@]}"; do [[ $m == Pmodules/* ]] && continue s+="module load $m;\n" done # save collection echo -e "$s" > "${collection}" || \ die_cannot_save_collection "${collection}" } ############################################################################## # # restore [collection] # Subcommands['restore']='restore' Options['restore']='-o \?H -l help' Help['restore']=" USAGE: module restore [collection] Restore the environment state as defined in collection. If collection name is not specified, then it is assumed to be the default collection. " search_collection(){ local -n _path="$1" local -- _collection="$2" if [[ -r "${UsrCollectionsDir}/${_collection}" ]]; then _path="${UsrCollectionsDir}" return 0 fi local -- _ol for _ol in "${UsedOverlays[@]}"; do if [[ -r "${OverlayInfo[${_ol}:install_root]}/collections/${_collection}" ]]; then _path="${OverlayInfo[${_ol}:install_root]}/collections" return 0 fi done die_collection_doesnt_exist "${_collection}" } subcommand_restore() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done (( ${#args[@]} > 1 )) && die_too_many_args (( ${#args[@]} == 0 )) && args=( 'default' ) local -- path='' local -- collection='' local -a collections=() for collection in "${args[@]}"; do search_collection path "${collection}" collections+=( "${path}/${collection}" ) done # reset/unload everything (except Pmodules/) IFS=':' read -r -a modules <<< "${LOADEDMODULES}" local -- m='' for ((i=${#modules[@]}-1; i>=0; i--)); do [[ ${modules[$i]} == Pmodules/* ]] && continue subcommand_unload "${modules[$i]}" done local -a items=() local -a tmp=() local -- item='' local -a default_grps=() IFS=':' read -r -a tmp <<< "${UsedReleaseStages}" items+=( "${tmp[@]}") # remove all groups with the exception of the default groups IFS=':' read -r -a tmp <<< "${UsedGroups}" IFS=':' read -r -a default_grps <<< "${DefaultGroups}" for item in "${tmp[@]}"; do (( ${GroupDepths["${item}"]} > 0 )) && continue std::is_member_of_array "${item}" default_grps && continue items+=( "${grp}" ) done IFS=':' read -r -a tmp <<< "${UsedOverlays}" for item in "${tmp[@]}"; do [[ "${item}" == 'base' ]] && continue items+=( "${tmp[@]}") done local -- item='' for item in "${items[@]}"; do [[ -z "${item}" ]] && continue subcommand_unuse "${item}" done save_env export_env PMODULES_ENV # load collection for collection in "${collections[@]}"; do ${cat} "${collection}" done EnvMustBeSaved='no' } ############################################################################## # # module savelist [pattern...] # Subcommands['savelist']='savelist' Options['savelist']='-o \?H -l help' Help['savelist']=" USAGE: module savelist [pattern...] List collections that are currently saved under the user's collection directory. If a pattern is given, then the collections are filtered to only list those whose name matches this pattern. It may contain wildcard characters. pattern is matched in a case insensitive manner by default. If multiple patterns are given, collection has to match at least one of them to be listed. " subcommand_savelist() { get_collections() { local -n _result="$1" local -n gc_dirs="$2" shift 2 _result=() local _pattern local _coll for _pattern in "$@"; do while read -r _coll; do _result+=( "${_coll}" ) done < <(find "${gc_dirs[@]}" -type f -ipath "*/${_pattern}" -printf "%P\n" 2>/dev/null) done } print_collections() { local -r prt_header="$1" local -n prt_dirs="$2" shift 2 local -a prt_collections=() get_collections prt_collections prt_dirs "$@" (( ${#prt_collections[@]} == 0 )) && return 0 std::info "${prt_header}" for prt_col in "${prt_collections[@]}"; do std::info "\t${prt_col}" done } local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then args[0]='*' fi local -a _dirs=( "${UsrCollectionsDir[@]}" ) print_collections "User collections:" _dirs "${args[@]}" _dirs=() local _ol for _ol in "${UsedOverlays[@]}"; do _dirs+=( "${OverlayInfo[${_ol}:install_root]}/collections" ) done print_collections "\nSystem collections:" _dirs "${args[@]}" } ############################################################################## # # module saverm [collection...] # Subcommands['saverm']='saverm' Options['saverm']='-o \?H -l help' Help['saverm']=" USAGE: module saverm [collection] Delete the collection file under the user's collection directory. If collection name is not specified, then it is assumed to be the default collection. " subcommand_saverm() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then args[0]='default' fi local -- path='' local -- collection='' for collection in "${args[@]}"; do search_collection path "${collection}" test -e "${path}/${collection}" || \ die_collection_doesnt_exist "${collection}" ${rm} -f "${path}/${collection}" 2>/dev/null || \ die_removing_collection_failed "${collection}" # remove directories if empty ${rmdir} -p "${path}/${collection%%/*}" 2>/dev/null done } ############################################################################## # # module saveshow [collection] # Subcommands['saveshow']='saveshow' Options['saveshow']='-o \?H -l help' Help['saveshow']=" USAGE: module saveshow [collection...] Display the content of collection. If collection name is not specified, then it is assumed to be the default collection, " subcommand_saveshow() { local -a args=() while (( $# > 0 )); do case $1 in -\? | -H | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then args[0]='default' fi local -- collection='' local -- path='' for collection in "${args[@]}"; do search_collection path "${collection}" test -e "${path}/${collection}" || \ die_collection_doesnt_exist "${collection}" std::info "Collection '${collection}':" cat "${path}/${collection}" 1>&2 done } ############################################################################## # # initadd module... # Subcommands['initadd']='initadd' Options['initadd']='-o \?H -l help' Help['initadd']=" USAGE: module initadd modulefile... Add modulefile(s) to the shell's initialization file in the user's home directory. The startup files checked (in order) are: csh - .modules, .cshrc(.ext), .csh_variables, and .login(.ext) tcsh - .modules, .tcshrc, .cshrc(.ext), .csh_variables, and .login(.ext) (k)sh - .modules, .profile(.ext), and .kshenv(.ext) bash - .modules, .bash_profile, .bash_login, .profile(.ext) and .bashrc(.ext) zsh - .modules, .zcshrc(.ext), .zshenv(.ext), and .zlogin(.ext) If a 'module load' line is found in any of these files, the modulefile(s) is(are) appended to any existing list of modulefiles. The 'module load' line must be located in at least one of the files listed above for any of the 'init' sub-commands to work properly. If the 'module load' line line is found in multiple shell initialization files, all of the lines are changed. " subcommand_initadd() { subcommand_generic1plus "$@" } ############################################################################## # # initprepend module... # Subcommands['initprepend']='initprepend' Options['initprepend']='-o \?H -l help' Help['initprepend']=" USAGE: module initprepend modulefile... Does the same as initadd but prepends the given modules to the beginning of the list. " subcommand_initprepend() { subcommand_generic1plus "$@" } ############################################################################## # # initrm module... # Subcommands['initrm']='initrm' Options['initrm']='-o \?H -l help' Help['initrm']=" USAGE: module initrm modulefile... Remove modulefile(s) from the shell's initialization files. " subcommand_initrm() { subcommand_generic1plus "$@" } ############################################################################## # # initswitch module1 module2 # Subcommands['initswitch']='initswitch' Options['initswitch']='-o \?H -l help' Help['initswitch']=" USAGE: module initswitch modulefile1 modulefile2 Switch modulefile1 with modulefile2 in the shell's initialization files. " subcommand_initswitch() { local args=() while (( $# > 0 )); do case $1 in -\? | --help ) print_help "${SubCommand}" ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} != 2 )); then std::die 3 "%s %s: %s" \ "${CMD}" "${SubCommand}" \ "two arguments required not less not more" fi "${modulecmd}" "${Shell}" "${SubCommand}" "${args[@]}" } ############################################################################## # # initlist # Subcommands['initlist']='initlist' Options['initlist']='-o \?H -l help' Help['initlist']=" USAGE: module initlist List all of the modulefiles loaded from the shell's initialization file. " subcommand_initlist() { subcommand_generic0 "$@" } ############################################################################## # # initclear # Subcommands['initclear']='initclear' Options['initclear']='-o \?H -l help' Help['initclear']=" USAGE: module initclear Clear all of the modulefiles from the shell's initialization files. " subcommand_initclear() { subcommand_generic0 "$@" } ############################################################################## # # main # # parse arguments # (( $# > 0 )) || print_help 'help' # first argument must be a shell! case "$1" in sh | bash | zsh ) declare Shell="sh" ;; csh | tcsh ) declare Shell='csh' ;; python ) declare Shell='python' ;; * ) std::die 1 "${CMD}: unsupported shell -- $1" ;; esac shift debug(){ : } # parse agruments till and including the sub-command declare -a opts=() while (( $# > 0 )); do case $1 in -\? | -H | --help | -help ) print_help 'help' ;; -V | --version ) print_help 'version' ;; --debug ) set -x ;; --verbose ) debug(){ echo "INFO: " "$@" 1>&2 } ;; '' ) ;; -* ) opts+=( "$1" ) ;; * ) SubCommand="$1" shift break ;; esac shift done if [[ -z "${SubCommand}" ]]; then std::die 1 "${CMD}: no sub-command specified." fi if [[ ! -v Subcommands[${SubCommand}] ]]; then std::die 1 "${CMD}: unknown sub-command -- ${SubCommand}" fi # restore variables from last call if [[ -v PMODULES_ENV ]]; then eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)" fi # Version should now be defined again, if not: # - PMODULES_ENV was not set # - Version was not defined the last time the status was saved. # This is true for older Pmodules versions. # We (re-)initialise the Pmodules system, if # - PMODULES_ENV was not set/is empty # - A new Pmodules version has been loaded if [[ ! -v Version ]] || \ [[ ${Version} != "${PMODULES_VERSION}" ]] || \ [[ ! -v PMODULES_ENV ]] || \ [[ -z ${PMODULES_ENV} ]]; then declare _tmp_loaded_modules_="${LOADEDMODULES}" declare _tmp_lmfiles_="${_LMFILES_}" pmodules_init LOADEDMODULES="${_tmp_loaded_modules_}" _LMFILES_="${_tmp_lmfiles_}" export_env \ LOADEDMODULES \ _LMFILES_ fi # we need to handle help text and options for sub-cmd aliases SubCommand=${Subcommands[${SubCommand}]} # parse arguments of the sub-command and call it temp=$("${getopt}" --name="${CMD}" ${Options[${SubCommand}]} -- "${opts[@]}" "$@" ) \ || print_help "${SubCommand}" eval set -- "${temp}" unset temp "subcommand_${SubCommand}" "$@" # Local Variables: # mode: sh # sh-basic-offset: 8 # tab-width: 8 # End: