#!@BASH@ --noprofile PATH='/bin:/usr/bin' unset CDPATH # unset CDPATH, otherwise 'cd' prints the directoy! unset IFS # use default IFS shopt -s nullglob shopt -s extglob # used for some output only declare -r CMD='modmanage' declare mydir=$(cd $(dirname "$0") && pwd) declare prefix=$(dirname "${mydir}") declare libdir="${prefix}/lib" declare libexecdir="${prefix}/libexec" declare -r bindir="${prefix}/bin" source "${libdir}/libstd.bash" source "${libdir}/libpmodules.bash" _exit () { std::die 1 "\nInterrupted..." } trap '_exit' INT TERM _err () { std::info "\nOops: got an error in function '${FUNCNAME[1]}', line ${BASH_LINENO[0]}" std::die 1 "Aborting ..." } trap '_err' ERR path="/bin:/usr/bin:${bindir}" [[ $(uname -s) == 'Darwin' ]] && path+=":${libexecdir}" std::def_cmds "${path}" 'chown' 'dirname' 'mkdir' 'rsync' 'rm' 'getopt' 'find' 'modulecmd' unset mydir unset prefix unset libdir unset libexecdir # bindir we still need declare PMODULES_VERSION='@PMODULES_VERSION@' ############################################################################## # # help [module|sub-command] # Subcommands[help]='help' Options[help]='-o hHV\? -l version -l help' Help[help]=' USAGE: modmanage [switches] subcommand [subcommand-args]... SWITCHES: -h|-H|-?|--help this usage info -V|--version modules version & configuration options --debug enable debug output --dry-run dry run SUBCOMMANDS: + init [switches] TARGET_DIR + install [switches] module... + search [switches] [string|pattern]... + help [subcommand] ' subcommand_help() { local -r subcommand='help' local -a args=() while (( $# > 0 )); do case $1 in -h | -\? | -H | --help ) print_help "${subcommand}" ;; -V | --version ) print_help 'version' ;; -- ) shift 1 args+=( "$@" ) break ;; * ) args+=( "$1" ) ;; esac shift done for arg in "${args[@]}"; do if [[ -n "${Help[${arg}]}" ]] ; then print_help "${arg}" else std::die 1 "Unknown sub-command -- ${subcommand}" fi done } ############################################################################## # # Derive the module installation path from the modulefile path. # The passed modulefile must be absolute. # # Arguments: # $1: absolute module file path # get_module_prefix() { "${modulecmd}" bash show "$1" 2>&1 \ |awk '/_HOME |_PREFIX / {print $3; exit}' } ############################################################################## # # Derive the module release-file path from the module file-path. # # Arguments: # $1: module file-path # get_releasefile_name() { echo "$(${dirname} "$1")/.release-$(basename "$1")" } ############################################################################## # # Sync a module from one Pmodules environment to another: # - sync module installation # - sync modulefile # - sync release file # # Arguments: # $1: relative modulefile path (something like: Tools/gnuplot/5.0.0) # $2: source prefix of Pmodule environment # $3: target prefix of Pmodule environment # sync_module() { local -r rel_modulefile="$1" local -r src_root="$2" local -r target_root="$3" local -r src_prefix=$( get_module_prefix "${src_root}/${rel_modulefile}" ) local -r rel_prefix=${src_prefix#${src_root}/} local -r target_prefix="${target_root}/${rel_prefix}" # install/update module if [[ ! -d "${target_prefix}" ]] || [[ "${force}" == 'yes' ]]; then ${mkdir} -p "${target_prefix}" || exit $? ${rsync} --links --perms --recursive --delete \ "${src_prefix}/" \ "${target_prefix}/" || exit $? fi # create modulefile direcrory and install modulefile local -r src_modulefile="${src_root}/${rel_modulefile}" local -r target_modulefile="${target_root}/${rel_modulefile}" ${mkdir} -p "$(${dirname} "${target_modulefile}")" || exit $? if [[ -e "${src_modulefile}" ]]; then ${rsync} --links --perms --recursive \ "${src_modulefile}" "${target_modulefile}" || exit $? fi # install release-file local -r rel_releasefile=$( get_releasefile_name "${rel_modulefile}" ) local -r src_releasefile="${src_root}/${rel_releasefile}" local -r target_releasefile="${target_root}/${rel_releasefile}" if [[ -e "${src_releasefile}" ]]; then ${rsync} --links --perms --recursive \ "${src_releasefile}" "${target_releasefile}" || exit $? fi } ############################################################################## # # initialize a new module environment # # # Subcommands[init]='init' Options[init]='-o \?hfy -l src: -l user: -l help -l force -l yes' Help[init]=" USAGE: modmanage init [switches] TARGET_DIR Initialize a new minimal Pmodule environment in TARGET_DIR. A user must be specified with '--user=' if the programm is executed as root. SWITCHES: --user If this scripts runs with root privileges, a user name ore ID must be specified. --force|--yes|-f|-y re-initialise an already existing Pmodule environment. " subcommand_init() { check_env() { [[ -n "${PMODULES_ROOT}" ]] && [[ -d "${PMODULES_ROOT}" ]] && [[ -n "${PMODULES_HOME}" ]] && [[ -n "${PMODULES_VERSION}" ]] || \ std::die 1 " Error: the module environment you are going to use as source has not been initialized properly!" } #..................................................................... # # Sync the Pmodules configuration and templates # # Arguments: # $1: source prefix of Pmodule environment # $2: target prefix of Pmodule environment # sync_config() { src="$1/${PMODULES_CONFIG_DIR}/" dst="$2/${PMODULES_CONFIG_DIR}/" ${rsync} --links --perms \ "${src}"/profile.{bash,csh,zsh} "${dst}" \ || return $? ${rsync} --links --perms \ "${src}"/profile.{bash,csh,zsh}-"${PMODULES_VERSION}" "${dst}" \ || return $? ${rsync} --links --perms \ "${src}/Pmodules.conf" "${dst}" \ || return $? ${rsync} --links --perms \ "${src}/modbuild.conf" "${dst}" \ || return $? } local target_root=() local user='' while (($# > 0)); do case $1 in -h | -H | -\? | --help | -help ) print_help "${subcommand}" ;; --force | --yes | -f | -y ) force='yes' ;; --user | --user=* ) if [[ "$1" == '--user' ]]; then user="$2" shift else user="${1#--*=}" fi ;; -- ) : ;; * ) # assign and remove trailing slashes target_root="${1%%*([\/])}" ;; esac shift done if [[ -z ${target_root} ]]; then std::die 1 "Error: no target directory specified!" fi local -i euid=$(id -u) if (( euid == 0 )); then [[ -n "${user}" ]] || \ std::die 1 "Error: --user parameter is required!" id -u "${user}" > /dev/null 2>&1 || \ std::die 1 "Error: Unable to retrieve user id of user '${user}'" else [[ -z "${user}" ]] || \ std::die 1 "Error: --user option is only allowed if running as root!" fi local src_root="$(std::get_abspath "${bindir}/../../../..")" local config_file="${src_root}/${PMODULES_CONFIG_DIR}/profile.bash" if [[ -r "${config_file}" ]]; then source "${config_file}" fi check_env || \ std::die 1 "Giving up..." echo "Creating a minimal Pmodule environment from the environment at" echo " ${PMODULES_ROOT}" echo "in" echo " ${target_root}" if [[ -d "${target_root}" ]] && [[ ${force} == no ]]; then echo "Warning: ${target_root} already exists." std::get_YN_answer \ "Do you really want to re-run the initialization? (y/N) " \ || std::die 1 "Abort ..." fi force='yes' echo "Creating target directory '${target_root}'..." ${mkdir} -p "${target_root}" || \ std::die 1 "Error: make directory failed!" echo "Syncing configuration ..." sync_config "${src_root}" \ "${target_root}" || \ std::die 1 "Error: configuration synchronization failed!" echo "Syncing Pmodules ${PMODULES_VERSION} from '${src_root}' to '${target_root}'..." sync_module "Tools/${PMODULES_MODULEFILES_DIR}/Pmodules/${PMODULES_VERSION}" \ "${src_root}" \ "${target_root}" || \ std::die 1 "Error: sync Pmodules failed!" for d in "${src_root}"/*/${PMODULES_MODULEFILES_DIR}; do ${mkdir} -p "${target_root}/${d#${src_root}/}" done if [[ -n "${user}" ]]; then echo "Changing user of new module environment to '${user}'..." ${chown} -R "${user}" "${target_root}" || \ std::die 1 "Error: changing owner failed!" echo fi echo "SourceRoot=${src_root}" > "${target_root}/${PMODULES_CONFIG_DIR}/modmanage.conf" echo "New minimal module environment created at '${target_root}'." echo "To use this environment, execute" echo " sudo ln -fs ${target_root} /opt/psi" echo " source /opt/psi/${PMODULES_CONFIG_DIR}/profile.bash" } ############################################################################## # # sub-command 'install' # # Arguments: Subcommands[install]='install' Options[install]='-o hf -l force -l with: -l help -l src: -l target:' Help[install]=' USAGE: modmanage install [switches] ... Install modules SWITCHES: --force] | -f Install module even it already exists --src Install from module environment in --with Install module(s) in this sub-group only Install modules matching given pattern ' subcommand_install() { local -r subcommand='install' local opts='' local -a with=() local -a module_pattern=() local src_root="${PMODULES_INSTALL_SOURCE}" local target_root="${PMODULES_ROOT}" local modulefile='' local -A modules_to_install local -A dependencies_to_install local -A group_map local -a modulepath=() #...................................................................... # set_initial_modulepath() { local group for group in "${!GroupDepths[@]}"; do (( ${GroupDepths[${group}]} == 0 )) || continue modulepath+=( "${src_root}/${group}/${PMODULES_MODULEFILES_DIR}" ) done } #...................................................................... # create_group_map() { # # For the dependency resolution we need to know, whether a # module - if loaded - adds a hierarchical group to MODULEPATH # or not. # # Examples: # Loading a compiler adds the hierarchical group for # this compiler. The command # module load gcc/10.3.0 # prepends # /Compiler/modulefiles/gcc/10.3.0 # to MODULEPATH. # # The dependency files do not convey the information whether # loading a module extends MODULEPATH or not. What we need to # know is # 1) does loading a specific module extends MODULEPATH? # 2) if yes: what is the hierarchical group? # # Example: # If we know that loading 'gcc/10.3.0' adds a directory # in the hierarchical group 'Compiler' to MODULEPATH, we # know that this directory is # /Compiler/modulefiles/gcc/10.3.0 # # This information we store in the dictionary 'group_map'. # For concinience reasons we store the string 'src_root/group'. # So, 'group_map' maps # module/version -> src_root/group # # Example: # group_map[gcc/10.3.0]="${src_root}/Compiler" # local group='' for group in "${!GroupDepths[@]}"; do (( ${GroupDepths[${group}]} > 0 )) || continue local fname='' while read fname; do local -a parts=() std::split_relpath parts "${fname}" if (( ${#parts[@]} - 2 != ${GroupDepths[${group}]} )); then std::warn "error in source group ${group}:" std::warn "modulefile: ${fname}" continue fi if [[ ${#parts[@]} < 4 ]]; then echo "${group} ${parts[@]}" fi local key="${parts[-4]}/${parts[-3]}" [[ -z "${group_map[${key}]}" ]] || continue group_map[${key}]="${src_root}/${group}" done < <(${find} -L "${src_root}/${group}/${PMODULES_MODULEFILES_DIR}" \ \( -type l -o -type f \) \ \! -name ".*" \ -printf "%P\n" \ ) done } #...................................................................... # # Resolve dependencies to given module # # Arguments: # $1 absolute module file name # # Notes: # Following variables from the enclosing function are used: # modulepath # group_map (read-only) # resolve_dependencies () { local -r modulefile="$1" local -- prefix=$(get_module_prefix "${modulefile}") local -a rdeps=() local -- rdeps_file="${prefix}/.dependencies" local -a ideps=() local -- ideps_file="${prefix}/.install_dependencies" if [[ -r "${rdeps_file}" ]]; then mapfile -t rdeps < <(grep -v '^ *#' "${rdeps_file}" ) fi if [[ -r "${ideps_file}" ]]; then mapfile -t ideps < <(grep -v '^ *#' "${ideps_file}" ) fi # loop over all dependecies local dep for dep in "${rdeps[@]}" "${ideps}"; do [[ -n ${dep} ]] || continue # search module with current modulepath local modulename=$(${find} "${modulepath[@]}" -path "*/${dep}" \ -print -quit 2>/dev/null) [[ -n ${modulename} ]] || \ std::die 3 "Oops: required module '${dep}' not found!" local rel_modulename="${modulename#${src_root}/}" dependencies_to_install[${rel_modulename}]='.' resolve_dependencies "${modulename}" [[ -v group_map[${dep}] ]] || continue local dir="${group_map[${dep}]}" # = ${src_root}/ dir+="/${rel_modulename##+([!/])/}" # += rel.name with group removed modulepath+=( "${dir}" ) done } #...................................................................... # # Print list of modules which will be installed and ask user wheter # he wants to continue or abort. # # Arguments: # none # # Notes: # Following variables from the enclosing function are used: # target_root (read-only) # modules_to_install (read-only) # dependencies_to_install (read-only) # print_modules() { local modulefile for modulefile in "$@"; do local -a parts std::split_relpath parts "${modulefile}" local s='' if (( ${#parts[@]} >= 6 )); then s="(${parts[2]}/${parts[3]}" for ((i = 4; i < ${#parts[@]}-2; i+=2)); do s+=" ${parts[i]}/${parts[i+1]}" done s+=')' fi std::info "%-20s %s" "${parts[-2]}/${parts[-1]}" "$s" done 2>&1 | sort } print_modules_to_install() { std::info "The following modules will be installed/updated:" print_modules "${!modules_to_install[@]}" if (( ${#dependencies_to_install[@]} > 0 )); then std::info "\nThe following dependencies will be installed/updated:" print_modules "${!dependencies_to_install[@]}" fi std::info "" std::get_YN_answer "Do you want to continue? [n] " || \ std::die 1 "Aborting..." std::info "" } while (($# > 0)); do case $1 in -h | -H | -\? | --help | -help ) print_help "${subcommand}" ;; --force | -f ) force='yes' ;; --src | --src=*) if [[ $1 == --src ]]; then src_root="$2" shift else src_root="${1#--*=}" fi ;; --target | --target=*) if [[ $1 == --target ]]; then target_root="$2" shift else target_root="${1#--*=}" fi ;; --with | --with=* ) if [[ "$1" == --with ]]; then with+=( "$2" ) shift else with+=( "${1#--*=}" ) fi ;; -- ) : ;; * ) module_pattern+=( "$1" ) ;; esac shift done if [[ -z ${src_root} ]]; then local conf_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/modmanage.conf" if [[ -r ${conf_file} ]]; then source "${conf_file}" src_root="${SourceRoot}" fi fi [[ -n ${src_root} ]] \ || std::die 3 "Oops: no installation source given." [[ -d ${src_root} ]] \ || std::die 3 "Oops: '${src_root}' is not a valid installation source." source "${src_root}/${PMODULES_CONFIG_DIR}/profile.bash" scan_groups "${src_root}" set_initial_modulepath create_group_map # search for to be installed modules and their dependencies while read modulefile; do modules_to_install["${modulefile#${src_root}/}"]+='.' resolve_dependencies "${modulefile}" done < <("${modulecmd}" bash search \ "${module_pattern[@]}" \ "${with[@]/#/--with=}" \ -a --glob \ --no-header --print-modulefiles \ --src="${src_root}" 2>&1 1>/dev/null) (( ${#modules_to_install[@]} == 0 )) && \ std::die 0 "No matching modules found ..." print_modules_to_install # install/update ... for modulefile in "${!modules_to_install[@]}" "${!dependencies_to_install[@]}"; do std::split_relpath parts "${modulefile}" std::info " ${parts[-2]}/${parts[-1]}" sync_module "${modulefile}" "${src_root}" "${target_root}" done std::info "\nDone!\n" } # subcommand_install ############################################################################## # # sub-command 'search' # Subcommands[search]='search' Options[search]='-o \?h -l with: -l help -l all-dep -l wrap -l glob -l src:' Help[install]=' USAGE: modmanage search [switches] ... search modules SWITCHES: --src Search modules in environment . Default is the source defined in modmanage.conf. --with Search module(s) in this sub-group. Search modules matching given string. Search modules matching given shell glob-pattern. ' subcommand_search() { local -a args=() while (($# > 0)); do case $1 in -h | -H | -\? | --help | -help ) print_help "${subcommand}" ;; --src | --src=*) if [[ $1 == --src ]]; then src_root="$2" shift else src_root="${1#--*=}" fi ;; --with | --with=* ) if [[ "$1" == --with ]]; then args+=( '--with' "$2" ) shift else args+=( "$1" ) fi ;; --all-deps | --glob | --wrap ) args+=( "$1" ) ;; -- ) : ;; * ) args+=( "$1" ) ;; esac shift done if [[ -z ${src_root} ]]; then local conf_file="${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}/modmanage.conf" if [[ -r ${conf_file} ]]; then source "${conf_file}" src_root="${SourceRoot}" fi fi [[ -n ${src_root} ]] \ || std::die 3 "Oops: no installation source given." [[ -d ${src_root} ]] \ || std::die 3 "Oops: '${src_root}' is not a valid installation source." ${modulecmd} bash search --src="${src_root}" --all-release-stages \ "${args[@]}" 2>&1 1>/dev/null } declare force='no' declare subcommand='' declare -a opts=() while (($# > 0)); do case $1 in -h | -H | -\? | --help | -help ) print_help 'help' ;; -V | --version ) print_help 'version' ;; --debug ) set -x ;; --dry-run ) chown="echo ${chown}" mkdir="echo ${mkdir}" rsync="echo ${rsync}" ;; -* ) opts+=( "$1" ) ;; * ) subcommand="$1" shift break ;; esac shift || : done if [[ -z "${subcommand}" ]]; then std::die 1 "${CMD}: no sub-command specified.\n" print_help 'help' fi if [[ -z "${Subcommands[${subcommand}]}" ]]; then std::die 1 "${CMD}: unknown sub-command -- ${subcommand}\n" fi if [[ "${subcommand}" != "init" ]] && [[ -z "${PMODULES_ROOT}" ]]; then std::die 1 "Error: No current module environment configured!" fi tmp=$("${getopt}" --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \ || print_help "${subcommand}" eval args=( "$tmp" ) unset tmp umask 022 subcommand_${Subcommands[$subcommand]} "${args[@]}" # Local Variables: # mode: sh # sh-basic-offset: 8 # tab-width: 8 # End: