diff --git a/Pmodules/Pmodules.conf b/Pmodules/Pmodules.conf new file mode 100644 index 0000000..fffe8a3 --- /dev/null +++ b/Pmodules/Pmodules.conf @@ -0,0 +1,8 @@ +# set groups which should be available after initialization +DefaultGroups='Tools Programming' + +# define available release stages +ReleaseStages=':unstable:stable:deprecated:' + +# set releases which should be available after initialization +DefaultReleaseStages='stable' diff --git a/Pmodules/bash b/Pmodules/bash index c7e9bf5..5afa1be 100644 --- a/Pmodules/bash +++ b/Pmodules/bash @@ -35,6 +35,11 @@ unset MODULE_VERSION_STACK unset MODULESHOME unset PMODULES_ENV +declare -x PMODULES_MODULEFILES_DIR='modulefiles' +declare -x PMODULES_CONFIG_DIR='config' +declare -x PMODULES_DIR="${PMODULES_HOME}" + + ############################################################################# # implement module comand as shell function # diff --git a/Pmodules/bash_completion b/Pmodules/bash_completion index 76c789a..ed47153 100644 --- a/Pmodules/bash_completion +++ b/Pmodules/bash_completion @@ -39,9 +39,9 @@ _module() { COMPREPLY=() - cmds="add apropos avail clear dependencies display help\ + cmds="add apropos avail clear display help\ initadd initclear initlist initprepend initrm initswitch\ - keyword list load purge refresh rm search show swap switch sync\ + keyword list load purge refresh rm search show swap switch \ unload unuse update use whatis" opts="-c -f -h -i -l -s -t -u -v -H -V\ diff --git a/Pmodules/csh b/Pmodules/csh index 854152a..5fb9926 100644 --- a/Pmodules/csh +++ b/Pmodules/csh @@ -35,7 +35,7 @@ unset prefix unset postfix setenv MODULEPATH -foreach group ( ${PMODULES_DEFAULT_GROUPS} ) +foreach group ( ${DefaultGroups} ) if ( "${MODULEPATH}" == "" ) then setenv MODULEPATH "${PMODULES_ROOT}/${group}/${PMODULES_MODULEFILES_DIR}" else diff --git a/Pmodules/libmodules.tcl b/Pmodules/libmodules.tcl index 3a830d3..b287f88 100644 --- a/Pmodules/libmodules.tcl +++ b/Pmodules/libmodules.tcl @@ -3,7 +3,7 @@ # switch/swap # unload modules if parent removed # -package require base64 + if {[info exists env(PMODULES_DEBUG)] && $env(PMODULES_DEBUG)} { proc debug {msg} { @@ -21,17 +21,33 @@ if {[info exists env(PMODULES_DEBUG)] && $env(PMODULES_DEBUG)} { proc debug {msg} {} } -puts stderr "loading libmodules" debug "loading libmodules" +package require base64 + +set ::MODULEFILES_DIR "modulefiles" +set ::ol_replacing "r" + proc _pmodules_parse_pmodules_env { } { + debug "enter" foreach line [split [base64::decode $::env(PMODULES_ENV)] "\n"] { - if { ![regexp -- {.* -[aA] (.*)=\((.*)\)} $line -> name value] } { + if { ![regexp -- {.* -[aAx]* (.*)=\((.*)\)} $line -> name value] } { continue } switch $name { - Overlays { - array set ::Overlays [regsub -all {[]=[]} $value " "] + OverlayDict { + array set ::OverlayDict [regsub -all {[]=[]} $value " "] + } + OverlayList { + array set tmp [regsub -all {[]=[]} $value " "] + set ::OverlayList {} + set l [lsort [array names tmp]] + foreach k $l { + lappend ::OverlayList $tmp($k) + } + } + UsedGroups { + set ::UsedGroups $value } } } @@ -53,33 +69,31 @@ proc module-addgroup { group } { set ::${group} $name set ::${group}_version $version - debug "mode=[module-info mode]" if { [module-info mode load] } { - array set overlayed_groups {} - foreach overlay [lreverse_n $::PmodulesOverlays 1] { + set overlays_to_add {} + foreach overlay $::OverlayList { + lappend overlays_to_add $overlay + debug $overlay + if { [string compare $::OverlayDict($overlay) $::ol_replacing] == 0 } { + break + } + } + foreach overlay [lreverse_n $overlays_to_add 1] { debug "overlay=$overlay" - if {![info exists overlayed_groups($group)]} { - set dir [file join \ - $overlay \ - $group \ - $::PmodulesModulfilesDir \ - {*}$::variant] - if { [file isdirectory $dir] } { - prepend-path MODULEPATH $dir - } + debug "group=$group" + debug "::variant=$::variant" + set dir [file join \ + $overlay \ + $group \ + $::MODULEFILES_DIR \ + {*}$::variant] + if { [file isdirectory $dir] } { + debug "prepend $dir to MODULEPATH " + prepend-path MODULEPATH $dir } - # don't add if overlayed - if { [string compare $::Overlays($overlay) "g"] == 0 } { - # get groups in this overlay - set dirs [glob -directory $overlay -type d {[A-Z]*}] - foreach dir $dirs { - set overlayed_groups([lindex [file split $dir] end]) 1 - } - } - } + } + debug "end foreach" prepend-path UsedGroups $group - debug "mode=load: new MODULEPATH=$env(MODULEPATH)" - debug "mode=load: new UsedGroups=$env(UsedGroups)" } elseif { [module-info mode remove] } { set GROUP [string toupper $group] debug "mode=remove: hierarchical group '${GROUP}'" @@ -100,12 +114,13 @@ proc module-addgroup { group } { debug "mode=remove: no orphan modules to unload" } debug "mode=remove: $env(MODULEPATH)" - foreach overlay $::PmodulesOverlays { + foreach overlay $::OverlayList { set dir [file join \ $overlay \ $group \ - $::PmodulesModulfilesDir \ + $::MODULEFILES_DIR \ {*}$::variant] + debug "remove $dir" remove-path MODULEPATH $dir } remove-path UsedGroups $group @@ -137,7 +152,6 @@ proc lreverse_n { list n } { set res } -debug "test" # # set standard environment variables # @@ -292,15 +306,15 @@ proc ModulesHelp { } { # intialize global vars # Modulefile is something like # -# ${PMODULES_ROOT}/group/${PMODULES_MODULEFILES_DIR}/name/version +# ${PMODULES_ROOT}/group/modulefiles/name/version # or -# ${PMODULES_ROOT}/group/${PMODULES_MODULEFILES_DIR}/X1/Y1/name/version +# ${PMODULES_ROOT}/group/modulefiles/X1/Y1/name/version # or -# ${PMODULES_ROOT}/group/${PMODULES_MODULEFILES_DIR}/X1/Y1//X2/Y2/name/version +# ${PMODULES_ROOT}/group/modulefiles/X1/Y1//X2/Y2/name/version # proc _find_overlay { modulefile_components } { - debug "_is_in_overlay" - foreach overlay $::PmodulesOverlays { + debug "_find_overlay()" + foreach overlay $::OverlayList { debug "$overlay" if { [string range $overlay end end] == "/" } { set overlay [string range $overlay 0 end-1] @@ -315,6 +329,7 @@ proc _find_overlay { modulefile_components } { return $overlay_components } } + debug "not found" return {} } @@ -333,8 +348,6 @@ proc _pmodules_init_global_vars { } { global variant global PREFIX # prefix of package - set ::PmodulesOverlays [split $::env(PMODULES_OVERLAYS) ':'] - set ::PmodulesModulfilesDir $::env(PMODULES_MODULEFILES_DIR) set modulefile_components [file split $::ModulesCurrentModulefile] set overlay_components [_find_overlay ${modulefile_components}] @@ -361,6 +374,7 @@ proc _pmodules_init_global_vars { } { debug "group of module $name: $group" } + if { [info exists ::whatis] } { module-whatis "$whatis" } diff --git a/Pmodules/libpbuild.bash b/Pmodules/libpbuild.bash index 526d9c3..20732b8 100644 --- a/Pmodules/libpbuild.bash +++ b/Pmodules/libpbuild.bash @@ -90,7 +90,7 @@ pbuild::version_lt() { pbuild::version_le() { pbuild::version_compare "$1" "$2" local -i exit_code=$? - (( exit_code == 0 || exit_code = 2 )) + (( exit_code == 0 || exit_code == 2 )) } @@ -98,7 +98,7 @@ pbuild::version_gt() { pbuild::version_compare "$1" "$2" (( $? == 1 )) local -i exit_code=$? - (( exit_code == 0 || exit_code = 1 )) + (( exit_code == 0 || exit_code == 1 )) } pbuild::version_eq() { @@ -764,12 +764,12 @@ pbuild::make_all() { fi modulefile_dir=$(join_by '/' \ - "${overlay}/${GROUP}/${PMODULES_MODULEFILES_DIR}" \ + "${overlay_dir}/${GROUP}/${PMODULES_MODULEFILES_DIR}" \ "${names[@]}" \ "${module_name}") modulefile_name="${modulefile_dir}/${module_version}" - PREFIX="${overlay}/${GROUP}/${module_name}/${module_version}" + PREFIX="${overlay_dir}/${GROUP}/${module_name}/${module_version}" local -i i=0 for ((i=${#names[@]}-1; i >= 0; i--)); do PREFIX+="/${names[i]}" @@ -1303,7 +1303,7 @@ pbuild.build_module() { build_dependency() { local -r m=$1 std::debug "${m}: module not available" - local rels=( ${PMODULES_DEFINED_RELEASES//:/ } ) + local rels=( ${ReleaseStages//:/ } ) [[ ${dry_run} == yes ]] && \ std::die 1 \ "%s " \ @@ -1479,7 +1479,7 @@ pbuild.bootstrap() { MODULECMD=$(which true) GROUP='Tools' - PREFIX="${overlay}/${GROUP}/Pmodules/${PMODULES_VERSION}" + PREFIX="${overlay_dir}/${GROUP}/Pmodules/${PMODULES_VERSION}" C_INCLUDE_PATH="${PREFIX}/include" CPLUS_INCLUDE_PATH="${PREFIX}/include" diff --git a/Pmodules/libstd.bash b/Pmodules/libstd.bash index eb8525d..fedd0c4 100644 --- a/Pmodules/libstd.bash +++ b/Pmodules/libstd.bash @@ -220,8 +220,8 @@ std.get_os_release_linux() { fi case "${ID}" in - RedHatEnterpriseServer | RedHatEnterprise | Scientific | rhel | centos | fedora ) - echo "rhel${VERSION_ID%.*}" + RedHatEnterpriseServer | RedHatEnterprise | Scientific | rhel | centos | CentOS | fedora ) + echo "rhel${VERSION_ID%%.*}" ;; Ubuntu ) echo "Ubuntu${VERSION_ID%.*}" @@ -244,6 +244,33 @@ std::get_os_release() { ${func_map[${OS}]} } +std::get_type() { + local -a signature=$(typeset -p "$1") + case ${signature[1]} in + -Ai* ) + echo 'int dict' + ;; + -A* ) + echo 'dict' + ;; + -ai* ) + echo 'int array' + ;; + -a* ) + echo 'array' + ;; + -i* ) + echo 'integer' + ;; + -- ) + echo 'string' + ;; + * ) + echo 'none' + return 1 + esac +} + # Local Variables: # mode: sh # sh-basic-offset: 8 diff --git a/Pmodules/modbuild.in b/Pmodules/modbuild.in index 4827218..a9dd620 100755 --- a/Pmodules/modbuild.in +++ b/Pmodules/modbuild.in @@ -154,7 +154,7 @@ declare opt_verbose='no' # array collecting all modules specified on the command line via '--with=module' declare -a opt_with_modules=() -declare overlay='' +declare opt_overlay_name_or_dir='' parse_args() { @@ -259,10 +259,10 @@ parse_args() { opt_update_modulefiles='yes' ;; --overlay ) - overlay=$2 + opt_overlay_name_or_dir=$2 ;; --overlay=* ) - overlay=${1/*=} + opt_overlay_name_or_dir=${1/*=} ;; -- ) : @@ -366,7 +366,6 @@ build_modules() { fi done - local variants=() local variants_files=() for f in "${files[@]}"; do @@ -408,15 +407,70 @@ build_modules() { done } +declare -r OVERLAY_CONF="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/overlays.conf" + +# +# Return overlay name *and* directory for an overlay given +# by name *or* directory. Search in the config file ${OVERLAY_CONF}. +# +get_overlay_name_and_dir() { + local "$1" # upvar for overlay name + local "$2" # upvar for overlay directory + if [[ -r "${OVERLAY_CONF}" ]]; then + local toks=() + while read -a toks; do + if [[ ${toks[0]} == $3 ]] || [[ ${toks[1]} == $3 ]]; then + std::upvar $1 "${toks[0]}" + std::upvar $2 "${toks[1]}" + return 0 + fi + done < "${OVERLAY_CONF}" + fi + return 1 +} + #............................................................................. # main parse_args "$@" -if [[ -z "${opt_system}" ]]; then - opt_system=$(std::get_os_release) +# +# set overlay_name, overlay_dir and opt_system +# +# --overlay=name +# lookup overlay directory, system name equivalent to overlay name +# --overlay=name --system=sysname +# lookup name, use given system name +# --overlay=dir +# use given overlay dir and default system name +# --system=name +# lookup name, set overlay_dir of found config +declare overlay_name='' +declare overlay_dir='' +if [[ -n "${opt_overlay_name_or_dir}" ]]; then + if ! get_overlay_name_and_dir overlay_name overlay_dir "${opt_overlay_name_or_dir}"; then + # not found in config + if [[ ${opt_overlay_name_or_dir:0:1} == / ]]; then + overlay_dir="${opt_overlay_name_or_dir}" + else + std::die 1 "Invalid overlay name -- ${opt_overlay_name_or_dir}" + fi + fi + if [[ -z "${opt_system}" ]] && [[ -n "${overlay_name}" ]]; then + opt_system="${overlay_name}" + fi +elif [[ -n "${opt_system}" ]]; then + get_overlay_name_and_dir overlay_name overlay_dir "${opt_system}" || : fi +[[ -z "${overlay_name}" ]] && overlay_name='unknown' +[[ -z "${overlay_dir}" ]] && overlay_dir="${PMODULES_ROOT}" +[[ -z "${opt_system}" ]] && opt_system=$(std::get_os_release) + +echo overlay_name=$overlay_name +echo overlay_dir=$overlay_dir +echo opt_system=$opt_system + pbuild.jobs "${opt_jobs}" pbuild.force_rebuild "${opt_force_rebuild}" pbuild.build_target "${opt_build_target}" @@ -441,8 +495,6 @@ fi source "${opt_build_config}" || \ std::die 3 "Oops: Cannot source configuration file -- '$_'" -[[ -z "${overlay}" ]] && overlay=${PMODULES_ROOT} - # :FIXME: should go dist files to # ${PMODULES_ROOT}/var/distfiles # or diff --git a/Pmodules/modmanage.bash.in b/Pmodules/modmanage.bash.in index 9a59809..6e11be2 100755 --- a/Pmodules/modmanage.bash.in +++ b/Pmodules/modmanage.bash.in @@ -1,22 +1,42 @@ -#!@PMODULES_HOME@/sbin/bash +#!@BASH@ --noprofile # we have to unset CDPATH, otherwise 'cd' prints the directoy! unset CDPATH # used for some output only -declare -r CMD=$(basename "$0") +declare -r CMD='modmanage' -declare -r mydir=$(cd $(dirname "$0") && pwd) -declare -r prefix=$(dirname "${mydir}") +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 -r bindir="${prefix}/bin" -declare -r sbindir="${prefix}/sbin" declare -r libdir="${prefix}/lib" declare -r libexecdir="${prefix}/libexec" -source "${libdir}/libstd.bash" -source "${libdir}/libpmodules.bash" -PATH="${sbindir}:${bindir}:${PATH}" +source "${libdir}/libstd.bash" + _exit () { std::die 1 "Interrupted..." @@ -205,7 +225,7 @@ get_module_prefix() { # $1: relative module file path # get_releasefile_name() { - echo "$(dirname "$1")/.release-$(basename "$1")" + echo "$(${dirname} "$1")/.release-$(basename "$1")" } ############################################################################## @@ -234,8 +254,8 @@ sync_module() { # 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 \ + $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 @@ -246,8 +266,8 @@ sync_module() { # 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 $? + local dir=$( ${dirname} "${target_modulefile}" ) + $DRY ${mkdir} -p "${dir}" || return $? fi # copy modulefile template @@ -260,20 +280,20 @@ sync_module() { 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 \ + $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 \ + $DRY ${rsync} --links --perms --recursive \ "${src_modulefile}" "${target_modulefile}" || exit $? fi # copy release-file if [[ -e "${src_releasefile}" ]]; then - $DRY rsync --links --perms --recursive \ + $DRY ${rsync} --links --perms --recursive \ "${src_releasefile}" "${target_releasefile}" || exit $? fi @@ -290,9 +310,8 @@ sync_module() { sync_config() { src="$1/${PMODULES_CONFIG_DIR}/" dst="$2/${PMODULES_CONFIG_DIR}/" - $DRY rsync --recursive --links --perms --delete \ + $DRY ${rsync} --recursive --links --perms --delete \ "${src}" "${dst}" 2>/dev/null || return $? - sed -i.bak "s/PMODULES_VERSION=\(.*\)/PMODULES_VERSION=${PMODULES_VERSION}/" "${dst}/environment.bash" echo } @@ -326,11 +345,26 @@ delete_module() { # Initialize a new module environment in this directory- # subcommand_init() { + check_env() { + [[ -n "${PMODULES_ROOT}" ]] && + [[ -d "${PMODULES_ROOT}" ]] && + [[ -n "${PMODULES_HOME}" ]] && + [[ -n "${PMODULES_VERSION}" ]] || \ + std::die 1 " +Error: the module environment you are going to use as so urce 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=() local user='' local opts='' - opts=$(pmodules::get_options -o h -l src: -l user: -l help -l version: -- "$@") + opts=$(${getopt} -o h -l src: -l user: -l help -l version: -- "$@") if [[ $? != 0 ]]; then subcommand_help_init exit 1 @@ -388,7 +422,7 @@ subcommand_init() { std::die 1 "Error: --user option is only allowed if running as root!" fi - pmodules::check_env || \ + check_env || \ std::die 1 "Giving up..." echo " @@ -416,7 +450,7 @@ environment at '${PMODULES_ROOT}' fi force='yes' echo "Creating target directory '${target_prefix}'..." - $DRY mkdir -p "${target_prefix}" || \ + $DRY ${mkdir} -p "${target_prefix}" || \ std::die 1 "Error: make directory failed!" echo @@ -430,13 +464,7 @@ environment at '${PMODULES_ROOT}' "${src_prefix}" \ "${target_prefix}" || \ std::die 1 "Error: sync Pmodules failed!" - mkdir -p "${target_prefix}/Tools/${PMODULES_MODULEFILES_DIR}" - echo - - dst="${target_prefix}/${PMODULES_CONFIG_DIR}/environment.bash" - echo "Adding installation source '${src_prefix}' to '${dst}'..." - sed -i.bak '/PMODULES_INSTALL_SOURCE/d' "${dst}" - echo "declare -x PMODULES_INSTALL_SOURCE=\"${src_prefix}\"" >> "${dst}" + ${mkdir} -p "${target_prefix}/Tools/${PMODULES_MODULEFILES_DIR}" echo if [[ -n "${user}" ]]; then @@ -505,7 +533,7 @@ get_group_depths () { cd "${root}" local group for group in "${Groups[@]}"; do - local fname=$(find "${group}/${modulefiles_dir}" \ + local fname=$(${find} "${group}/${modulefiles_dir}" \ -depth \( -type f -o -type l \) -print -quit) [[ -n ${fname} ]] || continue local -a tmp @@ -591,7 +619,7 @@ subcommand_install() { [[ -z ${dep} ]] && continue # search for module with current modulepath and remember - local modulename=$(find "${modulepath[@]}" -path "*/${dep}" \ + local modulename=$(${find} "${modulepath[@]}" -path "*/${dep}" \ 2>/dev/null | head -n 1 ) [[ -n ${modulename} ]] || \ std::die 3 "Oops: required module '${dep}' not found!" @@ -606,7 +634,7 @@ subcommand_install() { modulepath+=( "${path}" ) fi done < "${tmpfile}" - rm "${tmpfile}" + ${rm} "${tmpfile}" } #...................................................................... @@ -649,7 +677,7 @@ subcommand_install() { std::info "" } - opts=$(pmodules::get_options -o hf -l dry-run -l force -l with: -l release: -l help -l src: -- "$@") + 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 @@ -729,7 +757,7 @@ subcommand_install() { map_to_family[$_key]=${_family} fi done < <({ cd "${src_prefix}" && \ - find */"${PMODULES_MODULEFILES_DIR}" \ + ${find} */"${PMODULES_MODULEFILES_DIR}" \ \( -type l -o -type f \) \! -name ".*"; } 2>/dev/null ) # diff --git a/Pmodules/modulecmd.bash.in b/Pmodules/modulecmd.bash.in index 5a38df8..a32b78b 100644 --- a/Pmodules/modulecmd.bash.in +++ b/Pmodules/modulecmd.bash.in @@ -22,6 +22,10 @@ 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" @@ -34,10 +38,6 @@ fi source "${libdir}/libstd.bash" -: ${PMODULES_DEFINED_RELEASES:=':unstable:stable:deprecated:'} - -declare -r version='@PMODULES_VERSION@' - if [[ ${PMODULES_PURETCL} == yes ]]; then declare -r modulecmd="${libexecdir}/modulecmd.tcl" else @@ -45,26 +45,49 @@ else declare -r modulecmd="${libexecdir}/modulecmd.bin" fi +declare -r ol_normal='n' +declare -r ol_hiding='h' +declare -r ol_replacing='r' + declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} -# we use newline as internal field separator -IFS=$' \t\n' -declare -r __IFS=${IFS} # used to restore IFS +# use default IFS +unset IFS shopt -s nullglob declare -A GroupDepths='()' -declare -A MapDirsToOverlays='()' +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 + +# set groups which should be available after initialization +declare -- DefaultGroups='Tools Programming' + +# define available release stages +declare -- ReleaseStages=':unstable:stable:deprecated:' + +# set releases which should be available after initialization +declare -- DefaultReleaseStages='stable' + +# 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 ${version} using Tcl Environment Modules @MODULES_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 @@ -87,6 +110,18 @@ export_env() { printf "${fmt}" "$1" "${!1}" shift done + # :FIXME: UsedGroups can be modified in libmodule.tcl using + # append-path/remove-path! But we keep the state in PMODULES_ENV + # so we don't have to export it. + # + case "${Shell}" in + sh | bash | zsh ) + echo "unset UsedGroups; " + ;; + csh | tcsh ) + echo "unsetenv UsedGroups; " + ;; + esac } # @@ -99,56 +134,123 @@ export_env() { declare g_env_must_be_saved='no' save_env() { - [[ ${g_env_must_be_saved} == 'no' ]] && return 0 - local vars=( GroupDepths UsedReleases UseFlags UsedGroups ) - vars+=( PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES ) - vars+=( PMODULES_DEFAULT_RELEASES ) - vars+=( PMODULES_OVERLAYS ) - vars+=( Overlays MapDirsToOverlays) + [[ $1 == 'no' ]] && return 0 + local vars=( Version ) + vars+=( UsedReleaseStages UsedFlags UsedGroups ) + vars+=( DefaultGroups DefaultReleaseStages ) + vars+=( ReleaseStages ) + vars+=( GroupDepths ) + vars+=( OverlayList ) + vars+=( OverlayDict Dir2OverlayMap) local s=$(typeset -p ${vars[@]}) declare -g PMODULES_ENV=$( "${base64}" --wrap=0 <<< "$s" ) export_env 'PMODULES_ENV' } -trap 'save_env ' EXIT +_exit() { + save_env "${g_env_must_be_saved}" + if [[ -n "${tmpfile}" ]] && [[ -e "${tmpfile}" ]]; then + ${rm} -f "${tmpfile}" || : + fi +} + +trap '_exit' EXIT + +get_overlay_of_moduledir() { + local "$1" + local -r moduledir=$2 + + if [[ ! -v Dir2OverlayMap[${moduledir}] ]]; then + for overlay in "${OverlayList[@]}" 'other'; do + [[ ${moduledir} == ${overlay}/* ]] && break + done + Dir2OverlayMap[${moduledir}]="${overlay}" + fi + std::upvar $1 "${Dir2OverlayMap[${moduledir}]}" +} # -# get release of module +# get release stage of module # Note: -# - the release of a module outside ${Overlays[@]} is always 'stable' -# - the release of a module inside ${Overlays[@]} without a -# coresponding release file is always 'unstable' +# - the release stage of a module outside ${OverlayDict[@]} is always 'stable' +# - the release stage of a module inside ${OverlayDict[@]} without a +# coresponding file is always 'unstable' # # Args: -# $1 upvar for returned release +# $1 upvar for returned release stage # $2 modulefile directory (element of MODULEPATH) # $3 module name/version # -get_release() { +get_release_stage() { local "$1" local -r moduledir=$2 local -r name=$3 local -r modulefile="$2/$3" - + + local overlay + get_overlay_of_moduledir overlay "${moduledir}" + + if [[ "${overlay}" == 'other' ]]; then + std::upvar $1 'stable' + return + fi # # In an overlay the name of the module-file is something like # dir/modulefiles/name/version - # the corresponding release file is + # the corresponding file is # dir/modulefiles/name/.release-version # - local -r releasefile="${modulefile%/*}/.release-${modulefile##*/}" - if [[ -r ${releasefile} ]]; then - # read releasefile, remove empty lines, spaces etc - local -r data=$( < "${releasefile}" ) - std::upvar $1 "${data}" + local -r rel_stage_file="${modulefile%/*}/.release-${modulefile##*/}" + if [[ -r ${rel_stage_file} ]]; then + # read file, remove empty lines, spaces etc + std::upvar $1 $( < "${rel_stage_file}" ) else std::upvar $1 'unstable' fi } -is_release() { - [[ ${PMODULES_DEFINED_RELEASES} =~ :$1: ]] +is_release_stage() { + [[ ${ReleaseStages} =~ :$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} -L "${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} + g_env_must_be_saved='yes' +} + +# +# (Re-)Scan available groups in given overlays and compute group depth's +# +# Args: +# $1: array of overlays +# +scan_groups () { + local -r overlays=( "$@" ) + local overlay + for overlay in "${overlays[@]}"; do + local moduledir + for moduledir in ${overlay}/*/${PMODULES_MODULEFILES_DIR}; do + compute_group_depth "${moduledir}" + done + done } # @@ -164,18 +266,16 @@ find_overlay () { local "$1" local "$2" local -r path=$3 - local overlay=${MapDirsToOverlays[${path}]} - if [[ -z "${overlay}" ]]; then - for overlay in "${!Overlays[@]}" 'ZZZZZZ'; do - [[ ${path}/ =~ ^${overlay}/ ]] && break - done - [[ "${overlay}" == 'ZZZZZZ' ]] && return 1 - fi + local overlay=${Dir2OverlayMap[${path}]} + get_overlay_of_moduledir overlay "${path}" std::upvar $1 "${overlay}" + + [[ "${overlay}" == 'other' ]] && return 1 + local group="${path#${overlay}/}" group=${group%%/*} std::upvar $2 "${group}" - return 0 + return 0 } module_is_loaded() { @@ -209,6 +309,7 @@ subcommand_generic0() { args+=( "$1" ) ;; esac + shift 1 done if (( ${#args[@]} > 0 )); then std::die 3 "%s %s: %s" \ @@ -295,14 +396,11 @@ USAGE: subcommand_load() { local -r subcommand='load' + local rel_stage='undef' local current_modulefile='' local prefix='' local m='' - IFS=':' - local -a modulepath=(${MODULEPATH}) - IFS=${__IFS} - # # Test whether a given module is available. # The passed module-name can be @@ -327,12 +425,12 @@ subcommand_load() { # # possible return values: # 0: module is loadable - # 1: either not a modulefile or unsused release + # 1: either not a modulefile or unsused release stage # # The following variables in the enclosing function are set: # current_modulefile # prefix - # release + # rel_stage # is_available() { local m=$1 @@ -353,7 +451,7 @@ subcommand_load() { current_modulefile="${array[0]}" prefix="${array[1]}" test -n "${current_modulefile}" || return 1 - get_release release "${current_modulefile}" "${UsedReleases}" + get_release_stage rel_stage "${current_modulefile}" "${UsedReleaseStages}" } # @@ -368,12 +466,12 @@ subcommand_load() { get_load_hints() { local "$1" local output='' - local release='' + local rel_stage='' while read -a line; do [[ -z ${line} ]] && continue - release=${line[1]} - if [[ ! ":${UsedReleases}:" =~ "${release}" ]]; then - output+="module use ${release}; " + rel_stage=${line[1]} + if [[ ! ":${UsedReleaseStages}:" =~ "${rel_stage}" ]]; then + output+="module use ${rel_stage}; " fi local group=${line[2]} if [[ ! ":${UsedGroups}:" =~ ":${group}:" ]] && \ @@ -419,11 +517,12 @@ subcommand_load() { # hierarchical depth of a group must always be the same. # is_group () { + local "$1" find_an_overlay_providing_group () { local "$1" local -r group="$2/${PMODULES_MODULEFILES_DIR}" local overlay - for overlay in "${!Overlays[@]}"; do + for overlay in "${OverlayList[@]}"; do if [[ -d "${overlay}/${group}" ]]; then std::upvar $1 "${overlay}" return 0 @@ -448,7 +547,7 @@ subcommand_load() { while (($# > 0)); do case $1 in -H | --help ) - print_help "${subcommand_load}" + print_help "${subcommand}" ;; -f | --force ) opts+=(' -f') @@ -476,34 +575,32 @@ subcommand_load() { "No module specified" fi - local saved_IFS="${IFS}"; IFS=':' local -a modulepath=(${MODULEPATH}) - IFS=${saved_IFS} + unset IFS local m='' for m in "${args[@]}"; do if [[ "$m" =~ ":" ]]; then - # $m is an extended module name - # the format is one of - # - group:name - # - group:name:release - # - release:name - # - release:group:name - # - name:release + # extendet module name is either + # - group:name or + # - group:name:rel_stage or + # - rel_stage:name or + # - rel_stage:group:name or + # - name:rel_stage IFS=':' local -a toks=($m) - IFS=${__IFS} + unset IFS local group='' - local release='' + local rel_stage='' if is_group "${toks[0]}"; then group=${toks[0]} m=${toks[1]} - release=${toks[2]} - elif is_release "${toks[0]}"; then - release=${toks[0]} + rel_stage=${toks[2]} + elif is_release_stage "${toks[0]}"; then + rel_stage=${toks[0]} if is_group "${toks[1]}"; then group=${toks[1]} m=${toks[2]} @@ -515,9 +612,9 @@ subcommand_load() { m=${toks[0]} if is_group "${toks[1]}"; then group=${toks[1]} - release=${toks[2]} + rel_stage=${toks[2]} else - release=${toks[1]} + rel_stage=${toks[1]} group=${toks[2]} fi fi @@ -536,22 +633,22 @@ subcommand_load() { MODULEPATH="" modulepath=() group+="${PMODULES_MODULEFILES_DIR}" - for overlay in "${!Overlays[@]}"; do - MODULEPATH+=":${overlay}/${group}/" - modulepath+=( ${MODULEPATH} ) + for overlay in "${OverlayList[@]}"; do + MODULEPATH="${overlay}/${group}/:${MODULEPATH}" + modulepath=( "${overlay}/${group}/" "${modulepath[@]}" ) done fi - if [[ -n ${release} ]]; then - is_release "${release}" || \ + if [[ -n ${rel_stage} ]]; then + is_release_stage "${rel_stage}" || \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ - "illegal release name" - "${release}" - UsedReleases=( "${release}" ) + "illegal release stage" \ + "${rel_stage}" + std::append_path UsedReleaseStages "${rel_stage}" + g_env_must_be_saved='yes' fi fi # handle extended module names - local release='' - find_module current_modulefile release "${MODULEPATH}" "${m}" + find_module current_modulefile rel_stage "${m}" "${modulepath[@]}" if [[ -z ${current_modulefile} ]]; then local text='' get_load_hints text @@ -569,8 +666,17 @@ subcommand_load() { std::die 3 "" fi fi - if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then - # already loaded + if [[ ${m} == Pmodules/* ]] && [[ -n ${LOADEDMODULES} ]]; then + std::error "%s %s: %s" \ + "${CMD}" "${subcommand}" \ + "cannot load a Pmodules module because other modules are already load!" + std::die 3 "%s %s: %s -- %s\n" \ + "${CMD}" "${subcommand}" \ + "failed" \ + "${m}" + fi + if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then + # already loaded continue fi @@ -581,8 +687,6 @@ subcommand_load() { test -r "${prefix}/.dependencies" && load_dependencies "$_" fi - local tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \ - || std::die 1 "Oops: unable to create tmp file!" local output=$("${modulecmd}" 'bash' ${opts} 'load' \ "${current_modulefile}" 2> "${tmpfile}") @@ -619,14 +723,14 @@ subcommand_load() { fi local msg=$(printf "%s %s: %s -- %s" \ - "${CMD}" 'load' \ - "${release} module has been loaded" \ - "${m}") + "${CMD}" 'load' \ + "${rel_stage} module has been loaded" \ + "${m}") if [[ ${verbosity_lvl} != silent ]] && \ - [[ ${release} != stable ]]; then + [[ ${rel_stage} != stable ]]; then std::info "%s" "${msg}" fi - logger "${msg}" + ${logger} -t Pmodules "${msg}" done # fix LOADEDMODULES LOADEDMODULES="${_LMFILES_}" @@ -634,7 +738,9 @@ subcommand_load() { while read dir; do [[ "${dir: -1}" == "/" ]] || dir+="/" LOADEDMODULES="${LOADEDMODULES//${dir}}" + get_overlay_of_moduledir overlay "${dir}" done <<< "${MODULEPATH//:/$'\n'}" + g_env_must_be_saved='yes' export_env 'LOADEDMODULES' } @@ -705,6 +811,7 @@ subcommand_unload() { PMODULES_HOME=${saved_home} export_env 'PMODULES_HOME' fi + g_env_must_be_saved='yes' } ############################################################################## @@ -807,18 +914,18 @@ subcommand_show() { # # Find all modules in a given modulepath matching a specific string. -# The search can be restricted to certain releases. +# The search can be restricted to certain release stages. # # return list like -# modulename1 release1 modulefile1 modulename2 release2 modulefile2 ... +# modulename_1 rel_stage_1 modulefile_1 modulename_2 rel_stage_2 modulefile_1 ... # get_available_modules() { local var="$1" local -r module="$2" - local -r releases="${3:-${UsedReleases}}" + local -r used_rel_stages="${3:-${UsedReleaseStages}}" shift 3 # in the for loop below we use $@ to loop over the directories local -a mods=() - local release + local rel_stage local -A dict local -A modulenames @@ -842,39 +949,46 @@ get_available_modules() { local entries=$(echo *) [[ -n ${entries} ]] || continue while read mod; do - get_release \ - release \ + get_release_stage \ + rel_stage \ "${dir}" \ "${mod}" - [[ :${releases}: =~ ${release} ]] || continue - # - # add to list of available modules, if - # - first time found by name only - # - in same overlay as first found - # - new version and not hidden by overlay - local name="${mod%/*}" + [[ :${used_rel_stages}: =~ :${rel_stage}: ]] || continue local add='no' - if [[ -z "${modulenames[${name}]}" ]]; then - if [[ "${Overlays[$overlay]}" == 'h' ]]; then - modulenames[${name}]="${overlay}" - else - modulenames[${name}]='0' + if [[ -n "${overlay}" ]]; then + # module is in an overlay + # + # add to list of available modules, if + # - first time found by name only + # - in same overlay as first found + # - new version and not hidden by overlay + local name="${mod%/*}" + if [[ -z "${modulenames[${name}]}" ]]; then + if [[ "${OverlayDict[$overlay]}" == "${ol_hiding}" ]]; then + modulenames[${name}]="${overlay}" + else + modulenames[${name}]='0' + fi + add='yes' + elif [[ "${modulenames[${name}]}" == "${overlay}" ]]; then + add='yes' + elif [[ "${modulenames[${name}]}" == '0' ]] \ + && [[ -z ${dict[${mod}]} ]]; then + add='yes' fi + else + # module is NOT in an overlay add='yes' - elif [[ "${modulenames[${name}]}" == "${overlay}" ]]; then - add='yes' - elif [[ "${modulenames[${name}]}" == '0' ]] \ - && [[ -z ${dict[${mod}]} ]]; then - add='yes' - fi + fi if [[ "${add}" == 'yes' ]]; then - mods+=( "${mod}" ${release} "${dir}/${mod}" ) + mods+=( "${mod}" ${rel_stage} "${dir}/${mod}" ) dict[${mod}]=1 fi - done < <(${find} ${entries} \ + done < <(${find} -L ${entries} \ \( -type f -o -type l \) \ -not -name ".*" \ - -ipath "${module}*") + -ipath "${module}*" \ + | ${sort} --version-sort) } done std::upvar ${var} "${mods[@]}" @@ -883,9 +997,9 @@ get_available_modules() { # # find module(file) to load. Input arguments are # $1 upvar: return module file -# $2 upvar: return module release -# $3 a modulepath (usually MODULEPATH) -# $4 module to load +# $2 upvar: return module release stage +# $3 module to load +# $4 a modulepath (usually MODULEPATH) # # The module name can be # name @@ -900,64 +1014,62 @@ get_available_modules() { # if no modulefile could be found # find_module() { - local saved_IFS=${IFS}; - IFS=':' - local -a dirs=($3) - IFS=${saved_IFS} - local -r module="$4" + local "$1" + local "$2" + local -r module="$3" + local -a dirs=("${@:4}") for dir in "${dirs[@]}"; do test -d "${dir}" || continue local -i col=$((${#dir} + 2 )) local -a modules - local -a releases if [[ ${module} == */* ]]; then # a version number has been specified. But we still might # have the same module/version with different use flags. - # Releases we ignore in this case. - modules=$(${find} "${dir}" -type f -not -name ".*" \ + # The different release stages we ignore in this case. + modules=$(${find} -L "${dir}" -type f -not -name ".*" \ -ipath "${dir}/${module}*" \ | cut -b${col}-) for mod in "${modules[@]}"; do # # loop over all used flags. If a module with # a used flag is available load this module. + local rel_stage for flag in "${UseFlags[@]/#/_}" ""; do [[ ${mod} == ${module}${flag} ]] || continue std::upvar $1 "${dir}/${mod}" - get_release \ - release \ + get_release_stage \ + rel_stage \ "${dir}" \ "${mod}" - std::upvar $2 "${release}" + std::upvar $2 "${rel_stage}" return 0 done done else # no version has been specified. This makes it more # difficult. We have to load the newest version taking - # the used releases and flags into account. + # the used release stages and flags into account. (( col += ${#module} + 1 )) - modules=( $(${find} "${dir}" -type f -not -name ".*" \ + modules=( $(${find} -L "${dir}" -type f -not -name ".*" \ -ipath "${dir}/${module}/*" \ | cut -b${col}- \ | sort -rV ) ) modules=( "${modules[@]/#/${module}/}" ) - releases=":${UsedReleases}:" for mod in "${modules[@]}"; do # # loop over all used flags. If a module with # a used flag is available load this module. - local release='' + local rel_stage='' for flag in "${UseFlags[@]/#/_}" ""; do [[ ${mod} == ${module}/*${flag} ]] || continue std::upvar $1 "${dir}/${mod}" - get_release \ - release \ + get_release_stage \ + rel_stage \ "${dir}" \ - "${mod}" \ - std::upvar $2 "${release}" - [[ :${release}: =~ :${UsedReleases}: ]] && \ + "${mod}" + std::upvar $2 "${rel_stage}" + [[ :${release}: =~ :${UsedReleaseStages}: ]] && \ return 0 done done @@ -971,7 +1083,7 @@ find_module() { # avail [-hlt] [...] # Subcommands[avail]='avail' -Options[avail]='-l help -o Hahlmt -l all -l all-releases -l human -l long -l machine -l terse' +Options[avail]='-l help -o Hahlmt -l all -l all-release-stages -l human -l long -l machine -l terse' Help[avail]=" USAGE: module avail [switches] string @@ -985,8 +1097,8 @@ USAGE: e.g. a compiler, or with the sub-command 'use'. SWITCHES: - -a|--all||--all-releases - List all available modules independend of the release. + -a|--all||--all-release-stages + List all available modules independend of the release stage. -t|--terse Output in short format. @@ -1010,7 +1122,7 @@ subcommand_avail() { cols=$(tput cols) output_header() { - local caption=$1 + local caption="$1" let i=($cols-${#caption})/2-2 printf -- "%0.s-" $(seq 1 $i) 1>&2 printf -- " %s " "${caption}" 1>&2 @@ -1020,15 +1132,16 @@ subcommand_avail() { terse_output() { output_header "$1" + local -i i=0 for (( i=0; i<${#mods[@]}; i+=3 )); do local mod=${mods[i]} - local release=${mods[i+1]} - case $release in + local rel_stage=${mods[i+1]} + case ${rel_stage} in stable ) out='' ;; * ) - out="${release}" + out="${rel_stage}" ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 @@ -1048,13 +1161,13 @@ subcommand_avail() { output_header "$1" for (( i=0; i<${#mods[@]}; i+=3 )); do local mod=${mods[i]} - local release=${mods[i+1]} - case $release in + local rel_stage=${mods[i+1]} + case ${rel_stage} in stable ) out='' ;; * ) - out=${release} + out=${rel_stage} ;; esac printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2 @@ -1065,25 +1178,32 @@ subcommand_avail() { human_readable_output() { output_header "$1" - local -i column=$cols - local -i colsize=16 + local -a available_modules=() + local mod='' + local -i max_length=1 for ((i=0; i<${#mods[@]}; i+=3)); do if [[ ${verbosity_lvl} == 'verbose' ]]; then - local release=${mods[i+1]} - case ${mods[i+1]} in + local rel_stage=${mods[i+1]} + case ${rel_stage} in stable ) - mod=${mods[i]} + mod="${mods[i]}" ;; * ) - mod="${mods[i]}(${release:0:1})" + mod="${mods[i]}(${rel_stage:0:1})" ;; esac else mod=${mods[i]} fi + local -i n=${#mod} + (( n > max_length )) && (( max_length=n )) + available_modules+=("${mod}") + done + local -i span=$(( max_length / 16 + 1 )) # compute column size + local -i colsize=$(( span * 16 )) # as multiple of 16 + local -i column=$cols # force a line-break + for mod in "${available_modules[@]}"; do local -i len=${#mod} - local -i span=$(( len / 16 + 1 )) - local -i colsize=$(( span * 16 )) if (( column+len >= cols )); then printf -- "\n" 1>&2 column=0 @@ -1100,19 +1220,16 @@ subcommand_avail() { } local pattern=() local output_function='human_readable_output' - local opt_all_groups='no' - local opt_use_releases="${UsedReleases}" + local opt_use_rel_stages="${UsedReleaseStages}" + local -A opt_groups=() + local val='' while (($# > 0)); do case $1 in - -H | --help ) + -H | --help | -\? ) print_help "${subcommand}" ;; - -a | --all ) - opt_all_groups='yes' - opt_use_releases="${PMODULES_DEFINED_RELEASES}" - ;; - --all-releases ) - opt_use_releases="${PMODULES_DEFINED_RELEASES}" + -a | --all | --all-release-stages ) + opt_use_rel_stages="${ReleaseStages}" ;; -h | --human ) output_function='human_readable_output' @@ -1126,6 +1243,15 @@ subcommand_avail() { -m | --machine ) output_function='machine_output' ;; + -g | --group | --group=* ) + if [[ $1 == --group=* ]]; then + val="${1/--group=}" + else + val="$2" + shift + fi + opt_groups[${val}]=1 + ;; -- | '' ) ;; * ) @@ -1137,93 +1263,54 @@ subcommand_avail() { if (( ${#pattern[@]} == 0 )); then pattern+=( '' ) fi - local -a modulepath IFS=':' - modulepath=(${MODULEPATH}) - IFS=${__IFS} - - local overlay='' + local -a modulepath=(${MODULEPATH}) + unset IFS + local dir local group='' - local -A modulepath_of_group - local -a groups=() + local overlay='' for dir in "${modulepath[@]}"; do group='other' find_overlay overlay group "${dir}" - if [[ ${modulepath_of_group[${group}]} ]]; then - modulepath_of_group[${group}]+=:${dir} - else - modulepath_of_group[${group}]=${dir} - groups+=( "${group}" ) - fi + if [[ ! -v modulepath_${group} ]]; then + typeset -a modulepath_${group} + fi + typeset -n path=modulepath_${group} + path+=("${dir}") done - local string - for string in "${pattern[@]}"; do - for group in "${groups[@]}"; do + local p + for string in "${pattern[@]}"; do + for group in ${UsedGroups//:/ } other; do + if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then + continue + fi + [[ -v modulepath_${group} ]] || continue + typeset -n path=modulepath_${group} get_available_modules \ mods \ "${string}" \ - "${opt_use_releases}" \ - "${modulepath_of_group[${group}]}" + "${opt_use_rel_stages}" \ + "${path[@]}" [[ ${#mods[@]} == 0 ]] && continue - ${output_function} "${group}" done done } -# -# 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} - g_env_must_be_saved='yes' -} - -# -# (Re-)Scan available groups in given overlays and compute group depth's -# -# Args: -# $1: array of overlays -# -scan_groups () { - local -r overlays=( "$@" ) - local overlay - for overlay in "${overlays[@]}"; do - local moduledir - for moduledir in ${overlay}/*/${PMODULES_MODULEFILES_DIR}; do - compute_group_depth "${moduledir}" - done - done -} - ############################################################################## # -# use [-a|--append|-p|--prepend] [directory|group|release...] +# use [-a|--append|-p|--prepend] [directory|group|release_stage...] # Subcommands[use]='use' Options[use]='-l help -o Hap -l append -l prepend' Help[use]=" USAGE: - module use [-a|--append|-p|--prepend] [directory|group|release...] + module use [-a|--append|-p|--prepend] [directory|group|release_stage|...] 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. + the module search path, used groups and release stages. You can + use this sub-command to get a list of available groups and + releases stages. With a directory as argument, this directory will either be prepended or appended to the module search path. The default @@ -1245,17 +1332,13 @@ subcommand_use() { local -r subcommand='use' IFS=':' local -a modulepath=(${MODULEPATH}) - IFS=${__IFS} + unset IFS local add2path_func='std::append_path' group_is_used() { [[ :${UsedGroups}: =~ :$1: ]] } - release_is_used() { - [[ ":${UsedReleases}:" =~ :$1: ]] - } - print_info() { local f local r @@ -1273,31 +1356,26 @@ subcommand_use() { fi done - std::info '' - std::info "Used releases:" - for r in ${UsedReleases//:/ }; do + std::info "\nUsed releases stages:" + for r in ${UsedReleaseStages//:/ }; do std::info "\t${r}" done - std::info '' - std::info "Unused releases:" - for r in ${PMODULES_DEFINED_RELEASES//:/ }; do - if ! release_is_used $r; then - std::info "\t${r}" - fi + std::info "\nUnused release stages:" + for r in ${ReleaseStages//:/ }; do + [[ ! ":${UsedReleaseStages}:" =~ :$r: ]] && std::info "\t${r}" done - std::info '' - std::info "Used flags:" - for flag in "${!UseFlags[@]}"; do + std::info "\nUsed flags:" + for flag in "${UsedFlags[@]}"; do std::info "\t${flag}" done local overlay std::info '' std::info "Used overlays:" - for overlay in "${!Overlays[@]}"; do + for overlay in "${OverlayList[@]}"; do local hiding='' - [[ "${Overlays[${overlay}]}" != '0' ]] && hiding=' (hiding)' + [[ "${OverlayDict[${overlay}]}" != '0' ]] && hiding=' (hiding)' std::info "\t${overlay}${hiding}" done @@ -1319,38 +1397,49 @@ subcommand_use() { use () { use_overlay() { - if [[ -n "${_LMFILES_}" ]]; then - std::die 3 "%s %s: %s -- %s" \ - "${CMD}" "${subcommand}" \ - "overlays can be added as long as no modules are loaded!" \ - "${overlay}" + if [[ -n ${LOADEDMODULES} ]]; then + if [[ $LOADEDMODULES == *:* ]] \ + || [[ $LOADEDMODULES != Pmodules/* ]]; then + std::error "%s %s: %s" \ + "${CMD}" "${subcommand}" \ + "overlay cannot be added since some modules are already loaded!" + std::die 3 "%s %s: %s -- %s" \ + "${CMD}" "${subcommand}" \ + "overlay failed" \ + "${overlay}" + fi fi local overlay='' - local modifier='0' + local type="${ol_normal}" if [[ $1 == *:* ]]; then - modifier=${1##*:} + type=${1##*:} overlay=${1%:*} - case ${modifier} in - g | h ) - : - ;; - * ) - std::die 3 "%s %s: %s -- %s" \ - "${CMD}" "${subcommand}" \ - "invalid modifier '${modifier}!" \ - "${overlay}" - ;; - esac + elif [[ -d $1 ]] && [[ -r $1/config/overlay.conf ]]; then + overlay=$1 + source "${overlay}/config/overlay.conf" else overlay=$1 fi + + case ${type} in + ${ol_normal} | ${ol_replacing} | ${ol_hiding} ) + : + ;; + * ) + std::die 3 "%s %s: %s -- %s" \ + "${CMD}" "${subcommand}" \ + "invalid type '${type}!" \ + "${overlay}" + ;; + esac + overlay=${overlay%/} # remove trailing '/' if there is one [[ -d "${overlay}" ]] || \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "is not an overlay directory" \ "${overlay}" - if [[ -n "${Overlays[${overlay}]}" ]]; then + if [[ -n "${OverlayDict[${overlay}]}" ]]; then std::info "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "overlay already in use" \ @@ -1358,16 +1447,16 @@ subcommand_use() { return 0 fi - if [[ "${modifier}" == "g" ]]; then - # if this overlay hides groups, we have to remove - # the modules made available by other overlays + if [[ "${type}" == "${ol_replacing}" ]]; then + # if this overlay replaces groups, we have to remove + # the modules made available by other overlays in these groups for group in ${UsedGroups//:/ }; do # first test whether this group is in the to be added overlay local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue # no - for dir in "${!Overlays[@]}"; do + for dir in "${OverlayList[@]}"; do dir+="/${group}/${PMODULES_MODULEFILES_DIR}" std::remove_path MODULEPATH "${dir}" done @@ -1379,28 +1468,28 @@ subcommand_use() { dir+="${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" - MapDirsToOverlays[${dir}]=${overlay} + Dir2OverlayMap[${dir}]=${overlay} fi done - Overlays[${overlay}]=${modifier} - PMODULES_OVERLAYS+=":${overlay}" - export_env PMODULES_OVERLAYS + OverlayDict[${overlay}]=${type} + OverlayList=( "${overlay}" "${OverlayList[@]}" ) + export_env OverlayList g_env_must_be_saved='yes' - scan_groups "${!Overlays[@]}" + scan_groups "${OverlayList[@]}" local group } use_group() { std::append_path UsedGroups "$1" local overlay group - for overlay in "${!Overlays[@]}"; do + for overlay in "${OverlayList[@]}"; do for group in ${UsedGroups//:/ }; do local dir="${overlay}/" dir+="${group}/${PMODULES_MODULEFILES_DIR}" if [[ -d "${dir}" ]]; then std::prepend_path MODULEPATH "${dir}" - MapDirsToOverlays[${dir}]=${overlay} + Dir2OverlayMap[${dir}]=${overlay} fi done done @@ -1408,14 +1497,14 @@ subcommand_use() { local arg=$1 - if is_release "${arg}"; then - # argument is release - std::append_path UsedReleases "${arg}" + if is_release_stage "${arg}"; then + # argument is release stage + std::append_path UsedReleaseStages "${arg}" return fi if [[ "${arg}" =~ "flag=" ]]; then # argument is flag - UseFlags["${arg/flag=}"]=1 + UsedFlags+=( "${arg/flag=}" ) return fi if [[ "${arg}" =~ "overlay=" ]]; then @@ -1446,29 +1535,30 @@ subcommand_use() { "${CMD}" "${subcommand}" \ "illegal group" \ "${arg}" - return fi # arg must be a directory! if [[ ! -d ${arg} ]]; then std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ - "illegal argument" \ + "illegal argument: is neither a valid keyword, group nor directory" \ "${arg}" - return fi local dir="$(cd "${arg}" && pwd)" - local overlay - for overlay in "${!Overlays[@]}"; do - if [[ ${dir}/ =~ ^${overlay}/ ]]; then - # dir is in one of our used overlays - std::die 3 "%s %s: %s -- %s" \ - "${CMD}" "${subcommand}" \ - "illegal argument" \ - "${arg}" - return - fi - done + if [[ -r ${dir}/config/overlay.conf ]]; then + use_overlay "${dir}" + return + fi + # Is ${dir} a used overlay? + # Note: the config.file 'overlay.conf is *not* required + # in an overlay!' + if [[ -v OverlayDict[${dir}] ]]; then + std::die 3 "%s %s: %s -- %s" \ + "${CMD}" "${subcommand}" \ + "illegal argument: is already loaded as overlay" \ + "${dir}" + fi + # argument is a modulepath ${add2path_func} MODULEPATH "${dir}" } @@ -1507,32 +1597,36 @@ subcommand_use() { ############################################################################## # -# unuse directory|group|release... +# unuse directory|group|release_stage|... # Subcommands[unuse]='unuse' Options[unuse]='-o H -l help' Help[unuse]=' unuse directory|group|release... - Remove the given directory, group or release from the search - path. + Remove the given modulefiles directory, group, release stage, + flag from the search path. ' subcommand_unuse() { local -r subcommand='unuse' - local saved_IFS=${IFS}; IFS=':' local -a modulepath=(${MODULEPATH}) - IFS=${saved_IFS} - + unset IFS unuse() { unuse_overlay() { - [[ -n "${_LMFILES_}" ]] && \ - std::die 3 "%s %s: %s -- %s" \ - "${CMD}" "${subcommand}" \ - "overlays can be removed as long as modules are loaded!" \ - "${overlay}" - - overlay=${overlay%:*} # ignore any modifier + local overlay="$1" + if [[ -n ${LOADEDMODULES} ]]; then + if [[ $LOADEDMODULES == *:* ]] || [[ $LOADEDMODULES != Pmodules/* ]]; then + std::error "%s %s: %s" \ + "${CMD}" "${subcommand}" \ + "overlay cannot be removed since some modules are still loaded!" + std::die 3 "%s %s: %s -- %s" \ + "${CMD}" "${subcommand}" \ + "overlay failed" \ + "${overlay}" + fi + fi + overlay=${overlay%:*} # ignore any type overlay=${overlay%/} # remove trailing '/' if there is one [[ -d "${overlay}" ]] || \ std::die 3 "%s %s: %s -- %s" \ @@ -1544,13 +1638,13 @@ subcommand_unuse() { "${CMD}" "${subcommand}" \ "cannot remove root overlay" \ "${overlay}" - [[ -z ${Overlays[${overlay}]} ]] && \ + [[ -z ${OverlayDict[${overlay}]} ]] && \ std::die 3 "%s %s: %s -- %s" \ "${CMD}" "${subcommand}" \ "not an used overlay" \ "${overlay}" - if [[ "${modifier}" == 'g' ]]; then + if [[ "${OverlayDict[${overlay}]}" == "${ol_replacing}" ]]; then # if this overlay hides groups, we have to re-add # the modules made available by other overlays for group in ${UsedGroups//:/ }; do @@ -1559,17 +1653,20 @@ subcommand_unuse() { dir+="${group}/${PMODULES_MODULEFILES_DIR}" [[ -d "${dir}" ]] || continue # no - for dir in "${!Overlays[@]}"; do + for dir in "${OverlayList[@]}"; do dir+="/${group}/${PMODULES_MODULEFILES_DIR}" std::remove_path MODULEPATH "${dir}" done done fi - unset "Overlays[${overlay}]" - std::remove_path PMODULES_OVERLAYS "${overlay}" + unset "OverlayDict[${overlay}]" + local i + for i in "${!OverlayList[@]}"; do + [[ ${OverlayList[i]} == ${overlay} ]] && unset 'OverlayList[i]' + done g_env_must_be_saved='yes' - export_env PMODULES_OVERLAYS + export_env OverlayList local dir for dir in "${modulepath[@]}"; do if [[ "${dir}" =~ "${overlay}" ]]; then @@ -1588,7 +1685,7 @@ subcommand_unuse() { fi std::remove_path UsedGroups "${arg}" local overlay - for overlay in "${!Overlays[@]}"; do + for overlay in "${OverlayList[@]}"; do local dir="${overlay}/${arg}/${PMODULES_MODULEFILES_DIR}" std::remove_path MODULEPATH "${dir}" done @@ -1596,14 +1693,18 @@ subcommand_unuse() { local arg=$1 - if is_release "${arg}"; then - # argument is release - std::remove_path UsedReleases "${arg}" + if is_release_stage "${arg}"; then + # argument is release stage + std::remove_path UsedReleaseStages "${arg}" return fi if [[ "${arg}" =~ "flag=" ]]; then # argument is flag - unset UseFlags["${arg/flag=}"] + local flag="${arg/flag=}" + local i + for i in ${!UsedFlags[@]}; do + [[ ${UsedFlags[i]} == ${flag} ]] && unset UsedFlags[i] + done return fi if [[ ${arg} =~ ^overlay= ]]; then @@ -1624,7 +1725,6 @@ subcommand_unuse() { "${CMD}" "${subcommand}" \ "illegal group" \ "${arg}" - return fi # user wants to append a directory to MODULEPATH if [[ ! -d ${arg} ]]; then @@ -1632,21 +1732,14 @@ subcommand_unuse() { "${CMD}" "${subcommand}" \ "illegal argument" \ "${arg}" - return fi local dir="$(cd "${arg}" && pwd)" - local overlay - for overlay in "${!Overlays[@]}"; do - if [[ ${dir}/ =~ ^${overlay}/ ]]; then - # dir is in one of our used overlays - std::die 3 "%s %s: %s -- %s" \ - "${CMD}" "${subcommand}" \ - "illegal argument" \ - "${arg}" - return - fi - done + if [[ -r ${dir}/config/overlay.conf ]] || [[ -v OverlayDict[${dir}] ]]; then + unuse_overlay "${dir}" + return + fi + # argument is a modulepath std::remove_path MODULEPATH "${dir}" @@ -1717,79 +1810,86 @@ subcommand_refresh() { subcommand_generic0 'refresh' "$@" } +# +# help function, used during initialization and for purging all modules +# +reset_modulepath() { + MODULEPATH='' + local group + local overlay + for overlay in "${!OverlayDict[@]}"; do + for group in ${UsedGroups//:/ }; do + local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}" + if [[ -d "${dir}" ]]; then + std::prepend_path MODULEPATH "${dir}" + Dir2OverlayMap[${dir}]=${overlay} + fi + done + done +} + +reset_used_groups() { + UsedGroups='' + local group + for group in ${DefaultGroups}; do + std::append_path UsedGroups "${group}" + done + g_env_must_be_saved='yes' +} + +reset_used_releases() { + declare -g UsedReleaseStages='' + for r in ${DefaultReleaseStages//:/ }; do + std::append_path UsedReleaseStages "${r}" + done + g_env_must_be_saved='yes' +} + +init_path() { + std::replace_path PATH "${PMODULES_HOME%/*}/.*" + std::prepend_path PATH "${PMODULES_HOME}/bin" +} + +init_manpath() { + std::replace_path MANPATH "${PMODULES_HOME%/*}/.*" + + if [[ -r /etc/man.config ]]; then + declare _manconf='/etc/man.config' + elif [[ -r /etc/man.conf ]]; then + declare _manconf='/etc/man.conf' + fi + if [[ -n ${_manconf} ]]; then + while read name value rest; do + std::append_path MANPATH "${value}" + done < <(grep "^MANPATH\s" "${_manconf}") + unset _manconf + else + std::append_path MANPATH "${PMODULES_HOME}/share/man" + std::append_path MANPATH "/usr/share/man" + fi +} + pmodules_init() { + if [[ -r "${pmodules_config_file}" ]]; then + source "${pmodules_config_file}" || \ + std::die 3 "Oops: cannot parse config file -- %s\n" \ + "${pmodules_config_file}" + fi declare -gx LOADEDMODULES='' declare -gx _LMFILES_='' - - declare -Ag Overlays=([${PMODULES_ROOT}]="0") - declare -gx PMODULES_OVERLAYS="${PMODULES_ROOT}" - declare -g UsedGroups='' + declare -gx UsedGroups='' declare -gx MODULEPATH='' declare -Ag GroupDepths='()' - declare -Ag UseFlags=() - - reset_used_groups() { - UsedGroups='' - local group - for group in ${PMODULES_DEFAULT_GROUPS}; do - std::append_path UsedGroups "${group}" - done - g_env_must_be_saved='yes' - } - - reset_modulepath() { - MODULEPATH='' - local group - local overlay - for overlay in "${!Overlays[@]}"; do - for group in ${PMODULES_DEFAULT_GROUPS}; do - local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}" - if [[ -d "${dir}" ]]; then - std::prepend_path MODULEPATH "${dir}" - MapDirsToOverlays[${dir}]=${overlay} - fi - done - done - } - - reset_used_releases() { - declare -g UsedReleases='' - for r in ${PMODULES_DEFAULT_RELEASES//:/ }; do - std::append_path UsedReleases "${r}" - done - g_env_must_be_saved='yes' - } - - init_path() { - std::replace_path PATH "${PMODULES_HOME%/*}/.*" - std::prepend_path PATH "${PMODULES_HOME}/bin" - } - - init_manpath() { - std::replace_path MANPATH "${PMODULES_HOME%/*}/.*" - - if [[ -r /etc/man.config ]]; then - declare _manconf='/etc/man.config' - elif [[ -r /etc/man.conf ]]; then - declare _manconf='/etc/man.conf' - fi - if [[ -n ${_manconf} ]]; then - while read name value rest; do - std::append_path MANPATH "${value}" - done < <(grep "^MANPATH\s" "${_manconf}") - unset _manconf - else - std::append_path MANPATH "${PMODULES_HOME}/share/man" - std::append_path MANPATH "/usr/share/man" - fi - } + declare -ag UsedFlags=() + declare -g Version="${PMODULES_VERSION}" + declare -Ag OverlayDict=([${PMODULES_ROOT}]="0") + declare -ag OverlayList=( "${PMODULES_ROOT}" ) reset_used_groups reset_modulepath reset_used_releases init_manpath export_env \ - PMODULES_OVERLAYS \ LOADEDMODULES \ _LMFILES_ \ MODULEPATH \ @@ -1810,6 +1910,13 @@ USAGE: ' subcommand_purge() { + # + # unload all loaded modules + # + # Note: + # If a Pmodule module is loaded, it will *not* be + # unloaded! + # local -r subcommand='purge' local -a args=() while (( $# > 0)); do @@ -1830,13 +1937,53 @@ subcommand_purge() { "${CMD}" "${subcommand}" \ "no arguments allowd" fi - # we cannot unset PMODULES_HOME, otherwise the module function - # would fail. - local saved_home="${PMODULES_HOME}" - "${modulecmd}" "${Shell}" "${subcommand}" + + # is a Pmodule module loaded? + # if yes, save name in variable 'pmodule' + local pmodule='' + IFS=':' + local -a lmfiles=($_LMFILES_) + unset IFS + for f in "${lmfiles[@]}"; do + if [[ $f == */${PMODULES_MODULEFILES_DIR}/Pmodules/* ]]; then + pmodule="${f##*/${PMODULES_MODULEFILES_DIR}/}" + break; + fi + done + + # run module purge + # since we might have to reload a Pmodules module, we cannot + # just run 'modulecmd ${Shell} purge' + local output=$("${modulecmd}" 'bash' 'purge' 2> "${tmpfile}") + + local error=$( < "${tmpfile}") + if [[ "${error}" =~ ":ERROR:" ]]; then + local s=${error%%$'\n'*} + local error_txt='failed' + std::die 3 "%s %s: %s" \ + "${CMD}" "${subcommand}" \ + "${error_txt}" + fi + if [[ "${Shell}" == "sh" ]]; then + # for sh-like shells just echo + echo "${output}" + else + # re-run with right shell + "${modulecmd}" "${Shell}" 'purge' + fi + eval "${output}" + if [[ -n "${error}" ]]; then + echo "${error}" 1>&2 + fi + + if [[ -n "${pmodule}" ]]; then + # reload a previously loaded Pmodule module + # stderr is redirected to /dev/null, otherwise + # we may get output like + # 'unstable module has been loaded' + subcommand_load "${pmodule}" 2> /dev/null + fi reset_modulepath - reset_used_groups - PMODULES_HOME="${saved_home}" export_env MODULEPATH PMODULES_HOME } @@ -1933,7 +2080,7 @@ subcommand_clear() { # Subcommands[search]='search' Options[search]='-o aH -l help -l no-header -l print-modulefiles ' -Options[search]+='-l release: -l with: -l all-releases -l src: -l print-csv ' +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' Help[search]=' @@ -1943,7 +2090,7 @@ USAGE: for modules whose name match the argument. SWITCHES: - -a|--all-releases + -a|--all-release-stages Search within all releases. --all-deps @@ -1952,10 +2099,10 @@ 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. + --release-stage=RELEASE_STAGE + Search for modules within this release stage. You can specify + this switch multiple times. Without this switch, the release + stages in use will be searched. --verbose vebose output @@ -2015,7 +2162,7 @@ subcommand_search() { print_header_default() { std::info '' - std::info "${fmt}" "Module" "Release" "Group" "Requires" + std::info "${fmt}" "Module" "Rel.stage" "Group" "Requires" std::info '-%.0s' $(seq 1 ${cols}) } @@ -2054,7 +2201,7 @@ subcommand_search() { print_header_verbose() { std::info '' - std::info "${fmt}" "Module" "Release" "Group" "Dependencies/Modulefile" + std::info "${fmt}" "Module" "Rel.stage" "Group" "Dependencies/Modulefile" std::info '-%.0s' $(seq 1 ${cols}) } @@ -2098,8 +2245,6 @@ subcommand_search() { func_print_line='print_line_csv' } - local -r tmpfile=$1 - if [[ "${opt_print_modulefiles}" == 'yes' ]]; then print_modulefiles elif [[ "${opt_print_csv}" == 'yes' ]]; then @@ -2113,7 +2258,7 @@ subcommand_search() { ${func_print_header} while read -a toks; do ${func_print_line} "${toks[@]}" - done < <("${sort}" -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | \ + done < <("${sort}" --version-sort -k 1,1 -k 4,4 -k 5,5 "${tmpfile}" | \ ${awk} "${with_modules}") } @@ -2137,8 +2282,6 @@ subcommand_search() { search () { local -r module=$1 # write results to a temporary file for later processing - local -r tmpfile=$( "${mktemp}" /tmp/Pmodules.XXXXXX ) \ - || std::die 1 "Oops: unable to create tmp file!" local group # loop over all groups for group in "${!GroupDepths[@]}"; do @@ -2152,19 +2295,18 @@ subcommand_search() { local modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} ) # get and print all available modules in $mpath - # with respect to the requested releases - # tmpfile: module/version release group group- - # dependencies... + # with respect to the requested release stage + # tmpfile: module/version rel_stage group dependencies... local mods get_available_modules \ mods \ "${module}" \ - "${opt_use_releases}" \ + "${opt_use_rel_stages}" \ "${modulepath[@]}" \ for (( i=0; i<${#mods[@]}; i+=3 )); do local name=${mods[i]} - local release=${mods[i+1]} + local rel_stage=${mods[i+1]} local modulefile=${mods[i+2]} if (( ${#name} > max_len_modulename)); then @@ -2184,20 +2326,19 @@ subcommand_search() { # get dependencies encoded in directory name local deps=() local -i j - IFS='/' + IFS='/' # note: IFS is used to concat in the for loop! local toks=( ${modulefile} ) for ((j = -depth-2; j < -2; j += 2)); do deps+=( "${toks[*]: $j:2}" ); done - IFS=${__IFS} + unset IFS fi - echo ${name} ${release} ${group} ${modulefile} \ + echo ${name} ${rel_stage} ${group} ${modulefile} \ ${deps[@]} >> "${tmpfile}" done done - print_result "${tmpfile}" - ${rm} -f "${tmpfile}" + print_result } while (( $# > 0 )); do @@ -2219,19 +2360,19 @@ subcommand_search() { opt_print_csv='yes' opt_print_header='no' ;; - --release | --release=* ) + --release-stage | --release-stage=* ) if [[ "$1" == "--release" ]]; then local arg=$2 shift else local arg=${1/--release=} fi - is_release "${arg}" || \ + is_release_stage "${arg}" || \ std::die 1 "%s %s: %s -- %s" \ "${CMD}" 'search' \ - "illegal release name" \ + "illegal release stage" \ "${arg}" - opt_use_releases+="${arg}:" + opt_use_rel_stages+="${arg}:" ;; --with | --with=* ) if [[ "$1" == --with ]]; then @@ -2253,7 +2394,7 @@ subcommand_search() { done ;; -a | --all-releases ) - opt_use_releases="${PMODULES_DEFINED_RELEASES}" + opt_use_rel_stages+="${ReleaseStages}" ;; --src ) # :FIXME: do we have to add some sanity checks here? @@ -2275,14 +2416,11 @@ subcommand_search() { shift done if [[ -z "${src_prefix}" ]]; then - local saved_IFS="${IFS}"; - IFS=':' - local -a src_prefix=(${PMODULES_OVERLAYS}) - IFS=${saved_IFS} + local -a src_prefix=( "${OverlayList[@]}" ) fi - if [[ "${opt_use_releases}" == ":" ]]; then - opt_use_releases=":${UsedReleases}:" + if [[ "${opt_use_rel_stages}" == ":" ]]; then + opt_use_rel_stages=":${UsedReleaseStages}:" fi if [[ ${#modules[@]} == 0 ]]; then @@ -2368,7 +2506,7 @@ subcommand_help() { print_help "${arg}" else # :FIXME: print help of newest *available* module - # (respecting UsedReleases) + # (respecting UsedReleaseStages) "${modulecmd}" "${Shell}" "${subcommand}" "${arg}" fi done @@ -2607,16 +2745,91 @@ if [[ -z "${Subcommands[${subcommand}]}" ]]; then std::die 1 "${CMD}: unknown sub-command -- ${subcommand}" fi +_init_overlay_vars() { + declare -ag OverlayList=( "${PMODULES_ROOT}" ) + declare -Ag OverlayDict=([${PMODULES_ROOT}]="0") + local overlay + for overlay in "${!OverlayDict[@]}"; do + local group + for group in ${UsedGroups//:/ }; do + local dir="${overlay}/${group}/${PMODULES_MODULEFILES_DIR}" + if [[ -d "${dir}" ]]; then + Dir2OverlayMap[${dir}]=${overlay} + fi + done + done + save_env 'yes' +} + +case ${subcommand} in + add ) + subcommand='load' + ;; + display ) + subcommand='show' + ;; + keyword ) + subcommand='apropos' + ;; + rm ) + subcommand='unload' + ;; + switch ) + subcommand='swap' + ;; +esac + if [[ -n ${PMODULES_ENV} ]]; then eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)" + if [[ -z ${Version} ]] || [[ ${Version} != ${PMODULES_VERSION} ]]; then + # the Pmodules version changed! + declare -g Version="${PMODULES_VERSION}" + _init_overlay_vars + # renamed in version 1.0.0rc10 and type changed from + # associative array to normal array + if [[ -v UseFlags ]]; then + declare -a UsedFlags=( "${!UseFlags[@]}" ) + unset UseFlags + fi + if [[ ! -v UsedFlags ]]; then + declare -a UsedFlags=() + fi + if [[ -v UsedReleases ]]; then + declare -- UsedReleaseStages="${UsedReleases}" + unset UsedReleases + fi + if [[ -v PMODULES_DEFAULT_GROUPS ]]; then + declare -- DefaultGroups="${PMODULES_DEFAULT_GROUPS}" + unset PMODULES_DEFAULT_GROUPS + fi + if [[ -v PMODULES_DEFINED_RELEASES ]]; then + declare -- ReleaseStages="${PMODULES_DEFINED_RELEASES}" + unset PMODULES_DEFINED_RELEASES + fi + if [[ -v PMODULES_DEFAULT_RELEASES ]]; then + declare -- DefaultReleaseStages="${PMODULES_DEFAULT_RELEASES}" + unset PMODULES_DEFAULT_RELEASES + fi + g_env_must_be_saved='yes' + fi else pmodules_init fi if (( ${#GroupDepths[@]} == 0 )); then - scan_groups "${!Overlays[@]}" + scan_groups "${!OverlayDict[@]}" fi +case ${subcommand} in + load|purge|search|swap ) + declare -r tmpfile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \ + || std::die 1 "Oops: unable to create tmp file!" + ;; + * ) + declare -r tmpfile='' + ;; +esac + declare options options=$( "${getopt}" ${Options[${subcommand}]} -- -- "${opts[@]}" "$@" ) \ || print_help "${subcommand}" diff --git a/Pmodules/modulefile b/Pmodules/modulefile index 65aa4c1..1893fdb 100644 --- a/Pmodules/modulefile +++ b/Pmodules/modulefile @@ -8,3 +8,17 @@ module-maintainer "Achim Gsell " module-help " Pmodules are a hierarchical module environment based on Environment Modules. " + +# +# It might be that '${PMODULES_ROOT}/Tools/Pmodules/VERSION' is in PATH. +# Why? With older version the PATH might have been set without loading +# a module. +# +if { [module-info mode load] } { + set PATH ":$::env(PATH):" + if { [regexp -- {.*:(.*/Pmodules/[^:]*):} $PATH -> str] } { + if { [string compare $str $PREFIX/bin] != 0 } { + remove-path PATH $str + } + } +} diff --git a/Pmodules/profile.bash.in b/Pmodules/profile.bash.in index 3cc380c..f25eee8 100644 --- a/Pmodules/profile.bash.in +++ b/Pmodules/profile.bash.in @@ -3,36 +3,11 @@ # The following settings are system defaults. They can be (re-)defined # in a system wide profile or in a user's profile. -# set groups which should be available after initialization -: ${PMODULES_DEFAULT_GROUPS:='Tools Programming'} - -# set releases which should be available after initialization -: ${PMODULES_DEFAULT_RELEASES:='stable'} - # set default version : ${PMODULES_VERSION:=@PMODULES_VERSION@} -############################################################################# -# N O C H A N G E S B E L O W T H I S L I N E ! # -# -# Notes: -# - PMODULES_ROOT is derived from the location of this file. -# - Some for PMODULES_CONFIG_DIR. -# - The Pmodules software must be installed in -# ${PMODULES_ROOT}/Tools/Pmodules/${PMODULES_VERSION} -# - -export PMODULES_DEFAULT_GROUPS -export PMODULES_DEFAULT_RELEASES -export PMODULES_VERSION - -declare -x PMODULES_MODULEFILES_DIR='modulefiles' -declare -x PMODULES_DEFINED_RELEASES=':unstable:stable:deprecated:' - declare -x PMODULES_ROOT=$(cd $(dirname "${BASH_SOURCE}")/.. && pwd) -declare -x PMODULES_CONFIG_DIR=$(basename $(cd $(dirname "${BASH_SOURCE}") && pwd)) declare -x PMODULES_HOME="${PMODULES_ROOT}/Tools/Pmodules/${PMODULES_VERSION}" -declare -x PMODULES_DIR="${PMODULES_HOME}" test -r "${PMODULES_HOME}/init/bash" && source "$_" diff --git a/Pmodules/profile.csh.in b/Pmodules/profile.csh.in index 0047bdb..b00013e 100755 --- a/Pmodules/profile.csh.in +++ b/Pmodules/profile.csh.in @@ -1,7 +1,7 @@ #!/bin/tcsh -setenv PMODULES_DEFAULT_GROUPS 'Tools Programming' -setenv PMODULES_DEFAULT_RELEASES 'stable' +setenv DefaultGroups 'Tools Programming' +setenv DefaultReleaseStages 'stable' if ( ! $?PMODULES_VERSION ) then setenv PMODULES_VERSION "@PMODULES_VERSION@" endif @@ -14,7 +14,7 @@ endif ############################################################################# setenv PMODULES_MODULEFILES_DIR 'modulefiles' -setenv PMODULES_DEFINED_RELEASES ':unstable:stable:deprecated:' +setenv ReleaseStages ':unstable:stable:deprecated:' setenv PMODULES_ROOT "@PMODULES_ROOT@" setenv PMODULES_CONFIG_DIR 'config' diff --git a/Pmodules/profile.zsh.in b/Pmodules/profile.zsh.in index 83a2ac9..8077e79 100644 --- a/Pmodules/profile.zsh.in +++ b/Pmodules/profile.zsh.in @@ -7,12 +7,12 @@ # ${PMODULES_ROOT}/Tools/Pmodules/${PMODULES_VERSION} # -declare -xa PMODULES_DEFAULT_GROUPS -declare -xa PMODULES_DEFAULT_RELEASES +declare -xa DefaultGroups +declare -xa DefaultReleaseStages declare -x PMODULES_VERSION -: ${PMODULES_DEFAULT_GROUPS:=(Tools Programming)} -: ${PMODULES_DEFAULT_RELEASES:=(stable)} +: ${DefaultGroups:=(Tools Programming)} +: ${DefaultReleaseStages:=(stable)} : ${PMODULES_VERSION:=@PMODULES_VERSION@} ############################################################################# @@ -20,7 +20,7 @@ declare -x PMODULES_VERSION ############################################################################# declare -x PMODULES_MODULEFILES_DIR='modulefiles' -declare -x PMODULES_DEFINED_RELEASES=':unstable:stable:deprecated:' +declare -x ReleaseStages=':unstable:stable:deprecated:' declare -x PMODULES_ROOT=$(cd $(dirname "${(%):-%N}")/.. && pwd) declare -x PMODULES_CONFIG_DIR=$(basename $(cd $(dirname "${(%):-%N}") && pwd)) diff --git a/Pmodules/zsh b/Pmodules/zsh index 80dc338..f1a0a4c 100644 --- a/Pmodules/zsh +++ b/Pmodules/zsh @@ -70,7 +70,7 @@ declare -x _LMFILES_='' # build initial MODULEPATH declare -x MODULEPATH='' typeset -T MODULEPATH modulepath -for group in ${PMODULES_DEFAULT_GROUPS[@]}; do +for group in ${DefaultGroups[@]}; do dir="${PMODULES_ROOT}/${group}/${PMODULES_MODULEFILES_DIR}" modulepath=( "${dir}" ${(m)modulepath:#${dir}} ) done @@ -78,16 +78,16 @@ done # build initial list of used releases declare -x UsedReleases='' typeset -T UsedReleases usedreleases -for r in ${PMODULES_DEFAULT_RELEASES[@]}; do +for r in ${DefaultReleaseStages[@]}; do usedreleases=( "${r}" ${(m)usedreleases:#${r}} ) done -eval $(save_env UsedReleases PMODULES_DEFAULT_RELEASES PMODULES_DEFAULT_GROUPS PMODULES_DEFINED_RELEASES) +eval $(save_env UsedReleases DefaultReleaseStages DefaultGroups ReleaseStages) unset UsedReleases -unset PMODULES_DEFAULT_RELEASES -unset PMODULES_DEFAULT_GROUPS -unset PMODULES_DEFINED_RELEASES +unset DefaultReleaseStages +unset DefaultGroups +unset ReleaseStages # initialize MANPATH with output of `man --path` if not set [[ -z "${MANPATH}" ]] && manpath=$( man --path ) diff --git a/Tools/tcllib/build b/Tools/tcllib/build new file mode 100755 index 0000000..ea151ba --- /dev/null +++ b/Tools/tcllib/build @@ -0,0 +1,5 @@ +#!/usr/bin/env modbuild + +pbuild::set_download_url "https://core.tcl-lang.org/tcllib/uv/$P-$V.tar.xz" + + diff --git a/build b/build index 3495bcb..755daf8 100755 --- a/build +++ b/build @@ -338,10 +338,6 @@ pmodules::compile() { echo " root of Pmodules environment: ${prefix}" echo " Pmodule prefix: ${PMODULES_HOME}" - #if [[ ! -f "${PMODULES_HOME}/${UTILBIN_DIR}/base64" ]] || [[ ${opt_force} == 'yes' ]]; then - # build coreutils - #fi - if [[ "${OS}" == 'Darwin' ]]; then if [[ ! -f "${PMODULES_HOME}/${UTILBIN_DIR}/getopt" ]] || [[ ${opt_force} == 'yes' ]]; then build getopt @@ -356,12 +352,12 @@ pmodules::compile() { build bash fi - if [[ ! -f "${PMODULES_HOME}/sbin/find" ]] || [[ ${opt_force} == 'yes' ]]; then - build findutils + if [[ ! -e "${PMODULES_HOME}/${UTILBIN_DIR}/tclsh" ]] || [[ ${opt_force} == 'yes' ]]; then + build Tcl fi - if [[ ! -e "${PMODULES_HOME}/sbin/tclsh" ]] || [[ ${opt_force} == 'yes' ]]; then - build Tcl + if [[ ! -e "${PMODULES_HOME}/lib/tcllib1.20" ]] || [[ ${opt_force} == 'yes' ]]; then + build tcllib fi if [[ ! -e "${PMODULES_HOME}/libexec/modulecmd.bin" ]] || [[ ${opt_force} == 'yes' ]]; then @@ -499,6 +495,9 @@ pmodules::install() { sed "${sed_cmd}" "${SRC_DIR}/modmanage.bash.in" > "${PMODULES_HOME}/libexec/modmanage.bash" chmod 0755 "${PMODULES_HOME}/libexec/modmanage.bash" + test -e "${PMODULES_ROOT}/${CONFIG_DIR}/Pmodules.conf" || \ + install -m 0644 "${SRC_DIR}/Pmodules.conf" "${PMODULES_ROOT}/${CONFIG_DIR}" + install -m 0644 "${SRC_DIR}/bash" "${PMODULES_HOME}/init" install -m 0644 "${SRC_DIR}/bash_completion" "${PMODULES_HOME}/init" install -m 0644 "${SRC_DIR}/csh" "${PMODULES_HOME}/init" diff --git a/config/modbuild.conf.in b/config/modbuild.conf.in index 60d33c3..cfce2aa 100644 --- a/config/modbuild.conf.in +++ b/config/modbuild.conf.in @@ -12,7 +12,7 @@ declare -x PMODULES_MODULEFILES_DIR='modulefiles' declare -x PMODULES_HOME="${PMODULES_ROOT}/Tools/Pmodules/${PMODULES_VERSION}" -declare -x PMODULES_DEFAULT_GROUPS='Tools Programming' -declare -x PMODULES_DEFINED_RELEASES=':unstable:stable:deprecated:' -declare -x PMODULES_DEFAULT_RELEASES='stable' +declare -x DefaultGroups='Tools Programming' +declare ReleaseStages=':unstable:stable:deprecated:' +declare DefaultReleaseStages='stable' diff --git a/config/versions.conf b/config/versions.conf index 37deb08..15a3cf2 100644 --- a/config/versions.conf +++ b/config/versions.conf @@ -4,5 +4,6 @@ findutils 4.7.0 getopt 1.1.6 gettext 0.21 modules 3.2.10.1 -Pmodules 1.1.0 +Pmodules 1.1.2 Tcl 8.6.10 +tcllib 1.20