diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc8906..fd9a77f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,60 @@ # Changelog of Pmodules -## Version 1.1.10 +## Version 1.1.10 (not yet tagged) * **modulecmd** * *User visible changes* - * ... + * New options for `module search`. + * With the option `--group` the search can be restricted to a + group. + * With the option `--newest` only the newest versions are + displayed. + * `find` as alias for the sub-command `search` added. + * Bugfix: the sub-commands `whatis` and `keyword|apropos` were + broken by design. + * Bugfix: after loading a `Pmodules` module, it was not shown with + `module list`. + * Bugfix in scanning the depth of groups. + * Bugfix: after `module purge` the environment variable + `PMODULES_HOME` was not defined an more. + * Bugfix: source the shell init file only if a `Pmodules` module + is loaded. + * Bugfix: unsetting aliases in modulefiles was not handled + properly in `module purge` * *Internal changes and fixes* - * ... + * initialisation error for bash and zsh fixed * **build-system** * *User visible changes* - * ... - * *Internal changes and fixes* - * ... + * `modbuild` is now defined as function like `module`. Therefor no + `Pmodules` module must be loaded to build a module with `modbuild` + * The system can now be defined in the module (YAML) configuration + file. + * Build dependencies can (and should) now be specified with + `build_requires` in the YAML configuration file. + * Bugfix: cleanup of modulefiles in overlays fixed. A module can be in + more than one overlay. These overlays must be specified in the + module configuration file. + * Bugfix: querying dependencies from YAML configuration file + fixed. Under some conditions the string 'null' was in the list + of dependencies. + * Bugfix: create group directory if it doesn't exist. + * Bugfix: create the module `$PREFIX` before processing the + install targets not before all targets. If `$PREFIX` is created + before processing any target and the build fails, `modbuild` + assumes that the module have been already built successfully. + * *Internal changes and fixes* + * code review/re-factoring + * `modbuild` is now using the Bash installed in `Pmodules` itself. + * test code with `set -o nounset`, several issues with this + setting fixed (not necessarily bugs). * **other changes** - * ... + * The build script to bootstrap Pmodules itself doesn't use modbuild + any more to compile required software packages. With this change + we can remove some special cases from modbuild. + * The bootstrap script requires Bash 5.0 or newer now. + * Bugfix: in the `Pmodules` modulefile force the sourcing of the + shell init script while in mode `load` only. ## Version 1.1.9 * **modulecmd** @@ -47,6 +87,10 @@ - to override the overlay in a YAML variants file. * The new keyword `with` has been introduced in YAML variants file to specified hierarchical dependencies. + * The function `pbuild::supported_os` has been + removed. `pbuild::supported_systems` provides the same + functionality for legacy configuration files. In YAML module + configuration files `systems` have to be used. * **Internal changes and fixes** * bugfix in setting `PATH` diff --git a/Pmodules/libpbuild.bash b/Pmodules/libpbuild.bash index ddb954f..4d766ca 100644 --- a/Pmodules/libpbuild.bash +++ b/Pmodules/libpbuild.bash @@ -116,149 +116,26 @@ pbuild.verbose() { readonly -f pbuild.verbose -############################################################################### +#****************************************************************************** # # function in the "namespace" (with prefix) 'pbuild::' can be used in # build-scripts # -#.............................................................................. +############################################################################### # -# compare two version numbers +# general functions # -# original implementation found on stackoverflow: -# https://stackoverflow.com/questions/4023830/how-to-compare-two-strings-in-dot-separated-version-format-in-bash -# -pbuild::version_compare () { - is_uint() { - [[ $1 =~ ^[0-9]+$ ]] - } - - [[ $1 == $2 ]] && return 0 - local IFS=. - local i ver1=($1) ver2=($2) - - # fill empty fields in ver1 with zeros - for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do - ver1[i]=0 - done - for ((i=0; i<${#ver1[@]}; i++)); do - [[ -z ${ver2[i]} ]] && ver2[i]=0 - if is_uint ${ver1[i]} && is_uint ${ver2[i]}; then - ((10#${ver1[i]} > 10#${ver2[i]})) && return 1 - ((10#${ver1[i]} < 10#${ver2[i]})) && return 2 - else - [[ ${ver1[i]} > ${ver2[i]} ]] && return 1 - [[ ${ver1[i]} < ${ver2[i]} ]] && return 2 - fi - done - return 0 -} -readonly -f pbuild::version_compare #.............................................................................. -# version less than -# -# return 0 if version passed in $1 is older then $2 -# -pbuild::version_lt() { - pbuild::version_compare "$1" "$2" - (( $? == 2 )) -} -readonly -f pbuild::version_lt - -#.............................................................................. -# version less than or equal -# -# return 0 if version passed in $1 is older or equal then $2 -# -pbuild::version_le() { - pbuild::version_compare "$1" "$2" - local -i exit_code=$? - (( exit_code == 0 || exit_code == 2 )) -} -readonly -f pbuild::version_le - -#.............................................................................. -# version greater than -# -# return 0 if version passed in $1 is newer then $2 -# -pbuild::version_gt() { - pbuild::version_compare "$1" "$2" - (( $? == 1 )) - local -i exit_code=$? - (( exit_code == 0 || exit_code == 1 )) -} -readonly -f pbuild::version_gt - -#.............................................................................. -# version greater than -# -# return 0 if version passed in $1 and $2 are equal -# -pbuild::version_eq() { - pbuild::version_compare "$1" "$2" -} -readonly -f pbuild::version_eq - -############################################################################## -# -# Set flag to build module in source tree. -# -# Arguments: -# none -# -pbuild::compile_in_sourcetree() { - BUILD_DIR="${SRC_DIR}" -} -readonly -f pbuild::compile_in_sourcetree - -############################################################################## -# -# Check whether the script is running on a supported OS. -# -# Arguments: -# $@: supported opertating systems (something like RHEL6, macOS10.14, ...). -# Default is all. -# -pbuild::supported_systems() { - SUPPORTED_SYSTEMS+=( "$@" ) -} -readonly -f pbuild::supported_systems - -############################################################################## -# -# Check whether the script is running on a supported OS. -# -# Arguments: -# $@: supported opertating systems (like Linux, Darwin). -# Default is all. -# -pbuild::supported_os() { - SUPPORTED_OS+=( "$@" ) -} -readonly -f pbuild::supported_os - -############################################################################## -# -# Check whether the loaded compiler is supported. -# -# Arguments: -# $@: supported compiler (like GCC, Intel, PGI). -# Default is all. -# -pbuild::supported_compilers() { - SUPPORTED_COMPILERS+=( "$@" ) -} -readonly -f pbuild::supported_compilers - -############################################################################## # # Install module in given group. # +# Note: +# This function is deprecated with YAML module configuration files. +# # Arguments: -# $1: group +# $1: group # pbuild::add_to_group() { if (( $# == 0 )); then @@ -266,23 +143,26 @@ pbuild::add_to_group() { "%s " "${module_name}/${module_version}:" \ "${FUNCNAME}: missing group argument." fi - GROUP="$1" + if (( $# > 1 )); then + std::die 42 \ + "%s " "${module_name}/${module_version}:" \ + "${FUNCNAME}: only one argument is allowed." + fi + if [[ ${yaml_config} == 'yes' ]]; then + std::info \ + "Using ${FUNCNAME} is deprecated with YAML module configuration files." + fi + pbuild.add_to_group "$@" } readonly -f pbuild::add_to_group -############################################################################## -# -# Set documentation file to be installed. -# -# Arguments: -# $@: documentation files relative to source -# -pbuild::install_docfiles() { - MODULE_DOCFILES+=("$@") +declare -gx GROUP='' +pbuild.add_to_group(){ + GROUP="$1" } -readonly -f pbuild::install_docfiles +readonly -f pbuild.add_to_group -############################################################################## +#.............................................................................. # # Test whether a module with the given name is available. If yes, return # release @@ -315,8 +195,155 @@ pbuild::module_is_avail() { } readonly -f pbuild::module_is_avail +#.............................................................................. +# +# compare two version numbers +# +# pbuild::version_compare +# - returns 0 if the version numbers are equal +# - returns 1 if first version number is higher +# - returns 2 if second version number is higher +# +# pbuild::version_lt +# - returns 0 if second version number is higher +# pbuild::version_le +# - returns 0 if second version number is higher or equal +# pbuild::version_gt +# - returns 0 if first version number is higher +# pbuild::version_ge +# - returns 0 if first version number is higher or equal +# pbuild::version_eq +# - returns 0 if version numbers are equal +# +# otherwise a value != 0 is returned +# +# Arguments: +# $1 first version number +# $2 second version number +# +# Note: +# Original implementation found on stackoverflow: +# https://stackoverflow.com/questions/4023830/how-to-compare-two-strings-in-dot-separated-version-format-in-bash +# +pbuild::version_compare () { + is_uint() { + [[ $1 =~ ^[0-9]+$ ]] + } + + [[ $1 == $2 ]] && return 0 + local IFS=. + local i ver1=($1) ver2=($2) + + # fill empty fields in ver1 with zeros + for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do + ver1[i]=0 + done + for ((i=0; i<${#ver1[@]}; i++)); do + [[ -z ${ver2[i]} ]] && ver2[i]=0 + if is_uint ${ver1[i]} && is_uint ${ver2[i]}; then + ((10#${ver1[i]} > 10#${ver2[i]})) && return 1 + ((10#${ver1[i]} < 10#${ver2[i]})) && return 2 + else + [[ ${ver1[i]} > ${ver2[i]} ]] && return 1 + [[ ${ver1[i]} < ${ver2[i]} ]] && return 2 + fi + done + return 0 +} +readonly -f pbuild::version_compare + +pbuild::version_lt() { + pbuild::version_compare "$1" "$2" + (( $? == 2 )) +} +readonly -f pbuild::version_lt + +pbuild::version_le() { + pbuild::version_compare "$1" "$2" + local -i exit_code=$? + (( exit_code == 0 || exit_code == 2 )) +} +readonly -f pbuild::version_le + +pbuild::version_gt() { + pbuild::version_compare "$1" "$2" + (( $? == 1 )) + local -i exit_code=$? + (( exit_code == 1 )) +} +readonly -f pbuild::version_gt + +pbuild::version_ge() { + pbuild::version_compare "$1" "$2" + (( $? == 1 )) + local -i exit_code=$? + (( exit_code == 0 || exit_code == 1 )) +} +readonly -f pbuild::version_gt + +pbuild::version_eq() { + pbuild::version_compare "$1" "$2" +} +readonly -f pbuild::version_eq + +#.............................................................................. +# +# Check whether the loaded compiler is supported. +# +# Arguments: +# $@: supported compiler (like GCC, Intel, PGI). +# Default is all. +# +pbuild::supported_compilers() { + if [[ ${yaml_config} == 'yes' ]]; then + std::info \ + "Using ${FUNCNAME} is deprecated with YAML module configuration files." + fi + pbuild.supported_compilers "$@" +} +readonly -f pbuild::supported_compilers + +declare SUPPORTED_COMPILERS=() +pbuild.supported_compilers(){ + SUPPORTED_COMPILERS+=( "$@" ) +} +readonly -f pbuild.supported_compilers + +#.............................................................................. +# +# Check whether the script is running on a supported OS. +# +# Arguments: +# $@: supported opertating systems (something like RHEL6, macOS10.14, ...). +# Default is all. +# +pbuild::supported_systems() { + if [[ ${yaml_config} == 'yes' ]]; then + std::info \ + "Using ${FUNCNAME} is deprecated with YAML module configuration files." + fi + pbuild.supported_systems "$@" +} +readonly -f pbuild::supported_systems + +declare SUPPORTED_SYSTEMS=() +pbuild.supported_systems() { + SUPPORTED_SYSTEMS+=( "$@" ) +} + +#.............................................................................. +# +pbuild::use_flag() { + [[ "${USE_FLAGS}" =~ ":${1}:" ]] +} +readonly -f pbuild::use_flag + ############################################################################## # +# functions to prepare the sources + +#.............................................................................. +# # Set the download URL and name of downloaded file. # # Arguments: @@ -333,7 +360,7 @@ pbuild::set_download_url() { } readonly -f pbuild::set_download_url -############################################################################## +#.............................................................................. # # Set hash sum for file. # @@ -348,7 +375,7 @@ pbuild::set_sha256sum() { } readonly -f pbuild::set_sha256sum -############################################################################## +#.............................................................................. # # Unpack file $1 in directory $2 # @@ -361,23 +388,7 @@ pbuild::set_unpack_dir() { } readonly -f pbuild::set_unpack_dir -############################################################################## -# -# Use this C-compiler -# -# Arguments: -# $1 C-compiler to use. -# -pbuild::use_cc() { - [[ -x "$1" ]] || std::die 3 \ - "%s " "${module_name}/${module_version}:" \ - "Error in setting CC:" \ - "'$1' is not an executable!" - CC="$1" -} -readonly -f pbuild::use_cc - -############################################################################### +#.............................................................................. # pbuild::add_patch() { [[ -z "$1" ]] && \ @@ -393,7 +404,7 @@ pbuild::add_patch() { } readonly -f pbuild::add_patch -############################################################################### +#.............................................................................. # pbuild::set_default_patch_strip() { [[ -n "$1" ]] || \ @@ -405,35 +416,7 @@ pbuild::set_default_patch_strip() { } readonly -f pbuild::set_default_patch_strip -############################################################################### -# -pbuild::use_flag() { - [[ "${USE_FLAGS}" =~ ":${1}:" ]] -} -readonly -f pbuild::use_flag - -############################################################################### -# -pbuild::add_configure_args() { - CONFIGURE_ARGS+=( "$@" ) -} -readonly -f pbuild::add_configure_args - -############################################################################### -# -pbuild::use_autotools() { - configure_with='autotools' -} -readonly -f pbuild::use_autotools - -############################################################################### -# -pbuild::use_cmake() { - configure_with='cmake' -} -readonly -f pbuild::use_cmake - -############################################################################### +#.............................................................................. # # extract sources. For the time being only tar-files are supported. # @@ -595,6 +578,59 @@ pbuild::prep() { ############################################################################### # +# functions to configure the sources + +#.............................................................................. +# +pbuild::add_configure_args() { + CONFIGURE_ARGS+=( "$@" ) +} +readonly -f pbuild::add_configure_args + +#.............................................................................. +# +pbuild::use_autotools() { + configure_with='autotools' +} +readonly -f pbuild::use_autotools + +#.............................................................................. +# +pbuild::use_cmake() { + configure_with='cmake' +} +readonly -f pbuild::use_cmake + +#.............................................................................. +# +# Use this C-compiler +# +# Arguments: +# $1 C-compiler to use. +# +pbuild::use_cc() { + [[ -x "$1" ]] || std::die 3 \ + "%s " "${module_name}/${module_version}:" \ + "Error in setting CC:" \ + "'$1' is not an executable!" + CC="$1" +} +readonly -f pbuild::use_cc + +#.............................................................................. +# +# Set flag to build module in source tree. +# +# Arguments: +# none +# +pbuild::compile_in_sourcetree() { + BUILD_DIR="${SRC_DIR}" +} +readonly -f pbuild::compile_in_sourcetree + +#.............................................................................. +# # Configure the software to be compiled. # # Arguments: @@ -648,7 +684,11 @@ pbuild::configure() { } -############################################################################### +############################################################################## +# +# functions to compile the sources + +#.............................................................................. # # Default compile function. # @@ -663,7 +703,23 @@ pbuild::compile() { "compilation failed!" } -############################################################################### +############################################################################## +# +# functions to install everything + +#.............................................................................. +# +# Set documentation file to be installed. +# +# Arguments: +# $@: documentation files relative to source +# +pbuild::install_docfiles() { + MODULE_DOCFILES+=("$@") +} +readonly -f pbuild::install_docfiles + +#.............................................................................. # # Default install function. # @@ -677,7 +733,7 @@ pbuild::install() { "compilation failed!" } -############################################################################### +#.............................................................................. # pbuild::install_shared_libs() { local -r binary="$1" @@ -720,9 +776,26 @@ pbuild::install_shared_libs() { ############################################################################### # -# This is the main entry function called by modbuild! +# The following two functions are the entry points called by modbuild! # -pbuild.build_module() { + +declare yaml_config='yes' +pbuild.build_module_legacy(){ + yaml_config='no' + _build_module "$@" +} +readonly -f pbuild.build_module_legacy + +pbuild.build_module_yaml(){ + _build_module "$@" +} +readonly -f pbuild.build_module_yaml + +#.............................................................................. +# +# The real worker function. +# +_build_module() { declare -gx module_name="$1" declare -gx module_version="$2" declare -gx module_release="$3" @@ -987,7 +1060,6 @@ pbuild.build_module() { P="${module_name}" V="${module_version}" parse_version "${module_version}" - declare -gx GROUP='' declare -g PREFIX='' SOURCE_URLS=() @@ -995,9 +1067,6 @@ pbuild.build_module() { SOURCE_NAMES=() declare -Ag SOURCE_UNPACK_DIRS=() CONFIGURE_ARGS=() - SUPPORTED_SYSTEMS=() - SUPPORTED_OS=() - SUPPORTED_COMPILERS=() PATCH_FILES=() PATCH_STRIPS=() PATCH_STRIP_DEFAULT='1' @@ -1016,17 +1085,6 @@ pbuild.build_module() { "Not available for ${system}." } - #...................................................................... - check_supported_os() { - (( ${#SUPPORTED_OS[@]} == 0 )) && return 0 - for os in "${SUPPORTED_OS[@]}"; do - [[ ${os,,} == ${OS,,} ]] && return 0 - done - std::die 1 \ - "%s " "${module_name}/${module_version}:" \ - "Not available for ${OS}." - } - #...................................................................... check_supported_compilers() { (( ${#SUPPORTED_COMPILERS[@]} == 0 )) && return 0 @@ -1547,7 +1605,6 @@ pbuild.build_module() { # check whether this module is supported check_supported_systems - check_supported_os check_supported_compilers # setup module name and prefix set_full_module_name_and_prefix @@ -1583,7 +1640,7 @@ pbuild.build_module() { cleanup_modulefiles std::info "* * * * *\n" } -readonly -f pbuild.build_module +readonly -f _build_module # Local Variables: # mode: sh diff --git a/Pmodules/modbuild.in b/Pmodules/modbuild.in index a87ab5f..e7303ee 100755 --- a/Pmodules/modbuild.in +++ b/Pmodules/modbuild.in @@ -436,7 +436,7 @@ build_modules_legacy() { version="${tokens[0]#*/}" release="${tokens[1]}" with_modules=( "${tokens[@]:2}" ) - pbuild.build_module \ + pbuild.build_module_legacy \ "${name}" "${version}" \ "${release}" "${with_modules[@]}" done @@ -519,25 +519,25 @@ build_modules_yaml(){ local fname="$2" local version="$3" local idx="$4" - _result=$(${yq} -Ne e ".\"${version}\"[${idx}].systems" \ - "${fname}" 2>/dev/null) + _result=( $(${yq} -Ne e ".\"${version}\"[${idx}].systems" \ + "${fname}" 2>/dev/null) ) (( $? == 0 )) && return - _result=$(${yq} -Ne e ".systems" "${fname}" 2>/dev/null) + _result=( $(${yq} -Ne e ".systems" "${fname}" 2>/dev/null) ) (( $? == 0 )) && return - _result='any' + _result=() } - yaml_get_operating_systems(){ + yaml_get_compilers(){ local -n _result="$1" local fname="$2" local version="$3" local idx="$4" - _result=$(${yq} -Ne e ".\"${version}\"[${idx}].operating_systems" \ - "${fname}" 2>/dev/null) + _result=( $(${yq} -Ne e ".\"${version}\"[${idx}].compilers" \ + "${fname}" 2>/dev/null) ) (( $? == 0 )) && return - _result=$(${yq} -Ne e ".operating_systems" "${fname}" 2>/dev/null) + _result=( $(${yq} -Ne e ".compilers" "${fname}" 2>/dev/null) ) (( $? == 0 )) && return - _result='any' + _result=() } yaml_get_dependencies(){ @@ -595,6 +595,7 @@ build_modules_yaml(){ local -a deps=() local -a build_requires=() local relstage + local group local ol_name for (( i=0; i 0 )); then while read -a with_modules; do - pbuild.build_module \ + pbuild.build_module_yaml \ "${name}" "${v##*/}" \ "${relstage}" "${with_modules[@]}" done < <(bash_expand "" ${deps[@]}|${awk} "${pattern}") else - pbuild.build_module \ + pbuild.build_module_yaml \ "${name}" "${v##*/}" \ "${relstage}" fi @@ -655,7 +647,6 @@ build_modules_yaml(){ done } - build_modules() { if [[ -n $(ls "${BUILDBLOCK_DIR}/files/${BNAME_VARIANTS}"*.yaml 2>/dev/null) ]]; then build_modules_yaml "$@" diff --git a/Pmodules/modulecmd.bash.in b/Pmodules/modulecmd.bash.in index b4a2035..2f747ee 100644 --- a/Pmodules/modulecmd.bash.in +++ b/Pmodules/modulecmd.bash.in @@ -15,12 +15,12 @@ unset IFS # use default IFS shopt -s nullglob # used in some output messages only -declare -r CMD='module' +declare -r CMD='module' -declare -r mydir=$(cd $(dirname "$0") && pwd) -declare prefix=$(dirname "${mydir}") -declare -r libdir="${prefix}/lib" -declare -r libexecdir="${prefix}/libexec" +declare -r mydir=$(cd $(dirname "$0") && pwd) +declare -- prefix=$(dirname "${mydir}") +declare -r libdir="${prefix}/lib" +declare -r libexecdir="${prefix}/libexec" source "${libdir}/libstd.bash" source "${libdir}/libpmodules.bash" @@ -32,20 +32,16 @@ std::def_cmds "${path}" \ 'awk' 'base64' 'find' 'getopt' 'logger' 'mktemp' \ 'rm' 'sort' 'yq' -if [[ ${PMODULES_PURETCL} == yes ]]; then - declare -r modulecmd="${libexecdir}/modulecmd.tcl" +if [[ -n ${TCLLIBPATH} ]]; then + declare -x TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules:${TCLLIBPATH}" else - if [[ -n ${TCLLIBPATH} ]]; then - declare -x TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules:${TCLLIBPATH}" - else - declare -x TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules" - fi - declare -r modulecmd="${libexecdir}/modulecmd.bin" + declare -x TCLLIBPATH="${PMODULES_HOME}/lib/Pmodules" fi +declare -r modulecmd="${libexecdir}/modulecmd.bin" -declare verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} +declare -- verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'} -declare Shell='' +declare -- Shell='' # the following settings are used if the config file doesn't exist @@ -95,30 +91,31 @@ export_env() { } # -# Save/cache some variables. -# This function is called on exit via a trap handler. +# Save/cache state in the environment variable PMODULES_ENV. The content is +# base64 encoded. This function is called on exit via a trap handler. # -# Args; +# Arguments: # none # + declare g_env_must_be_saved='no' -encode_base64(){ - case "${os_name}" in - Linux ) - "${base64}" --wrap=0 <<< "$1" - ;; - Darwin ) - # does not wrap if running in a script - "${base64}" <<< "$1" - ;; - * ) - std::die 255 "Oops: Unsupported OS" - ;; - esac -} - save_env() { + encode_base64(){ + case "${os_name}" in + Linux ) + "${base64}" --wrap=0 <<< "$1" + ;; + Darwin ) + # does not wrap if running in a script + "${base64}" <<< "$1" + ;; + * ) + std::die 255 "Oops: Unsupported OS" + ;; + esac + } + [[ $1 == 'no' ]] && return 0 local vars=( Version ) vars+=( UsedReleaseStages UsedFlags UsedGroups ) @@ -133,6 +130,9 @@ save_env() { declare -gx PMODULES_ENV=$( encode_base64 "$s" ) } +# +# function called on exit +# _exit() { save_env "${g_env_must_be_saved}" export_env 'PMODULES_ENV' @@ -140,7 +140,6 @@ _exit() { ${rm} -f "${tmpfile}" || : fi } - trap '_exit' EXIT # @@ -179,6 +178,9 @@ get_release_stage() { fi } +# +# check whether the argument in $1 is a valid release stage. +# is_release_stage() { [[ :${ReleaseStages}: =~ :$1: ]] } @@ -188,9 +190,10 @@ is_release_stage() { # If yes, return 0 and the overlay with upvar of first argument # otherwise return 1 # -# $1 upvar to return overlay -# $2 upvar to return group -# $3 moduledir to check +# Arguments +# $1 upvar to return overlay +# $2 upvar to return group +# $3 moduledir to check # find_overlay () { local "$1" @@ -209,29 +212,52 @@ find_overlay () { return 0 } +# +# Check whether the module passed in argument $1 is loaded. +# module_is_loaded() { [[ :${LOADEDMODULES}: =~ :$1: ]] } # -# check shebang -# $1: file name to test +# Check shebang. +# +# Arguments: +# $1 file name to test +# is_modulefile() { local -r fname="$1" - local shebang + local -- shebang [[ -r ${fname} ]] || return 1 read -n 11 shebang < "${fname}" [[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]] } +# +# Get the value of _PREFIX. +# +# Arguments: +# $1 upvar to return result +# $2 modulefile +# get_module_prefix() { - local "$1" - local modulefile="$2" - local -r _prefix=$("${modulecmd}" bash show "${modulefile}" 2>&1 | \ + local -n _prefix="$1" + local -- _prefix=$("${modulecmd}" bash show "$2" 2>&1 | \ ${awk} '/_PREFIX |_HOME / {print $3; exit}') - std::upvar $1 "${_prefix}" } +# +# Generic wrappers of 'modulecmd': +# +# subcommand_generic0: +# no argument allowed +# subcommand_generic1: +# Exact one argument must be passed +# subcommand_generic1plus: +# One or more arguments must be passed +# +# The options to output help are always accepted. +# subcommand_generic0() { local -r subcommand="$1" shift @@ -754,7 +780,7 @@ subcommand_unload() { local arg for arg in "${args[@]}"; do local output=$("${modulecmd}" "${Shell}" 'unload' "${arg}") - eval "$(echo "${output}"|sed -e 's/;unalias [^;]*//g')" + eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')" case ${Shell} in sh | bash | zsh ) echo "${output}" @@ -1496,20 +1522,21 @@ subcommand_use() { if is_release_stage "${arg}"; then # argument is release stage std::append_path UsedReleaseStages "${arg}" - return + return $? fi if [[ "${arg}" =~ "flag=" ]]; then # argument is flag UsedFlags+=( "${arg/flag=}" ) - return + return $? fi if [[ -v OverlayInfo[${arg}:type] ]]; then use_overlay "${arg}" - return 0 + return $? fi if [[ -d ${arg} ]]; then local dir=$(cd "${arg}" && pwd -L) ${add2path_func} MODULEPATH "${dir}" + return $? fi if [[ ! -v GroupDepths[${arg}] ]]; then # this scan is required if a new group has been @@ -1519,7 +1546,7 @@ subcommand_use() { fi if [[ -n ${GroupDepths[${arg}]} ]]; then use_group "${arg}" - return + return $? fi std::die 3 "%s %s: %s -- %s" \ @@ -1771,7 +1798,10 @@ subcommand_refresh() { } # -# help function, used during initialization and for purging all modules +# Helper functions, used during initialization and for purging all modules. +# +# Arguments: +# none # init_modulepath() { declare -gx MODULEPATH='' @@ -1922,8 +1952,8 @@ subcommand_purge() { "${error_txt}" fi if [[ "${Shell}" == "sh" ]]; then - # for sh-like shells just echo - echo "${output}" + # for sh-like shells just echo + eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')" else # re-run with right shell "${modulecmd}" "${Shell}" 'purge' @@ -2041,14 +2071,17 @@ subcommand_clear() { # search [switches] [STRING...] # Subcommands[search]='search' +Subcommands[find]='search' Options[search]='-o a\?H -l help -l no-header -l print-modulefiles ' Options[search]+='-l release-stage: -l with: -l all-release-stages -l src: -l print-csv ' Options[search]+='-l verbose ' Options[search]+='-l all-deps -l wrap ' -Options[search]+='-l glob' +Options[search]+='-l glob ' +Options[search]+='-l newest ' +Options[search]+='-l group:' Help[search]=' USAGE: - module search [switches] STRING... + module find|search [switches] STRING... Search installed modules. If an argument is given, search for modules whose name match the argument. @@ -2088,6 +2121,7 @@ SWITCHES: subcommand_search() { local -r subcommand='search' local modules=() + local groups=() local with_modules='//' local -ir cols=$(tput cols) # get number of columns of terminal local -i max_len_modulename=0 @@ -2100,6 +2134,7 @@ subcommand_search() { local opt_all_deps='no' local opt_wrap='no' local opt_glob='no' + local opt_newest='no' #..................................................................... # @@ -2167,9 +2202,7 @@ subcommand_search() { } print_header_verbose() { - std::info '' - #std::info "${fmt}" "Module" "Rel.stage" "Group" "Overlay" "Dependencies/Modulefile" - #std::info '-%.0s' $(seq 1 ${cols}) + : } print_line_verbose() { @@ -2195,7 +2228,9 @@ subcommand_search() { } print_line_modulefile() { - std::info "$4" + if (( $# >= 4 )) && [[ -n $4 ]]; then + std::info "$1 $4" + fi } print_line_csv() { @@ -2218,11 +2253,15 @@ subcommand_search() { print_default fi + local _script='' + if [[ ${opt_newest} == 'yes' ]]; then + _script='{} END{print}' + fi ${func_print_header} while read -a toks; do ${func_print_line} "${toks[@]}" done < <("${sort}" --version-sort -k 1,1 -k 6,6 -k 7,7 "${tmpfile}" | \ - ${awk} "${with_modules}") + ${awk} "${with_modules} ${_script}") } #..................................................................... @@ -2235,72 +2274,65 @@ subcommand_search() { # :FIXME: # search () { - if [[ ${opt_glob} == 'yes' ]]; then - local -r module="$1" - else - local -r module="${1}*" - fi - + local module="$1" + local group="$2" # write results to a temporary file for later processing - local group - # loop over all groups - for group in "${!GroupDepths[@]}"; do - # loop over all directories which can be added to - # MODULEPATH inside current group - local depth=${GroupDepths[${group}]} - local s='' - if (( depth > 0 )); then - s=$(printf '/*%.0s' $(seq 1 ${depth})) - fi - local modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} ) + + # loop over all directories which can be added to + # MODULEPATH inside current group + local depth=${GroupDepths[${group}]} + local s='' + if (( depth > 0 )); then + s=$(printf '/*%.0s' $(seq 1 ${depth})) + fi + local modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} ) + + # get and print all available modules in $mpath + # with respect to the requested release stage + # tmpfile: module/version rel_stage group dependencies... + local mods + get_available_modules \ + mods \ + "${module}" \ + "${opt_use_rel_stages}" \ + "${modulepath[@]}" + local i=0 + for (( i=0; i<${#mods[@]}; i+=4 )); do + local name=${mods[i]} + local rel_stage=${mods[i+1]} + local modulefile=${mods[i+2]} + local ol=${mods[i+3]} - # get and print all available modules in $mpath - # with respect to the requested release stage - # tmpfile: module/version rel_stage group dependencies... - local mods - get_available_modules \ - mods \ - "${module}" \ - "${opt_use_rel_stages}" \ - "${modulepath[@]}" \ - - for (( i=0; i<${#mods[@]}; i+=4 )); do - local name=${mods[i]} - local rel_stage=${mods[i+1]} - local modulefile=${mods[i+2]} - local ol=${mods[i+3]} - - if (( ${#name} > max_len_modulename)); then - max_len_modulename=${#name} - fi - - if [[ "${opt_print_verbose}" == 'yes' ]] || [[ "${opt_all_deps}" == 'yes' ]]; then - local prefix='' - get_module_prefix prefix "${modulefile}" - local dependencies_file="${prefix}/.dependencies" - if [[ -n ${prefix} ]] && [[ -r "${dependencies_file}" ]]; then - deps=($(< "${dependencies_file}")) - else - deps=() - fi + if (( ${#name} > max_len_modulename)); then + max_len_modulename=${#name} + fi + + if [[ "${opt_print_verbose}" == 'yes' ]] || \ + [[ "${opt_all_deps}" == 'yes' ]]; then + local prefix='' + get_module_prefix prefix "${modulefile}" + local dependencies_file="${prefix}/.dependencies" + if [[ -n ${prefix} ]] && [[ -r "${dependencies_file}" ]]; then + deps=($(< "${dependencies_file}")) else - # get dependencies encoded in directory name - local deps=() - local -i j - 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 - unset IFS + deps=() fi - - echo ${name} ${rel_stage} ${group} ${modulefile} \ - ${ol} \ - ${deps[@]} >> "${tmpfile}" - done + else + # get dependencies encoded in directory name + local deps=() + local -i j + 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 + unset IFS + fi + + echo ${name} ${rel_stage} ${group} ${modulefile} \ + ${ol} \ + ${deps[@]} >> "${tmpfile}" done - print_result } while (( $# > 0 )); do @@ -2388,6 +2420,18 @@ subcommand_search() { --glob ) opt_glob='yes' ;; + --newest ) + opt_newest='yes' + ;; + --group | --group=* ) + if [[ $1 == *=* ]]; then + groups+=( ${1/--*=} ) + else + groups+=( $2 ) + shift + fi + ;; + -- ) shift 1 modules+=( "$@" ) @@ -2422,8 +2466,19 @@ subcommand_search() { fi local module + if (( ${#groups[@]} == 0 )); then + groups=( "${!GroupDepths[@]}" ) + fi for module in "${modules[@]}"; do - search "${module}" + if [[ ${opt_glob} != 'yes' ]]; then + module+="*" + fi + local group + for group in "${groups[@]}"; do + search "${module}" "${group}" + done + print_result + echo -n > ${tmpfile} done } @@ -2508,7 +2563,7 @@ subcommand_help() { # whatis # Subcommands[whatis]='whatis' -Options[whatis]='-o \?H -l help' +Options[whatis]='-o \?Ha -l help -l all' Help[whatis]=' USAGE: module whatis [modulefile...] @@ -2518,11 +2573,46 @@ USAGE: ' subcommand_whatis() { - if (( $# == 0 )); then - subcommand_generic0 'whatis' - else - subcommand_generic1plus 'whatis' "$@" - fi + local options=() + local args=() + while (( $# > 0 )); do + case $1 in + -\? | --help ) + print_help "${subcommand}" + ;; + -a | --all ) + options+=( '-a' ) + ;; + -- ) + shift 1 + args+=( "$@" ) + break + ;; + * ) + args+=( "$1" ) + ;; + esac + shift + done + + local group='' + for group in "${!GroupDepths[@]}"; do + local mod_name='' + local file_name='' + while read mod_name file_name; do + [[ -n ${file_name} ]] || continue + local whatis=$("${modulecmd}" bash \ + whatis \ + "${file_name}" \ + 2>&1 1>/dev/null) + printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2 + done < <(set +x; subcommand_search \ + --group "${group}" \ + --print-modulefiles \ + --newest \ + "${options[@]}" \ + "${args[@]}" 2>&1) + done } ############################################################################## @@ -2531,7 +2621,7 @@ subcommand_whatis() { # Subcommands[apropos]='apropos' Subcommands[keyword]='apropos' -Options[apropos]='-o \?H -l help' +Options[apropos]='-o \?Ha -l help -l all' Help[apropos]=' USAGE: module apropos string @@ -2541,7 +2631,55 @@ USAGE: ' subcommand_apropos() { - subcommand_generic1 'apropos' "$@" + local options=() + local args=() + while (( $# > 0 )); do + case $1 in + -\? | --help ) + print_help "${subcommand}" + ;; + -a | --all ) + options+=( '-a' ) + ;; + -- ) + shift 1 + args+=( "$@" ) + break + ;; + * ) + args+=( "$1" ) + ;; + esac + shift + done + if (( ${#args[@]} == 0 )); then + std::die 3 "%s %s: %s" \ + "${CMD}" "${subcommand}" \ + "no search string specified" + elif (( ${#args[@]} > 1 )); then + std::die 3 "%s %s: %s" \ + "${CMD}" "${subcommand}" \ + "more then one search string specified" + fi + local arg="${args[0]}" + local group='' + for group in "${!GroupDepths[@]}"; do + local mod_name='' + local file_name='' + while read mod_name file_name; do + [[ -n ${file_name} ]] || continue + local whatis=$("${modulecmd}" bash \ + whatis \ + "${file_name}" \ + 2>&1 1>/dev/null) + if [[ ${whatis,,} =~ ${arg,,} ]]; then + printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2 + fi + done < <(set +x; subcommand_search \ + --group "${group}" \ + --print-modulefiles \ + "${options[@]}" 2>&1) + done } ############################################################################## @@ -2691,6 +2829,11 @@ subcommand_initclear() { # # main # + +# parse arguments +# + +# first argument must be a shell! case "$1" in sh | bash | zsh ) declare Shell="sh" @@ -2704,6 +2847,7 @@ case "$1" in esac shift +# parse agruments till and including the sub-command declare -a opts=() while (( $# > 0 )); do case $1 in @@ -2738,38 +2882,25 @@ if [[ -z "${Subcommands[${subcommand}]}" ]]; then std::die 1 "${CMD}: unknown sub-command -- ${subcommand}" fi -case ${subcommand} in - add ) - subcommand='load' - ;; - display ) - subcommand='show' - ;; - keyword ) - subcommand='apropos' - ;; - rm ) - subcommand='unload' - ;; - switch ) - subcommand='swap' - ;; -esac - +# restore variables from last call +unset Version if [[ -n ${PMODULES_ENV} ]]; then eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)" fi +# Version should now be defined again, if not: +# - PMODULES_ENV was not set +# - Version was not defined the last time the status was saved. +# This is true for older Pmodules versions. + +# We (re-)initialise the Pmodules system, if +# - PMODULES_ENV was not set/is empty +# - A new Pmodules version has been loaded if [[ -z ${Version} ]] || [[ ${Version} != ${PMODULES_VERSION} ]]; then - # this can only happen if the last command was - # module load Pmodules/${PMODULES_VERSION} - # - # the values these two variables must be saved before initialising declare _tmp_loaded_modules_="${LOADEDMODULES}" declare _tmp_lmfiles_="${_LMFILES_}" pmodules_init - # restore and export LOADEDMODULES="${_tmp_loaded_modules_}" _LMFILES_="${_tmp_lmfiles_}" export_env \ @@ -2777,13 +2908,10 @@ if [[ -z ${Version} ]] || [[ ${Version} != ${PMODULES_VERSION} ]]; then _LMFILES_ fi -if (( ${#GroupDepths[@]} == 0 )); then - scan_groups "${UsedOverlays[@]}" - g_env_must_be_saved='yes' -fi - +# We need a tmp-file in the following sub-commands. It will be removed +# in the exit function if exists. case ${subcommand} in - load|purge|search|swap ) + load|purge|search|swap|whatis|apropos ) declare -r tmpfile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \ || std::die 1 "Oops: unable to create tmp file!" ;; @@ -2792,12 +2920,10 @@ case ${subcommand} in ;; esac - -tmp=$("${getopt}" --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \ - || print_help "${subcommand}" -eval args=( "$tmp" ) -unset tmp -subcommand_${Subcommands[$subcommand]} "${args[@]}" +# parse arguments of the sub-command and call it +set -- $("${getopt}" --unquoted --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \ + || print_help "${subcommand}" +subcommand_${Subcommands[$subcommand]} $@ # Local Variables: # mode: sh diff --git a/Pmodules/profile.zsh.in b/Pmodules/profile.zsh.in index a2f9d58..6accc21 100644 --- a/Pmodules/profile.zsh.in +++ b/Pmodules/profile.zsh.in @@ -7,9 +7,9 @@ ##### no changes below this line ###### export PMODULES_VERSION -declare __pm_root__=${(%):-%N} -declare -x PMODULES_HOME="${__pm_root__:h:h}/Tools/Pmodules/${PMODULES_VERSION}" -unset __pm_root__ +declare __this_file__=${(%):-%N} +declare -x PMODULES_HOME="${__this_file____:h:h}/Tools/Pmodules/${PMODULES_VERSION}" +unset __this_file__ test -r "${PMODULES_HOME}/init/zsh" && source "$_"