#!@PMODULES_HOME@/sbin/bash --noprofile # #set -o nounset # we have to unset CDPATH, otherwise 'cd' prints the directoy! unset CDPATH # used for some output only declare -r CMD='module' declare -r mydir=$(cd $(dirname "$0") && pwd) declare prefix=$(dirname "${mydir}") declare -r sbindir="${prefix}/sbin" declare -r libdir="${prefix}/lib" declare -r libexecdir="${prefix}/libexec" declare -r base64="${sbindir}/base64" declare -r mktemp="${sbindir}/mktemp" declare -r sort="${sbindir}/sort" source "${libdir}/libstd.bash" source "${libdir}/libpmodules.bash" declare -r version='@PMODULES_VERSION@' if [[ ${PMODULES_PURETCL} == yes ]]; then declare -r modulecmd="${libexecdir}/modulecmd.tcl" else declare -rx TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules" declare -r modulecmd="${libexecdir}/modulecmd.bin" fi # required for pre 0.99.3 modulefiles declare -rx PSI_LIBMODULES="${TCLLIBPATH}/libmodules.tcl" declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} shopt -s nullglob declare -A GroupDepths='()' declare current_modulefile='' declare g_shell='' pbuild::export_env() { local -r shell="$1" shift case "${shell}" in bash | zsh ) local -r fmt="export %s=\"%s\"; " ;; csh | tcsh ) local -r fmt="setenv %s \"%s\"; " ;; * ) std::die 1 "Unsupported shell -- ${shell}\n" ;; esac while (( $# > 0 )); do printf "${fmt}" "$1" "${!1}" shift done } pbuild::save_env() { local -r shell="$1" shift local s='' local tmp while (( $# > 0 )); do tmp="$( typeset -p $1 2> /dev/null)" [[ -n "${tmp}" ]] && s+="${tmp};" shift done declare -g PMODULES_ENV=$( "${base64}" --wrap=0 <<< "$s" ) pbuild::export_env ${shell} PMODULES_ENV } trap 'pbuild::save_env ${g_shell} Overlays PMODULES_OVERLAYS GroupDepths UsedReleases UseFlags UsedGroups PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES PMODULES_DEFAULT_RELEASES' EXIT print_version() { echo " Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@ Copyright GNU GPL v2 " 1>&2 } usage() { print_version echo " USAGE: module [ switches ] [ subcommand ] [subcommand-args ] SWITCHES: -h|-H|-?|--help this usage info -V|--version modules version & configuration options SUBCOMMANDS: + add|load [switches] modulefile [modulefile ...] + rm|unload modulefile [modulefile ...] + switch|swap [modulefile1] modulefile2 + display|show modulefile [modulefile ...] + avail [switches] [modulefile [modulefile ...]] + search [switches] [args] + use [switches] [dir|group|release ...] + unuse dir|group|release [dir|group|release ...] + refresh + purge + list [switches] + clear + help [modulefile|subcommand] + whatis [modulefile [modulefile ...]] + apropos|keyword string + initadd modulefile [modulefile ...] + initprepend modulefile [modulefile ...] + initrm modulefile [modulefile ...] + initswitch modulefile1 modulefile2 + initlist + initclear " 1>&2 std::die 1 } subcommand_help_add() { echo " 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. " 1>&2 std::die 1 } subcommand_help_load() { subcommand_help_add } subcommand_help_rm() { echo " USAGE: module rm modulefile... moudle unload modulefile... Remove modulefile(s) from the shell environment. Removing a 'group-head' will also unload all modules belonging to this group. " 1>&2 std::die 1 } subcommand_help_unload() { subcommand_help_rm } subcommand_help_switch() { echo " USAGE: module switch [modulefile1] modulefile2 module swap [modulefile1] modulefile2 Switch loaded modulefile1 with modulefile2. If modulefile1 is not specified, then it is assumed to be the currently loaded module with the same root name as modulefile2. " 1>&2 std::die 1 } subcommand_help_swap() { subcommand_help_switch } subcommand_help_display() { echo " 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. " 1>&2 std::die 1 } subcommand_help_show() { subcommand_help_display } subcommand_help_apropos() { echo " 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. " 1>&2 std::die 1 } subcommand_help_keyword() { subcommand_help_apropos } subcommand_help_avail() { echo " USAGE: module avail [switches] string List all available modulefiles in the current MODULEPATH. If an argument is given, then each directory in the MODULEPATH is searched for modulefiles whose pathname match the argument. This command does *not* display all installed modules on the system. Only *loadable* modules are listed. The list of available modules may change either by loading other modules, e.g. a compiler, or with the sub-command 'use'. SWITCHES: -a|--all||--all-releases List all available modules independend of the release. -t|--terse Output in short format. -l|--long Output in long format. -h|--human Output in human readable format. -m|--machine Output in machine readable format " 1>&2 std::die 1 } subcommand_help_search() { echo " USAGE: module search [switches] string... Search installed modules. If an argument is given, search for modules whose name match the argument. SWITCHES: --no-header Suppress output of a header. --release=RELEASE Search for modules within this release. You can specify this switch multiple times. Without this switch, the used releases will be searched. -a|--all-releases Search within all releases. --with=STRING Search for modules compiled with modules matching string. The command module search --with=gcc/4.8.3 lists all modules in the hierarchy compiled with gcc 4.8.3. " 1>&2 std::die 1 } subcommand_help_use() { echo " USAGE: module use [-a|--append|-p|--prepend] [directory|group|release...] Without arguments this sub-command displays information about the module search path, used families and releases. You can use this sub-command to get a list of available families and releases. With a directory as argument, this directory will either be prepended or appended to the module search path. The default is to prepend the directory. With a group as argument, the modules in this group will be made available. With a release as argument, this modules with this release will be made available. SWITCHES: -a | --append -p | --prepend ) Append/prepend agrument to module search path or list of to be searched releases. " 1>&2 std::die 1 } subcommand_help_unuse() { echo " unuse directory|group|release... Remove the given directory, group or release from the search path. " 1>&2 std::die 1 } subcommand_help_update() { echo " USAGE: module update Attempt to reload all loaded modulefiles. " 1>&2 std::die 1 } subcommand_help_refresh() { echo " 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. " 1>&2 std::die 1 } subcommand_help_purge() { echo " USAGE: module purge Unload all loaded modulefiles. " 1>&2 std::die 1 } subcommand_help_list() { echo " USAGE: module list List loaded modules. " 1>&2 std::die 1 } subcommand_help_clear() { echo " USAGE: module clear Force the Modules package to believe that no modules are currently loaded. " 1>&2 std::die 1 } subcommand_help_whatis() { echo " 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. " 1>&2 std::die 1 } subcommand_help_initadd() { echo " 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. " 1>&2 std::die 1 } subcommand_help_initprepend() { echo " USAGE: module initprepend modulefile... Does the same as initadd but prepends the given modules to the beginning of the list. " 1>&2 std::die 1 } subcommand_help_initrm() { echo " USAGE: module initrm modulefile... Remove modulefile(s) from the shell's initialization files. " 1>&2 std::die 1 } subcommand_help_initswitch() { echo " USAGE: module initswitch modulefile1 modulefile2 Switch modulefile1 with modulefile2 in the shell's initialization files. " 1>&2 std::die 1 } subcommand_help_initlist() { echo " USAGE: module initlist List all of the modulefiles loaded from the shell's initialization file. " 1>&2 std::die 1 } subcommand_help_initclear() { echo " USAGE: module initclear Clear all of the modulefiles from the shell's initialization files. " 1>&2 std::die 1 } # # get release of module # Note: # - the release of a module outside ${Overlays[@]} is always 'stable' # - the release of a module inside ${Overlays[@]} without a # coresponding release file is always 'unstable' # # Args: # $1 upvar for returned release # $2 absolute modulefile name # $3 colon seperated list of accepted releases # get_release() { local "$1" local -r modulefile=$2 local -r releases=$3 # is modulefile in a used overlay? for root in "${!Overlays[@]}" 'ZZZZZZZ'; do [[ ${modulefile} =~ ${root} ]] && break done if [[ "${root}" == 'ZZZZZZZ' ]]; then std::upvar $1 'stable' return 0 fi # we are inside the used overlays local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}" if [[ -r ${releasefile} ]]; then # read releasefile, remove empty lines, spaces etc local -r data=$( < "${releasefile}" ) std::upvar $1 "${data}" else std::upvar $1 'unstable' fi [[ :${releases}: =~ ${release} ]] } : ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'} is_release() { [[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]] } is_used_release() { [[ ":${UsedReleases}:" =~ :$1: ]] } get_overlay_of_group () { local "$1" local -r group="$2" for overlay in "${!Overlays[@]}"; do if [[ -d "${overlay}/${group}/${PMODULES_MODULEFILES_DIR}" ]]; then std::upvar $1 "${overlay}" return 0 fi done return 1 } is_group () { local -r group="$1" # arg isn't emtpy and group already in cache [[ -n ${group} ]] && [[ -n ${GroupDepths[${group}]} ]] && return 0 # not yet cached or not a group local overlay='' get_overlay_of_group overlay "${group}" || return 1 get_group_depths "${overlay}" "${group}" } # # Check whether a given path is in an overlay. # If yes, return 0 and the overlay with upvar of first argument # otherwise return 1 # # $1 upvar to return overlay # $2 upvar to return group # $3 path to check # find_overlay () { local "$1" local "$2" local -r path=$3 for overlay in "${!Overlays[@]}"; do if [[ "${path}" =~ "${overlay}" ]]; then std::upvar $1 "${overlay}" local group="${path#${overlay}/}" group=${group%%/*} std::upvar $2 "${group}" return 0 fi done return 1 } is_used_group() { [[ :${UsedGroups}: =~ :$1: ]] } module_is_loaded() { [[ :${LOADEDMODULES}: =~ :$1: ]] } # # check shebang # $1: file name to test is_modulefile() { local -r fname="$1" local shebang [[ -r ${fname} ]] || return 1 read -n 11 shebang < "${fname}" [[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]] } subcommand_generic0() { local -r subcommand="$1" shift local opts=() pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand} eval set -- "${opts[@]}" while (( $# > 0 )); do case $1 in -- ) shift ;; * ) std::die 3 "%s %s: illegal argument -- %s\n" \ "${CMD}" "${subcommand}" "$1" ;; esac done "${modulecmd}" "${g_shell}" "${subcommand}" } subcommand_generic1() { local -r subcommand="$1" shift local opts=() pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand} eval set -- "${opts[@]}" local args=() while (( $# > 0 )); do case $1 in -- ) ;; * ) if (( ${#args[@]} == 0 )); then args+=( "$1" ) else std::die 3 "%s %s: only one argument allowed\n" \ "${CMD}" "${subcommand}" fi ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 3 "%s %s: missing argument\n" \ "${CMD}" "${subcommand}" fi "${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}" } subcommand_generic1plus() { local -r subcommand="$1" shift local opts=() pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand} eval set -- "${opts[@]}" local args=() while (( $# > 0 )); do case $1 in -- ) ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 3 "%s %s: missing argument\n" \ "${CMD}" "${subcommand}" fi "${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}" } subcommand_generic1or2() { local -r subcommand="$1" shift local opts=() pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand} eval set -- "${opts[@]}" local args=() while (( $# > 0 )); do case $1 in -- ) ;; * ) if (( ${#args[@]} > 2 )); then std::die 3 "%s %s: only one or two arguments are allowed\n" \ "${CMD}" "${subcommand}" fi args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 3 "%s %s: missing argument\n" \ "${CMD}" "${subcommand}" fi "${modulecmd}" "${g_shell}" "${subcommand}" "${args[@]}" } # # load [-fsvw] # # $1: module to load # subcommand_load() { local release='undef' local current_modulefile='' local prefix='' local m='' local saved_IFS="${IFS}"; IFS=':' local -a modulepath=(${MODULEPATH}) IFS=${saved_IFS} # # Test whether a given module is available. # The passed module-name can be # # - an absolute file- or link-name. # The module can be either in- or outside our hierarchy. # # - a relative file- or link-name. # The module can be either in- or outside out hierarchy. # # - specified with name and version (like gcc/5.2.0). # The module can be either in- or outside our hierarchy. # # - specified with name only (without version, like gcc). # The module can be either in- or outside our hierarchy. # # - directory in- or outsite our hierarchy (not supported by modulecmd.tcl!) # # arguments: # $1: module name or file # # possible return values: # 0: module is loadable # 1: either not a modulefile or unsused release # # Notes: # The variable 'release' in function 'subcommand_load()' will be set. # is_available() { local m=$1 local -a array # # the next command assigns the absolute modulefile path # to ${arry[0]} and - if the module is in our root - the # prefix of the module to ${array[1]}. # # The trick with the first line matching "_PREFIX" is not # 100% reliable: One of the Pmodules extensions must be # called before something like # setenv FOO_PREFIX bar # can be used. # mapfile -t array < <("${modulecmd}" "${shell}" show "$m" 2>&1 | \ awk 'NR == 2 {print substr($0, 1, length($0)-1)}; /_PREFIX |_HOME / {print $3; exit}') current_modulefile="${array[0]}" prefix="${array[1]}" test -n "${current_modulefile}" || return 1 local release='' get_release release "${current_modulefile}" "${UsedReleases}" } # # output load 'hints' # # Note: # The variable 'm' from the parent function will be used # but not changed. # # Args: # none output_load_hints() { local output='' local release='' while read -a line; do release=${line[1]} if [[ ! ":${UsedReleases}:" =~ "${release}" ]]; then output+="module use ${release}; " fi output+="module load ${line[@]:3} ${line[0]}\n" done < <(subcommand_search "${m}" -a --no-header 2>&1) if [[ -n "${output}" ]]; then std::info "\nTry with one of the following command(s):\n" std::die 3 "${output}\n" fi } load_dependencies() { local -r fname="$1" while read dep; do [[ -z ${dep} ]] && continue [[ ${dep:0:1} == \# ]] && continue module_is_loaded "${dep}" && continue local output=$( subcommand_load --internal "${dep}") echo ${output} eval ${output} done < "${fname}" } local opts=() pmodules::get_options opts \ -o fsvwi -l force -l silent -l verbose -l warn -l internal \ -- "$@" || subcommand_help_load eval set -- "${opts[@]}" local args=() opts=() local shell="${g_shell}" while (($# > 0)); do case $1 in -f | --force ) opts+=(' -f') ;; -s | --silent ) verbosity_lvl='silent' ;; -v | --verbose ) verbosity_lvl='verbose' ;; -w | --warn ) verbosity_lvl='warn' ;; -i | --internal ) shell='bash' ;; -- ) ;; * ) args+=( $1 ) ;; esac shift done if (( ${#args[@]} == 0 )); then std::die 2 "${CMD} load: No module specified\n" fi for m in "${args[@]}"; do if [[ "$m" =~ ":" ]]; then # $m is an extendet module # the format is one of # - group:name # - group:name:release # - release:name # - release:group:name # - name:release local save_ifs=${IFS} IFS=':' local -a toks=($m) IFS=${save_ifs} local group='' local release='' if is_group "${toks[0]}"; then group=${toks[0]} m=${toks[1]} release=${toks[2]} elif is_release "${toks[0]}"; then release=${toks[0]} if is_group "${toks[1]}"; then group=${toks[1]} m=${toks[2]} else m=${toks[1]} group=${toks[2]} fi else m=${toks[0]} if is_group "${toks[1]}"; then group=${toks[1]} release=${toks[2]} else release=${toks[1]} group=${toks[2]} fi fi if [[ -n ${group} ]]; then is_group "${group}" || \ std::die 3 "%s %s: illegal group name -- %s\n" \ "${CMD}" 'load' "${group}" local -i depth=${GroupDepths[${group}]} (( depth != 0 )) && \ std::die 3 "%s %s: illegal group name -- %s\n" \ "${CMD}" 'load' "${group}" MODULEPATH="" modulepath=() for root in "${!Overlays[@]}"; do MODULEPATH+="${root}/${group}/${PMODULES_MODULEFILES_DIR}:" modulepath+=( ${MODULEPATH} ) done fi if [[ -n ${release} ]]; then is_release "${release}" || \ std::die 3 "%s %s: illegal release name -- %s\n" \ "${CMD}" 'load' "${release}" std::append_path UsedReleases "${release}" fi fi local found='' for flag in "${UseFlags[@]/#/_}" ""; do if is_available "${m}${flag}"; then m+="${flag}" found=':' break fi done if [[ ! "${found}" ]]; then std::info "%s %s: module unavailable -- %s\n" \ "${CMD}" 'load' "${m}" [[ ${verbosity_lvl} == 'verbose' ]] && output_load_hints std::die 3 "" fi if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then std::die 3 "%s %s: module already loaded -- %s\n" \ "${CMD}" 'load' "${m}" fi for root in "${!Overlays[@]}"; do if [[ ${current_modulefile} =~ ${root} ]]; then # modulefile is in our hierarchy # ${prefix} was set in is_available()! test -r "${prefix}/.dependencies" && load_dependencies "$_" break fi done local -r tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!\n" local output=$("${modulecmd}" "${shell}" ${opts} load "${current_modulefile}" 2> "${tmpfile}") echo "${output}" eval "${output}" local error=$( < "${tmpfile}") # :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. if [[ "${error}" =~ ":ERROR:" ]]; then std::info "%s %s: failed -- %s\n" \ "${CMD}" 'load' "${m}" elif [[ -n ${error} ]]; then echo "${error}" 1>&2 fi if [[ ${verbosity_lvl} != silent ]] && [[ ${release} != stable ]]; then std::info "%s %s: a %s module has been loaded -- %s\n" \ "${CMD}" 'load' ${release} "${m}" fi done # fix LOADEDMODULES LOADEDMODULES="${_LMFILES_}" while read dir; do [[ "${dir: -1}" == "/" ]] || dir+="/" LOADEDMODULES="${LOADEDMODULES//${dir}}" done <<< "${MODULEPATH//:/$'\n'}" pbuild::export_env "${g_shell}" LOADEDMODULES } # # unload # 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. while (( $# > 0 )); do subcommand_generic1 unload "$1" shift done } # # swap [] # subcommand_swap() { subcommand_generic1or2 swap "$@" } # # show # subcommand_show() { while (( $# > 0 )); do subcommand_generic1 show "$1" shift done } # # Get all available modules in given modulepath. Whereby modulepath is # a colon separated list. # return list like # modulename1 release1 modulename2 release2 ... # get_available_modules() { local saved_IFS=${IFS}; IFS=':' local -a dirs=($1) IFS=${saved_IFS} local -r module="$2" local -r releases="${3:-${UsedReleases}}" local -a mods=() local release local -A dict for dir in "${dirs[@]}"; do test -d "${dir}" || continue { cd "${dir}" while read mod; do get_release release "${dir}/${mod}" "${releases}" || continue if [[ -z ${dict[${mod}]} ]]; then mods+=( "${mod}" ${release} ) dict[${mod}]=1 fi done < <(find * \( -type f -o -type l \) -not -name ".*" -ipath "${module}*") } done echo "${mods[@]}" } # # avail [-hlt] [...] # subcommand_avail() { # use this variable in the output functions local -a mods=() local dir='' # get number of columns of terminal cols=$(tput cols) output_header() { local caption=$1 let i=($cols-${#caption})/2-2 printf -- "%0.s-" $(seq 1 $i) 1>&2 printf -- " %s " "${caption}" 1>&2 printf -- "%0.s-" $(seq 1 $i) 1>&2 printf -- "\n" 1>&2 } terse_output() { output_header "$1" for (( i=0; i<${#mods[@]}; i+=2 )); do local mod=${mods[i]} local release=${mods[i+1]} case $release in stable ) out='' ;; * ) out="${release}" ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 done std::info "\n" } machine_output() { for (( i=0; i<${#mods[@]}; i+=2 )); do printf "%-20s\t%s\n" "${mods[i]}" "${mods[i+1]}" 1>&2 done } # # :FIXME: for the time being, this is the same as terse_output! long_output() { output_header "$1" for (( i=0; i<${#mods[@]}; i+=2 )); do local mod=${mods[i]} local release=${mods[i+1]} case $release in stable ) out='' ;; * ) out=${release} ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 done std::info "\n" } human_readable_output() { output_header "$1" local -i column=$cols local -i colsize=16 for ((i=0; i<${#mods[@]}; i+=2)); do if [[ ${verbosity_lvl} == 'verbose' ]]; then local release=${mods[i+1]} case ${mods[i+1]} in stable ) mod=${mods[i]} ;; * ) mod="${mods[i]}(${release:0:1})" ;; esac else mod=${mods[i]} fi local -i len=${#mod} local -i span=$(( len / 16 + 1 )) local -i colsize=$(( span * 16 )) if (( column+len >= cols )); then printf -- "\n" 1>&2 column=0 fi if (( column+colsize < cols )); then fmt="%-${colsize}s" else fmt="%-s" fi printf "${fmt}" "${mod}" 1>&2 column+=colsize done printf -- "\n\n" 1>&2 } local opts=() pmodules::get_options opts -o ahlmt \ -l all -l all-releases \ -l human -l long -l machine -l terse -- "$@" || subcommand_help_avail eval set -- "${opts[@]}" local pattern=() local output_function='human_readable_output' local opt_all_groups='no' local opt_use_releases="${UsedReleases}" while (($# > 0)); do case $1 in -a | --all ) opt_all_groups='yes' opt_use_releases="${PMODULES_DEFINED_RELEASES}" ;; --all-releases ) opt_use_releases="${PMODULES_DEFINED_RELEASES}" ;; -h | --human ) output_function='human_readable_output' ;; -l | --long ) output_function='long_output' ;; -t | --terse ) output_function='terse_output' ;; -m | --machine ) output_function='machine_output' ;; -- ) ;; * ) pattern+=( "$1" ) ;; esac shift done if [[ "${opt_all_groups}" = 'yes' ]]; then rescan_groups "${!Overlays[@]}" fi if (( ${#pattern[@]} == 0 )); then pattern+=( '' ) fi local -a modulepath local saved_IFS=${IFS} IFS=':' modulepath=(${MODULEPATH}) IFS=${saved_IFS} local overlay='' local group='' local -A modulepath_of_group local -a groups=() for dir in "${modulepath[@]}"; do group='other' find_overlay overlay group "${dir}" if [[ ${modulepath_of_group[${group}]} ]]; then modulepath_of_group[${group}]+=:${dir} else modulepath_of_group[${group}]=${dir} groups+=( "${group}" ) fi done groups+=( 'other' ) for string in "${pattern[@]}"; do for group in "${groups[@]}"; do mods=( $( get_available_modules \ "${modulepath_of_group[${group}]}" \ "${string}" \ "${opt_use_releases}" ) ) [[ ${#mods[@]} == 0 ]] && continue ${output_function} "${group}" done done } # compute depths or passed group # Note: cwd must be Pmodules root directory # $1: group # get_group_depth () { local -r group="$1" local -r dir="${group}/${PMODULES_MODULEFILES_DIR}" test -d "${dir}" || return 1 local tmp=$(find "${dir}" -depth -type f -o -type l 2>/dev/null| head -1) local -a tmp2=( ${tmp//\// } ) local depth=${#tmp2[@]} (( depth-=4 )) # if a group doesn't contain a module, the above computed depth is < 0. # We set it to 0 (even this might be wrong). # :FIXME: better solution? (( depth < 0 )) && (( depth = 0 )) GroupDepths[$group]=${depth} } # # Compute depth for all known groups # $1: root of modulefile hierarchy get_group_depths () { local -r roots=( "$@" ) local root for root in "${roots[@]}"; do { cd "${root}" local group # for some unknown reason [A-Z]* doesn't work # on (some?) SL6 systems for group in [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*; do get_group_depth "${group}" done }; done } # re-scan available groups. # # Note: # Removing groups is not supported for the time being. Be aware, that # a user might have a module loaded from this group. This cannot be checked. # # $1: root of modulefile hierarchy rescan_groups() { local -r roots="$@" local root for root in "${roots[@]}"; do { cd "${root}" # for some unknown reason [A-Z]* doesn't work with # some bash versions for group in [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*; do if [[ -z "${GroupDepths[${group}]}" ]]; then get_group_depth "${group}" fi done }; done } # # use [-a|--append|-p|--prepend] [directory|group|release...] # subcommand_use() { if (( ${#GroupDepths[@]} == 0 )); then get_group_depths "${!Overlays[@]}" fi local saved_IFS=${IFS} IFS=':' local -a modulepath=(${MODULEPATH}) IFS=${saved_IFS} local add2path_func='std::append_path' print_info() { local f local r std::info "Used groups:\n" for f in ${UsedGroups//:/ }; do std::info "\t${f}\n" done std::info "\nUnused groups:\n" local _group for _group in "${!GroupDepths[@]}"; do local -i depth=${GroupDepths[${_group}]} if ! is_used_group "${_group}" && (( depth == 0 )); then std::info "\t${_group}\n" fi done std::info "\nUsed releases:\n" for r in ${UsedReleases//:/ }; do std::info "\t${r}\n" done std::info "\nUnused releases:\n" for r in ${PMODULES_DEFINED_RELEASES//:/ }; do if ! is_used_release $r; then std::info "\t${r}\n" fi done std::info "\nUsed flags:\n" for flag in "${UseFlags//:/ }"; do std::info "\t${flag}\n" done local overlay std::info "\nUsed overlays:\n" for overlay in "${!Overlays[@]}"; do std::info "\t${overlay}\n" done std::info "\nAdditonal directories in MODULEPATH:\n" let n=0 local group for (( i=0; i<${#modulepath[@]}; i++)); do if ! find_overlay overlay group "${modulepath[i]}"; then std::info "\t${modulepath[i]}\n" let n+=1 fi done if (( n == 0 )); then std::info "\tnone\n" fi std::info "\n" } use () { declare -g UsedGroups declare -g MODULEPATH local dirs_to_add=() while (( $# > 0)); do arg="$1" # if is release # ... # elif is group # ... # elif matches modulepath root # ... # elif is directory # ... # else # error # if is_release "${arg}"; then # releases are always *appended* std::append_path UsedReleases "${arg}" elif [[ "${arg}" =~ "flag=" ]]; then std::append_path UseFlags "${arg/flag=}" elif [[ "${arg}" =~ "overlay=" ]]; then local overlay="${arg/overlay=}" [[ -d "${overlay}" ]] || \ std:die 3 "%s %s: is not a directory -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${overlay}" if [[ ! ${Overlays[${overlay}]} ]]; then Overlays[${overlay}]=1 PMODULES_OVERLAYS="${PMODULES_OVERLAYS}:${overlay}" get_group_depths "${!Overlays[@]}" for group in ${UsedGroups//:/ }; do local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" fi done fi elif [[ ! ${arg} =~ */* ]] && is_group "${arg}"; then if (( ${GroupDepths[$arg]} != 0 )); then std::die 3 "%s %s: cannot add group to module path -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${arg}" fi std::append_path UsedGroups "${arg}" for overlay in "${!Overlays[@]}"; do for group in ${UsedGroups//:/ }; do local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" fi done done elif [[ ${arg} =~ ^${PMODULES_ROOT} ]]; then std::die 3 "%s %s: illegal directory -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${arg}" elif [[ -d ${arg} ]]; then ${add2path_func} "$(cd "${arg}" && pwd)" else std::die 3 "%s %s: neither a directory, release or group -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${arg}" fi shift done pbuild::export_env ${g_shell} MODULEPATH UsedGroups } local opts=() pmodules::get_options opts -o 'ap' -l 'append' -l 'prepend' -- "$@" || subcommand_help_use eval set -- "${opts[@]}" local -a args=() while (( $# > 0)); do case "$1" in -a | --append ) add2path_func='std::append_path' ;; -p | --prepend ) add2path_func='std::prepend_path' ;; -- ) ;; * ) args+=( "$1" ) ;; esac shift done if (( ${#args[@]} == 0 )); then print_info else use "${args[@]}" fi } # # unuse directory|group|release... # subcommand_unuse() { local opts=() local saved_IFS=${IFS} IFS=':' local -a modulepath=(${MODULEPATH}) IFS=${saved_IFS} pmodules::get_options opts -o '' -- "$@" || subcommand_help_unuse eval set -- "${opts[@]}" local dirs_to_remove=() while (( $# > 0)); do if [[ "$1" == "--" ]]; then shift continue fi arg=$1 # if is release # ... # elif is group # ... # elif matches modulepath root # ... # elif is directory # ... local modulefiles_dir="${PMODULES_ROOT}/${arg}/${PMODULES_MODULEFILES_DIR}" if is_release "${arg}"; then std::remove_path UsedReleases "${arg}" elif [[ "${arg}" =~ "flag=" ]]; then std::remove_path UseFlags "${arg/flag=}" elif [[ "${arg}" =~ "overlay=" ]]; then local overlay="${arg/overlay=}" [[ -d "${overlay}" ]] || \ std::die 3 "%s %s: is not a directory -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${overlay}" [[ "${overlay}" == "${PMODULES_ROOT}" ]] && \ std::die 3 "%s %s: cannot remove root overlay -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${overlay}" if [[ ${Overlays[${overlay}]} ]]; then [[ "${_LMFILES_}" =~ "${overlay}" ]] && \ std::die 3 "%s %s: cannot remove overlay -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${overlay}" unset Overlays[${overlay}] std::remove_path PMODULES_OVERLAYS "${overlay}" for dir in "${modulepath[@]}"; do if [[ "${dir}" =~ "${overlay}" ]]; then std::remove_path MODULEPATH "${dir}" fi done fi elif [[ ! ${arg} =~ */* ]] && [[ -d ${modulefiles_dir} ]]; then if (( ${GroupDepths[$arg]} != 0 )); then std::die 3 "%s %s: cannot remove group from module path -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${arg}" fi std::remove_path UsedGroups "${arg}" dirs_to_remove+=( ${modulefiles_dir} ) elif [[ -d ${arg} ]]; then local normalized_dir=$(cd "${arg}" && pwd) dirs_to_remove+=( ${normalized_dir} ) elif [[ ${arg} =~ ^${PMODULES_ROOT} ]]; then std::die 3 "%s %s: illegal directory -- %s\n." \ "${CMD}" "${FUNCNAME[0]##*_}" "${arg}" else std::die 3 "%s %s: not a directory -- %s\n" \ "${CMD}" "${FUNCNAME[0]##*_}" "${arg}" fi shift done declare -gx UsedGroups pbuild::export_env ${g_shell} UsedGroups [[ ${#dirs_to_remove[@]} == 0 ]] && return for dir in "${dirs_to_remove[@]}"; do subcommand_generic1 unuse "${dir}" done } # # update # # :FIXME: either compile Modules with --enable-beginenv or remove the sub-command # subcommand_update() { subcommand_generic0 update "$@" } # # refresh # subcommand_refresh() { subcommand_generic0 refresh "$@" } reset_modulepath() { MODULEPATH='' local group local root for root in "${!Overlays[@]}"; do for group in ${PMODULES_DEFAULT_GROUPS}; do local dir="${root}/${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] && std::prepend_path MODULEPATH "${dir}" done done } reset_used_groups() { UsedGroups='' local group for group in ${PMODULES_DEFAULT_GROUPS}; do std::append_path UsedGroups "${group}" done } reset_used_releases() { declare -g UsedReleases='' for r in ${PMODULES_DEFAULT_RELEASES//:/ }; do std::append_path UsedReleases "${r}" done } ############################################################################## # # purge # subcommand_purge() { subcommand_generic0 purge "$@" reset_modulepath reset_used_groups pbuild::export_env ${g_shell} MODULEPATH UsedGroups } ############################################################################## # # list [-hlt] # subcommand_list() { local opts=() pmodules::get_options opts -o hlt -l human -l long -l terse -- "$@" || \ subcommand_help_list eval set -- "${opts[@]}" local opts=() while (( $# > 0 )); do case $1 in -h | --human ) opts+=( '-h' ) ;; -l | --long ) opts+=( '-l' ) ;; -t | --terse ) opts+=( '-t' ) ;; -- ) ;; * ) std::die 1 "%s %s: invalid argument -- %s" \ "${CMD}" "list" "$1" ;; esac shift done "${modulecmd}" "${g_shell}" list "${opts[@]}" } init_path() { std::replace_path PATH "${PMODULES_HOME%/*}/.*" std::prepend_path PATH "${PMODULES_HOME}/bin" } init_manpath() { std::replace_path MANPATH "${PMODULES_HOME%/*}/.*" if [[ -r /etc/man.config ]]; then declare _manconf='/etc/man.config' elif [[ -r /etc/man.conf ]]; then declare _manconf='/etc/man.conf' fi if [[ -n ${_manconf} ]]; then while read name value rest; do std::append_path MANPATH "${value}" done < <(grep "^MANPATH\s" "${_manconf}") unset _manconf else std::append_path MANPATH "${PMODULES_HOME}/share/man" std::append_path MANPATH "/usr/share/man" fi } ############################################################################## pmodules_init() { declare -gx LOADEDMODULES='' declare -gx _LMFILES_='' declare -Ag Overlays=([${PMODULES_ROOT}]="1") declare -gx PMODULES_OVERLAYS="${PMODULES_ROOT}" declare -g UsedGroups='' declare -gx MODULEPATH='' declare -Ag GroupDepths='()' declare -g UseFlags=() reset_modulepath reset_used_groups reset_used_releases init_path init_manpath pbuild::export_env "${g_shell}" \ LOADEDMODULES \ _LMFILES_ \ MODULEPATH \ PATH \ MANPATH } ############################################################################## # # clear # subcommand_clear() { local -r subcommand="${FUNCNAME##*_}" local opts=() pmodules::get_options opts -- '' "$@" || subcommand_help_${subcommand} eval set -- "${opts[@]}" while (( $# > 0 )); do case $1 in -- ) shift ;; * ) std::die 3 "%s %s: illegal argument -- %s" \ "${CMD}" "${subcommand}" "$1" ;; esac done pmodules_init pbuild::export_env ${g_shell} LOADEDMODULES MODULEPATH _LMFILES_ } ############################################################################## # # search [switches] [STRING...] # subcommand_search() { local -r subcommand="${FUNCNAME##*_}" local modules=() local with_modules='//' local src_prefix=() local opt_print_header='yes' local opt_print_modulefiles='no' local opt_print_csv='no' local opt_use_releases=':' local -r fmt="%-20s %-10s %-12s %-s\n" # no args print_header() { printf '\n' 1>&1 printf "${fmt}" "Module" "Release" "Group" "Requires" 1>&2 printf -- '-%.0s' {1..60} 1>&2 printf '\n' 1>&2 } #..................................................................... # # output result of search # Args: # $1: tmp file # # variables used from enclosing function: # opt_print_header # opt_print_modulefiles # with_modules # print_result() { local -r tmpfile=$1 [[ "${opt_print_header}" == "yes" ]] && print_header if [[ "${opt_print_modulefiles}" == "yes" ]]; then while read -a line; do # group first local out="${line[2]}/" # add directory of modulefiles out+="${PMODULES_MODULEFILES_DIR}/" for d in "${line[@]:3}"; do out+="$d/" done out+="${line[0]}" std::info "${out}\n" done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}") elif [[ "${opt_print_csv}" == "yes" ]]; then while read -a toks; do : done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}") else "${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}" 1>&2 fi } #..................................................................... # # search modules # Args: # $1: module name pattern # # Variables used from enclosing function # :FIXME: # search () { local -r module=$1 # write results to a temporary file for later processing local -r tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!\n" local group # loop over all groups for group in "${!GroupDepths[@]}"; do # loop over all directories which can be added to # MODULEPATH inside current group local depth=${GroupDepths[${group}]} local mpaths=( $(find \ "${src_prefix[@]/%//${group}/modulefiles}" \ -type d \ -mindepth ${depth} -maxdepth ${depth} \ 2>/dev/null)) local mpath local overlay local unused for mpath in "${mpaths[@]}"; do # get dependencies encoded in directory name find_overlay overlay unused "${mpath}" local p="${mpath/${overlay}}" p=( ${p//\// } ) local deps=() local -i i for ((i=2; i < ${#p[@]}; i+=2)); do deps+=( ${p[i]}/${p[i+1]} ) done local requires=${deps[@]} # get and print all available modules in $mpath # with respect to the requested releases # tmpfile: module/version release group group-dependencies... local mods=( $( get_available_modules \ "${mpath}" \ "${module}" \ "${opt_use_releases}" ) ) [[ ${#mods[@]} == 0 ]] && continue for (( i=0; i<${#mods[@]}; i+=2 )); do printf "${fmt}" ${mods[i]} "${mods[i+1]}" \ ${group} "${requires}" >> "${tmpfile}" done done done print_result "${tmpfile}" rm -f "${tmpfile}" } local opts=() pmodules::get_options opts \ -o 'ahH?' \ -l help \ -l no-header \ -l print-modulefiles \ -l release: \ -l with: \ -l all-releases \ -l src: \ -l print-csv \ -- "$@" || subcommand_help_${subcommand} eval set -- "${opts[@]}" while (( $# > 0 )); do case $1 in --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 ) is_release "$2" || \ std::die 1 "%s %s: illegal release name -- %s\n" \ "${CMD}" 'search' "$2" opt_use_releases+="$2:" shift ;; --with ) if [[ -z $2 ]] || [[ "$2" =~ "-*" ]]; then std::die 1 "%s %s: illegal value for --with option -- %s\n" \ "${CMD}" 'search' "$2" fi with_modules+=" && / ${2//\//\\/}/" shift ;; -a | --all-releases ) opt_use_releases="${PMODULES_DEFINED_RELEASES}" ;; --src ) src_prefix=$2 pmodules::check_directories "${src_prefix}" shift ;; -\? | -h | -H | --help ) usage ;; -- ) ;; * ) modules+=( "$1" ) ;; esac shift done if [[ -z "${src_prefix}" ]]; then src_prefix=( "${!Overlays[@]}" ) fi if [[ "${opt_use_releases}" == ":" ]]; then opt_use_releases=":${UsedReleases}:" fi if [[ ${#modules[@]} == 0 ]]; then modules+=( '' ) fi if (( ${#GroupDepths[@]} == 0 )) || [[ ${src_prefix} != ${PMODULES_ROOT} ]]; then get_group_depths "${src_prefix}" fi for module in "${modules[@]}"; do search "${module}" done } # # help [module|sub-command] # subcommand_help() { local opts=() pmodules::get_options opts -o HV\? -l version -l help -- "$@" || usage eval set -- "${opts[@]}" local arg='' while (( $# > 0 )); do case $1 in -[hH] | -\? | --help ) usage ;; -V | --version ) print_version std::die 1 ;; -- ) : ;; * ) [[ -z ${arg} ]] || \ std::die 1 "${CMD} help: only one argument allowed.\n" arg="$1" ;; esac shift done if [[ -z ${arg} ]]; then usage elif typeset -F subcommand_help_${arg} > /dev/null 2>&1 ; then # help for sub-command subcommand_help_${arg} else # :FIXME: print help of newest *available* module # (respecting UsedReleases) subcommand_generic1plus help "${arg}" fi } # # whatis [module] # subcommand_whatis() { if (( $# == 0 )); then subcommand_generic0 whatis else subcommand_generic1plus whatis "$@" fi } # # apropos string # subcommand_apropos() { subcommand_generic1 apropos "$@" } # # initadd module... # subcommand_initadd() { subcommand_generic1plus initadd "$@" } # # initprepend module... # subcommand_initprepend() { subcommand_generic1plus initprepend "$@" } # # initrm module... # subcommand_initrm() { subcommand_generic1plus initrm "$@" } # # initswitch module1 module2 # subcommand_initswitch() { subcommand_generic1or2 initswitch "$@" } # # initlist # subcommand_initlist() { subcommand_generic0 initlist "$@" } # # initclear # subcommand_initclear() { subcommand_generic0 initclear "$@" } case "$1" in bash | zsh ) declare g_shell="$1" ;; csh | tcsh ) declare g_shell='csh' ;; * ) std::die 1 "${CMD}: unsupported shell -- $1\n" ;; esac shift export PMODULES_OVERLAYS if [[ -n ${PMODULES_ENV} ]]; then eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)" else pmodules_init fi declare -A Subcommands=(\ [add]="subcommand_load" \ [load]="subcommand_load" \ [rm]="subcommand_unload" \ [unload]="subcommand_unload" \ [switch]="subcommand_swap" \ [swap]="subcommand_swap" \ [display]="subcommand_show" \ [show]="subcommand_show" \ [apropos]="subcommand_apropos" \ [keyword]="subcommand_apropos" \ [avail]="subcommand_avail" \ [search]="subcommand_search" \ [use]="subcommand_use" \ [unuse]="subcommand_unuse" \ [update]="subcommand_update" \ [refresh]="subcommand_refresh" \ [purge]="subcommand_purge" \ [list]="subcommand_list" \ [clear]="subcommand_clear" \ [whatis]="subcommand_whatis" \ [initadd]="subcommand_initadd" \ [initprepend]="subcommand_initprepend" \ [initrm]="subcommand_initrm" \ [initswitch]="subcommand_initswitch" \ [initlist]="subcommand_initlist" \ [initclear]="subcommand_initclear" \ [help]="subcommand_help" \ ) declare -a opts=() while (( $# > 0 )); do case $1 in -H | -\? | --help | -help ) usage ;; -V | --version ) print_version std::die 1 ;; --debug ) set -x ;; '' ) ;; -* ) opts+=( "$1" ) ;; * ) if [[ -z "${Subcommands[$1]}" ]]; then std::die 1 "${CMD}: unknown sub-command -- $1\n" fi subcommand="$1" shift break ;; esac shift done while (( $# > 0 )); do case "$1" in -- ) ;; * ) opts+=( "$1" ) ;; esac shift done if [[ -z "${subcommand}" ]]; then std::die 1 "${CMD}: no sub-command specified.\n" fi if (( ${#GroupDepths[@]} == 0 )); then get_group_depths "${!Overlays[@]}" fi ${Subcommands[$subcommand]} "${opts[@]}" # Local Variables: # mode: sh # sh-basic-offset: 8 # tab-width: 8 # End: