Files

696 lines
17 KiB
Python
Executable File

#!/usr/bin/env bash
#
declare -rx VERSION='2.0.5'
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'
# 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<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
}
}'
}
#-----------------------------------------------------------------------------
#
read_config_file() {
local fname="$1"
if [[ ! -r "${fname}" ]]; then
std::die 1 "Configuration file '${fname}' does not exist or is not readable!"
fi
eval $(std::parse_yaml "${fname}" '') || \
std::die 1 "Cannot read configuration file '${fname}'"
# In the YAML configuration file the root of the base overlay must be
# defined with Overlays.base.install_root. The function
# std::parse_yaml() stores the value in the environment variable
# 'Overlays_base_install_root'.
declare -xg INSTALL_ROOT="${Overlays_base_install_root}"
if [[ -z "${INSTALL_ROOT}" ]]; then
std::die 1 "Error in configuration file '${fname}': install root not defined!"
fi
declare -xg PREFIX="${INSTALL_ROOT}/${PMOD_DIR}"
declare -xg DOWNLOADS_DIR="${DistfilesDir:-${INSTALL_ROOT}/${DEFAULT_DISTFILES_DIR}}"
declare -xg TMP_DIR="${TmpDir:-${INSTALL_ROOT}/${DEFAULT_TMP_DIR}}"
}
#-----------------------------------------------------------------------------
# The next functions are used in the sub-commands, if an illegal option
# or argument has been passed.
#
illegal_option(){
local subcmd="$1"
local opt="$2"
std::die 1 \
"%s: %s -- %s" \
"$(basename $0) ${subcmd}" \
"Illegal option" \
"${opt}"
}
illegal_arg(){
local subcmd="$1"
local arg="$2"
std::die 1 \
"%s: %s -- %s" \
"$(basename $0) ${subcmd}" \
"Illegal argument" \
"${arg}"
}
#-----------------------------------------------------------------------------
# help for sub-command 'help' (usage)
build::help_help(){
local prog="$(basename "$0")"
echo "
Usage: ${prog} help|configure|compile|install|tar
This script must be used to
- Bootstrap/configure a new Pmodules environment,
- compile and install the required tools
- install a new Pmodules version
Run
${prog} help configure|compile|install
or
${prog} configure|compile|install --help
to get help for a specific sub-command.
"
std::die 1 ""
}
#-----------------------------------------------------------------------------
# sub-command 'help'
#
# print help for sub-commands
#
build::help() {
if (( $# == 0 )); then
build::help_help
else
case $1 in
configure|compile|install )
build::help_$1
;;
help )
build::help_help
;;
-* )
illegal_option 'help' "$1"
;;
* )
std::error "No such command -- $1"
build::help_help
;;
esac
fi
}
#-----------------------------------------------------------------------------
# help for sub-command 'configure'
#
build::help_configure() {
echo "
Usage: $(basename $0) configure [OPTION...]
Configure and setup a new Pmodules environment. You need 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}'.
--distfilesdir=DIR
Directory where downloaded files are stored.
The default is '${DEFAULT_INSTALL_ROOT}/${DEFAULT_DISTFILES_DIR}' in the
Pmodules root directory.
--tmpdir=DIR
Directory for temporary files.
The default is '${DEFAULT_INSTALL_ROOT}/${DEFAULT_TMP_DIR}'
--force|-f
Override existing configuration.
--help|-h|-?
Print this help text.
" 1>&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: