#!/usr/bin/env bash # declare -rx VERSION='2.1.0' declare -rx BASH5_VERSION='5.3' declare -rx TCL_VERSION='8.6.16' declare -rx TCLLIB_VERSION='1.21' declare -rx MODULES_VERSION='3.2.10.2' declare -rx LUA_VERSION='5.4.7' declare -rx LUAROCKS_VERSION='3.9.2' declare -rx LMOD_VERSION='8.7.56' declare -rx PATCHELF_VERSION='0.14.5' # for macOS only declare -rx GETOPT_VERSION='1.1.6' declare -rx FINDUTILS_VERSION='4.9.0' if (( "${BASH_VERSINFO[0]}" < 5 )); then echo "BASH version 5.0 or newer is required and must be available in PATH!" 1>&2 exit 1 fi set -o nounset set -o pipefail shopt -s nullglob declare -r BOOTSTRAP_DIR="$(cd "$(dirname "$0")" && pwd -P)" declare -r SRC_DIR="${BOOTSTRAP_DIR}/Pmodules" declare -r PMOD_DIR="Tools/Pmodules/${VERSION}" # config directory and file relative to install root declare -rx CONFIG_DIR='config' declare -rx CONFIG_FILE="${CONFIG_DIR}/Pmodules.yaml" # directory where the required tools will be installed (like bash, tclsh, etc) declare -rx UTILBIN_DIR='libexec' # defaults declare -rx DEFAULT_INSTALL_ROOT='/opt/psi' declare -rx DEFAULT_DISTFILES_DIR='var/distfiles' declare -rx DEFAULT_TMP_DIR='var/tmp/${USER}' std::log() { local -ri fd=$1 local -r fmt="$2" shift 2 printf -- "${fmt}" "$@" 1>&$fd printf -- "\n" 1>&$fd } std::info() { std::log 2 "$1" "${@:2}" } std::error() { std::log 2 "$1" "${@:2}" } std::debug() { [[ -v PMODULES_DEBUG ]] || return 0 std::log 2 "$@" } std::die() { local -ri ec=$1 shift if [[ -n $@ ]]; then local -r fmt=$1 shift std::log 2 "$fmt" "$@" fi exit $ec } std::parse_yaml() { # # parse a YAML file # See: https://gist.github.com/pkuczynski/8665367 # local -r fname="$1" local -r prefix="$2" local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034') sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "${fname}" | awk -F$fs '{ indent = length($1)/2; vname[indent] = $2; for (i in vname) { if (i > indent) {delete vname[i]} } if (length($3) > 0) { vn=""; for (i=0; i&2 std::die 1 "" } #----------------------------------------------------------------------------- # sub-command 'configure' # # Create basic directory hierachy and the configuration file in the given # installation root directory # build::configure() { local opt_force='no' while (( $# > 0 )); do case "$1" in --install_root | --install_root=* ) if [[ $1 == *=* ]]; then INSTALL_ROOT="${1#*=}" else INSTALL_ROOT="$2" shift 1 fi ;; --distfilesdir ) if [[ $1 == *=* ]]; then DOWNLOADS_DIR="${1#*=}" else DOWNLOADS_DIR="$2" shift 1 fi ;; -f | --force ) opt_force='yes' ;; --tmpdir | --tmpdir=* ) if [[ $1 == *=* ]]; then TMP_DIR="${1#*=}" else TMP_DIR="$2" shift 1 fi ;; --help | -h | -\? ) build::help_configure ;; -* ) illegal_option 'configure' "$1" ;; * ) illegal_arg 'configure' "$1" ;; esac shift 1 done : ${INSTALL_ROOT:=${DEFAULT_INSTALL_ROOT}} : ${DOWNLOADS_DIR:=${INSTALL_ROOT}/${DEFAULT_DISTFILES_DIR}} : ${TMP_DIR:=${INSTALL_ROOT}/${DEFAULT_TMP_DIR}} PREFIX="${INSTALL_ROOT}/${PMOD_DIR}" #--- # check/create the install root if [[ ! -d ${INSTALL_ROOT} ]]; then std::info "%s\n%s" \ "The root directory '${INSTALL_ROOT}' does not exist!" \ "Trying to create it..." if ! mkdir -p "${INSTALL_ROOT}"; then std::die 1 "%s\n%s" \ "Creating the root directory failed!" \ "Aborting..." fi fi if [[ ! -w ${INSTALL_ROOT} ]]; then std::die 1 "%s\n%s" \ "The root directory '${INSTALL_ROOT}' is not writable!" \ "Aborting..." fi #--- # check/create YAML config file in install root mkdir -p "${INSTALL_ROOT}/${CONFIG_DIR}" || \ std::die 1 "Aborting..." local config_file="${INSTALL_ROOT}/${CONFIG_FILE}" if [[ "${opt_force}" != 'yes' ]] && [[ -e "${config_file}" ]]; then std::die 1 "%s\n%s" \ "The Pmodules environment in '${INSTALL_ROOT}' has already been configured!" \ "Use the option --force to override. Aborting..." fi sed_cmd="s:@INSTALL_ROOT@:${INSTALL_ROOT}:g;" sed_cmd+="s:@PMODULES_DISTFILESDIR@:${DOWNLOADS_DIR}:g;" sed_cmd+="s:@PMODULES_TMPDIR@:${TMP_DIR}:g;" sed_cmd+="s:@PMODULES_VERSION@:${VERSION}:g" sed "${sed_cmd}" "${BOOTSTRAP_DIR}/${CONFIG_FILE}.in" \ > "${config_file}" || \ std::die 1 "Cannot create configuration file in Pmodules root\nAborting..." #--- # create basic directories install -d -m 0755 \ "${INSTALL_ROOT}/Tools/modulefiles/Pmodules" \ "${INSTALL_ROOT}/Libraries/modulefiles" \ "${INSTALL_ROOT}/Programming/modulefiles" \ "${DOWNLOADS_DIR}" || \ std::die 1 "%s" \ "Creating basic directories failed\n" \ "Aborting..." #--- echo "Configuration:" echo " root of Pmodules environment: ${INSTALL_ROOT}" echo " Pmodule prefix: ${PREFIX}" echo " tmp directory: ${TMP_DIR}" echo " store for downloaded files: ${DOWNLOADS_DIR}" echo "Done..." } #----------------------------------------------------------------------------- # help for sub-command 'compile' # build::help_compile() { echo " Usage: $(basename $0) compile [OPTION...] Compile and install the required tools for a new Pmodules environment. ou need the permissions to write to the installation root. Options: --install_root=DIR Root of the Pmodules environment installation. Everything will be installed in a directory hierarchy with 'DIR' as prefix. The default is '${DEFAULT_INSTALL_ROOT}'. --disable-cleanup Do not cleanup the tmp directory after compilation and installation. --help Print this help text. " 1>&2 std::die 1 "" } #----------------------------------------------------------------------------- # sub-command 'compile' # # compile all required tools like bash, tclsh etc for a Pmodules module. # The version is defined at the beginning of this file. # build::compile() { local opt_force='no' local opt_cleanup='yes' while (( $# > 0 )); do case $1 in --install_root | --install_root=* ) if [[ $1 == *=* ]]; then INSTALL_ROOT="${1#*=}" else INSTALL_ROOT="$2" shift 1 fi ;; --disable-cleanup ) opt_cleanup='no' ;; --help | -h | -\? ) build::help_compile ;; -* ) illegal_option 'compile' "$1" ;; * ) illegal_arg 'compile' "$1" ;; esac shift 1 done : ${INSTALL_ROOT:=${DEFAULT_INSTALL_ROOT}} PREFIX="${INSTALL_ROOT}/${PMOD_DIR}" read_config_file "${INSTALL_ROOT}/${CONFIG_FILE}" echo "Configuration:" echo " root of Pmodules environment: ${INSTALL_ROOT}" echo " Pmodule prefix: ${PREFIX}" install -m 0755 -d "${PREFIX}"/{bin,init,lib,libexec} \ for recipe in recipes/[0-9]*; do "./${recipe}" "${PREFIX}" || \ std::die 1 "Oops" done if [[ "${opt_cleanup}" == 'yes' ]]; then rm -rf "${TMP_DIR}/*" rm -f "${PREFIX}/lib/libtcl*.a" rm -rf "${PREFIX}/include" fi echo "Done..." } #----------------------------------------------------------------------------- # help for sub-command 'install' # build::help_install() { echo " Usage: $(basename $0) install [OPTION...] Install a new Pmodules version. Options: --install_root=DIR Root of the Pmodules environment installation. Everything will be installed in a directory hierarchy with 'DIR' as prefix. The default is '${DEFAULT_INSTALL_ROOT}'. --debug Enable verbose/debug output. --help|-h|-? Print this help text. " 1>&2 std::die 1 "" } #----------------------------------------------------------------------------- # sub-command 'install' # # Install Pmodules files. # build::install() { while (( $# > 0 )); do case $1 in --debug ) set -x ;; --install_root | --install_root=* ) if [[ $1 == *=* ]]; then INSTALL_ROOT="${1#*=}" else INSTALL_ROOT="$2" shift 1 fi ;; --help | -h | -\? ) build::help_install ;; -* ) illegal_option 'install' "$1" ;; * ) illegal_arg 'install' "$1" ;; esac shift 1 done : ${INSTALL_ROOT:=${DEFAULT_INSTALL_ROOT}} PREFIX="${INSTALL_ROOT}/${PMOD_DIR}" read_config_file "${INSTALL_ROOT}/${CONFIG_FILE}" ### # # begin installation # echo "Configuration:" echo " root of Pmodules environment: ${INSTALL_ROOT}" echo " Pmodule prefix: ${PREFIX}" sed_cmd+="s:@PMODULES_VERSION@:${VERSION}:g;" sed_cmd+="s:@MODULES_VERSION@:${MODULES_VERSION}:g;" sed_cmd+="s:@BASH@:${PREFIX}/${UTILBIN_DIR}/bash:g;" sed_cmd+="s:@MODULECMD@:${PREFIX}/${UTILBIN_DIR}/modulecmd.bash:g;" sed_cmd+="s:@TCL_VERSION@:${TCL_VERSION%.*}:g;" sed "${sed_cmd}" "${SRC_DIR}/profile.bash.in" \ > "${INSTALL_ROOT}/${CONFIG_DIR}/profile.bash-${VERSION}" sed "${sed_cmd}" "${SRC_DIR}/profile.csh.in" \ > "${INSTALL_ROOT}/${CONFIG_DIR}/profile.csh-${VERSION}" sed "${sed_cmd}" "${SRC_DIR}/profile.zsh.in" \ > "${INSTALL_ROOT}/${CONFIG_DIR}/profile.zsh-${VERSION}" chmod 0644 "${INSTALL_ROOT}/${CONFIG_DIR}"/*-${VERSION} test -e "${INSTALL_ROOT}/${CONFIG_DIR}/profile.bash" || \ install -m 0644 "$_-${VERSION}" "$_" test -e "${INSTALL_ROOT}/${CONFIG_DIR}/profile.csh" || \ install -m 0644 "$_-${VERSION}" "$_" test -e "${INSTALL_ROOT}/${CONFIG_DIR}/profile.zsh" || \ install -m 0644 "$_-${VERSION}" "$_" sed "${sed_cmd}" "${SRC_DIR}/modulecmd.in" \ > "${PREFIX}/bin/modulecmd" chmod 0755 "${PREFIX}/bin/modulecmd" sed "${sed_cmd}" "${SRC_DIR}/modulecmd.bash.in" \ > "${PREFIX}/libexec/modulecmd.bash" chmod 0755 "${PREFIX}/libexec/modulecmd.bash" sed "${sed_cmd}" "${SRC_DIR}/libpmodules.bash.in" \ > "${PREFIX}/lib/libpmodules.bash" chmod 0755 "${PREFIX}/lib/libpmodules.bash" sed "${sed_cmd}" "${SRC_DIR}/modbuild.in" \ > "${PREFIX}/bin/modbuild" chmod 0755 "${PREFIX}/bin/modbuild" test -e "${INSTALL_ROOT}/${CONFIG_FILE}" || \ install -m 0644 "$_" "${INSTALL_ROOT}/${CONFIG_DIR}" install -m 0755 "${SRC_DIR}/yq.$(uname -m)_$(uname -s)" "${PREFIX}/libexec/yq" install -m 0644 "${SRC_DIR}/bash" "${PREFIX}/init" install -m 0644 "${SRC_DIR}/bash_completion" "${PREFIX}/init" install -m 0644 "${SRC_DIR}/csh" "${PREFIX}/init" install -m 0644 "${SRC_DIR}/Pmodules.py" "${PREFIX}/init" install -m 0644 "${SRC_DIR}/zsh" "${PREFIX}/init" install -m 0644 "${SRC_DIR}/libpbuild.bash" "${PREFIX}/lib" install -m 0644 "${SRC_DIR}/libstd.bash" "${PREFIX}/lib" install -m 0755 -d "${PREFIX}/lib/Pmodules" install -m 0644 "${SRC_DIR}/libmodules.tcl" "${PREFIX}/lib/Pmodules" { PATH="${PREFIX}/${UTILBIN_DIR}:${PATH}" cd "${PREFIX}/lib/Pmodules" "${BOOTSTRAP_DIR}/mkindex.tcl" } install -m 0644 \ "${SRC_DIR}/modulefile" \ "${INSTALL_ROOT}/Tools/modulefiles/Pmodules/${VERSION}" echo "Done..." } build::help_tar(){ echo " Usage: $(basename $0) tar [OPTION...] Create a Pmodules tar-ball. Options: --debug Enable verbose/debug output. --help|-h|-? Print this help text. " 1>&2 std::die 1 "" } build::tar(){ while (( $# > 0 )); do case $1 in --debug ) set -x ;; --help | -h | -\? ) build::help_tar ;; -* ) illegal_option 'tar' "$1" ;; * ) illegal_arg 'tar' "$1" ;; esac shift 1 done local -r target_dir="${BOOTSTRAP_DIR}/Pmodules-${VERSION}" mkdir -p "${target_dir}/src/etc" mkdir -p "${target_dir}/src/init" mkdir -p "${target_dir}/src/profiles" mkdir -p "${target_dir}/config" cp -v CHANGELOG.md LICENSE mkindex.tcl "${target_dir}" local -- sed_cmd='' sed_cmd+="s:@PMODULES_VERSION@:${VERSION}:g;" sed_cmd+="s:@MODULES_VERSION@:${MODULES_VERSION}:g;" sed_cmd+="s:@VERSIONING@:#:g;" sed_cmd+="s:@TCL_VERSION@:${TCL_VERSION%.*}:g;" sed_cmd+="s:@PMODULES_TMPDIR@:${DEFAULT_INSTALL_ROOT}/${DEFAULT_TMP_DIR}:g;" sed_cmd+="s:@PMODULES_DISTFILESDIR@:${DEFAULT_INSTALL_ROOT}/${DEFAULT_DISTFILES_DIR}:g;" sed_cmd+="s:@INSTALL_ROOT@:${DEFAULT_INSTALL_ROOT}:g;" local -- libexec_dir='/usr/share/Pmodules/libexec' sed_cmd+="s:@BASH@:${libexec_dir}/bash:g;" sed_cmd+="s:@MODULECMD@:${libexec_dir}/modulecmd.bash:g;" sed "${sed_cmd}" "${SRC_DIR}/profile.bash.in" \ > "${target_dir}/src/profiles/profile.bash" sed "${sed_cmd}" "${SRC_DIR}/profile.csh.in" \ > "${target_dir}/src/profiles/profile.csh" sed "${sed_cmd}" "${SRC_DIR}/profile.zsh.in" \ > "${target_dir}/src/profiles/profile.zsh" sed "${sed_cmd}" "${SRC_DIR}/modulecmd.in" \ > "${target_dir}/src/modulecmd" sed "${sed_cmd}" "${SRC_DIR}/modulecmd.bash.in" \ > "${target_dir}/src/modulecmd.bash" sed "${sed_cmd}" "${SRC_DIR}/libpmodules.bash.in" \ > "${target_dir}/src/libpmodules.bash" sed "${sed_cmd}" "${SRC_DIR}/modbuild.in" \ > "${target_dir}/src/modbuild" sed "${sed_cmd}" "${BOOTSTRAP_DIR}/config/Pmodules.yaml.in" \ > "${target_dir}/config/Pmodules.yaml" cp -v "${SRC_DIR}/yq.$(uname -m)_$(uname -s)" "${target_dir}/src/yq" cp -v "${SRC_DIR}/bash" "${target_dir}/src/init" cp -v "${SRC_DIR}/bash_completion" "${target_dir}/src/init" cp -v "${SRC_DIR}/csh" "${target_dir}/src/init" cp -v "${SRC_DIR}/Pmodules.py" "${target_dir}/src/init" cp -v "${SRC_DIR}/zsh" "${target_dir}/src/init" cp -v "${SRC_DIR}/libpbuild.bash" "${target_dir}/src" cp -v "${SRC_DIR}/libstd.bash" "${target_dir}/src" cp -v "${SRC_DIR}/libmodules.tcl" "${target_dir}/src" tar cvzf "Pmodules-${VERSION}.tar.gz" "Pmodules-${VERSION}" } #============================================================================= # declare subcmd='' declare -a subcmd_args=() while (( $# > 0 )); do case "$1" in --help | -h | -\? ) usage ;; --debug ) set -x ;; -* ) std::die 1 "$1: illegal option" ;; help | configure | compile | install | tar ) subcmd="$1" shift 1 subcmd_args=( "$@" ) shift $# break ;; * ) std::die 1 "Invalid sub-command '$1'.\n\nUse 'build --help' to get help..." ;; esac shift 1 done [[ -n "${subcmd}" ]] || std::die 1 "Missing sub-command.\n\nUse 'build --help' to get help..." build::${subcmd} "${subcmd_args[@]}" # Local Variables: # mode: sh # sh-basic-offset: 8 # tab-width: 8 # End: