diff --git a/scripts/Bootstrap/Pmodules/modulecmd.bash.in b/scripts/Bootstrap/Pmodules/modulecmd.bash.in new file mode 100755 index 0000000..4637e3f --- /dev/null +++ b/scripts/Bootstrap/Pmodules/modulecmd.bash.in @@ -0,0 +1,1595 @@ +#!@PMODULES_HOME@/bin/bash --noprofile +# + +#set -o nounset +# we have to unset CDPATH, otherwise 'cd' prints the directoy! +unset CDPATH + +# used inside output text only +declare -r CMD='module' + +declare -r bindir=$(cd $(dirname "$0") && pwd) +declare -r prefix=$(dirname "${bindir}") +declare -r libdir="${prefix}/lib" + +source "${libdir}/libpmodules.bash" + +declare -r version='@PMODULES_VERSION@' +declare -r modulecmd="${bindir}/modulecmd.tcl" + +declare -rx TCL_LIBRARY="${libdir}/tcl8.6" + +# required by pre 0.99.3 modulefiles +declare -rx PSI_LIBMODULES="${TCL_LIBRARY}/libmodules.tcl" + +# :FIXME: this is not save, if a component contains spaces. +declare -ra modulepath=( ${MODULEPATH//:/ } ) + +declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} + +shopt -s nullglob +declare -a Groups='()' +declare -A HierarchyDepths='()' + +export_env() { + local s='' + if [[ "${shell}" == "bash" ]]; then + while (( $# > 0 )); do + echo -n "export $1=\"${!1}\";" + shift + done + return + elif [[ "${shell}" == "tcsh" ]]; then + while (( $# > 0 )); do + echo "setenv $1 ${!1}" + shift + done + return + fi +} + +save_env() { + local s='' + local tmp + while (( $# > 0 )); do + tmp="$( typeset -p $1 2> /dev/null)" + [[ -n "${tmp}" ]] && s+="${tmp};" + shift + done + declare -g PMODULES_ENV=$( "${PMODULES_HOME}/bin/base64" --wrap=0 <<< "$s" ) + export_env PMODULES_ENV +} + +trap 'save_env Groups HierarchyDepths UsedReleases 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 + 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 + 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 + 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 + 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 + 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 + die 1 +} + +subcommand_help_keyword() { + subcommand_help_apropos +} + + +subcommand_help_avail() { + echo " +USAGE: + module avail 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'. +" 1>&2 + 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 + 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 + die 1 +} + +subcommand_help_unuse() { + echo " +unuse directory|group|release... + Remove the given directory, group or release from the search + path. +" 1>&2 + die 1 +} +subcommand_help_update() { + echo " +USAGE: + module update + Attempt to reload all loaded modulefiles. +" 1>&2 + 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 + die 1 +} + +subcommand_help_purge() { + echo " +USAGE: + module purge + Unload all loaded modulefiles. +" 1>&2 + die 1 +} + +subcommand_help_list() { + echo " +USAGE: + module list + List loaded modules. +" 1>&2 + die 1 +} + +subcommand_help_clear() { + echo " +USAGE: + module clear + Force the Modules package to believe that no modules are + currently loaded. +" 1>&2 + 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 + 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 + 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 + die 1 +} + +subcommand_help_initrm() { + echo " +USAGE: + module initrm modulefile... + Remove modulefile(s) from the shell's initialization files. +" 1>&2 + die 1 +} + +subcommand_help_initswitch() { + echo " +USAGE: + module initswitch modulefile1 modulefile2 + Switch modulefile1 with modulefile2 in the shell's initialization files. +" 1>&2 + die 1 +} + +subcommand_help_initlist() { + echo " +USAGE: + module initlist + List all of the modulefiles loaded from the shell's initialization file. +" 1>&2 + die 1 +} + +subcommand_help_initclear() { + echo " +USAGE: + module initclear + Clear all of the modulefiles from the shell's initialization files. +" 1>&2 + die 1 +} + +# +# get release of module +# Note: +# - the release of a modulefile outside ${PMODULES_ROOT} is 'stable' +# - the release of a modulefile inside ${PMODULES_ROOT} without a +# coresponding release file is 'unstable' +# +# Args: +# $1: absolute modulefile name +# +get_release() { + local -r modulefile=$1 + + # is modulefile outside ${PMODULES_ROOT}? + if [[ ! ${modulefile} =~ ${PMODULES_ROOT} ]]; then + echo 'stable' + return 0 + fi + + # we are inside ${PMODULES_ROOT} + local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}" + if [[ -r ${releasefile} ]]; then + # read releasefile, remove empty lines, spaces etc + local -r data=$( < "${releasefile}" ) + echo ${data} + else + echo 'unstable' + fi + return 0 +} + +: ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'} + +is_release() { + [[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]] +} + +is_used_release() { + [[ ":${UsedReleases}:" =~ :$1: ]] +} + +declare used_groups=":${PMODULES_USED_GROUPS}:" + +is_used_group() { + [[ ${used_groups} =~ :$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}" == "#%Module1.0" ]] +} + +subcommand_generic0() { + local -r subcommand=$1 + shift + local opts='' + opts=$(get_options -- '' "$@") || subcommand_help_${subcommand} + eval set -- "${opts}" + while (( $# > 0 )); do + case $1 in + -- ) + shift + ;; + * ) + die 3 "${CMD} ${subcommand}: illegal argument -- $1" + ;; + esac + done + "${modulecmd}" "${shell}" "${subcommand}" +} + +subcommand_generic1() { + local -r subcommand=$1 + shift + local opts='' + opts=$(get_options -- '' "$@") || subcommand_help_${subcommand} + eval set -- "${opts}" + local args=() + while (( $# > 0 )); do + case $1 in + -- ) + ;; + * ) + if (( ${#args[@]} == 0 )); then + args+=( "$1" ) + else + die 3 "${CMD} ${subcommand}: only one argument allowed" + fi + ;; + esac + shift + done + if (( ${#args[@]} == 0 )); then + die 3 "${CMD} ${subcommand}: missing argument" + fi + "${modulecmd}" "${shell}" "${subcommand}" "${args[@]}" +} + +subcommand_generic1plus() { + local -r subcommand=$1 + shift + local opts='' + opts=$(get_options -- '' "$@") || subcommand_help_${subcommand} + eval set -- "${opts}" + local args=() + while (( $# > 0 )); do + case $1 in + -- ) + ;; + * ) + args+=( "$1" ) + ;; + esac + shift + done + if (( ${#args[@]} == 0 )); then + die 3 "${CMD} ${subcommand}: missing argument" + fi + "${modulecmd}" "${shell}" "${subcommand}" "${args[@]}" +} + +subcommand_generic1or2() { + local -r subcommand=$1 + shift + local opts='' + opts=$(get_options -- '' "$@") || subcommand_help_${subcommand} + eval set -- "${opts}" + local args=() + while (( $# > 0 )); do + case $1 in + -- ) + ;; + * ) + if (( ${#args[@]} > 2 )); then + die 3 "${CMD} ${subcommand}: only one or two arguments are allowed" + fi + args+=( "$1" ) + ;; + esac + shift + done + if (( ${#args[@]} == 0 )); then + die 3 "${CMD} ${subcommand}: missing argument" + fi + "${modulecmd}" "${shell}" "${subcommand}" "${args[@]}" +} + +# +# load [-fsvw] +# +# $1: module to load +# +subcommand_load() { + local release='undef' + local moduledir='' + local m='' + + # + # Test whether a given module can be loaded according to the + # accepted releases. + # + # Notes: + # The variable 'release' in function 'subcommand_load()' will be set. + # The release of a modulefile outsite our hierarchy is 'stable'. + # + # $1: absolute name of modulefile + # + is_loadable() { + release=$( get_release "$1" ) + [[ :${UsedReleases}: =~ ${release} ]] && return 0 + return 1 + } + + # + # Test whether a given module is available. + # Possible cases: + # - absolute file- or link-name in- or outside our hierarchy + # - relative file- or link-name in- or outside out hierarchy + # - full module name in- or outside our hierarchy + # - module name without version 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: is a loadable module + # 1: nothing found + # 2: wrong shebang + # 3: has unused release + # 4: inside our hierarchy but not loadable + # + # Notes: + # The variable 'release' in function 'subcommand_load()' will be set. + # The variable 'm' in function 'subcommand_load()' may be set. + # + is_available() { + local m=$1 + + # handle the case of an absolute or relative file- or link-name + if [[ -f ${m} ]]; then + if [[ "${m:0:1}" != "/" ]]; then + # convert to absolte path if relative + m=$(get_abspath "${m}") + fi + is_modulefile "${m}" || return 2 + is_loadable "${m}" || return 3 + if [[ "${m}" =~ "${PMODULES_ROOT}" ]]; then + for dir in "${modulepath[@]}"; do + [[ "${m}" =~ "${dir}" ]] && return 0 + done + return 4 + else + return 0 + fi + fi + + # check whether $m is in our modulepath + for dir in "${modulepath[@]}"; do + if [[ -d ${dir}/$1 ]]; then + # module specified without version, like 'hdf5' + while read fname; do + is_modulefile "${fname}" || return 2 + if is_loadable "${fname}"; then + moduledir="${dir}" + return 0 + fi + done < <(find "${dir}/$1" -mindepth 1 -maxdepth 1 -type l -o -type f \! -name ".*") + else + # module specified with name/version, like 'hdf5/1.8.14' + [[ -f ${dir}/$1 ]] || continue + [[ -r ${dir}/$1 ]] || continue + is_modulefile "${dir}/$1" || return 2 + if is_loadable "${dir}/$1"; then + moduledir="${dir}" + return 0 + fi + fi + done + return 1 + } + + # + # output load 'hints' + # + # Note: + # The variable 'm' from the parent function will be used + # but not changed. + # + # Args: + # none + output_load_hints() { + local -ra rels=( ${PMODULES_DEFINED_RELEASES//:/ } ) + for rel in "${rels[@]}"; do + eval $( subcommand_use "${rel}" ) + if is_available "${m}"; then + info "${m}: is ${rel}! If you want to load this module, run" + info "\tmodule use ${rel}" + info "before running" + info "\tmodule load ${m}" + exit 42 + fi + done + local something_found='no' + local -a output=() + local -a release=() + local -a loadable=() + local -i i=0 + local -i n=0 + while read -a line; do + output[n]="module load ${line[@]:3} ${line[0]}" + release[n]=${line[1]} + if [[ ":${UsedReleases}:" =~ "${release[n]}" ]]; then + loadable[n]='yes' + else + loadable[n]='no' + fi + n+=1 + done < <(subcommand_search "${m}" -a --no-header 2>&1) + info "${CMD} load: module unavailable -- ${m}" + if (( n > 0 )); then + info "\nBut the following modules chain(s) are available in the hierarchy:" + for ((i=n-1; i >=0; i--)); do + if [[ "${loadable[i]}" == "no" ]]; then + info "${output[i]}\t# ${release[i]}" + else + info "${output[i]}" + fi + done + fi + } + + local opts + opts=$(get_options -o fsvw -l force -l silent -l verbose -l warn -- "$@") || \ + subcommand_help_load + eval set -- "${opts}" + local args=() + opts='' + 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' + ;; + -- ) + ;; + * ) + args+=( $1 ) + ;; + esac + shift + done + if (( ${#args[@]} == 0 )); then + die 2 "${CMD} load: No module specified." + fi + for m in "${args[@]}"; do + if is_available "${m}"; then + if [[ ${verbosity_lvl} != silent ]] && [[ ${release} != stable ]]; then + info "Warning: the ${release} module '${m}' has been loaded." + fi + "${modulecmd}" "${shell}" ${opts} load "${m}" + else + if [[ ${verbosity_lvl} == 'verbose' ]]; then + output_load_hints + else + die 3 "${CMD} load: module unavailable -- ${m}" + fi + fi + done +} + +# +# unload +# +subcommand_unload() { + # :FIXME: add dependency tests: don't unload if module is required be + # another module + 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 directory. +# return list like +# modulename1 release1 modulename2 release2 ... +# +get_available_modules() { + local -r dir=$1 + local -r module=$2 + local -r use_releases=${3:-${UsedReleases}} + local -a mods=() + while read mod; do + local release=$( get_release "${dir}/${mod}" ) + + if [[ :${use_releases}: =~ :${release}: ]]; then + mods+=( "${mod}" ${release} ) + fi + done < <(MODULEPATH="${dir}" "${modulecmd}" bash -t avail "${module}" 2>&1 | tail -n +2) + 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=${dir/${PMODULES_ROOT}\/} + local caption=${caption/\/${PMODULES_MODULEFILES_DIR}/: } + local caption=${caption/: \//: } + let i=($cols-${#caption})/2-2 + printf -- "%0.s-" $(seq 1 $i) 1>&2 + printf -- " %s " "${caption}" 1>&2 + printf -- "%0.s-" $(seq 1 $i) 1>&2 + printf -- "\n" 1>&2 + } + + terse_output() { + output_header + for (( i=0; i<${#mods[@]}; i+=2 )); do + local mod=${mods[i]} + local release=${mods[i+1]} + case $release in + stable ) + out='' + ;; + * ) + out="${release}" + ;; + esac + printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 + done + info "" + } + + # + # :FIXME: for the time being, this is the same as terse_output! + long_output() { + output_header + for (( i=0; i<${#mods[@]}; i+=2 )); do + local mod=${mods[i]} + local release=${mods[i+1]} + case $release in + stable ) + out='' + ;; + * ) + out=${release} + ;; + esac + printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 + done + info "" + } + + human_readable_output() { + output_header + + local -i column=$cols + local -i colsize=16 + for ((i=0; i<${#mods[@]}; i+=2)); do + if [[ ${verbosity_lvl} == 'verbose' ]]; then + local release=${mods[i+1]} + case ${mods[i+1]} in + stable ) + mod=${mods[i]} + ;; + * ) + mod="${mods[i]}(${release:0:1})" + ;; + esac + else + mod=${mods[i]} + fi + local -i len=${#mod} + local -i span=$(( len / 16 + 1 )) + local -i colsize=$(( span * 16 )) + if (( column+len >= cols )); then + printf -- "\n" 1>&2 + column=0 + fi + if (( column+colsize < cols )); then + fmt="%-${colsize}s" + else + fmt="%-s" + fi + printf "${fmt}" "${mod}" 1>&2 + column+=colsize + done + printf -- "\n\n" 1>&2 + } + local opts='' + opts=$(get_options -o hlt -l human -l long -l terse -- "$@") || subcommand_help_avail + eval set -- "${opts}" + local pattern=() + local output_function='' + local opts='' + while (($# > 0)); do + case $1 in + -h | --human ) + [[ -z ${opts} ]] || \ + die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'." + opts=$1 + output_function='human_readable_output' + ;; + -l | --long ) + [[ -z ${opts} ]] || \ + die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'." + opts=$1 + output_function='long_output' + ;; + -t | --terse ) + [[ -z ${opts} ]] || \ + die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'." + opts=$1 + output_function='terse_output' + ;; + -- ) + ;; + * ) + pattern+=( "$1" ) + ;; + esac + shift + done + output_function=${output_function:-human_readable_output} + if (( ${#pattern[@]} == 0 )); then + pattern+=( '' ) + fi + for string in "${pattern[@]}"; do + for dir in "${modulepath[@]}"; do + mods=( $( get_available_modules "${dir}" "${string}" ) ) + [[ ${#mods[@]} == 0 ]] && continue + + ${output_function} + done + done +} + +# get available groups +# $1: root of modulefile hierarchy +# +get_groups () { + local -r root="$1" + { + cd "${root}" + # for some unknown reason [A-Z]* doesn't work on (some?) SL6 systems + for f in [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*; do + Groups+=( $f ) + done + }; +} + +# +# $1: root of modulefile hierarchy +get_hierarchy_depth () { + local -r root="$1" + { + cd "${root}" + local _group + for _group in "${Groups[@]}"; do + local tmp=$(find "${_group}/modulefiles" -depth -type f -o -type l | head -1) + local -a tmp2=( ${tmp//\// } ) + local depth=${#tmp2[@]} + let depth-=4 + HierarchyDepths[$_group]=${depth} + done + }; +} + +# +# use [-a|--append|-p|--prepend] [directory|group|release...] +# +subcommand_use() { + if (( ${#Groups[@]} == 0 )); then + get_groups "${PMODULES_ROOT}" + get_hierarchy_depth "${PMODULES_ROOT}" + fi + print_info() { + local f + local r + info "Used groups:" + for f in ${used_groups//:/ }; do + info "\t${f}" + done + info "\nUnused groups:" + local _group + for _group in "${Groups[@]}"; do + local -i depth=${HierarchyDepths[${_group}]} + if ! is_used_group "${_group}" && (( depth == 0 )); then + info "\t${_group}" + fi + done + + info "\nUsed releases:" + for r in ${UsedReleases//:/ }; do + info "\t${r}" + done + info "\nUnused releases:" + for r in ${PMODULES_DEFINED_RELEASES//:/ }; do + if ! is_used_release $r; then + info "\t${r}" + fi + done + + info "\nAdditonal directories in MODULEPATH:" + let n=0 + for (( i=0; i<${#modulepath[@]}; i++)); do + if [[ ! ${modulepath[i]} =~ ${PMODULES_ROOT} ]]; then + info "\t${modulepath[i]}" + let n+=1 + fi + done + if (( n == 0 )); then + info "\tnone" + fi + info "\n" + } + + use () { + + local dirs_to_add=() + local subcommand_switches='' + while (( $# > 0)); do + 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 [[ ${arg} == -a ]] || [[ ${arg} == --append ]]; then + subcommand_switches='--append' + elif [[ ${arg} == -p ]] || [[ ${arg} == --prepend ]]; then + subcommand_switches='' + elif is_release "${arg}"; then + # releases are always *appended* + append_path UsedReleases "${arg}" + elif [[ ! ${arg} =~ */* ]] && [[ -d ${modulefiles_dir} ]]; then + if (( ${HierarchyDepths[$arg]} != 0 )); then + die 3 "${CMD} ${0##_}: cannot add group ${arg} to module path" + fi + append_path PMODULES_USED_GROUPS "${arg}" + dirs_to_add+=( ${modulefiles_dir} ) + elif [[ ${arg} =~ ^${PMODULES_ROOT} ]]; then + die 3 "${CMD} ${0##_}: illegal directory: ${arg}" + elif [[ -d ${arg} ]]; then + local normalized_dir=$(cd "${arg}" && pwd) + dirs_to_add+=( ${normalized_dir} ) + elif [[ ${arg} =~ "-*" ]]; then + die 3 "${CMD} ${0##_}: illegal switch: ${arg}" + else + die 3 "${CMD} ${0##_}: neither a directory, release or group: ${arg}" + fi + shift + done + #echo "export PMODULES_USED_GROUPS=${PMODULES_USED_GROUPS}" + declare -g PMODULES_USED_GROUPS="${PMODULES_USED_GROUPS}" + export_env PMODULES_USED_GROUPS + [[ ${#dirs_to_add[@]} == 0 ]] && return + + for dir in "${dirs_to_add[@]}"; do + subcommand_generic1 use ${subcommand_switches} "${dir}" + done + } + + if [[ $# == 0 ]]; then + print_info + else + use "$@" + fi +} + +# +# unuse directory|group|release... +# +subcommand_unuse() { + local opts='' + opts=$(get_options -- '' "$@") || 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 + remove_path UsedReleases "${arg}" + elif [[ ! ${arg} =~ */* ]] && [[ -d ${modulefiles_dir} ]]; then + if (( ${HierarchyDepths[$arg]} != 0 )); then + die 3 "${CMD} ${0##_}: cannot remove group ${arg} from module path" + fi + remove_path PMODULES_USED_GROUPS "${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 + die 3 "${CMD} ${0##_}: illegal directory: ${arg}" + elif [[ ${arg} =~ "-*" ]]; then + die 3 "${CMD} ${0##*_}: illegal option: ${arg}" + else + die 3 "${CMD} ${0##*_}: not a directory: ${arg}" + fi + shift + done + #echo "export PMODULES_USED_GROUPS=${PMODULES_USED_GROUPS}" + declare -g PMODULES_USED_GROUPS="${PMODULES_USED_GROUPS}" + export_env PMODULES_USED_GROUPS + [[ ${#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 "$@" +} + +# +# purge +# +subcommand_purge() { + subcommand_generic0 purge "$@" +} + +# +# list [-hlt] +# +subcommand_list() { + local opts='' + opts=$(get_options -o hlt -l human -l long -l terse -- "$@") || subcommand_help_list + eval set -- "${opts}" + local opts='' + while (( $# > 0 )); do + case $1 in + -h | --human ) + [[ -z ${opts} ]] || \ + die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'." + opts='-h' + ;; + -l | --long ) + [[ -z ${opts} ]] || \ + die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'." + opts='-l' + ;; + -t | --terse ) + [[ -z ${opts} ]] || \ + die 1 "${CMD} list: you cannot set both options: '$1' and '${opts}'." + opts='-t' + ;; + -- ) + ;; + * ) + die 1 "${CMD} list: invalid argument -- $1" + ;; + esac + shift + done + "${modulecmd}" "${shell}" list "${opts}" +} + +pmodules_init() { + declare -g PMODULES_DEFAULT_GROUPS='' + declare -g PMODULES_DEFAULT_RELEASES='' + + source "${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/environment.bash" + + declare -g LOADEDMODULES='' + declare -g PMODULES_USED_GROUPS='' + declare -g MODULEPATH='' + declare -g _LMFILES_='' + + for group in ${PMODULES_DEFAULT_GROUPS}; do + append_path MODULEPATH "${PMODULES_ROOT}/${group}/${PMODULES_MODULEFILES_DIR}" + append_path PMODULES_USED_GROUPS "${group}" + done + declare -ag Groups='()' + declare -Ag HierarchyDepths='()' + declare -g UsedReleases='' + for r in ${PMODULES_DEFAULT_RELEASES//:/ }; do + append_path UsedReleases "${r}" + done + +} + +# +# clear +# +subcommand_clear() { + local opts='' + opts=$(get_options -- '' "$@") || subcommand_help_${subcommand} + eval set -- "${opts}" + while (( $# > 0 )); do + case $1 in + -- ) + shift + ;; + * ) + die 3 "${CMD} ${subcommand}: illegal argument -- $1" + ;; + esac + done + pmodules_init + export_env LOADEDMODULES PMODULES_USED_GROUPS MODULEPATH _LMFILES_ +} + +# +# search [switches] [STRING...] +# +subcommand_search() { + local modules=() + local with_modules='//' + local src_prefix='' + local _print_header='yes' + local _print_modulefiles='no' + local 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 + } + + # args: + # $1: module name pattern + search () { + local -r module=$1 + # we must write temporary results to a file for sorting + local -r tmpfile=$( mktemp /tmp/$(basename $0).XXXXXX ) \ + || die 1 "Oops: unable to create tmp file!" + local _group + # loop over all groups + for _group in "${Groups[@]}"; do + local depth=${HierarchyDepths[${_group}]} + # get all potential directories of group with module-files + local mpaths=( $(find \ + "${src_prefix}/${_group}/modulefiles" \ + -type d \ + -mindepth ${depth} -maxdepth ${depth} \ + 2>/dev/null)) + local mpath + for mpath in "${mpaths[@]}"; do + # get dependencies encoded in directory name + local p="${mpath/${src_prefix}}" + p=( ${p//\// } ) + local deps=() + local -i i + for ((i=2; i < ${#p[@]}; i+=2)); do + deps+=( ${p[i]}/${p[i+1]} ) + done + local requires=${deps[@]} + + # get and print all available modules in $mpath + # with respect to the requested releases + local mods=( $( get_available_modules \ + "${mpath}" \ + "${module}" \ + "${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 + if [[ "${_print_modulefiles}" == "no" ]]; then + sort -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}" 1>&2 + else + 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]}" + info "${out}" + done < <(sort -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | awk "${with_modules}") + fi + rm -f "${tmpfile}" + } + + opts=$(get_options -o 'ahH?' \ + -l help \ + -l no-header \ + -l print-modulefiles \ + -l release: \ + -l with: \ + -l all-releases \ + -l src: \ + -- "$@") || subcommand_help_${0##*_} + eval set -- "${opts}" + + while (( $# > 0 )); do + case $1 in + --no-header ) + _print_header='no' + ;; + --print-modulefiles ) + _print_modulefiles='yes' + _print_header='no' + ;; + --release ) + is_release "$2" || \ + die 1 "${CMD} search: illegal release name -- $2" + use_releases+="$2:" + shift + ;; + --with ) + if [[ -z $2 ]] || [[ "$2" =~ "-*" ]]; then + die 1 "${CMD} search: with what?" + fi + with_modules+=" && / ${2//\//\\/}/" + shift + ;; + -a | --all-releases ) + use_releases="${PMODULES_DEFINED_RELEASES}" + ;; + --src ) + src_prefix=$2 + check_pmodules_directories "${src_prefix}" + + shift + ;; + -\? | -h | -H | --help ) + usage + ;; + -- ) + ;; + * ) + modules+=( "$1" ) + ;; + esac + shift + done + if [[ -z "${src_prefix}" ]]; then + src_prefix="${PMODULES_ROOT}" + fi + + if [[ "${use_releases}" == ":" ]]; then + use_releases=":${UsedReleases}:" + fi + + [[ "${_print_header}" == "yes" ]] && print_header + if [[ ${#modules[@]} == 0 ]]; then + modules+=( '' ) + fi + + if (( ${#Groups[@]} == 0 )) || [[ ${src_prefix} != ${PMODULES_ROOT} ]]; then + get_groups "${src_prefix}" + get_hierarchy_depth "${src_prefix}" + fi + + for module in "${modules[@]}"; do + search "${module}" + done +} + +# +# help [module|sub-command] +# +subcommand_help() { + local opts='' + opts=$(get_options -- '' "$@") || usage + eval set -- "${opts}" + local arg='' + + while (( $# > 0 )); do + case $1 in + -- ) + : + ;; + * ) + [[ -z ${arg} ]] || \ + die 1 "${CMD} help: only one argument allowed." + 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 "$@" +} + +if [[ -n ${PMODULES_ENV} ]]; then + eval "$("${PMODULES_HOME}/bin/base64" -d <<< "${PMODULES_ENV}" 2>/dev/null)" +else + pmodules_init +fi + +case $1 in + bash ) + declare shell="$1" + ;; + tcsh ) + declare shell="$1" + ;; + * ) + die 1 "${CMD}: unsupported shell -- $1" + ;; +esac +shift + +declare -a sargs=() +declare -a opts=() +while (( $# > 0 )); do + case $1 in + -H | -\? | --help | -help ) + usage + ;; + -V | --version ) + print_version + die 1 + ;; + -f | --force | -s | --silent | -v | --verbose | -w | --warn ) + opts+=( "$1" ) + ;; + -t | --terse | -l | --long | -h | --human ) + opts+=( $1 ) + ;; + -a | --appent | -p | --prepend ) + opts+=( "$1" ) + ;; + --debug ) + set -x + ;; + -* ) + die 1 "$1: unknown switch." + ;; + add|load ) + subcommand='subcommand_load' + shift + sargs=( "$@" ) + shift $# + ;; + rm|unload ) + subcommand='subcommand_unload' + shift + sargs=( "$@" ) + shift $# + ;; + switch|swap ) + subcommand='subcommand_swap' + shift + sargs=( "$@" ) + shift $# + ;; + display|show ) + subcommand='subcommand_show' + shift + sargs=( "$@" ) + shift $# + ;; + apropos|keyword ) + subcommand='subcommand_apropos' + shift + sargs=( "$@" ) + shift $# + ;; + avail|search|use|unuse|update|refresh|purge|list|clear|whatis|help ) + subcommand=subcommand_$1 + shift + if (( $# > 0 )); then + sargs=( "$@" ) + shift $# + fi + ;; + initadd|initprepend|initrm|initswitch|initlist|initclear ) + subcommand=subcommand_$1 + shift + sargs=( "$@" ) + shift $# + ;; + * ) + die 1 "${CMD}: unknown sub-command -- $1" + ;; + esac + shift +done + +$subcommand "${sargs[@]}" "${opts[@]}" + +# Local Variables: +# mode: sh +# sh-basic-offset: 8 +# tab-width: 8 +# End: