Files
Pmodules/Pmodules/libpbuild.bash
T
gsell b082bfcaf4 build-system: fix case in hierarchical group name 'HDF5_serial'
the name of the hierarchical group for modules compiled
with a serial HDF5 is HDF5_serial not HDF5_SERIAL
2021-12-06 14:30:34 +01:00

1497 lines
36 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
#.............................................................................
#
# We need GNU versions of the following utilities. This code works
# well on Linux and Mac OS X with MacPorts.
# :FIXME: implement a smarter, portable solution.
#
shopt -s expand_aliases
unalias -a
__path=$(which gsed 2>/dev/null || : )
if [[ $__path ]]; then
alias sed=$__path
else
alias sed=$(which sed 2>/dev/null)
fi
unset __path
#.............................................................................
# disable auto-echo feature of 'cd'
unset CDPATH
#.............................................................................
# define constants
declare -r BNAME_VARIANTS='variants'
declare -r FNAME_RDEPS='.dependencies'
declare -r FNAME_IDEPS='.install_dependencies'
declare -r FNAME_BDEPS='.build_dependencies'
#.............................................................................
declare -A SOURCE_UNPACK_DIRS
#.............................................................................
#
# Exit script on errror.
#
# $1 exit code
#
set -o errexit
error_handler() {
local -i ec=$?
std::die ${ec} "Oops"
}
trap "error_handler" ERR
declare configure_with='undef'
#..............................................................................
#
# compare two version numbers
#
# 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
}
pbuild::version_lt() {
pbuild::version_compare "$1" "$2"
(( $? == 2 ))
}
pbuild::version_le() {
pbuild::version_compare "$1" "$2"
local -i exit_code=$?
(( exit_code == 0 || exit_code == 2 ))
}
pbuild::version_gt() {
pbuild::version_compare "$1" "$2"
(( $? == 1 ))
local -i exit_code=$?
(( exit_code == 0 || exit_code == 1 ))
}
pbuild::version_eq() {
pbuild::version_compare "$1" "$2"
}
#..............................................................................
#
# The following variables are available in build-blocks and set read-only
# :FIXME: do we have to export them?
# install prefix of module.
declare -x PREFIX=''
declare -r OS=$(uname -s)
pbuild::get_num_cores() {
case "${OS}" in
Linux )
echo $(grep -c ^processor /proc/cpuinfo)
;;
Darwin )
echo $(sysctl -n hw.ncpu)
;;
* )
std::die 1 "OS ${OS} is not supported\n"
;;
esac
}
#..............................................................................
# global variables which can be set/overwritten by command line args
declare force_rebuild=''
pbuild.force_rebuild() {
force_rebuild="$1"
}
declare dry_run=''
pbuild.dry_run() {
dry_run="$1"
}
declare enable_cleanup_build=''
pbuild.enable_cleanup_build() {
enable_cleanup_build="$1"
}
declare enable_cleanup_src=''
pbuild.enable_cleanup_src() {
enable_cleanup_src="$1"
}
declare build_target=''
pbuild.build_target() {
build_target="$1"
}
declare opt_update_modulefiles=''
pbuild.update_modulefiles() {
opt_update_modulefiles="$1"
}
# number of parallel make jobs
declare -i JOBS=$(pbuild::get_num_cores)
pbuild.jobs() {
if (( $1 == 0 )); then
JOBS=$(pbuild::get_num_cores)
(( JOBS > 10 )) && JOBS=10 || :
else
JOBS="$1"
fi
}
declare system=''
pbuild.system() {
system="$1"
}
declare verbose=''
pbuild.verbose() {
verbose="$1"
}
# group this module is in (ex: 'Programming')
declare -x GROUP=''
# name, version and release of module
declare -x module_name=''
declare -x module_version=''
declare -x module_release=''
# relative path of documentation
# abs. path is "${PREFIX}/${_docdir}/${module_name}"
declare -r _DOCDIR='share/doc'
##############################################################################
#
# Set flag to build module in source tree.
#
# Arguments:
# none
#
pbuild::compile_in_sourcetree() {
BUILD_DIR="${SRC_DIR}"
}
##############################################################################
#
# 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+=( "$@" )
}
##############################################################################
#
# 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+=( "$@" )
}
##############################################################################
#
# Check whether the loaded compiler is supported.
#
# Arguments:
# $@: supported compiler (like GCC, Intel, PGI).
# Default is all.
#
pbuild::supported_compilers() {
SUPPORTED_COMPILERS+=( "$@" )
}
##############################################################################
#
# Install module in given group.
#
# Arguments:
# $1: group
#
pbuild::add_to_group() {
if [[ -z ${1} ]]; then
std::die 42 \
"%s " "${module_name}/${module_version}:" \
"${FUNCNAME}: missing group argument."
fi
GROUP="$1"
}
##############################################################################
#
# Set documentation file to be installed.
#
# Arguments:
# $@: documentation files relative to source
#
pbuild::install_docfiles() {
MODULE_DOCFILES+=("$@")
}
##############################################################################
#
# Test whether a module with the given name is available. If yes, return
# release
#
# Arguments:
# $1: module name
# $2: optional variable name to return release via upvar
#
# Notes:
# The passed module name must be NAME/VERSION!
#
pbuild::module_is_avail() {
local "$2"
local uvar="$2"
[[ -n "${uvar}" ]] || uvar="__unused__"
local output=( $("${MODULECMD}" bash avail -a -m "$1" \
2>&1 1>/dev/null) )
local i
for (( i = 0; i < ${#output[@]}; i += 2 )); do
if [[ "${output[$i]}" == "$1" ]]; then
std::upvar "${uvar}" "${output[i+1]}"
return 0
fi
done
return 1
}
pbuild::set_download_url() {
local -i _i=${#SOURCE_URLS[@]}
SOURCE_URLS[_i]="$1"
SOURCE_NAMES[_i]="$2"
}
pbuild::set_sha256sum() {
SOURCE_SHA256_SUMS+=("$1")
}
pbuild::set_unpack_dir() {
SOURCE_UNPACK_DIRS[$1]=$2
}
pbuild::use_cc() {
[[ -x "$1" ]] || std::die 3 \
"%s " "${module_name}/${module_version}:" \
"Error in setting CC:" \
"'$1' is not an executable!"
CC="$1"
}
pbuild::pre_prep() {
:
}
pbuild::post_prep() {
:
}
###############################################################################
#
# extract sources. For the time being only tar-files are supported.
#
pbuild::prep() {
#......................................................................
#
# Find/download tarball for given module.
#
# Arguments:
# $1: store file name with upvar here
# $2: download URL
# $3: output filename (can be empty string)
# $4...: download directories
#
# Returns:
# 0 on success otherwise a value > 0
#
download_with_curl() {
local -r output="$1"
local -r url="$2"
curl \
-L \
--output "${output}" \
"${url}"
if (( $? != 0 )); then
curl \
--insecure \
--output "${output}" \
"${url}"
fi
}
check_hash_sum() {
local -r fname="$1"
local -r expected_hash_sum="$2"
local hash_sum=''
if which 'sha256sum' 1>/dev/null; then
hash_sum=$(sha256sum "${fname}" | awk '{print $1}')
elif which 'shasum' 1>/dev/null; then
hash_sum=$(shasum -a 256 "${fname}" | awk '{print $1}')
else
std::die 42 \
"%s " "${module_name}/${module_version}:" \
"Binary to compute SHA256 sum missing!"
fi
test "${hash_sum}" == "${expected_hash_sum}" || \
std::die 42 \
"%s " "${module_name}/${module_version}:" \
"hash-sum missmatch for file '%s'" "${fname}"
}
download_source_file() {
local "$1"
local var="$1"
local -r url="$2"
local fname="$3"
shift 3
dirs+=( "$@" )
[[ -n "${fname}" ]] || fname="${url##*/}"
local expr='s/.*\(.tar.bz2\|.tbz2\|.tar.gz\|.tgz\|.tar.xz\|.zip\)/\1/'
local -r extension=$(echo ${fname} | sed "${expr}")
local dir=''
dirs+=( 'not found' )
for dir in "${dirs[@]}"; do
[[ -r "${dir}/${fname}" ]] && break
done
if [[ "${dir}" == 'not found' ]]; then
dir="${dirs[0]}"
local -r method="${url%:*}"
case "${method}" in
http | https | ftp )
download_with_curl "${dir}/${fname}" "${url}"
;;
* )
std::die 4 \
"%s " "${module_name}/${module_version}:" \
"Error in download URL:" \
"unknown download method '${method}'!"
;;
esac
fi
local sha256_sum=''
local hash=''
for hash in "${SOURCE_SHA256_SUMS[@]}"; do
if [[ ${hash} =~ $fname: ]]; then
sha256_sum="${hash#*:}"
fi
done
if [[ -n "${sha256_sum}" ]]; then
check_hash_sum "${dir}/${fname}" "${sha256_sum}"
fi
std::upvar "${var}" "${dir}/${fname}"
[[ -r "${dir}/${fname}" ]]
}
unpack() {
local -r file="$1"
local -r dir="${2:-${SRC_DIR}}"
tar --directory="${dir}" -xv --strip-components 1 -f "${file}" || {
rm -f "${file}"
std::die 4 \
"%s " \
"${module_name}/${module_version}:" \
"cannot unpack sources!"
}
}
patch_sources() {
cd "${SRC_DIR}"
local i=0
for ((_i = 0; _i < ${#PATCH_FILES[@]}; _i++)); do
std::info \
"%s " \
"${module_name}/${module_version}:" \
"Appling patch '${PATCH_FILES[_i]}' ..."
local -i strip_val="${PATCH_STRIPS[_i]:-${PATCH_STRIP_DEFAULT}}"
patch -p${strip_val} < "${BUILDBLOCK_DIR}/${PATCH_FILES[_i]}" || \
std::die 4 \
"%s " \
"${module_name}/${module_version}:" \
"error patching sources!"
done
}
if [[ -z "${SOURCE_URLS}" ]]; then
for fname in ${VERSIONS[@]/#/pbuild::set_download_url_}; do
if typeset -F ${fname} 2>/dev/null; then
$f
break
fi
done
fi
[[ -z "${SOURCE_URLS}" ]] && \
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"Download source not set!"
mkdir -p "${PMODULES_DISTFILESDIR}"
local i=0
local source_fname
for ((i = 0; i < ${#SOURCE_URLS[@]}; i++)); do
download_source_file \
source_fname \
"${SOURCE_URLS[i]}" \
"${SOURCE_NAMES[i]}" \
"${PMODULES_DISTFILESDIR}" \
"${BUILDBLOCK_DIR}" ||
std::die 4 \
"%s " "${module_name}/${module_version}:" \
"sources for not found."
unpack "${source_fname}" "${SOURCE_UNPACK_DIRS[${source_fname##*/}]}"
done
patch_sources
# create build directory
mkdir -p "${BUILD_DIR}"
}
pbuild::add_patch() {
[[ -z "$1" ]] && \
std::die 1 \
"%s " "${module_name}/${module_version}:" \
"${FUNCNAME}: missing argument!"
PATCH_FILES+=( "$1" )
PATCH_STRIPS+=( "$2" )
}
pbuild::set_default_patch_strip() {
[[ -n "$1" ]] || \
std::die 1 \
"%s " "${module_name}/${module_version}:" \
"${FUNCNAME}: missing argument!"
PATCH_STRIP_DEFAULT="$1"
}
pbuild::use_flag() {
[[ "${USE_FLAGS}" =~ ":${1}:" ]]
}
###############################################################################
#
#
pbuild::pre_configure() {
:
}
pbuild::set_configure_args() {
CONFIGURE_ARGS+=( "$@" )
}
pbuild::add_configure_args() {
CONFIGURE_ARGS+=( "$@" )
}
pbuild::use_autotools() {
configure_with='autotools'
}
pbuild::use_cmake() {
configure_with='cmake'
}
pbuild::configure() {
case "${configure_with}" in
autotools )
if [[ ! -r "${SRC_DIR}/configure" ]]; then
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"${FNCNAME[0]}:" \
"autotools configuration not available, aborting..."
fi
;;
cmake )
if [[ ! -r "${SRC_DIR}/CMakeLists.txt" ]]; then
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"${FNCNAME[0]}:" \
"CMake script not available, aborting..."
fi
;;
esac
if [[ -r "${SRC_DIR}/configure" ]] && \
[[ "${configure_with}" == 'undef' ]] || \
[[ "${configure_with}" == 'autotools' ]]; then
${SRC_DIR}/configure \
--prefix="${PREFIX}" \
"${CONFIGURE_ARGS[@]}" || \
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"configure failed"
elif [[ -r "${SRC_DIR}/CMakeLists.txt" ]] && \
[[ "${configure_with}" == 'undef' ]] || \
[[ "${configure_with}" == "cmake" ]]; then
cmake \
-DCMAKE_INSTALL_PREFIX="${PREFIX}" \
"${CONFIGURE_ARGS[@]}" \
"${SRC_DIR}" || \
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"cmake failed"
else
std::info \
"%s " \
"${module_name}/${module_version}:" \
"${FUNCNAME[0]}: skipping..."
fi
}
pbuild::post_configure() {
:
}
pbuild::pre_compile() {
:
}
pbuild::compile() {
make -j${JOBS} || \
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"compilation failed!"
}
pbuild::post_compile() {
:
}
pbuild::pre_install() {
:
}
pbuild::install() {
make install || \
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"compilation failed!"
}
pbuild::install_shared_libs() {
local -r binary="$1"
local -r dstdir="$2"
local -r pattern="${3//\//\\/}" # escape slash
install_shared_libs_Linux() {
local libs=( $(ldd "${binary}" | \
awk "/ => \// && /${pattern}/ {print \$3}") )
if [[ -n "${libs}" ]]; then
cp -vL "${libs[@]}" "${dstdir}" || return $?
fi
return 0
}
install_shared_libs_Darwin() {
# https://stackoverflow.com/questions/33991581/install-name-tool-to-update-a-executable-to-search-for-dylib-in-mac-os-x
local libs=( $(otool -L "${binary}" | \
awk "/${pattern}/ {print \$1}"))
if [[ -n "${libs}" ]]; then
cp -vL "${libs[@]}" "${dstdir}" || return $?
fi
return 0
}
test -e "${binary}" || \
std::die 3 \
"%s " "${module_name}/${module_version}:" \
"${binary}: does not exist or is not executable!"
mkdir -p "${dstdir}"
case "${OS}" in
Linux )
install_shared_libs_Linux
;;
Darwin )
install_shared_libs_Darwin
;;
esac
}
pbuild::post_install() {
:
}
#
# The 'do it all' function.
#
pbuild::make_all() {
source "${BUILD_SCRIPT}"
set -e
local -r logfile="${BUILDBLOCK_DIR}/pbuild.log"
# module name including path in hierarchy and version
# (ex: 'gcc/6.1.0/openmpi/1.10.2' for openmpi compiled with gcc 6.1.0)
local modulefile_dir=''
local modulefile_name=''
#
# To be able to set environment variables in one of the 'pbuild::TARGET'
# function we cannot use PIPE's like
# pbuild::configure | tee -a ...
#
rm -f "${logfile}"
if [[ "${verbose}" == 'yes' ]]; then
exec > >(tee -a "${logfile}")
else
exec > >(cat >> "${logfile}")
fi
exec 2> >(tee -a "${logfile}" >&2)
#
# everything set up?
#
[[ -n ${GROUP} ]] || \
std::die 5 \
"%s " "${module_name}/${module_version}:" \
"Module group not set! Aborting ..."
#
# helper functions
#
#......................................................................
check_supported_systems() {
[[ -z "${SUPPORTED_SYSTEMS}" ]] && return 0
for sys in "${SUPPORTED_SYSTEMS[@]}"; do
[[ ${sys,,} == ${system,,} ]] && return 0
done
std::die 1 \
"%s " "${module_name}/${module_version}:" \
"Not available for ${system}."
}
#......................................................................
check_supported_os() {
[[ -z "${SUPPORTED_OS}" ]] && 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() {
[[ -z "${SUPPORTED_COMPILERS}" ]] && return 0
for compiler in "${SUPPORTED_COMPILERS[@]}"; do
[[ ${compiler,,} == ${COMPILER,,} ]] && return 0
done
std::die 1 \
"%s " "${module_name}/${module_version}:" \
"Not available for ${COMPILER}."
}
#......................................................................
#
# compute full module name and installation prefix
#
# The following variables are expected to be set:
# GROUP module group
# P module name
# V module version
# variables defining the hierarchical environment like
# COMPILER and COMPILER_VERSION
#
# The following variables are set in this function
# modulefile_dir
# modulefile_name
# PREFIX
#
set_full_module_name_and_prefix() {
join_by() {
local IFS="$1"
shift
echo "$*"
}
[[ -n ${GROUP} ]] || std::die 1 \
"${module_name}/${module_version}:" \
"group not set."
# define defaults if not set in configuration file
: ${Compiler_HIERARCHY:='${COMPILER}/${COMPILER_VERSION}'}
: ${CUDA_HIERARCHY:='${COMPILER}/${COMPILER_VERSION} cuda/${CUDA_VERSION}'}
: ${MPI_HIERARCHY:='${COMPILER}/${COMPILER_VERSION} ${MPI}/${MPI_VERSION}'}
: ${HDF5_HIERARCHY:='${COMPILER}/${COMPILER_VERSION} ${MPI}/${MPI_VERSION} hdf5/${HDF5_VERSION}'}
: ${HDF5_serial_HIERARCHY:='${COMPILER}/${COMPILER_VERSION} hdf5_serial/${HDF5_SERIAL_VERSION}'}
# evaluate
local names=()
local vname="${GROUP}_HIERARCHY"
if [[ -n ${!vname} ]]; then
names=( $(eval echo ${!vname}) )
fi
modulefile_dir=$(join_by '/' \
"${PMODULES_ROOT}/${GROUP}/${PMODULES_MODULEFILES_DIR}" \
"${names[@]}" \
"${module_name}")
modulefile_name="${modulefile_dir}/${module_version}"
PREFIX="${PMODULES_ROOT}/${GROUP}/${module_name}/${module_version}"
local -i i=0
for ((i=${#names[@]}-1; i >= 0; i--)); do
PREFIX+="/${names[i]}"
done
}
#......................................................................
# Select the modulefile to install. Modulefiles can be versioned like
# modulefile-10.2.0
# modulefile-10.2
# modulefile-10
# modulefile
# the most specific modulefile will be selected. Example:
# For a version 10.2.1 the file moduelfile-10.2 would be selected.
#
# Arguments:
# $1 upvar to return the filename
#
# Used gloabal variables:
# VERSIONS
# BUILDBLOCK_DIR
#
find_modulefile() {
local "$1"
local fname=''
local modulefile=''
for fname in "${VERSIONS[@]/#/modulefile-}" 'modulefile'; do
if [[ -r "${BUILDBLOCK_DIR}/${fname}" ]]; then
modulefile="${BUILDBLOCK_DIR}/${fname}"
break;
fi
done
std::upvar $1 "${modulefile}"
[[ -n "${modulefile}" ]]
}
#......................................................................
# non-redefinable post-install. Install:
# - documentation files as defined in the build-script
# - modulefile and file with release
# .
post_install() {
#..............................................................
# install the doc-files specified in the build-script
#
# Arguments:
# none
#
install_doc() {
if [[ -z "${MODULE_DOCFILES}" ]]; then
for f in ${VERSIONS[@]/#/pbuild::install_docfiles_}; do
if typeset -F "$f" 2>/dev/null; then
$f
break
fi
done
fi
[[ -n "${MODULE_DOCFILES}" ]] || return 0
local -r docdir="${PREFIX}/${_DOCDIR}/${module_name}"
std::info \
"%s " \
"${module_name}/${module_version}:" \
"Installing documentation to ${docdir}"
install -m 0755 -d \
"${docdir}"
install -m0444 \
"${MODULE_DOCFILES[@]/#/${SRC_DIR}/}" \
"${docdir}"
return 0
}
#..............................................................
# install build-block files
# - modulefile
# - build-script
# - run-time and build dependencies
# in ${PREFIX}/share/${GROUP}/${module_name}
#
# Arguments:
# none
#
install_pmodules_files() {
local modulefile=''
find_modulefile modulefile || return 0
local -r target_dir="${PREFIX}/share/$GROUP/${module_name}"
mkdir -p "${target_dir}"
install -m0444 \
"${BUILD_SCRIPT}" \
"${target_dir}"
install -m0444 \
"${modulefile}" \
"${target_dir}"
#install -m 0755 \
# -d "${target_dir}/files"
#install -m0444 \
# "${variants_file}" \
# "${target_dir}/files"
local -r fname="${target_dir}/dependencies"
"${MODULECMD}" bash list -t 2>&1 1>/dev/null | \
grep -v "Currently Loaded" > "${fname}" || :
}
#..............................................................
# write run time dependencies to file
write_runtime_dependencies() {
local -r fname="$1"
shift
std::info \
"%s " \
"${module_name}/${module_version}:" \
"writing run-time dependencies to ${fname} ..."
local dep
echo -n "" > "${fname}"
for dep in "$@"; do
[[ -z $dep ]] && continue
if [[ ! $dep == */* ]]; then
# no version given: derive the version
# from the currently loaded module
dep=$( "${MODULECMD}" bash list -t 2>&1 1>/dev/null \
| grep "^${dep}/" )
fi
echo "${dep}" >> "${fname}"
done
}
#..............................................................
# for Linux we need a special post-install to solve the
# multilib problem with LIBRARY_PATH on 64-bit systems
post_install_linux() {
std::info \
"%s " \
"${module_name}/${module_version}:" \
"running post-installation for ${OS} ..."
cd "${PREFIX}"
[[ -d "lib" ]] && [[ ! -d "lib64" ]] && ln -s lib lib64
return 0
}
#..............................................................
cd "${BUILD_DIR}"
[[ "${OS}" == "Linux" ]] && post_install_linux
install_doc
install_pmodules_files
if [[ -n "${runtime_dependencies}" ]]; then
write_runtime_dependencies \
"${PREFIX}/${FNAME_RDEPS}" \
"${runtime_dependencies[@]}"
fi
if [[ -n "${install_dependencies}" ]]; then
write_runtime_dependencies \
"${PREFIX}/${FNAME_IDEPS}" \
"${install_dependencies[@]}"
fi
if [[ "${bootstrap}" == 'no' ]]; then
install_modulefile
install_release_file
fi
cleanup_build
cleanup_src
std::info \
"%s " \
"${module_name}/${module_version}:" \
"Done ..."
return 0
}
#......................................................................
# Install modulefile in ${PMODULES_ROOT}/${GROUP}/modulefiles/...
#
# Arguments
# none
install_modulefile() {
local src=''
find_modulefile src
if (( $? != 0 )); then
std::info \
"%s " \
"${module_name}/${module_version}:" \
"skipping modulefile installation ..."
return
fi
std::info \
"%s " \
"${module_name}/${module_version}:" \
"installing modulefile '${modulefile_name}' ..."
mkdir -p "${modulefile_dir}"
install -m 0444 "${src}" "${modulefile_name}"
}
install_release_file() {
local -r release_file="${modulefile_dir}/.release-${module_version}"
if [[ -r "${release_file}" ]]; then
local release
read release < "${release_file}"
if [[ "${release}" != "${module_release}" ]]; then
std::info \
"%s " \
"${module_name}/${module_version}:" \
"changing release from" \
"'${release}' to '${module_release}' ..."
echo "${module_release}" > "${release_file}"
fi
else
std::info \
"%s " \
"${module_name}/${module_version}:" \
"setting release to '${module_release}' ..."
echo "${module_release}" > "${release_file}"
fi
}
cleanup_build() {
[[ ${enable_cleanup_build} == yes ]] || return 0
[[ "${BUILD_DIR}" == "${SRC_DIR}" ]] && return 0
{
cd "/${BUILD_DIR}/.." || std::die 42 "Internal error"
[[ "$(pwd)" == "/" ]] && \
std::die 1 \
"%s " "${module_name}/${module_version}:" \
"Oops: internal error:" \
"BUILD_DIR is set to '/'"
std::info \
"%s " \
"${module_name}/${module_version}:" \
"Cleaning up '${BUILD_DIR}'..."
rm -rf "${BUILD_DIR##*/}"
};
return 0
}
cleanup_src() {
[[ ${enable_cleanup_src} == yes ]] || return 0
{
cd "/${SRC_DIR}/.." || std::die 42 "Internal error"
[[ $(pwd) == / ]] && \
std::die 1 \
"%s " "${module_name}/${module_version}:" \
"Oops: internal error:" \
"SRC_DIR is set to '/'"
std::info \
"%s " \
"${module_name}/${module_version}:" \
"Cleaning up '${SRC_DIR}'..."
rm -rf "${SRC_DIR##*/}"
};
return 0
}
build_target() {
local dir="$1" # src or build directory, depends on target
local target="$2" # prep, configure, compile or install
if [[ -e "${BUILD_DIR}/.${target}" ]] && \
[[ ${force_rebuild} != 'yes' ]]; then
return 0
fi
local targets=()
targets+=( ${VERSIONS[@]/#/pbuild::pre_${target}_${system}_} )
targets+=( pbuild::pre_${target}_${system} )
targets+=( ${VERSIONS[@]/#/pbuild::pre_${target}_${OS}_} )
targets+=( pbuild::pre_${target}_${OS} )
targets+=( ${VERSIONS[@]/#/pbuild::pre_${target}_} )
targets+=( pbuild::pre_${target} )
targets+=( ${VERSIONS[@]/#/pbuild::${target}_${system}_} )
targets+=( pbuild::${target}_${system} )
targets+=( ${VERSIONS[@]/#/pbuild::${target}_${OS}_} )
targets+=( pbuild::${target}_${OS} )
targets+=( ${VERSIONS[@]/#/pbuild::${target}_} )
targets+=( pbuild::${target} )
targets+=( ${VERSIONS[@]/#/pbuild::post_${target}_${system}_} )
targets+=( pbuild::post_${target}_${system} )
targets+=( ${VERSIONS[@]/#/pbuild::post_${target}_${OS}_} )
targets+=( pbuild::post_${target}_${OS} )
targets+=( ${VERSIONS[@]/#/pbuild::post_${target}_} )
targets+=( pbuild::post_${target} )
for t in "${targets[@]}"; do
# We cd into the dir before calling the function -
# just to be sure we are in the right directory.
#
# Executing the function in a sub-process doesn't
# work because in some function global variables
# might/need to be set.
#
cd "${dir}"
typeset -F "$t" 2>/dev/null && "$t" || :
done
touch "${BUILD_DIR}/.${target}"
}
#......................................................................
# build module ${module_name}/${module_version}
build_module() {
std::info \
"%s " \
"${module_name}/${module_version}:" \
"start building" \
${with_modules:+with ${with_modules[@]}} \
"..."
[[ ${dry_run} == yes ]] && std::die 0 ""
mkdir -p "${SRC_DIR}"
mkdir -p "${BUILD_DIR}"
std::info \
"%s " \
"${module_name}/${module_version}:" \
"preparing sources ..."
# write stdout and stderr to logfile, stderr to terminal
# write all to logfile and terminal
build_target "${SRC_DIR}" prep
[[ "${build_target}" == "prep" ]] && return 0
std::info \
"%s " \
"${module_name}/${module_version}:" \
"configuring ..."
build_target "${BUILD_DIR}" configure
[[ "${build_target}" == "configure" ]] && return 0
std::info \
"%s " \
"${module_name}/${module_version}:" \
"compiling ..."
build_target "${BUILD_DIR}" compile
[[ "${build_target}" == "compile" ]] && return 0
std::info \
"%s " \
"${module_name}/${module_version}:" \
"installing ..."
mkdir -p "${PREFIX}"
build_target "${BUILD_DIR}" install
post_install
}
remove_module() {
if [[ -d "${PREFIX}" ]]; then
std::info \
"%s " \
"${module_name}/${module_version}:" \
"removing all files in '${PREFIX}' ..."
[[ "${dry_run}" == 'no' ]] && rm -rf ${PREFIX}
fi
if [[ -e "${modulefile_name}" ]]; then
std::info \
"%s " \
"${module_name}/${module_version}:" \
"removing modulefile '${modulefile_name}' ..."
[[ "${dry_run}" == 'no' ]] && rm -v "${modulefile_name}"
fi
local release_file="${modulefile_dir}/.release-${module_version}"
if [[ -e "${release_file}" ]]; then
std::info \
"%s " \
"${module_name}/${module_version}:" \
"removing release file '${release_file}' ..."
[[ "${dry_run}" == 'no' ]] && rm -v "${release_file}"
fi
rmdir -p "${modulefile_dir}" 2>/dev/null || :
}
########################################################################
#
# here we really start with make_all()
#
# setup module specific environment
if [[ "${bootstrap}" == 'no' ]]; then
check_supported_systems
check_supported_os
check_supported_compilers
set_full_module_name_and_prefix
if [[ -e "${modulefile_name}" ]] \
&& [[ -d ${PREFIX} ]] \
&& [[ ${force_rebuild} != 'yes' ]]; then
if [[ "${module_release}" == 'removed' ]]; then
remove_module
else
std::info \
"%s " \
"${module_name}/${module_version}:" \
${with_modules:+with ${with_modules[@]}} \
"already exists, not rebuilding ..."
if [[ "${opt_update_modulefiles}" == "yes" ]]; then
install_modulefile
fi
install_release_file
fi
else
if [[ "${module_release}" == 'deprecated' ]]; then
std::info \
"%s " "${module_name}/${module_version}:" \
${with_modules:+with ${with_modules[@]}} \
"is deprecated, skiping!"
install_release_file
else
build_module
fi
fi
else
build_module
fi
return 0
}
pbuild.init_env() {
#......................................................................
#
# parse the passed version string
#
# the following global variables will be set in this function:
# V_MAJOR
# V_MINOR
# V_PATCHLVL
# V_RELEASE
# USE_FLAGS
#
parse_version() {
local v="$1"
V_MAJOR='' # first number in version string
V_MINOR='' # second number in version string (or empty)
V_PATCHLVL='' # third number in version string (or empty)
V_RELEASE='' # module release (or empty)
: ${USE_FLAGS:=''} # architectures (or empty)
local tmp=''
if [[ "$v" =~ "_" ]]; then
tmp="${v#*_}"
USE_FLAGS+=":${tmp//_/:}:"
v="${v%%_*}"
fi
V_PKG="${v%%-*}" # version without the release number
if [[ $v == *-* ]]; then
V_RELEASE="${v#*-}" # release number
else
V_RELEASE=''
fi
case "${V_PKG}" in
*.*.* )
V_MAJOR="${V_PKG%%.*}"
tmp="${V_PKG#*.}"
V_MINOR="${tmp%%.*}"
V_PATCHLVL="${tmp#*.}"
;;
*.* )
V_MAJOR="${V_PKG%.*}"
V_MINOR="${V_PKG#*.}"
;;
* )
V_MAJOR="${V_PKG}"
;;
esac
VERSIONS=( ${V_MAJOR} )
if [[ -n ${V_MINOR} ]]; then
VERSIONS=( ${V_MAJOR}.${V_MINOR} ${VERSIONS[@]} )
fi
if [[ -n ${V_PATCHLVL} ]]; then
VERSIONS=( ${V_MAJOR}.${V_MINOR}.${V_PATCHLVL} ${VERSIONS[@]} )
fi
if [[ -n ${V_RELEASE} ]]; then
VERSIONS=( ${V_MAJOR}.${V_MINOR}.${V_PATCHLVL}-${V_RELEASE} ${VERSIONS[@]} )
fi
}
local -r module_name="$1"
local -r module_version="$2"
SRC_DIR="${PMODULES_TMPDIR}/${module_name}-${module_version}/src"
BUILD_DIR="${PMODULES_TMPDIR}/${module_name}-${module_version}/build"
# P and V can be used in the build-script, so we have to set them here
P="${module_name}"
V="${module_version}"
parse_version "${module_version}"
SOURCE_URLS=()
SOURCE_SHA256_SUMS=()
SOURCE_NAMES=()
CONFIGURE_ARGS=()
SUPPORTED_SYSTEMS=()
SUPPORTED_OS=()
SUPPORTED_COMPILERS=()
PATCH_FILES=()
PATCH_STRIPS=()
PATCH_STRIP_DEFAULT='1'
MODULE_DOCFILES=()
configure_with='undef'
}
pbuild.build_module() {
module_name="$1"
module_version="$2"
module_release="$3"
shift 3
with_modules=( "$@" )
# used in pbuild::make_all
declare bootstrap='no'
declare -a runtime_dependencies=()
declare -a install_dependencies=()
#......................................................................
#
# test whether a module is loaded or not
#
# $1: module name
#
is_loaded() {
[[ :${LOADEDMODULES}: =~ :$1: ]]
}
#......................................................................
#
# build a dependency
#
# $1: name of module to build
#
# :FIXME: needs testing
#
build_dependency() {
local -r m=$1
std::debug "${m}: module not available"
local rels=( ${ReleaseStages//:/ } )
[[ ${dry_run} == yes ]] && \
std::die 1 \
"%s " \
"${m}: module does not exist," \
"cannot continue with dry run..."
std::info "%s " \
"$m: module does not exist, trying to build it..."
local args=( '' )
set -- ${ARGS[@]}
while (( $# > 0 )); do
case $1 in
-j )
args+=( "-j $2" )
shift
;;
--jobs=[0-9]* )
args+=( $1 )
;;
-v | --verbose)
args+=( $1 )
;;
--with=*/* )
args+=( $1 )
;;
esac
shift
done
find_build_script(){
local p=$1
local script=$(find "${BUILDBLOCK_DIR}/../.." -path "*/$p/build")
std::get_abspath "${script}"
}
local buildscript=$(find_build_script "${m%/*}")
[[ -x "${buildscript}" ]] || \
std::die 1 \
"$m: build-block not found!"
if ! "${buildscript}" "${m#*/}" ${args[@]}; then
std::die 1 \
"$m: oops: build failed..."
fi
}
#......................................................................
#
# Load build- and run-time dependencies.
#
# Arguments:
# none
#
# Variables
# [r] module_release set if defined in a variants file
# runtime_dependencies runtime dependencies from variants added
#
load_build_dependencies() {
local m=''
for m in "${with_modules[@]}"; do
# module name prefixes in dependency declarations:
# 'b:' this is a build dependency
# 'r:' this a run-time dependency, *not* required for
# building
# without prefix: this is a build and
# run-time dependency
if [[ "${m:0:2}" == "b:" ]]; then
m=${m#*:} # remove 'b:'
elif [[ "${m:0:2}" == "r:" ]]; then
m=${m#*:} # remove 'r:'
runtime_dependencies+=( "$m" )
elif [[ "${m:0:2}" == "R:" ]]; then
m=${m#*:} # remove 'R:'
install_dependencies+=( "$m" )
continue
else
runtime_dependencies+=( "$m" )
fi
is_loaded "$m" && continue
# 'module avail' might output multiple matches if module
# name and version are not fully specified or in case
# modules with and without a release number exist.
# Example:
# mpc/1.1.0 and mpc/1.1.0-1. Since we get a sorted list
# from 'module avail' and the full version should be set
# in the variants file, we look for the first exact
# match.
local release_of_dependency=''
if ! pbuild::module_is_avail "$m" release_of_dependency; then
build_dependency "$m"
pbuild::module_is_avail "$m" release_of_dependency || \
std::die 6 "Oops"
fi
# should be set, just in case it is not...
: ${release_of_dependency:='unstable'}
# for a stable module all dependencies must be stable
if [[ "${module_release}" == 'stable' ]] \
&& [[ "${release_of_dependency}" != 'stable' ]]; then
std::die 5 \
"%s " "${module_name}/${module_version}:" \
"release cannot be set to '${module_release}'" \
"since the dependency '$m' is ${release_of_dependency}"
# for a unstable module no dependency must be deprecated
elif [[ "${module_release}" == 'unstable' ]] \
&& [[ "${release_of_dependency}" == 'deprecated' ]]; then
std::die 5 \
"%s " "${module_name}/${module_version}:" \
"release cannot be set to '${module_release}'" \
"since the dependency '$m' is ${release_of_dependency}"
fi
std::info "Loading module: ${m}"
eval $( "${MODULECMD}" bash load "${m}" )
done
}
std::info \
"%s " \
"${module_name}/${module_version}:" \
${with_modules:+with ${with_modules[@]}} \
"building ..."
MODULECMD="${PMODULES_HOME}/bin/modulecmd"
[[ -x ${MODULECMD} ]] || \
std::die 2 "No such file or executable -- '${MODULECMD}'"
eval $( "${MODULECMD}" bash use unstable )
eval $( "${MODULECMD}" bash use deprecated )
eval $( "${MODULECMD}" bash purge )
# :FIXME: this is a hack!!!
# shouldn't this be set in the build-script?
eval $( "${MODULECMD}" bash use Libraries )
eval $( "${MODULECMD}" bash use System )
unset C_INCLUDE_PATH
unset CPLUS_INCLUDE_PATH
unset CPP_INCLUDE_PATH
unset LIBRARY_PATH
unset LD_LIBRARY_PATH
unset DYLD_LIBRARY_PATH
unset CFLAGS
unset CPPFLAGS
unset CXXFLAGS
unset LIBS
unset LDFLAGS
unset CC
unset CXX
unset FC
unset F77
unset F90
load_build_dependencies
pbuild.init_env "${module_name}" "${module_version}"
pbuild::make_all
std::info "* * * * *\n"
}
pbuild.bootstrap() {
local -r module_name="$1"
local -r module_version="$2"
# used in pbuild::make_all
bootstrap='yes'
pbuild.init_env "${module_name}" "${module_version}"
MODULECMD=$(which true)
GROUP='Tools'
PREFIX="${PMODULES_ROOT}/${GROUP}/Pmodules/${PMODULES_VERSION}"
C_INCLUDE_PATH="${PREFIX}/include"
CPLUS_INCLUDE_PATH="${PREFIX}/include"
CPP_INCLUDE_PATH="${PREFIX}/include"
LIBRARY_PATH="${PREFIX}/lib"
LD_LIBRARY_PATH="${PREFIX}/lib"
DYLD_LIBRARY_PATH="${PREFIX}/lib"
PATH+=":${PREFIX}/bin"
PATH+=":${PREFIX}/sbin"
pbuild::make_all
}
# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: