diff --git a/Pmodules/libpmodules.bash b/Pmodules/libpmodules.bash deleted file mode 100644 index cba80a7..0000000 --- a/Pmodules/libpmodules.bash +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -if [[ -z ${sbindir} ]]; then - local sbindir=$(dirname "${BASH_SOURCE}") - sbindir=$(cd "${sbindir}"/.. && pwd)"/sbin" -fi - -pmodules::get_options() { - local "$1" - std::upvar $1 $("${sbindir}/getopt" "${@:2}") -} - -pmodules::check_env_vars() { - [[ -n "${PMODULES_ROOT}" ]] && - [[ -n "${PMODULES_HOME}" ]] && - [[ -n "${PMODULES_VERSION}" ]] || std::die 1 " -Error: the module environment you are going to use as source has not been -initialized properly!" -} - -pmodules::check_directories() { - local -r src_prefix="$1" - - [[ -d "${src_prefix}" ]] && - [[ -d "${src_prefix}/${PMODULES_CONFIG_DIR}" ]] && - [[ -d "${src_prefix}/Tools/Pmodules/${PMODULES_VERSION}" ]] || std::die 1 " -Error: the module environment '${src_prefix}' has not been initialized properly!" -} - -pmodules::check_env() { - pmodules::check_env_vars - pmodules::check_directories "${PMODULES_ROOT}" -} - - -# Local Variables: -# mode: sh -# sh-basic-offset: 8 -# tab-width: 8 -# End: diff --git a/Pmodules/libpmodules.bash.in b/Pmodules/libpmodules.bash.in new file mode 100644 index 0000000..99e09f5 --- /dev/null +++ b/Pmodules/libpmodules.bash.in @@ -0,0 +1,63 @@ +#!/bin/bash + +declare PMODULES_MODULEFILES_DIR='modulefiles' +declare PMODULES_CONFIG_DIR='config' +declare -A GroupDepths=() +declare -A Subcommands=() +declare -A Options=() +declare -A Help=() + +# initialize help text of 'module --version' +Help['version']=" +Pmodules @PMODULES_VERSION@ using Tcl Environment Modules @MODULES_VERSION@ +Copyright GNU GPL v2 +" + +# +# display help text for command given in $1 +# +print_help() { + echo -e "${Help[$1]}" 1>&2 + std::die 1 +} + +# +# compute depth of modulefile directory. +# +# Args: +# $1: absolute path of a modulefile directory +# +compute_group_depth () { + local -r dir=$1 + test -d "${dir}" || return 1 + local group=${dir%/*} + local group=${group##*/} + [[ -n "${GroupDepths[${group}]}" ]] && return 0 + local -i depth=$(${find} "${dir}" -depth \( -type f -o -type l \) \ + -printf "%d" -quit 2>/dev/null) + (( depth-=2 )) + # if a group doesn't contain a modulefile, depth is negativ + # :FIXME: better solution? + (( depth < 0 )) && (( depth = 0 )) + GroupDepths[$group]=${depth} +} + +# +# (Re-)Scan available groups in given root and compute group depth's +# +# Args: +# $1: root of modulefile hierarchy +# +scan_groups () { + local -r root="$1" + local moduledir + for moduledir in ${root}/*/${PMODULES_MODULEFILES_DIR}; do + compute_group_depth "${moduledir}" + done +} + +# Local Variables: +# mode: sh +# sh-basic-offset: 8 +# tab-width: 8 +# End: diff --git a/Pmodules/libstd.bash b/Pmodules/libstd.bash index fedd0c4..1684727 100644 --- a/Pmodules/libstd.bash +++ b/Pmodules/libstd.bash @@ -35,6 +35,17 @@ std::die() { exit $ec } +std::def_cmds(){ + local path="$1" + shift + for cmd in "$@"; do + eval declare -g ${cmd}=$(PATH="${path}" which $cmd 2>/dev/null) + if [[ -z "${!cmd}" ]]; then + std::die 255 "${cmd} not found" + fi + done +} + # # get answer to yes/no question # @@ -133,16 +144,73 @@ std::replace_path () { } # -# split file name +# Functions to split a path into its components. # -std::split_fname() { - local -r savedIFS="${IFS}" +# Args: +# $1 upvar +# $2 absolute or relative path (depends on the function) +# $3 opt upvar: number of components +# +# Notes: +# std::split_path() +# if the path is absolute, the first element of the returned array is empty. +# +# std::split_abspath() +# the path must begin with a slash, otherwise std::die() is called with +# an internal error message. +# +# std::split_relpath() +# analog to std::split_abspath() with a relative path. +# +std::split_path() { + local parts="$1" + local -r path="$2" + IFS='/' - local std__split_fname_result__=( $(echo "${@: -1}") ) - IFS=${savedIFS} - eval $1=\(\"\${std__split_fname_result__[@]}\"\) + local std__split_path_result=( ${std__split_path_tmp} ) + unset IFS + std::upvar ${parts} "${std__split_path_result[@]}" if (( $# >= 3 )); then - eval $2=${#std__split_fname_result__[@]} + # return number of parts + std::upvar "$3" ${#std__split_path_result[@]} + fi +} + +std::split_abspath() { + local parts="$1" + local -r path="$2" + if [[ "${path:0:1}" == '/' ]]; then + local -r std__split_path_tmp="${path:1}" + else + std::die 255 "Oops: Internal error in '${FUNCNAME[0]}' called by '${FUNCNAME[1]}' }" + fi + + IFS='/' + local std__split_path_result=( ${std__split_path_tmp} ) + unset IFS + std::upvar ${parts} "${std__split_path_result[@]}" + if (( $# >= 3 )); then + # return number of parts + std::upvar "$3" ${#std__split_path_result[@]} + fi +} + +std::split_relpath() { + local parts="$1" + local -r path="$2" + if [[ "${path:0:1}" == '/' ]]; then + std::die 255 "Oops: Internal error in '${FUNCNAME[0]}' called by '${FUNCNAME[1]}' }" + else + local -r std__split_path_tmp="${path}" + fi + + IFS='/' + local std__split_path_result=( ${std__split_path_tmp} ) + unset IFS + std::upvar ${parts} "${std__split_path_result[@]}" + if (( $# >= 3 )); then + # return number of parts + std::upvar "$3" ${#std__split_path_result[@]} fi } diff --git a/Pmodules/modmanage.bash.in b/Pmodules/modmanage.bash.in index 6e11be2..7748c11 100755 --- a/Pmodules/modmanage.bash.in +++ b/Pmodules/modmanage.bash.in @@ -1,228 +1,120 @@ #!@BASH@ --noprofile -# we have to unset CDPATH, otherwise 'cd' prints the directoy! -unset CDPATH +PATH='/bin:/usr/bin' +unset CDPATH # unset CDPATH, otherwise 'cd' prints the directoy! +unset IFS # use default IFS + +shopt -s nullglob +shopt -s extglob # used for some output only declare -r CMD='modmanage' -dirname=$(PATH=/bin:/usr/bin which dirname) -declare -r dirname -uname=$(PATH=/bin:/usr/bin which uname) -declare -r uname -mkdir=$(PATH=/bin:/usr/bin which mkdir) -declare -r mkdir -rsync=$(PATH=/bin:/usr/bin which rsync) -declare -r rsync -rm=$(PATH=/bin:/usr/bin /usr/bin/which rm) -declare -r rm - -if [[ $(${uname} -s) == 'Darwin' ]]; then - declare -r getopt="${libexecdir}/getopt" - declare -r find="${libexecdir}/find" -else - getopt=$(PATH=/bin:/usr/bin /usr/bin/which getopt) - declare -r getopt - find=$(PATH=/bin:/usr/bin /usr/bin/which find) - declare -r find -fi - - -declare -r mydir=$(cd $(${dirname} "$0") && pwd) -declare -r prefix=$(${dirname} "${mydir}") +declare mydir=$(cd $(dirname "$0") && pwd) +declare prefix=$(dirname "${mydir}") +declare libdir="${prefix}/lib" +declare libexecdir="${prefix}/libexec" declare -r bindir="${prefix}/bin" -declare -r libdir="${prefix}/lib" -declare -r libexecdir="${prefix}/libexec" - source "${libdir}/libstd.bash" - +source "${libdir}/libpmodules.bash" _exit () { - std::die 1 "Interrupted..." + std::die 1 "\nInterrupted..." } +trap '_exit' INT TERM _err () { - std::info "Oops: got an error in function '${FUNCNAME[1]}', line ${BASH_LINENO[0]}" + std::info "\nOops: got an error in function '${FUNCNAME[1]}', line ${BASH_LINENO[0]}" std::die 1 "Aborting ..." } - -trap '_exit' INT TERM trap '_err' ERR +path="/bin:/usr/bin:${bindir}" +[[ $(uname -s) == 'Darwin' ]] && path+=":${libexecdir}" +std::def_cmds "${path}" 'chown' 'dirname' 'mkdir' 'rsync' 'rm' 'getopt' 'find' 'modulecmd' + +unset mydir +unset prefix +unset libdir +unset libexecdir +# bindir we still need -# make sure that everything is used from this version declare PMODULES_VERSION='@PMODULES_VERSION@' ############################################################################## # -# print version of program +# help [module|sub-command] # -# Arguments: -# none -# -print_version() { - echo " -Pmodules @PMODULES_VERSION@ using Tcl Environment Modules @MODULES_VERSION@ -Copyright GNU GPL v2 -" 1>&2 -} - -############################################################################## -# -# print usage -# -# Arguments: -# none -# -usage() { - local -r prog=$(basename $0) - print_version - echo " -Usage: ${prog} [ switches ] [ subcommand ] [subcommand-args ] - -Switches: - --dry-run do nothing - --force force overwrite - -Available SubCommands and Args: - init [--src=] [--user=] - Initialize a new minimal Pmodule environment. - - install [--with=...] - Install matching modules - - sync [--delete] [--dst=] - Synchronize modules. -" -} - -declare force='no' -declare dry_run='no' -declare DRY='' -declare subcommand='' -declare sargs=() - -############################################################################## -# -# help for subcommand 'init' -# -# Arguments: -# none -# -subcommand_help_init() { - echo " -init [--src=] [--user=] [--version=] - Initialize a new minimal Pmodule environment in directory - . The parameter must only be present if - ${prog} is executed as root. -" 1>&2 -} - -############################################################################## -# -# help for subcommand 'install' -# -# Arguments: -# none -# -subcommand_help_install() { - echo " -install ... [--with=...] [--release=...] [--src=] - Install matching modules -" 1>&2 -} - -############################################################################## -# -# help for subcommand 'search' -# -# Arguments: -# none -# -subcommand_help_search() { - echo " +Subcommands[help]='help' +Options[help]='-o hHV\? -l version -l help' +Help[help]=' USAGE: - module search [switches] string... - Search available modules. If an argument is given, search - for modules whose name match the argument. + modmanage [switches] subcommand [subcommand-args]... -SWITCHES: - --no-header - Suppress output of a header. +SWITCHES: + -h|-H|-?|--help this usage info + -V|--version modules version & configuration options + --debug enable debug output + --dry-run dry run - --with=STRING - Search for modules compiled with modules matching string. The - command +SUBCOMMANDS: + + init [switches] TARGET_DIR + + install [switches] module... + + search [switches] [string|pattern]... + + help [subcommand] +' - module search --with=gcc/4.8.3 - - lists all modules in the hierarchy compiled with gcc 4.8.3. -" 1>&2 -} - -############################################################################## -# -# help for subcommand 'sync' -# -# Arguments: -# none -# -subcommand_help_sync() { - echo " -sync [--delete] [--dst=] - Synchronize environment modules and configuration files - from Pmodule environment to Pmodule environment - (default: currently active Pmodule environment). - Not yet implemented: - If --delete is given, unmarked modules present in - will be deleted. -" 1>&2 -} - -############################################################################## -# -# print usage or help text for given sub-command -# -# Arguments: -# none or sub-command -# subcommand_help() { - if [[ $# == 0 ]]; then - usage - elif typeset -F subcommand_help_$1 > /dev/null 2>&1 ; then - # help for sub-command - subcommand_help_$1 - else - usage - fi + local -r 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 + for arg in "${args[@]}"; do + if [[ -n "${Help[${arg}]}" ]] ; then + print_help "${arg}" + else + std::die 1 "Unknown sub-command -- ${subcommand}" + fi + done } ############################################################################## # -# Derive the relative module installation path -# from the relative modulefile path -# +# Derive the module installation path from the modulefile path. +# The passed modulefile must be absolute. +# # Arguments: -# $1: relative module file path +# $1: absolute module file path # get_module_prefix() { - local -a comp=( ${1//\// } ) # split rel.path into components - local path="${comp[0]}" # result path - local -i i - for ((i=1; i<${#comp[@]}-1; i+=2)); do - path+="/${comp[$((-i-1))]}/${comp[$((-i))]}" - done - echo "${path}" + "${modulecmd}" bash show "$1" 2>&1 \ + |awk '/_HOME |_PREFIX / {print $3; exit}' } ############################################################################## # -# Derive the relative module release file path -# from the relative module file path +# Derive the module release-file path from the module file-path. # # Arguments: -# $1: relative module file path +# $1: module file-path # get_releasefile_name() { echo "$(${dirname} "$1")/.release-$(basename "$1")" @@ -235,115 +127,74 @@ get_releasefile_name() { # - sync modulefile # - sync release file # -# Note: -# We do not take care of files in $PMODULES_ROOT/$PMODULES_TEMPLATES_DIR. If -# the modulefile is a sym-link it is expected that the target exists. -# # Arguments: # $1: relative modulefile path (something like: Tools/gnuplot/5.0.0) # $2: source prefix of Pmodule environment # $3: target prefix of Pmodule environment # sync_module() { - local -r rel_modulefile=$1 - local -r src_prefix=$2 - local -r target_prefix=$3 + local -r rel_modulefile="$1" + local -r src_root="$2" + local -r target_root="$3" - local -r rel_module_prefix=$( get_module_prefix "${rel_modulefile}" ) - local -r rel_releasefile=$( get_releasefile_name "${rel_modulefile}" ) + local -r src_prefix=$( get_module_prefix "${src_root}/${rel_modulefile}" ) + local -r rel_prefix=${src_prefix#${src_root}/} + local -r target_prefix="${target_root}/${rel_prefix}" # install/update module - if [[ ! -d "${target_prefix}/${rel_module_prefix}" ]] || [[ "${force}" == 'yes' ]]; then - $DRY ${mkdir} -p "${target_prefix}/${rel_module_prefix}" || return $? - $DRY ${rsync} --links --perms --recursive --delete \ - "${src_prefix}/${rel_module_prefix}/" \ - "${target_prefix}/${rel_module_prefix}/" || exit $? - fi - local -r src_modulefile="${src_prefix}/${rel_modulefile}" - local -r src_releasefile="${src_prefix}/${rel_releasefile}" - local -r target_modulefile="${target_prefix}/${rel_modulefile}" - local -r target_releasefile="${target_prefix}/${rel_releasefile}" - - # create target directory for module- and release-file - if [[ -e "${src_modulefile}" ]] || [[ -e "${src_releasefile}" ]]; then - local dir=$( ${dirname} "${target_modulefile}" ) - $DRY ${mkdir} -p "${dir}" || return $? + if [[ ! -d "${target_prefix}" ]] || [[ "${force}" == 'yes' ]]; then + ${mkdir} -p "${target_prefix}" || exit $? + ${rsync} --links --perms --recursive --delete \ + "${src_prefix}/" \ + "${target_prefix}/" || exit $? fi - # copy modulefile template - local -a rel_modulefile_splitted - std::split_fname rel_modulefile_splitted "${rel_modulefile}" - local -r module_group="${rel_modulefile_splitted[0]}" - local -r module_name="${rel_modulefile_splitted[-2]}" + # create modulefile direcrory and install modulefile + local -r src_modulefile="${src_root}/${rel_modulefile}" + local -r target_modulefile="${target_root}/${rel_modulefile}" + + ${mkdir} -p "$(${dirname} "${target_modulefile}")" || exit $? - local -r template="${module_group}/${PMODULES_TEMPLATES_DIR}/${module_name}/" - local -r src_template="${src_prefix}/${template}" - local -r target_template="${target_prefix}/${template}" - if [[ -e "${src_template}" ]]; then - $DRY ${mkdir} -p "${target_template}" - $DRY ${rsync} --links --perms --recursive \ - "${src_template}" "${target_template}" || exit $? - fi - - # copy modulefile if [[ -e "${src_modulefile}" ]]; then - $DRY ${rsync} --links --perms --recursive \ - "${src_modulefile}" "${target_modulefile}" || exit $? + ${rsync} --links --perms --recursive \ + "${src_modulefile}" "${target_modulefile}" || exit $? fi - # copy release-file + # install release-file + local -r rel_releasefile=$( get_releasefile_name "${rel_modulefile}" ) + local -r src_releasefile="${src_root}/${rel_releasefile}" + local -r target_releasefile="${target_root}/${rel_releasefile}" + if [[ -e "${src_releasefile}" ]]; then - $DRY ${rsync} --links --perms --recursive \ - "${src_releasefile}" "${target_releasefile}" || exit $? + ${rsync} --links --perms --recursive \ + "${src_releasefile}" "${target_releasefile}" || exit $? fi } -############################################################################## -# -# Sync the Pmodules configuration and templates -# -# Arguments: -# $1: source prefix of Pmodule environment -# $2: target prefix of Pmodule environment -# -sync_config() { - src="$1/${PMODULES_CONFIG_DIR}/" - dst="$2/${PMODULES_CONFIG_DIR}/" - $DRY ${rsync} --recursive --links --perms --delete \ - "${src}" "${dst}" 2>/dev/null || return $? - echo -} - -############################################################################## -# -# Delete a module -# -# Arguments: -# $1: relative modulefile path -# $2: target prefix of Pmodule environment -# -delete_module() { - echo "Not implemented yet!" -} - ############################################################################## # # initialize a new module environment # -# Arguments: -# [--src ] -# Module environment we are going to sync from. If not -# specified, the module environment this script is in -# will be used. -# [--user ] -# If this scripts runs with root privileges, a user name -# ore ID must be specified. -# [--version ] -# Set PMODULES_VERSION to -# TARGET_DIR -# Initialize a new module environment in this directory- -# +# +# +Subcommands[init]='init' +Options[init]='-o \?hfy -l src: -l user: -l help -l force -l yes' +Help[init]=" +USAGE: + modmanage init [switches] TARGET_DIR + Initialize a new minimal Pmodule environment in TARGET_DIR. + A user must be specified with '--user=' if the + programm is executed as root. + +SWITCHES: + --user + If this scripts runs with root privileges, a user name + ore ID must be specified. + --force|--yes|-f|-y + re-initialise an already existing Pmodule environment. +" + subcommand_init() { check_env() { [[ -n "${PMODULES_ROOT}" ]] && @@ -351,65 +202,66 @@ subcommand_init() { [[ -n "${PMODULES_HOME}" ]] && [[ -n "${PMODULES_VERSION}" ]] || \ std::die 1 " -Error: the module environment you are going to use as so urce has not been +Error: the module environment you are going to use as source has not been initialized properly!" - - [[ -d "${src_prefix}/${PMODULES_CONFIG_DIR}" ]] && - [[ -d "${src_prefix}/Tools/Pmodules/${PMODULES_VERSION}" ]] || \ - std::die 1 " -Error: the module environment '${src_prefix}' has not been initialized properly!" } - - local src='' - local target_prefixes=() + + #..................................................................... + # + # Sync the Pmodules configuration and templates + # + # Arguments: + # $1: source prefix of Pmodule environment + # $2: target prefix of Pmodule environment + # + sync_config() { + src="$1/${PMODULES_CONFIG_DIR}/" + dst="$2/${PMODULES_CONFIG_DIR}/" + ${rsync} --links --perms \ + "${src}"/profile.{bash,csh,zsh} "${dst}" \ + || return $? + ${rsync} --links --perms \ + "${src}"/profile.{bash,csh,zsh}-"${PMODULES_VERSION}" "${dst}" \ + || return $? + ${rsync} --links --perms \ + "${src}/Pmodules.conf" "${dst}" \ + || return $? + ${rsync} --links --perms \ + "${src}/modbuild.conf" "${dst}" \ + || return $? + } + + local target_root=() local user='' - local opts='' - opts=$(${getopt} -o h -l src: -l user: -l help -l version: -- "$@") - if [[ $? != 0 ]]; then - subcommand_help_init - exit 1 - fi - eval set -- "${opts}" while (($# > 0)); do case $1 in - --src ) - src=$2 - shift + -h | -H | -\? | --help | -help ) + print_help "${subcommand}" ;; - --user ) - user=$2 - shift + --force | --yes | -f | -y ) + force='yes' ;; - --version ) - PMODULES_VERSION=$2 - shift + --user | --user=* ) + if [[ "$1" == '--user' ]]; then + user="$2" + shift + else + user="${1#--*=}" + fi ;; -- ) : ;; - -* | -h | --help ) - echo "$1: illegal option" 1>&2 - subcommand_help_init - exit 1 - ;; * ) - target_prefixes+=( "$1" ) + # assign and remove trailing slashes + target_root="${1%%*([\/])}" ;; esac shift done - (( ${#target_prefixes[@]} != 0 )) || \ + if [[ -z ${target_root} ]]; then std::die 1 "Error: no target directory specified!" - - # if source directory is not passed as argument, derive it from script name - if [[ -z "${src}" ]]; then - src=$(cd "${bindir}/../../../.." && pwd) fi - [[ -d "${src}" ]] || \ - std::die 1 "Error: ${src}: source directory does not exist!" - [[ -r "${src}/config/profile.bash" ]] || \ - std::die 1 "Error: ${src}: shell profile does not exist or is not readable!" - source "${src}/config/profile.bash" local -i euid=$(id -u) if (( euid == 0 )); then @@ -422,219 +274,216 @@ Error: the module environment '${src_prefix}' has not been initialized properly! std::die 1 "Error: --user option is only allowed if running as root!" fi + local src_root="$(std::get_abspath "${bindir}/../../../..")" + local config_file="${src_root}/${PMODULES_CONFIG_DIR}/profile.bash" + if [[ -r "${config_file}" ]]; then + source "${config_file}" + fi check_env || \ std::die 1 "Giving up..." - echo " -Attempting to create a minimal module environment from the -environment at '${PMODULES_ROOT}' -" + echo "Creating a minimal Pmodule environment from the environment at" + echo " ${PMODULES_ROOT}" + echo "in" + echo " ${target_root}" + if [[ -d "${target_root}" ]] && [[ ${force} == no ]]; then + echo "Warning: ${target_root} already exists." + std::get_YN_answer \ + "Do you really want to re-run the initialization? (y/N) " \ + || std::die 1 "Abort ..." + fi + force='yes' + echo "Creating target directory '${target_root}'..." + ${mkdir} -p "${target_root}" || \ + std::die 1 "Error: make directory failed!" - #..................................................................... - # initialize new module environment in given directory - # - # Arguments: - # $1 target directory - # - init_pmodules_environment() { - local -r src_prefix="${PMODULES_ROOT}" - local -r target_prefix=$1 - local src='' - local dst='' - echo "Initializing target directory '${target_prefix}' ..." - echo - if [[ -d "${target_prefix}" ]] && [[ ${force} == no ]]; then - echo "Warning: ${target_prefix} already exists." - std::get_YN_answer "Do you really want to re-run the initialization? (y/N) " || \ - std::die 1 "Abort ..." - fi - force='yes' - echo "Creating target directory '${target_prefix}'..." - $DRY ${mkdir} -p "${target_prefix}" || \ - std::die 1 "Error: make directory failed!" - echo + echo "Syncing configuration ..." + sync_config "${src_root}" \ + "${target_root}" || \ + std::die 1 "Error: configuration synchronization failed!" + + echo "Syncing Pmodules ${PMODULES_VERSION} from '${src_root}' to '${target_root}'..." + sync_module "Tools/${PMODULES_MODULEFILES_DIR}/Pmodules/${PMODULES_VERSION}" \ + "${src_root}" \ + "${target_root}" || \ + std::die 1 "Error: sync Pmodules failed!" - echo "Syncing configuration ..." - sync_config "${src_prefix}" \ - "${target_prefix}" || \ - std::die 1 "Error: configuration synchronization failed!" - - echo "Syncing Pmodules ${PMODULES_VERSION} from '${src_prefix}' to '${target_prefix}'..." - sync_module "Tools/${PMODULES_MODULEFILES_DIR}/Pmodules/${PMODULES_VERSION}" \ - "${src_prefix}" \ - "${target_prefix}" || \ - std::die 1 "Error: sync Pmodules failed!" - ${mkdir} -p "${target_prefix}/Tools/${PMODULES_MODULEFILES_DIR}" - echo - - if [[ -n "${user}" ]]; then - echo "Changing user of new module environment to '${user}'..." - $DRY chown -R "${user}" "${target_prefix}" || \ - std::die 1 "Error: changing owner failed!" - echo - fi - echo "New minimal module environment created at '${target_prefix}'." - echo "To use this environment, execute" - echo " sudo ln -fs ${target_prefix} /opt/psi" - echo " source /opt/psi/${PMODULES_CONFIG_DIR}/profile.bash" - } - - umask 022 - for target_prefix in "${target_prefixes[@]}"; do - init_pmodules_environment "${target_prefix}" + for d in "${src_root}"/*/${PMODULES_MODULEFILES_DIR}; do + ${mkdir} -p "${target_root}/${d#${src_root}/}" done + + if [[ -n "${user}" ]]; then + echo "Changing user of new module environment to '${user}'..." + ${chown} -R "${user}" "${target_root}" || \ + std::die 1 "Error: changing owner failed!" + echo + fi + echo "SourceRoot=${src_root}" > "${target_root}/${PMODULES_CONFIG_DIR}/modmanage.conf" + echo "New minimal module environment created at '${target_root}'." + echo "To use this environment, execute" + echo " sudo ln -fs ${target_root} /opt/psi" + echo " source /opt/psi/${PMODULES_CONFIG_DIR}/profile.bash" } -declare -a Groups=() -declare -A GroupDepths - -############################################################################## -# -# Get available module groups. Found groups are added to the global array -# 'Groups'. -# -# Arguments: -# $1: root of module environment -# $2: relative directory with module files -# -get_groups () { - local -r root="$1" - local -r modulefiles_dir="$2" - { - cd "${root}" - # for some unknown reason [A-Z]* doesn't work on (some?) SL6 systems - for f in [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*; do - [[ -d ${f}/${modulefiles_dir} ]] || continue - Groups+=( $f ) - done - }; -} - -############################################################################## -# -# Compute hierarchy depth of all groups. Stores result in global array -# 'GroupDepths'. The group depth is defined as the hierarchy depth times 2. -# -# Examples: -# group depth of 'Programming' is 0 -# group depth of 'Compiler' is 2 -# group depth of 'MPI' is 4 -# -# Arguments: -# $1: root of module environment -# $2: relative directory with module files -# -get_group_depths () { - local -r root="$1" - local -r modulefiles_dir="$2" - local -ir off=4 - { - cd "${root}" - local group - for group in "${Groups[@]}"; do - local fname=$(${find} "${group}/${modulefiles_dir}" \ - -depth \( -type f -o -type l \) -print -quit) - [[ -n ${fname} ]] || continue - local -a tmp - std::split_fname tmp "${fname}" - (( GroupDepths[$group]=${#tmp[@]}-${off} )) - done - }; -} ############################################################################## # # sub-command 'install' # # Arguments: -# [--dry-run] -# Dry run -# -# [--force] | -f ] -# Install module even it already exists -# -# [--release ] -# Set release of module to -# -# [--src ] -# Install from module environment in -# -# [--with ] -# Install module(s) in this sub-group only -# -# -# Install modules matching given pattern -# +Subcommands[install]='install' +Options[install]='-o hf -l force -l with: -l help -l src: -l target:' +Help[install]=' +USAGE: + modmanage install [switches] ... + Install modules + +SWITCHES: + --force] | -f + Install module even it already exists + + --src + Install from module environment in + + --with + Install module(s) in this sub-group only + + + Install modules matching given pattern +' + subcommand_install() { + local -r subcommand='install' local opts='' local -a with=() - local -a releases=() local -a module_pattern=() - local src_prefix="${PMODULES_INSTALL_SOURCE}" - local -r target_prefix="${PMODULES_ROOT}" + local src_root="${PMODULES_INSTALL_SOURCE}" + local target_root="${PMODULES_ROOT}" local modulefile='' local -A modules_to_install local -A dependencies_to_install - local -A map_to_family - local -a initial_modulepath=() + local -A group_map + local -a modulepath=() + #...................................................................... + # + set_initial_modulepath() { + local group + for group in "${!GroupDepths[@]}"; do + (( ${GroupDepths[${group}]} == 0 )) || continue + modulepath+=( "${src_root}/${group}/${PMODULES_MODULEFILES_DIR}" ) + done + } + + #...................................................................... + # + create_group_map() { + # + # For the dependency resolution we need to know, whether a + # module - if loaded - adds a hierarchical group to MODULEPATH + # or not. + # + # Examples: + # Loading a compiler adds the hierarchical group for + # this compiler. The command + # module load gcc/10.3.0 + # prepends + # /Compiler/modulefiles/gcc/10.3.0 + # to MODULEPATH. + # + # The dependency files do not convey the information whether + # loading a module extends MODULEPATH or not. What we need to + # know is + # 1) does loading a specific module extends MODULEPATH? + # 2) if yes: what is the hierarchical group? + # + # Example: + # If we know that loading 'gcc/10.3.0' adds a directory + # in the hierarchical group 'Compiler' to MODULEPATH, we + # know that this directory is + # /Compiler/modulefiles/gcc/10.3.0 + # + # This information we store in the dictionary 'group_map'. + # For concinience reasons we store the string 'src_root/group'. + # So, 'group_map' maps + # module/version -> src_root/group + # + # Example: + # group_map[gcc/10.3.0]="${src_root}/Compiler" + # + local group='' + for group in "${!GroupDepths[@]}"; do + (( ${GroupDepths[${group}]} > 0 )) || continue + local fname='' + while read fname; do + local -a parts=() + std::split_relpath parts "${fname}" + if (( ${#parts[@]} - 2 != ${GroupDepths[${group}]} )); then + std::warn "error in source group ${group}:" + std::warn "modulefile: ${fname}" + continue + fi + if [[ ${#parts[@]} < 4 ]]; then + echo "${group} ${parts[@]}" + fi + local key="${parts[-4]}/${parts[-3]}" + [[ -z "${group_map[${key}]}" ]] || continue + group_map[${key}]="${src_root}/${group}" + done < <(${find} -L "${src_root}/${group}/${PMODULES_MODULEFILES_DIR}" \ + \( -type l -o -type f \) \ + \! -name ".*" \ + -printf "%P\n" \ + ) + done + } + #...................................................................... # # Resolve dependencies to given module # # Arguments: - # $1 modulefile relativ to src prefix. Something like: - # MPI/modulefiles/gcc/4.9.2/openmpi/1.8.4/hdf5/1.8.14 + # $1 absolute module file name # # Notes: - # The variables - # initial_modulepath - # modules_to_install - # map_to_family - # from the calling function are used! + # Following variables from the enclosing function are used: + # modulepath + # group_map (read-only) # - resolve_dependencies_of_module () { - local -r modulefile=$1 - local -a modulepath=( "${initial_modulepath[@]}" ) + resolve_dependencies () { + local -r modulefile="$1" - # compute filename with dependencies of given module - local -i i=0 n=0 - std::split_fname items n "${modulefile}" - local prefix="${src_prefix}/${items[0]}" - for (( i = n-2; i >= 2; i-=2 )); do - prefix+="/${items[$i]}/${items[i+1]}" - done - local tmpfile=$(mktemp /tmp/Pmodules_XXXXXX) - - local fname_dependencies="${prefix}/.dependencies" - [[ -r "${prefix}/.dependencies" ]] && cat "$_" > "${tmpfile}" - [[ -r "${prefix}/.install_dependencies" ]] && cat "$_" >> "${tmpfile}" + local -- prefix=$(get_module_prefix "${modulefile}") + local -a rdeps=() + local -- rdeps_file="${prefix}/.dependencies" + local -a ideps=() + local -- ideps_file="${prefix}/.install_dependencies" + + if [[ -r "${rdeps_file}" ]]; then + mapfile -t rdeps < <(grep -v '^ *#' "${rdeps_file}" ) + fi + if [[ -r "${ideps_file}" ]]; then + mapfile -t ideps < <(grep -v '^ *#' "${ideps_file}" ) + fi # loop over all dependecies local dep - while read dep; do - # skip empty lines - # :FIXME: skip comments?! - [[ -z ${dep} ]] && continue - - # search for module with current modulepath and remember + for dep in "${rdeps[@]}" "${ideps}"; do + [[ -n ${dep} ]] || continue + # search module with current modulepath local modulename=$(${find} "${modulepath[@]}" -path "*/${dep}" \ - 2>/dev/null | head -n 1 ) + -print -quit 2>/dev/null) [[ -n ${modulename} ]] || \ - std::die 3 "Oops: required module '${dep}' not found!" - modulename=${modulename/${src_prefix}\/} - dependencies_to_install[${modulename}]='.' - resolve_dependencies_of_module "${modulename}" - # append new node in hierarchy to modulepath - if [[ -n ${map_to_family[${dep}]} ]]; then - local path="${src_prefix}/${map_to_family[${dep}]}/" - path+="${PMODULES_MODULEFILES_DIR}/" - path+="${modulename/*\/${PMODULES_MODULEFILES_DIR}\/}" - modulepath+=( "${path}" ) - fi - done < "${tmpfile}" - ${rm} "${tmpfile}" + std::die 3 "Oops: required module '${dep}' not found!" + + local rel_modulename="${modulename#${src_root}/}" + dependencies_to_install[${rel_modulename}]='.' + resolve_dependencies "${modulename}" + [[ -v group_map[${dep}] ]] || continue + local dir="${group_map[${dep}]}" # = ${src_root}/ + dir+="/${rel_modulename##+([!/])/}" # += rel.name with group removed + modulepath+=( "${dir}" ) + done } #...................................................................... @@ -646,30 +495,33 @@ subcommand_install() { # none # # Notes: - # The following variables of the enclosing function are used: + # Following variables from the enclosing function are used: + # target_root (read-only) # modules_to_install (read-only) - # target_prefix (read-only) # dependencies_to_install (read-only) - - print_modules_to_install() { + # + print_modules() { local modulefile - std::info "The following modules will be installed/updated:" - for modulefile in "${!modules_to_install[@]}"; do - if [[ -e "${target_prefix}/${modulefile}" ]]; then - std::info " Updating: ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" - else - std::info " Installing: ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" + for modulefile in "$@"; do + local -a parts + std::split_relpath parts "${modulefile}" + local s='' + if (( ${#parts[@]} >= 6 )); then + s="(${parts[2]}/${parts[3]}" + for ((i = 4; i < ${#parts[@]}-2; i+=2)); do + s+=" ${parts[i]}/${parts[i+1]}" + done + s+=')' fi + std::info "%-20s %s" "${parts[-2]}/${parts[-1]}" "$s" done 2>&1 | sort + } + print_modules_to_install() { + std::info "The following modules will be installed/updated:" + print_modules "${!modules_to_install[@]}" if (( ${#dependencies_to_install[@]} > 0 )); then std::info "\nThe following dependencies will be installed/updated:" - for modulefile in "${!dependencies_to_install[@]}"; do - if [[ -e "${target_prefix}/${modulefile}" ]]; then - std::info " Updating: ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" - else - std::info " Installing: ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" - fi - done 2>&1 | sort + print_modules "${!dependencies_to_install[@]}" fi std::info "" std::get_YN_answer "Do you want to continue? [n] " || \ @@ -677,44 +529,41 @@ subcommand_install() { std::info "" } - opts=$(${getopt} -o hf -l dry-run -l force -l with: -l release: -l help -l src: -- "$@") - if [[ $? != 0 ]]; then - subcommand_help_install - exit 1 - fi - eval set -- "${opts}" while (($# > 0)); do case $1 in - --dry-run ) - DRY='echo' + -h | -H | -\? | --help | -help ) + print_help "${subcommand}" ;; --force | -f ) force='yes' ;; - --release ) - releases+=( "$2" ) - shift + --src | --src=*) + if [[ $1 == --src ]]; then + src_root="$2" + shift + else + src_root="${1#--*=}" + fi ;; - --src ) - src_prefix="$2" - shift + --target | --target=*) + if [[ $1 == --target ]]; then + target_root="$2" + shift + else + target_root="${1#--*=}" + fi ;; - --with ) - with+=( "$2" ) - shift + --with | --with=* ) + if [[ "$1" == --with ]]; then + with+=( "$2" ) + shift + else + with+=( "${1#--*=}" ) + fi ;; -- ) : ;; - -h | --help ) - subcommand_help_install - exit 1 - ;; - -* ) - echo "$1: illegal option" 1>&2 - subcommand_help_init - exit 1 - ;; * ) module_pattern+=( "$1" ) ;; @@ -722,177 +571,174 @@ subcommand_install() { shift done - [[ -n ${src_prefix} ]] \ + if [[ -z ${src_root} ]]; then + local conf_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/modmanage.conf" + if [[ -r ${conf_file} ]]; then + source "${conf_file}" + src_root="${SourceRoot}" + fi + fi + [[ -n ${src_root} ]] \ || std::die 3 "Oops: no installation source given." - [[ -d ${src_prefix} ]] \ - || std::die 3 "Oops: '${src_prefix}' is not a valid installation source." + [[ -d ${src_root} ]] \ + || std::die 3 "Oops: '${src_root}' is not a valid installation source." - # scan available groups and their depth - get_groups "${src_prefix}" "${PMODULES_MODULEFILES_DIR}" - get_group_depths "${src_prefix}" "${PMODULES_MODULEFILES_DIR}" + source "${src_root}/${PMODULES_CONFIG_DIR}/profile.bash" + scan_groups "${src_root}" + set_initial_modulepath + create_group_map - # set initial modulepath - local group - for group in "${!GroupDepths[@]}"; do - if (( ${GroupDepths[${group}]} == 0 )); then - initial_modulepath+=( "${src_prefix}/${group}/${PMODULES_MODULEFILES_DIR}" ) - fi - done - - # - # create a mapping from module name to their family. - # Examples: - # gcc/5.2.0 -> Compiler - # openmpi/1.8.4 -> MPI - local _fname='' - while read _fname; do - local _family="${_fname%/${PMODULES_MODULEFILES_DIR}/*}" - local -a items - std::split_fname items "${_fname#*/${PMODULES_MODULEFILES_DIR}/}" - local -i n=${#items[*]} - # We are only interested in families adding something to - # the modulepath. - if (( n >= 4 )); then - local _key=$( IFS='/'; echo "${items[*]:$n-4:2}" ) - map_to_family[$_key]=${_family} - fi - done < <({ cd "${src_prefix}" && \ - ${find} */"${PMODULES_MODULEFILES_DIR}" \ - \( -type l -o -type f \) \! -name ".*"; } 2>/dev/null ) - - # # search for to be installed modules and their dependencies - # - local -i n=0 while read modulefile; do - resolve_dependencies_of_module "${modulefile}" - modules_to_install["${modulefile}"]+='.' - let n+=1 - done < <(${PMODULES_HOME}/bin/modulecmd bash search \ + modules_to_install["${modulefile#${src_root}/}"]+='.' + resolve_dependencies "${modulefile}" + done < <("${modulecmd}" bash search \ "${module_pattern[@]}" \ "${with[@]/#/--with=}" \ - "${releases[@]/#/--release=}" \ + -a --glob \ --no-header --print-modulefiles \ - --src="${src_prefix}" 2>&1 1>/dev/null) - (( n == 0 )) && \ + --src="${src_root}" 2>&1 1>/dev/null) + (( ${#modules_to_install[@]} == 0 )) && \ std::die 0 "No matching modules found ..." print_modules_to_install - # install ... + # install/update ... for modulefile in "${!modules_to_install[@]}" "${!dependencies_to_install[@]}"; do - if [[ -e "${target_prefix}/${modulefile}" ]]; then - std::info " Updating: ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" - else - std::info " Installing: ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" - fi - sync_module "${modulefile}" \ - "${src_prefix}" \ - "${target_prefix}" + std::split_relpath parts "${modulefile}" + std::info " ${parts[-2]}/${parts[-1]}" + sync_module "${modulefile}" "${src_root}" "${target_root}" done std::info "\nDone!\n" -} +} # subcommand_install -############################################################################### +############################################################################## # -# delete specified module(s) -# -subcommand_delete() { - : -} +# sub-command 'search' +# +Subcommands[search]='search' +Options[search]='-o \?h -l with: -l help -l all-dep -l wrap -l glob -l src:' +Help[install]=' +USAGE: + modmanage search [switches] ... + search modules -############################################################################### -# -# remove modules which have been removed in our source -# -subcommand_cleanup() { - : -} +SWITCHES: + --src + Search modules in environment . + Default is the source defined in modmanage.conf. + + --with + Search module(s) in this sub-group. + + + Search modules matching given string. + + + Search modules matching given shell glob-pattern. +' -############################################################################### -# -# search modules in source -# :FIXME: this is still crap -# subcommand_search() { - local src_prefix="${PMODULES_INSTALL_SOURCE}" - local -r target_prefix="${PMODULES_ROOT}" - local -A modules_found - - print_modules_found() { - std::info "The following modules are available:" - for modulefile in "${!modules_found[@]}"; do - std::info " ${modulefile/\/${PMODULES_MODULEFILES_DIR}\//: }" - done 2>&1 | sort - } - - [[ -n ${src_prefix} ]] \ + local -a args=() + while (($# > 0)); do + case $1 in + -h | -H | -\? | --help | -help ) + print_help "${subcommand}" + ;; + --src | --src=*) + if [[ $1 == --src ]]; then + src_root="$2" + shift + else + src_root="${1#--*=}" + fi + ;; + --with | --with=* ) + if [[ "$1" == --with ]]; then + args+=( '--with' "$2" ) + shift + else + args+=( "$1" ) + fi + ;; + --all-deps | --glob | --wrap ) + args+=( "$1" ) + ;; + -- ) + : + ;; + * ) + args+=( "$1" ) + ;; + esac + shift + done + if [[ -z ${src_root} ]]; then + local conf_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/modmanage.conf" + if [[ -r ${conf_file} ]]; then + source "${conf_file}" + src_root="${SourceRoot}" + fi + fi + [[ -n ${src_root} ]] \ || std::die 3 "Oops: no installation source given." - [[ -d ${src_prefix} ]] \ - || std::die 3 "Oops: '${src_prefix}' is not a valid installation source." - - # scan available groups and their depth - get_groups "${src_prefix}" "${PMODULES_MODULEFILES_DIR}" - get_group_depths "${src_prefix}" "${PMODULES_MODULEFILES_DIR}" - - local -i n=0 - while read modulefile; do - modules_found["${modulefile}"]+='.' - let n+=1 - done < <(${PMODULES_HOME}/bin/modulecmd bash search \ - -a \ - --no-header --print-modulefiles \ - --src="${src_prefix}" \ - "$@" \ - 2>&1 1>/dev/null) - (( n == 0 )) && \ - std::die 0 "No matching modules found ..." - print_modules_found + [[ -d ${src_root} ]] \ + || std::die 3 "Oops: '${src_root}' is not a valid installation source." + ${modulecmd} bash search --src="${src_root}" --all-release-stages \ + "${args[@]}" 2>&1 1>/dev/null } +declare force='no' +declare subcommand='' +declare -a opts=() while (($# > 0)); do case $1 in -h | -H | -\? | --help | -help ) - usage - exit 1 + print_help 'help' ;; -V | --version ) - print_version - exit 1 - ;; - -f | --force ) - force='yes' + print_help 'version' ;; --debug ) set -x ;; --dry-run ) - dry_run='yes' - DRY='echo' + chown="echo ${chown}" + mkdir="echo ${mkdir}" + rsync="echo ${rsync}" ;; -* ) - echo "$1: unknown switch.\n" 1>&2 - exit 1 - ;; - init|install|sync|search|help ) - subcommand="subcommand_$1" - shift - sargs=( $* ) - shift $# + opts+=( "$1" ) ;; * ) - echo "$1: unknown sub-command" 1>&2 - exit 1 + subcommand="$1" + shift + break + ;; esac shift || : done -if [[ -z ${subcommand} ]]; then - usage - exit 1 +if [[ -z "${subcommand}" ]]; then + std::die 1 "${CMD}: no sub-command specified.\n" + print_help 'help' fi -[[ "${subcommand}" != "subcommand_init" ]] && [[ -z "${PMODULES_ROOT}" ]] && \ + +if [[ -z "${Subcommands[${subcommand}]}" ]]; then + std::die 1 "${CMD}: unknown sub-command -- ${subcommand}\n" +fi + +if [[ "${subcommand}" != "init" ]] && [[ -z "${PMODULES_ROOT}" ]]; then std::die 1 "Error: No current module environment configured!" -$subcommand "${sargs[@]}" +fi + +tmp=$("${getopt}" --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \ + || print_help "${subcommand}" +eval args=( "$tmp" ) +unset tmp + +umask 022 + +subcommand_${Subcommands[$subcommand]} "${args[@]}" # Local Variables: # mode: sh diff --git a/Pmodules/modmanage.in b/Pmodules/modmanage.in index 10508ad..6925f9f 100644 --- a/Pmodules/modmanage.in +++ b/Pmodules/modmanage.in @@ -2,4 +2,10 @@ unset BASH_ENV -"@BASH@" --noprofile --norc "@MODMANAGE@ "$@" +declare mydir=$(cd $(dirname "$0") && pwd) +declare libexecdir="$(dirname "${mydir}")/libexec" + +declare bash="${libexecdir}/bash" +declare modmanage="${libexecdir}/modmanage.bash" + +"${bash}" --noprofile --norc "${modmanage}" "$@" diff --git a/Pmodules/modulecmd.bash.in b/Pmodules/modulecmd.bash.in index a32b78b..035e4d9 100644 --- a/Pmodules/modulecmd.bash.in +++ b/Pmodules/modulecmd.bash.in @@ -1,9 +1,12 @@ #!@BASH@ --noprofile # -#set -o nounset -# we have to unset CDPATH, otherwise 'cd' prints the directoy! -unset CDPATH +PATH='/bin:/usr/bin' + +unset CDPATH # unset CDPATH, otherwise 'cd' prints the directoy! +unset IFS # use default IFS + +shopt -s nullglob # used in some output messages only declare -r CMD='module' @@ -13,30 +16,14 @@ declare prefix=$(dirname "${mydir}") declare -r libdir="${prefix}/lib" declare -r libexecdir="${prefix}/libexec" -base64=$(PATH=/bin:/usr/bin /usr/bin/which base64) -declare -r base64 -mktemp=$(PATH=/bin:/usr/bin /usr/bin/which mktemp) -declare -r mktemp -sort=$(PATH=/bin:/usr/bin /usr/bin/which sort) -declare -r sort -awk=$(PATH=/bin:/usr/bin /usr/bin/which awk) -declare -r awk -rm=$(PATH=/bin:/usr/bin /usr/bin/which rm) -declare -r rm -logger=$(PATH=/bin:/usr/bin /usr/bin/which logger) -declare -r logger - -if [[ $(uname -s) == 'Darwin' ]]; then - declare -r getopt="${libexecdir}/getopt" - declare -r find="${libexecdir}/find" -else - getopt=$(PATH=/bin:/usr/bin /usr/bin/which getopt) - declare -r getopt - find=$(PATH=/bin:/usr/bin /usr/bin/which find) - declare -r find -fi - source "${libdir}/libstd.bash" +source "${libdir}/libpmodules.bash" + +path="/bin:/usr/bin" +[[ $(uname -s) == 'Darwin' ]] && path+=":${libexecdir}" +std::def_cmds "${path}" \ + 'awk' 'base64' 'find' 'getopt' 'logger' 'mktemp' \ + 'rm' 'sort' 'find' if [[ ${PMODULES_PURETCL} == yes ]]; then declare -r modulecmd="${libexecdir}/modulecmd.tcl" @@ -51,21 +38,11 @@ declare -r ol_replacing='r' declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} -# use default IFS -unset IFS - -shopt -s nullglob - -declare -A GroupDepths='()' -declare -A Dir2OverlayMap='()' declare Shell='' -declare -A Subcommands -declare -A Options -declare -A Help declare -r pmodules_config_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/Pmodules.conf" -# the following settings are used if the Pmodules.conf is not available +# the following settings are used if the Pmodules.conf doesn't exist # set groups which should be available after initialization declare -- DefaultGroups='Tools Programming' @@ -76,22 +53,6 @@ declare -- ReleaseStages=':unstable:stable:deprecated:' # set releases which should be available after initialization declare -- DefaultReleaseStages='stable' -# In the dictionary Help we store the help text of each single command -# and for displaying the version. - -# initialize help text of 'module --version' -Help['version']=" -Pmodules @PMODULES_VERSION@ using Tcl Environment Modules @MODULES_VERSION@ -Copyright GNU GPL v2 -" - -# -# display help text for command given in $1 -# -print_help() { - echo -e "${Help[$1]}" 1>&2 - std::die 1 -} export_env() { case "${Shell}" in @@ -299,11 +260,13 @@ subcommand_generic0() { local -a args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) - : + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -325,11 +288,13 @@ subcommand_generic1() { local -a args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) - : + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -355,11 +320,13 @@ subcommand_generic1plus() { local args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) - : + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -383,7 +350,7 @@ subcommand_generic1plus() { # Subcommands[add]='load' Subcommands[load]='load' -Options['load']='-l help -o Hfsvwi -l force -l silent -l verbose -l warn -l internal' +Options[load]='-l help -o \?Hfsvw -l force -l silent -l verbose -l warn' Help[load]=' USAGE: module add modulefile... @@ -546,7 +513,7 @@ subcommand_load() { local overlay while (($# > 0)); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -f | --force ) @@ -562,6 +529,9 @@ subcommand_load() { verbosity_lvl='warn' ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( $1 ) @@ -750,7 +720,7 @@ subcommand_load() { # Subcommands[rm]='unload' Subcommands[unload]='unload' -Options[unload]='-o H -l help' +Options[unload]='-o \?H -l help' Help[unload]=" USAGE: module rm modulefile... @@ -769,10 +739,13 @@ subcommand_unload() { local args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -820,7 +793,7 @@ subcommand_unload() { # Subcommands[switch]='swap' Subcommands[swap]='swap' -Options[swap]='-o H -l help' +Options[swap]='-o \?H -l help' Help[swap]=" USAGE: module switch [modulefile1] modulefile2 @@ -835,10 +808,13 @@ subcommand_swap() { local args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -872,7 +848,7 @@ subcommand_swap() { # Subcommands[display]='show' Subcommands[show]='show' -Options[show]='-o H -l help' +Options[show]='-o \?H -l help' Help[show]=' USAGE: module display modulefile... @@ -889,10 +865,13 @@ subcommand_show() { local args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -1083,7 +1062,8 @@ find_module() { # avail [-hlt] [...] # Subcommands[avail]='avail' -Options[avail]='-l help -o Hahlmt -l all -l all-release-stages -l human -l long -l machine -l terse' +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 @@ -1106,8 +1086,12 @@ SWITCHES: -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 " @@ -1199,6 +1183,8 @@ subcommand_avail() { (( n > max_length )) && (( max_length=n )) available_modules+=("${mod}") done + IFS=$'\n' available_modules=($(sort --version-sort <<<"${available_modules[*]}")) + unset IFS 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 @@ -1225,7 +1211,7 @@ subcommand_avail() { local val='' while (($# > 0)); do case $1 in - -H | --help | -\? ) + -\? | -H | --help ) print_help "${subcommand}" ;; -a | --all | --all-release-stages ) @@ -1245,14 +1231,17 @@ subcommand_avail() { ;; -g | --group | --group=* ) if [[ $1 == --group=* ]]; then - val="${1/--group=}" + val="${1#--*=}" else val="$2" shift fi opt_groups[${val}]=1 ;; - -- | '' ) + -- ) + shift 1 + pattern+=( "$@" ) + break ;; * ) pattern+=( "$1" ) @@ -1288,7 +1277,7 @@ subcommand_avail() { typeset -n path=modulepath_${group} get_available_modules \ mods \ - "${string}" \ + "${string}*" \ "${opt_use_rel_stages}" \ "${path[@]}" @@ -1303,7 +1292,7 @@ subcommand_avail() { # use [-a|--append|-p|--prepend] [directory|group|release_stage...] # Subcommands[use]='use' -Options[use]='-l help -o Hap -l append -l prepend' +Options[use]='-l help -o \?Hap -l append -l prepend' Help[use]=" USAGE: module use [-a|--append|-p|--prepend] [directory|group|release_stage|...] @@ -1566,7 +1555,7 @@ subcommand_use() { local -a args=() while (( $# > 0)); do case "$1" in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -a | --append ) @@ -1576,6 +1565,9 @@ subcommand_use() { add2path_func='std::prepend_path' ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -1600,7 +1592,7 @@ subcommand_use() { # unuse directory|group|release_stage|... # Subcommands[unuse]='unuse' -Options[unuse]='-o H -l help' +Options[unuse]='-o \?H -l help' Help[unuse]=' unuse directory|group|release... Remove the given modulefiles directory, group, release stage, @@ -1748,10 +1740,13 @@ subcommand_unuse() { local -a args=() while (( $# > 0)); do case "$1" in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -1780,7 +1775,7 @@ subcommand_unuse() { # sub-command # Subcommands[update]='update' -Options[update]='-o H -l help' +Options[update]='-o \?H -l help' Help[update]=' USAGE: module update @@ -1796,7 +1791,7 @@ subcommand_update() { # refresh # Subcommands[refresh]='refresh' -Options[refresh]='-o H -l help' +Options[refresh]='-o \?H -l help' Help[refresh]=' USAGE: module refresh @@ -1902,7 +1897,7 @@ pmodules_init() { # purge # Subcommands[purge]='purge' -Options[purge]='-o H -l help' +Options[purge]='-o \?H -l help' Help[purge]=' USAGE: module purge @@ -1921,10 +1916,13 @@ subcommand_purge() { local -a args=() while (( $# > 0)); do case "$1" in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -1993,7 +1991,7 @@ subcommand_purge() { # list [-hlt] # Subcommands[list]='list' -Options[list]='-l help -o Hhlt -l human -l long -l terse' +Options[list]='-l help -o \?Hhlt -l human -l long -l terse' Help[list]=' USAGE: module list @@ -2006,7 +2004,7 @@ subcommand_list() { local args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -h | --human ) @@ -2019,6 +2017,9 @@ subcommand_list() { opts+=( '-t' ) ;; -- ) + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -2040,7 +2041,7 @@ subcommand_list() { # clear # Subcommands[clear]='clear' -Options[clear]='-o H -l help' +Options[clear]='-o \?H -l help' Help[clear]=' USAGE: module clear @@ -2053,11 +2054,13 @@ subcommand_clear() { local -a args=() while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; -- ) - : + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -2079,13 +2082,14 @@ subcommand_clear() { # search [switches] [STRING...] # Subcommands[search]='search' -Options[search]='-o aH -l help -l no-header -l print-modulefiles ' +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 all-deps -l wrap ' +Options[search]+='-l glob' Help[search]=' USAGE: - module search [switches] string... + module search [switches] STRING... Search installed modules. If an argument is given, search for modules whose name match the argument. @@ -2096,6 +2100,9 @@ SWITCHES: --all-deps Show all dependecies + --glob + Interpret STRING as shell pattern. + --no-header Suppress output of a header. @@ -2133,6 +2140,7 @@ subcommand_search() { local opt_use_releases=':' local opt_all_deps='no' local opt_wrap='no' + local opt_glob='no' #..................................................................... # @@ -2215,7 +2223,7 @@ subcommand_search() { print_modulefiles() { fmt='' func_print_header='print_header_none' - func_print_line='print_header_none' + func_print_line='print_line_modulefile' } print_header_none() { @@ -2223,16 +2231,7 @@ subcommand_search() { } print_line_modulefile() { - local line=( "$@" ) - # 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}" + std::info "$4" } print_line_csv() { @@ -2280,7 +2279,12 @@ subcommand_search() { # :FIXME: # search () { - local -r module=$1 + if [[ ${opt_glob} == 'yes' ]]; then + local -r module="$1" + else + local -r module="${1}*" + fi + # write results to a temporary file for later processing local group # loop over all groups @@ -2343,7 +2347,7 @@ subcommand_search() { while (( $# > 0 )); do case $1 in - -H | --help ) + -\? | -H | --help ) print_help "${subcommand}" ;; --all-deps ) @@ -2393,13 +2397,29 @@ subcommand_search() { with_modules+=" && / ${module//\//\\/}/" done ;; - -a | --all-releases ) + -a | --all-releases-stages ) opt_use_rel_stages+="${ReleaseStages}" ;; - --src ) - # :FIXME: do we have to add some sanity checks here? - src_prefix=$2 - shift + --src | --src=*) + if [[ "$1" == --src ]]; then + local src_prefix="$2" + shift + else + local src_prefix="${1/--src=}" + fi + if [[ ! -e "${src_prefix}" ]]; then + std::die 1 "%s %s: %s -- %s" \ + "${CMD}" 'search' \ + "illegal value for --src option" \ + "${src_prefix} does not exist" + fi + if [[ ! -d "${src_prefix}" ]]; then + std::die 1 "%s %s: %s -- %s" \ + "${CMD}" 'search' \ + "illegal value for --src option" \ + "${src_prefix} is not a directory" + fi + src_prefix=$(std::get_abspath "${src_prefix}") ;; -v | --verbose ) opt_print_verbose='yes' @@ -2407,7 +2427,13 @@ subcommand_search() { --wrap ) opt_wrap='yes' ;; + --glob ) + opt_glob='yes' + ;; -- ) + shift 1 + modules+=( "$@" ) + break ;; * ) modules+=( "$1" ) @@ -2431,6 +2457,7 @@ subcommand_search() { if (( ${#GroupDepths[@]} == 0 )) || \ [[ ${src_prefix} != ${PMODULES_ROOT} ]]; then scan_groups "${src_prefix}" + g_env_must_be_saved='yes' fi local module @@ -2452,6 +2479,7 @@ USAGE: SWITCHES: -h|-H|-?|--help this usage info -V|--version modules version & configuration options + --debug enable debug output SUBCOMMANDS: + add|load [switches] modulefile [modulefile ...] @@ -2482,14 +2510,16 @@ subcommand_help() { local -a args=() while (( $# > 0 )); do case $1 in - -[hH] | --help ) + -\? | -h | -H | --help ) print_help "${subcommand}" ;; -V | --version ) print_help 'version' ;; -- ) - : + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -2517,7 +2547,7 @@ subcommand_help() { # whatis # Subcommands[whatis]='whatis' -Options[whatis]='-o H -l help' +Options[whatis]='-o \?H -l help' Help[whatis]=' USAGE: module whatis [modulefile...] @@ -2540,7 +2570,7 @@ subcommand_whatis() { # Subcommands[apropos]='apropos' Subcommands[keyword]='apropos' -Options[apropos]='-o H -l help' +Options[apropos]='-o \?H -l help' Help[apropos]=' USAGE: module apropos string @@ -2558,7 +2588,7 @@ subcommand_apropos() { # initadd module... # Subcommands[initadd]='initadd' -Options[initadd]='-o H -l help' +Options[initadd]='-o \?H -l help' Help[initadd]=" USAGE: module initadd modulefile... @@ -2594,7 +2624,7 @@ subcommand_initadd() { # initprepend module... # Subcommands[initprepend]='initprepend' -Options[initprepend]='-o H -l help' +Options[initprepend]='-o \?H -l help' Help[initprepend]=" USAGE: module initprepend modulefile... @@ -2611,7 +2641,7 @@ subcommand_initprepend() { # initrm module... # Subcommands[initrm]='initrm' -Options[initrm]='-o H -l help' +Options[initrm]='-o \?H -l help' Help[initrm]=" USAGE: module initrm modulefile... @@ -2627,7 +2657,7 @@ subcommand_initrm() { # initswitch module1 module2 # Subcommands[initswitch]='initswitch' -Options[initswitch]='-o H -l help' +Options[initswitch]='-o \?H -l help' Help[initswitch]=" USAGE: module initswitch modulefile1 modulefile2 @@ -2640,11 +2670,13 @@ subcommand_initswitch() { local args=() while (( $# > 0 )); do case $1 in - -h | --help ) + -\? | --help ) print_help "${subcommand}" ;; -- ) - : + shift 1 + args+=( "$@" ) + break ;; * ) args+=( "$1" ) @@ -2665,7 +2697,7 @@ subcommand_initswitch() { # initlist # Subcommands[initlist]='initlist' -Options[initlist]='-o H -l help' +Options[initlist]='-o \?H -l help' Help[initlist]=" USAGE: module initlist @@ -2682,7 +2714,7 @@ subcommand_initlist() { # initclear # Subcommands[initclear]='initclear' -Options[initclear]='-o H -l help' +Options[initclear]='-o \?H -l help' Help[initclear]=" USAGE: module initclear @@ -2714,7 +2746,7 @@ shift declare -a opts=() while (( $# > 0 )); do case $1 in - -H | -\? | --help | -help ) + -\? | -H | --help | -help ) print_help 'help' ;; -V | --version ) @@ -2723,7 +2755,7 @@ while (( $# > 0 )); do --debug ) set -x ;; - '' | -- ) + '' ) ;; -* ) opts+=( "$1" ) @@ -2830,11 +2862,12 @@ case ${subcommand} in ;; esac -declare options -options=$( "${getopt}" ${Options[${subcommand}]} -- -- "${opts[@]}" "$@" ) \ + +tmp=$("${getopt}" --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \ || print_help "${subcommand}" -eval set -- ${options} -subcommand_${Subcommands[$subcommand]} "$@" +eval args=( "$tmp" ) +unset tmp +subcommand_${Subcommands[$subcommand]} "${args[@]}" # Local Variables: # mode: sh diff --git a/build b/build index 755daf8..a006925 100755 --- a/build +++ b/build @@ -487,6 +487,9 @@ pmodules::install() { sed "${sed_cmd}" "${SRC_DIR}/modulecmd.tcl.in" > "${PMODULES_HOME}/libexec/modulecmd.tcl" chmod 0755 "${PMODULES_HOME}/libexec/modulecmd.tcl" + sed "${sed_cmd}" "${SRC_DIR}/libpmodules.bash.in" > "${PMODULES_HOME}/lib/libpmodules.bash" + chmod 0755 "${PMODULES_HOME}/lib/libpmodules.bash" + sed "${sed_cmd}" "${SRC_DIR}/modbuild.in" > "${PMODULES_HOME}/bin/modbuild" chmod 0755 "${PMODULES_HOME}/bin/modbuild" @@ -503,7 +506,6 @@ pmodules::install() { install -m 0644 "${SRC_DIR}/csh" "${PMODULES_HOME}/init" install -m 0644 "${SRC_DIR}/zsh" "${PMODULES_HOME}/init" - install -m 0644 "${SRC_DIR}/libpmodules.bash" "${PMODULES_HOME}/lib" install -m 0644 "${SRC_DIR}/libpbuild.bash" "${PMODULES_HOME}/lib" install -m 0644 "${SRC_DIR}/libpbuild_dyn.bash" "${PMODULES_HOME}/lib" install -m 0644 "${SRC_DIR}/libstd.bash" "${PMODULES_HOME}/lib"