Files
Pmodules/Pmodules/modbuild.in
T

1617 lines
41 KiB
Plaintext
Executable File

#!@BASH@
#
# The following build specific variables are set and used in libpbuild.bash:
# ARGS
# BUILD_SCRIPT
# BUILDBLOCK_DIR
#
#.............................................................................
declare VERSION='@PMODULES_VERSION@'
# get absolute path of script
declare mydir=$(dirname "$0")
declare -r mydir=$(cd ${mydir} && pwd -P)
source "${mydir}/../lib/libstd.bash" || {
echo "Oops: cannot source library -- '$_'" 1>&2; exit 3;
}
##############################################################################
#
# check availability of used commands and set environment variables
# cmd=$(which cmd)
# in the following we use these environment variable to call binaries.
#
declare -r MODULECMD="${PMODULES_HOME}/bin/modulecmd"
[[ -x ${MODULECMD} ]] || \
std::die 1 "Oops: modulecmd binary not available!"
std::def_cmds "${mydir}/../libexec" \
'sevenz' 'yq'
std::def_cmds '/usr/bin:/bin:/usr/sbin:/sbin' \
'awk' 'base64' 'cat' 'cp' 'envsubst' 'find' 'getopt' 'grep' \
'install' 'logger' 'make' 'mkdir' 'mktemp' 'patch' 'pwd' \
'rm' 'rmdir' 'sort' 'tar' 'tee' 'uname'
declare -r OS="$(${uname} -s)"
if [[ ${OS} == 'Darwin' ]]; then
std::def_cmds '/usr/bin:/bin:/usr/sbin:/sbin:/opt/local/bin' \
'curl' 'otool' 'shasum' 'sysctl'
declare -r sha256sum="${shasum -a 256}"
else
std::def_cmds '/usr/bin:/bin:/usr/sbin:/sbin' \
'ldd' 'curl' 'sha256sum'
fi
# for the time being, we still set PATH. Just in case we forgot a binary
PATH="${PMODULES_HOME}/bin:/usr/bin:/bin:/usr/sbin:/sbin"
##############################################################################
source "${mydir}/../lib/libpbuild.bash" || \
std::die 3 "Oops: cannot source library -- '$_'"
source "${mydir}/../lib/libpmodules.bash" || \
std::die 3 "Oops: cannot source library -- '$_'"
##############################################################################
set -o nounset
shopt -s nocaseglob
shopt -s extglob
shopt -s nullglob
##############################################################################
#
usage() {
std::error "
USAGE:
$0 [options..] [build_script] version
MANDATORY ARGUMENTS:
version
Version of module to build.
SELECT VARIANT TO BUILD:
--system
Specify the system for selecting a variants. Defaults to the
OS version and release like 'rhel6'.
--with=P/V
Select variant to compile. Use multiple '--with' arguments
to make the selected variant unique.
BUILD-STEPS OPTIONS:
--clean-install
Remove module if already exist before building.
--prep
Prepare sources: unpack sources and apply patches only.
--configure
Prepare and configure sources.
--compile
Prepare, configure and compile everything.
--install
Prepare, configure and compile everything. Finally run install
step. Do not cleanup build and source directory.
--all
Run throu all steps including cleanup.
-update-modulefiles
Only install the modulefile and set the release.
MISCELLANEOUS OPTIONS:
-? | -h | --help
Print usage.
-V | --version )
Print version.
-v | --verbose )
Verbose output.
--debug )
Run in debug mode.
-j N | --jobs=N
Run N parallel make jobs.
-f | --force-rebuild
Force rebuild of module.
--dry-run
Dry run.
--disable-cleanup-build
--disable-cleanup-src
--disable-cleanup
Disable the cleanup of files in the build and/or source
directory. Default is to remove all files in the build
respective source directory at the beginning of a build
and after a successful build.
--distdir
Directory where to store and lookup downloaded files.
--tmpdir
Directory used for building a module.
--legacy
Use legacy configuration files.
--overlay
Install in this overlay. Defaults to the base overlay. This
option can only be used with legacy configuration file.
DOCUMENTATION:
Full documentation is available at
http://pmodules.gitpages.psi.ch
"
exit 1
}
##############################################################################
#
# parse options and arguments
#
# command line arguments are taken first
# then configuration file
# last default
# save arguments, required for building dependencies
declare -r ARGS="$@"
# versions to be build, '.*' or none means all
declare versions_to_build=''
declare opt_build_target='all'
declare opt_dry_run='no'
declare opt_enable_cleanup_build='yes'
declare opt_enable_cleanup_src='yes'
declare opt_force_rebuild='no'
declare -i opt_jobs=0
declare opt_update_modulefiles='no'
declare opt_system=''
declare opt_overlay=''
declare opt_verbose='no'
# array collecting all modules specified on the command line via '--with=module'
declare -a opt_with_modules=() # :FIXME: legacy build should also use the dict
declare -A opt_with_dict=()
declare -- opt_config_file=''
declare -- opt_debug='no'
declare -- opt_yaml='yes'
declare -- opt_check_mode='no'
declare -- opt_variant=''
declare -- opt_clean_install='no'
declare -- opt_parent_prefix=''
declare -- BUILD_SCRIPT=''
declare -- yaml_config_file=''
declare -a legacy_config_files=()
declare -- module_name=''
declare -- module_type='module'
declare -- echo=':'
parse_args() {
#
# The first argument ($1) is the build-script, if called in the
# usual way:
# ./<build-script> <version> [options]
# If called via:
# modbuild <build-script> [options]
# the build-script MUST be passed as first argument.
#
(( $# == 0 )) && usage
while (( $# > 0 )); do
case $1 in
-j )
opt_jobs="$2"
shift
;;
--jobs=[0-9]* )
opt_jobs="${1/--jobs=}"
;;
-v | --verbose )
trap 'echo "$BASH_COMMAND"' DEBUG
opt_verbose='yes'
echo='echo'
;;
--debug )
opt_debug='yes'
set -x
;;
-f | --force-rebuild )
opt_force_rebuild='yes'
;;
-\? | -h | --help )
usage
;;
-V | --version )
std::die 0 "\nPmodules version ${VERSION}\nCopyright GNU GPL v2\n"
;;
--dry-run )
opt_dry_run='yes'
;;
--disable-cleanup )
opt_enable_cleanup_build='no'
opt_enable_cleanup_src='no'
;;
--disable-cleanup-build )
opt_enable_cleanup_build='no'
;;
--disable-cleanup-src )
opt_enable_cleanup_src='no'
;;
--distdir | --distdir=* )
if [[ $1 == *=* ]]; then
PMODULES_DISTFILESDIR="${1/--distdir=}"
else
PMODULES_DISTFILESDIR="$2"
shift
fi
;;
--tmpdir | --tmpdir=* )
if [[ $1 == *=* ]]; then
PMODULES_TMPDIR="${1#--*=}"
else
PMODULES_TMPDIR="$2"
shift
fi
;;
--system | --system=* )
if [[ $1 == *=* ]]; then
opt_system="${1#--*=}"
else
opt_system="$2"
shift
fi
;;
--overlay | --overlay=* )
if [[ $1 == *=* ]]; then
opt_overlay="${1#--*=}"
else
opt_overlay="$2"
shift
fi
;;
--yaml )
opt_yaml='yes'
;;
--check-mode )
opt_check_mode='yes'
;;
--legacy )
opt_yaml='no'
;;
--use-flags | --use-flags=* )
if [[ $1 == *=* ]]; then
USE_FLAGS=":${1#--*=}:"
else
USE_FLAGS=":$2:"
shift
fi
;;
--with | --with=*/* )
if [[ $1 == *=* ]]; then
opt_with_modules+=( "${1#--*=}" )
opt_with_dict[${1#--*=}]=0
else
opt_with_modules+=( "$2" )
opt_with_dict[$2]=0
shift
fi
;;
--prep | --configure | --compile | --install | --all )
opt_build_target=${1:2}
;;
--clean-install )
opt_clean_install='yes'
;;
--update-modulefiles )
opt_update_modulefiles='yes'
;;
--config-file | --config-file=* )
if [[ $1 == *=* ]]; then
opt_config_file=( "${1#--*=}" )
else
opt_config_file=( "$2" )
shift
fi
;;
--variant | --variant=* )
if [[ $1 == *=* ]]; then
opt_variant=( "${1#--*=}" )
else
opt_variant=( "$2" )
shift
fi
;;
--parent-prefix | --parent-prefix=* )
if [[ $1 == *=* ]]; then
opt_parent_prefix=( "${1#--*=}" )
else
opt_parent_prefix=( "$2" )
shift
fi
module_type='sub_package'
pbuild.set_prefix "${opt_parent_prefix}"
;;
-- | '' )
:
;;
-* )
std::die 1 "Invalid option -- '$1'"
;;
* )
local -- arg="$1"
if [[ -r ${arg} ]]; then
BUILD_SCRIPT="$(std::get_abspath "${arg}")"
BUILDBLOCK_DIR=$(dirname "${BUILD_SCRIPT}")
elif [[ "${arg}" == */* ]]; then
module_name="${arg%/*}"
versions_to_build+=" ${arg#*/}"
else
versions_to_build+=" $1"
fi
;;
esac
shift
done
[[ -z "${BUILD_SCRIPT}" ]] && \
std::die 1 "%s " \
"Build script argument missing?"
# if no version is specified on the cmd-line, build all versions
#(( ${#versions_to_build[@]} > 0)) || versions_to_build+=( '.*' )
[[ -z ${versions_to_build} ]] && versions_to_build='.*'
# set system if not set on the cmd-line
opt_system="${opt_system:-$(std::get_os_release)}"
# set config file
if [[ ${opt_yaml,,} == 'no' ]]; then
# look for legacy config files
if [[ -n ${opt_config_file} ]]; then
legacy_config_files=( "${opt_config_file}" )
else
shopt -q nullglob || :
local -i nullglob_set=$?
legacy_config_files=( "${BUILDBLOCK_DIR}"/*/variants.${opt_system} )
legacy_config_files+=( "${BUILDBLOCK_DIR}"/*/variants."$(uname -s)" )
legacy_config_files+=( "${BUILDBLOCK_DIR}"/*/variants )
(( nullglob_set == 1 )) && shopt -u nullglob
fi
(( ${#legacy_config_files[@]} == 0 )) && \
std::die 1 "No legacy configuration file found!"
std::info "Using legacy variants files."
opt_overlay="${opt_overlay:-'base'}"
else
yaml_config_file="${opt_config_file:-${BUILDBLOCK_DIR}/files/config.yaml}"
[[ -r "${yaml_config_file}" ]] || \
std::die 2 \
"%s -- %s" \
"YAML config file doesn't exist or is not readable" \
"${yaml_config_file}"
[[ "${opt_overlay}" == 'yes' ]] && \
std::die 2 \
"opt '--overlay' can only be used together with legacy config files!"
std::info "Using YAML configuration file - ${yaml_config_file}"
fi
}
#
# bash brace expansion of given args. Input args like:
#
# "text" "gcc/{9.3.0,10.3.0}" "openmpi/{4.0.5,4.1.0}"
#
# will be expanded to the following four lines:
#
# "text gcc/9.3.0 openmpi/4.0.5"
# "text gcc/9.3.0 openmpi/4.1.0"
# "text gcc/10.3.0 openmpi/4.0.5"
# "text gcc/10.3.0 openmpi/4.1.0"
#
bash_expand(){
local text="$1"
shift
local to_expand=( "${@}" )
if (( ${#to_expand[@]} == 0 )); then
echo ${text}
else
local list
eval list=( ${to_expand[0]} )
local s
for s in ${list[*]}; do
bash_expand "${text} ${s}" "${to_expand[@]:1}"
done;
fi;
}
build_modules_legacy() {
local -a files=( "${legacy_config_files[@]}" )
local -A mod_overlays=()
expand_variants_file(){
local -r input="$1"
local -a toks=()
while read -a toks; do
# skip empty and comment lines
(( ${#toks[@]} == 0 )) && continue
[[ ${toks[0]:0:1} == '#' ]] && continue
local -a deps=( ${toks[*]:2} )
bash_expand "${toks[0]} ${toks[1]}" "${deps[@]}"
done < "${input}"
}
local name="$1"
local version="$2"
local exact_match='no'
if [[ "${version:0:1}" == "=" ]]; then
exact_match='yes'
version="${version:1}"
fi
shift 2
local with_modules=( $* )
# if we have to build a dependency, we might have less dependencies
# on it. Or in other words: the list of "with modules" might be
# overdetermined. In the loop below we check, which dependencies
# specified with '--with' are required.
local m
local pattern="/^${name}\/${version}[[:blank:]]/"
for m in "${with_modules[@]}"; do
if [[ -n $(${awk} "/${m%/*}[\/ ]/" "${files[@]}") ]]; then
pattern+=" && /${m//\//\\/}/"
fi
done
local variants=()
for f in "${files[@]}"; do
local line=''
while read line; do
variants+=( "${line}" )
done < <(expand_variants_file "${f}" | ${awk} "${pattern}")
done
if (( ${#variants[@]} == 0 )); then
std::info "%s " \
"${name}/${version}:" \
"no suitable variant found!"
std::die 10 "Aborting..."
elif (( ${#variants[@]} > 1 )) && [[ ${exact_match} == 'yes' ]]; then
std::info "%s " \
"Multiple variants found:"
for variant in "${variants[@]}"; do
std::info "${variant}"
done
std::die 10 "Aborting..."
fi
[[ -v OverlayInfo[${opt_overlay}:inst_root] ]] || \
std::die 2 "%s" \
"Overlay doesn't exist - ${opt_overlay}"
local ol_name="${opt_overlay}"
declare ol_inst_root="${OverlayInfo[${opt_overlay}:inst_root]}"
declare ol_mod_root="${OverlayInfo[${opt_overlay}:mod_root]}"
local -i i=0
local -i num_variants=${#variants[@]}
for ((i = 0; i < num_variants; i++)); do
local tokens=( ${variants[i]} )
local name="${tokens[0]%/*}"
version="${tokens[0]#*/}"
release="${tokens[1]}"
case ${release} in
unstable|stable|deprecated|remove|removed)
:
;;
* )
std::info "%s " \
"${name}/${version}:" \
"invalid release stage '${release}'!"
std::die 10 "Aborting..."
;;
esac
with_modules=( "${tokens[@]:2}" )
pbuild.build_module_legacy \
"${name}" "${version}" \
"${release}" "${with_modules[@]}"
done
}
get_yaml_file_fmt(){
: "
Get format version of configuration file. Print the version number
to stdout if it is valid.
"
${yq} -e '.' "$1" &>/dev/null || \
std::die 3 "%s -- %s" \
"Error in YAML config file, please check with linter" \
"$1"
local -- fmt=''
fmt=$(${yq} -e ".format" "$1") || \
std::die 3 "Error reading config file format -- $1"
case "${fmt}" in
1 )
:
;;
* )
std::die 3 "Unknown YAML Pmodules config file format -- $1"
;;
esac
echo "${fmt}"
}
read_yaml_config_file() {
: "
Test whether the configuration file '$1' provides configurations
for the module '$2'. If yes, the YAML block with the configuration
is printed to stdout.
"
local -- file_name="$1"
local -- module_name="$2"
local -- yaml=''
yaml=$( ${yq} -Ne e ".${module_name}" "${file_name}" 2>/dev/null ) || \
std::die 3 "Configuration for '${module_name}' missing -- ${file_name}"
echo "${yaml}"
}
build_modules_yaml(){
local -- name="$1"
local -- version="$2"
shift 2
local -a with_modules=( $* )
if [[ "${opt_check_mode}" == 'yes' ]]; then
local -- yamllint
if ! which yamllint > /dev/null 2>&1; then
eval $( "${MODULECMD}" bash load yamllint/1.28.0 )
fi
which yamllint > /dev/null 2>&1 || \
std::die 3 "yamllint not found"
yamllint "${yaml_config_file}"
fi
local -- file_fmt=$(get_yaml_file_fmt "${yaml_config_file}")
local -- yaml_mod_config=''
yaml_mod_config=$(read_yaml_config_file "${yaml_config_file}" "${name}")
case "${file_fmt}" in
1 )
build_modules_yaml_v1 \
"${yaml_mod_config}" \
"${name}" "${version}" \
"${with_modules[@]}"
;;
* )
std::die 255 "Oops!" # we should never be here!
;;
esac
}
#......................................................................
#
# Initialise environment modules.
#
# Arguments:
# none
#
init_module_environment(){
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?
if [[ -e "${PMODULES_HOME%%/Tools*}/Libraries" ]]; then
eval $( "${MODULECMD}" bash use Libraries )
fi
if [[ -e "${PMODULES_HOME%%/Tools*}/System" ]]; then
eval $( "${MODULECMD}" bash use System )
fi
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
}
parse_version() {
local v="$1"
V="$1"
: ${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
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=()
if [[ -n ${V_RELEASE} ]]; then
VERSIONS+=( ${V_PKG}-${V_RELEASE} )
fi
if [[ -n ${V_PATCHLVL} ]]; then
VERSIONS+=( ${V_MAJOR}.${V_MINOR}.${V_PATCHLVL} )
fi
if [[ -n ${V_MINOR} ]]; then
VERSIONS+=( ${V_MAJOR}.${V_MINOR} )
fi
VERSIONS+=( ${V_MAJOR} )
}
# these variables must be export for envsubst(1)
declare -x P=''
declare -x V=''
declare -x V_PKG=''
declare -x V_MAJOR='' # first number in version string
declare -x V_MINOR='' # second number in version string (or empty)
declare -x V_PATCHLVL='' # third number in version string (or empty)
declare -x V_RELEASE='' # module release (or empty)
declare -A SHASUMS=()
declare -a MODULE_DOCFILES=()
declare -A Yaml_valid_keys_for_module=(
['defaults']=1
['shasums']=1
['type']=1
['versions']=1
)
declare -A Yaml_default_config=(
["build_requires"]='' # !!seq of strings
["compile_in_sourcetree"]='no' # !!str
["configure_with"]='auto' # !!str
["configure_args"]='' # !!seq of strings
["configure_args+"]='' # !!seq of strings
["default_variant"]='' # !!str
["docfiles"]='' # !!seq of strings
["docfiles+"]='' # !!seq of strings
["group"]='Tools' # !!str
["group_deps"]='' # !!map
["overlay"]='base' # !!str
["patch_files"]='' # !!seq
["patch_files+"]='' # !!seq
["relstage"]='unstable' # !!str
["runtime_deps"]='' # !!seq of strings
["script"]='build' # !!str
["suffix"]='' # !!str
["systems"]='' # !!seq of strings
["sub_packages"]='' # !!map
["urls"]='' # !!map
["variant"]='' # !!str
)
declare -A Yaml_valid_vk_keys=(
['config']=1 # !!map
['variants']=1 # !!map
)
declare -A hierarchical_groups=()
hierarchical_groups['compiler']='compiler'
hierarchical_groups['mpi']='compiler mpi'
hierarchical_groups['hdf5']='compiler mpi hdf5'
hierarchical_groups['hdf5_serial']='compiler hdf5_serial'
build_modules_yaml_v1(){
: "
"
local -- yaml_mod_config="$1"
local -- name="$2"
local -- version="$3"
shift 3
local -a with_modules=( $* )
check_yaml_keys(){
local -n valid_yaml_keys="$1"
local -n used_yaml_keys="$2"
used_yaml_keys=()
local -- key=''
local -a keys=()
keys=( $(${yq} '.|keys().[]' 2>/dev/null) )
debug "top-level keys: ${keys[@]}"
for key in "${keys[@]}"; do
[[ -v valid_yaml_keys[${key}] ]] || \
std::die 3 "Invalid key in YAML configuration file -- ${key}"
used_yaml_keys[${key}]=1
done
}
get_value(){
local -- yaml_input="$1"
local -n val="$2"
local -- key="$3"
local -- expected_type="$4"
local -- type=''
type=$( ${yq} ".${key} | type" 2>/dev/null <<<"${yaml_input}")
if [[ "${type}" != "${expected_type}" ]]; then
std::die 3 "%s" \
"Value of '${key}' must be of type '${expected_type:2}', but is '${type:2}'!"
fi
val=$( ${yq} -e ".${key}" \
2>/dev/null <<<"${yaml_input}" ) || val=''
}
get_seq(){
local -- yaml_input="$1"
local -n val="$2"
local -- key="$3"
local -- type=''
type=$( ${yq} ".${key} | type" 2>/dev/null <<<"${yaml_input}")
if [[ "${type}" != '!!seq' ]]; then
std::die 3 "%s" \
"Value of '${key}' must be of type 'seq', but is of type '${type:2}'!"
fi
val=$( ${yq} -e ".${key}[]" \
2>/dev/null <<<"${yaml_input}" ) || val=''
}
get_config(){
local -- yaml_input="$1"
local -n cfg="$2" # ref. to return configuration
local -n dfl="$3" # ref. to defaults
local -- key=''
local -- value=''
for key in "${!dfl[@]}"; do
cfg[${key}]="${dfl[${key}]}"
done
if [[ -z "${yaml_input}" ]]; then
return 0
fi
local -a keys=()
keys=( $( ${yq} -e ".|keys().[]" <<<"${yaml_input}" 2>/dev/null )) || \
std::die 3 "Oops: retrieving keys from:\n${yaml_input}"
debug "config keys: ${keys[@]}"
for key in "${keys[@]}"; do
[[ -v dfl[${key,,}] ]] || \
std::die 3 "%s -- %s\n%s" \
"Invalid key in configuration" \
"${key}" "${yaml_input}"
case ${key} in
compile_in_sourcetree )
get_value "${yaml_input}" value "${key}" '!!bool'
case ${value,,} in
true )
cfg[${key,,}]='yes'
;;
false )
cfg[${key,,}]='no'
;;
* )
std::die 3 "%s '%s' -- %s" \
"Invalid value for" \
'${key}' \
"${value}"
;;
esac
;;
configure_with )
get_value "${yaml_input}" value "${key}" '!!str'
case ${value,,} in
auto | cmake | autotools )
cfg[${key,,}]="${value,,}"
;;
* )
std::die 3 "%s '%s' -- %s" \
"Invalid value for" \
'configure_with' \
"${value}"
;;
esac
;;
default_variant | group | overlay | script | suffix )
get_value "${yaml_input}" value "${key}" '!!str'
cfg[${key,,}]="${value}"
;;
group_deps )
get_value "${yaml_input}" value "${key}" '!!map'
cfg[${key,,}]="${value}"
;;
relstage )
get_value "${yaml_input}" value "${key}" '!!str'
case ${value,,} in
unstable | stable | deprecated )
cfg[${key,,}]="${value,,}"
;;
remove | removed )
cfg[${key,,}]='remove'
;;
* )
std::die 3 "%s '%s' -- %s" \
"Invalid value for" \
'relstage' \
"${value}"
;;
esac
;;
urls | sub_packages )
get_value "${yaml_input}" value "${key}" '!!seq'
cfg[${key,,}]="${value}"
;;
build_requires | configure_args | docfiles | patch_files | runtime_deps | systems | variant )
get_seq "${yaml_input}" value "${key}"
cfg[${key,,}]="${value}"
;;
'configure_args+' | 'docfiles+' | 'patch_files+' )
get_seq "${yaml_input}" value "${key}"
key="${key:0:-1}"
if [[ -z "${cfg[${key,,}]}" ]]; then
cfg[${key,,}]="${value}"
else
cfg[${key,,}]+=$'\n'"${value}"
fi
;;
* )
std::die 3 "%s '%s' in %s" \
"Oops unhandled key" \
"${key}" \
"${FUNCNAME}"
esac
done
}
get_matching_version_keys(){
: "
return list of version keys in refvar $1 matching version specified in $2.
"
local -n refvar="$1"
local version="$2"
local -a keys=()
keys=( $( ${yq} -e '.versions|keys().[]' 2>/dev/null) ) || \
std::die 3 "No version keys in configuration file!"
refvar=()
for key in "${keys[@]}"; do
l=()
# loop over semicolon separated list of keys
for k in ${key//;/ }; do
# brace expansion of key
local list=()
eval list=( $k )
if [[ ${list[@]} =~ ${version} ]]; then
refvar+=("${key}")
break
fi
done
done
(( ${#refvar[@]} == 0 )) && \
std::die 3 "No configuration for version -- ${version}"
return 0
}
get_yaml_vk_config(){
: "
Get configuration for version key $1.
Please note: this can be the empty string.
"
local -- key="$1"
result=$( ${yq} ".versions.\"${key}\"" 2>/dev/null ) || \
result=""
echo "${result}"
}
get_variants(){
local yaml
yaml=$(${yq} '.variants' 2>/dev/null)
echo "${yaml}"
}
get_num_variants(){
local -i n=$(${yq} '.|length' 2>/dev/null)
echo "$n"
}
get_variant(){
local n="$1"
local yaml
yaml=$(${yq} -e ".[$n]" 2>/dev/null)
[[ "${yaml}" == 'null' ]] && _result=''
echo "${yaml}"
}
chk_group_deps(){
: "
Check the group dependencies:
1. are all keys valid?
2. all required group deps defined?
3. more group deps defined as required?
"
:
local -- yaml="$1" # yaml formatted string: value of group_deps
local -- group="$2" # compiler|mpi|hdf5|hdf5_serial
local -- name="$3" # module name
local -- version="$4" # module version
# query all specified group dependencies
local -a keys=()
keys=( $(${yq} ".|keys|.[]" <<<"${yaml}" 2>/dev/null) )
local -- key=''
for key in "${keys[@]}"; do
# is this a name of a hierarchical group?
[[ -v hierarchical_groups[${key}] ]] || \
die_illegal_group_dep "${name}" "${version}" "${group}" "${key}"
# is this in the list of required group dependencies?
is_in_array "${key}" "${hierarchical_groups[${key}]}" || \
die_invalid_group_dep "${name}" "${version}" "${group}"
done
# are all required group dependencies defined?
for key in "${hierarchical_groups[${group,,}]}"; do
is_in_array "${key,,}" "${keys[@]}" || \
die_missing_group_dep "${name}" "${version}" "${group}"
done
}
get_group_deps(){
local -- yaml="$1" # yaml formatted string with group config
local -- group="$2" # compiler|mpi|hdf5|hdf5_serial
local -n with_modules="$3" # refvar to return a list of modules
local -a modules=()
local keys=()
keys=( $(${yq} ".${group,,}|keys|.[]" <<<"${yaml}" 2>/dev/null) )
local key
for key in "${keys[@]}"; do
local versions=()
versions=( $( ${yq} -e ".${group,,}.${key}[]" <<<"${yaml}" 2>/dev/null) )
local version
for version in "${versions[@]}"; do
if [[ -v opt_with_dict[${key}/${version}] ]]; then
with_modules+=( "${key}/${version}" )
fi
modules+=( "${key}/${version}" )
done
done
if (( ${#with_modules[@]} == 0 )); then
with_modules=( "${modules[@]}" )
fi
}
die_missing_group_dep(){
std::die 3 "%s/%s: %s" \
"${1}" "${2}" \
"is in group '$3', but the group dependency for this group is missing!"
}
die_invalid_group_dep(){
std::die 3 "%s/%s: %s" \
"${1}" "${2}" \
"invalid group dependency '$3' for module in group '$4'!"
}
die_illegal_group_dep(){
std::die 3 "%s/%s: %s" \
"${1}" "${2}" \
"illegal group dependency '$4' for module in group '$3'!"
}
is_in_array(){
local -r key="$1"
shift 1
[[ $* =~ (^|[[:space:]])"${key}"($|[[:space:]]) ]]
}
is_subset(){
local -n subset="$1"
shift 1
local el=''
for el in "${subset[@]}"; do
is_in_array "${el}" "$@" || return 1
done
return 0
}
: "
To compile a module with a certain compiler||mpi||hdf5 dependency
the '--with' option can be used. Depending on the hierarchical
group the modules specified with the option '--with' must be a
subset of
- compiler: ( compiler)
- mpi: ( compiler mpi )
- hdf5: ( compiler mpi hdf5 )
- hdf5_serial: ( compiler hdf5_serial )
"
build_modules_compiler(){
local -- module_name="$1"
local -- module_version="$2"
local -n module_cfg="$3"
chk_group_deps "${module_cfg['group_deps']}" 'Compiler' \
"${module_name}" "${module_version}"
local -a with_compiler=()
get_group_deps "${module_cfg['group_deps']}" 'Compiler' with_compiler
debug "${with_compiler[@]}"
local compiler=''
for compiler in "${with_compiler[@]}"; do
# build if opt_with_modules is empty or compiler is in this array
(( ${#opt_with_modules[@]} != 0 )) \
&& [[ "${compiler}" != "${opt_with_modules[0]}" ]] \
&& continue
[[ "${opt_check_mode}" == 'yes' ]] && continue
pbuild.build_module_yaml \
"${module_name}" "${module_version}" \
"$3" \
"${compiler}" \
"${@:4}"
done
}
build_modules_hdf5_serial(){
local -- module_name="$1"
local -- module_version="$2"
local -n module_cfg="$3"
chk_group_deps "${module_cfg['group_deps']}" 'HDF5_serial' \
"${module_name}" "${module_version}"
local -a with_compiler=()
get_group_deps "${module_cfg['group_deps']}" 'Compiler' with_compiler
local -a with_hdf5=()
get_group_deps "${module_cfg['group_deps']}" 'HDF5_serial' with_hdf5
local -- compiler
local -- hdf5
for compiler in "${with_compiler[@]}"; do
for hdf5 in "${with_hdf5[@]}"; do
# build if opt_with_modules is empty or compiler is in this array
(( ${#opt_with_modules[@]} != 0 )) \
&& ! is_subset opt_with_modules "${compiler}" "${hdf5}" \
&& continue
debug "build $module_name/$module_version with $compiler and $hdf5"
debug " runtime deps: ${runtime_deps[@]}"
debug " build requires: ${build_requires[@]}"
pbuild.build_module_yaml \
"${module_name}" "${module_version}" \
"$3" \
"${compiler}" \
"${hdf5}" \
"${@:4}"
done
done
}
build_modules_mpi(){
local -- module_name="$1"
local -- module_version="$2"
local -n module_cfg="$3"
chk_group_deps "${module_cfg['group_deps']}" 'MPI' "${module_name}" "${module_version}"
local -a with_compiler=()
get_group_deps "${module_cfg['group_deps']}" 'Compiler' with_compiler
local -a with_mpi=()
get_group_deps "${module_cfg['group_deps']}" 'MPI' with_mpi
local -- compiler
local -- mpi
for compiler in "${with_compiler[@]}"; do
for mpi in "${with_mpi[@]}"; do
# build if opt_with_modules is empty or compiler is in this array
(( ${#opt_with_modules[@]} != 0 )) \
&& ! is_subset opt_with_modules "${compiler}" "${mpi}" \
&& continue
debug "build $module_name/$module_version with $compiler and $mpi"
debug " runtime deps: ${runtime_deps[@]}"
debug " build requires: ${build_requires[@]}"
pbuild.build_module_yaml \
"${module_name}" "${module_version}" \
"$3" \
"${compiler}" \
"${mpi}" \
"${@:4}"
done
done
}
build_modules_hdf5(){
local -- module_name="$1"
local -- module_version="$2"
local -n module_cfg="$3"
chk_group_deps "${module_cfg['group_deps']}" 'HDF5' "${module_name}" "${module_version}"
local -a with_compiler=()
get_group_deps "${module_cfg['group_deps']}" 'Compiler' with_compiler
local -a with_mpi=()
get_group_deps "${module_cfg['group_deps']}" 'MPI' with_mpi
local -a with_hdf5=()
get_group_deps "${module_cfg['group_deps']}" 'HDF5' with_hdf5
local -- compiler
local -- mpi
local -- hdf5
for compiler in "${with_compiler[@]}"; do
for mpi in "${with_mpi[@]}"; do
for hdf5 in "${with_hdf5[@]}"; do
debug "build $module_name/$module_version with $compiler, $mpi and $hdf5"
debug " runtime deps: ${runtime_deps[@]}"
debug " build requires: ${build_requires[@]}"
# build if opt_with_modules is empty or compiler is in this array
(( ${#opt_with_modules[@]} != 0 )) \
&& ! is_subset opt_with_modules \
"${compiler}" "${mpi}" "${hdf5}" \
&& continue
pbuild.build_module_yaml \
"${module_name}" "${module_version}" \
"$3" \
"${compiler}" \
"${mpi}" \
"${hdf5}" \
"${@:4}"
done
done
done
}
build_modules_other(){
local -- module_name="$1"
local -- module_version="$2"
local -n module_cfg="$3"
pbuild.build_module_yaml \
"${module_name}" "${module_version}" \
"$3" \
"${@:4}"
}
die_parsing(){
std::die 3 "error parsing YAML:\n----\n$1\n----"
}
die_invalid_value(){
std::die 3 "Invalid value for key '$3' in $2 -- '$4'\n----\n$1\n----"
}
die_invalid_key(){
std::die 3 "Invalid key '$3' in $2\n----\n$1\n----"
}
die_missing_key(){
std::die 3 "Key '$3' missing in $2\n----\n$1\n----"
}
local -A Unpackers=([tar]="tar" [7z]="7z")
set_urls() {
local -- yaml="$1"
local -i l=0
l=$( ${yq} -Ne e '.|length' <<<"${yaml}" 2>/dev/null) || \
die_parsing "{yaml}"
local -i i=0
local -- url_yaml=''
for ((i=0; i<l; i++)); do
url_yaml=$(${yq} -Ne e ".[$i]" <<<"${yaml}" 2>/dev/null) || \
die_parsing "{yaml}"
local url=''
local fname=''
local strip_dirs=''
local unpacker=''
local key=''
local value=''
while read key value; do
key=${key:0:-1}
case "${key}" in
url )
url=$(${envsubst} <<<"${value}")
;;
name )
fname=$(${envsubst} <<<"${value}")
;;
strip_dirs )
[[ ${value} =~ ^[0-9]+$ ]] || \
die_invalid_value \
"${url_yaml}" \
'list of URLS' \
'strip_dirs' \
"${value}"
strip_dirs="${value}"
;;
unpacker )
[[ -v Unpackers[${value}] ]] || \
die_invalid_value \
"${url_yaml}" \
'list of URLs' \
'unpacker' \
"${value}"
unpacker="${value}"
;;
;;
* )
die_invalid_key \
"${url_yaml}" \
'list of URLs' \
"${key}"
;;
esac
done <<<"${url_yaml}"
[[ -z "${url}" ]] && \
die_missing_key \
"${url_yaml}" \
'list of URLs' \
'url'
[[ -z "${fname}" ]] && fname="${url##*/}"
[[ -z "${strip_dirs}" ]] && strip_dirs=1
[[ -z "${unpacker}" ]] && unpacker='tar'
pbuild.set_urls "${url}" "${fname}" "${strip_dirs}" "${unpacker}"
done
}
set_configure_args(){
local -a args=()
readarray -t args <<< "$1"
pbuild.add_configure_args "${args[@]}"
}
set_patch_files(){
local -a args=()
readarray -t args <<< "$1"
pbuild.add_patch_files "${args[@]}"
}
build_sub_packages(){
local -- yaml="$1"
local -i l=0
l=$( ${yq} -Ne e '.|length' <<<"${yaml}" 2>/dev/null) || \
die_parsing "${yaml}"
local -i i=0
local -- fname=''
local -- pkgs_yaml=''
for ((i=0; i<l; i++)); do
pkgs_yaml=$(${yq} -Ne e ".[$i]" <<<"${yaml}" 2>/dev/null) || \
die_parsing "${yaml}"
local -- pkg_name=''
local -- pkg_version=''
local -a pkg_build_args=()
local -a keys=()
keys=( $( ${yq} -e ".|keys().[]" <<<"${pkgs_yaml}" 2>/dev/null )) || \
die_parsing "${pkgs_yaml}"
local -- key=''
for key in "${keys[@]}"; do
case ${key,,} in
'name' )
get_value "${pkgs_yaml}" pkg_name "${key}" '!!str'
;;
'version' )
get_value "${pkgs_yaml}" pkg_version "${key}" '!!str'
;;
'build_args' )
local -- value=''
get_seq "${pkgs_yaml}" value "${key}"
readarray -t pkg_build_args <<< "${value}"
;;
* )
die_invalid_key \
"${pkgs_yaml}" \
"in subpackage '$i'" \
"${key}"
;;
esac
done
[[ "${opt_verbose}" == 'yes' ]] && \
pkg_build_args+=( '--verbose' )
[[ "${opt_debug}" == 'yes' ]] && \
pkg_build_args+=( '--debug' )
[[ "${opt_force_rebuild}" == 'yes' ]] && \
pkg_build_args+=( '-f' )
pkg_build_args+=( "--parent-prefix=${PREFIX}" )
"$BUILDBLOCK_DIR/build-${pkg_name}" \
"${pkg_name}/${pkg_version}" \
"${pkg_build_args[@]}"
done
}
build_modules_variant(){
local -- module_name="$1"
local -- module_version="$2"
local -n module_config="$3"
check_system(){
[[ -z ${module_config['systems']} ]] && return 0
set -o noglob
local -a systems=( ${module_config['systems']} )
set +o noglob
local -- system
for system in "${systems[@]}"; do
[[ "${opt_system}" == ${system} ]] && return 0
[[ "${HOSTNAME}" == ${system} ]] && return 0
done
std::info "Skipping variant '${module_version}', neither OS nor hostname match:"
std::info " This system: ${opt_system}; hostname: ${HOSTNAME}"
std::info " Systems to build on: ${systems[@]}"
return 1
}
P="${module_name}"
parse_version "${module_version}"
local build_variant="${opt_variant:-${module_config['default_variant']}}"
# build this variant?
if [[ ":${module_config['variant']}:" != *:${build_variant}:* ]]; then
debug "don't build this variant: ${module_config['variant']} != *:${build_variant}:*"
return 0
fi
# build for this system?
check_system || return 0
debug "build variant ${module_name}/${module_version}"
local ol_name="${module_config['overlay']}"
[[ -v OverlayInfo[${ol_name}:inst_root] ]] || \
std::die 2 "%s" \
"Overlay doesn't exist - ${ol_name}"
declare ol_inst_root="${OverlayInfo[${ol_name}:inst_root]}"
declare ol_mod_root="${OverlayInfo[${ol_name}:mod_root]}"
module_version+="${module_config['suffix']}"
pbuild.compile_in_sourcetree "${module_config['compile_in_sourcetree']}"
pbuild.configure_with "${module_config['configure_with']}"
pbuild.add_to_group "${module_config['group']}"
set_urls "${module_config['urls']}"
set_configure_args "${module_config['configure_args']}"
set_patch_files "${module_config['patch_files']}"
local -a runtime_deps=()
if [[ -n ${module_config['runtime_deps']} ]]; then
readarray -t runtime_deps <<<"${module_config['runtime_deps']}"
debug "runtime_deps=${runtime_deps[@]} length: ${#runtime_deps[@]}"
fi
local -a build_requires=()
if [[ -n ${module_config['build_requires']} ]]; then
readarray -t build_requires <<<"${module_config['build_requires']}"
build_requires=( "${build_requires[@]/#/b:}" )
debug "build_requires=${build_requires[@]} length: ${#build_requires[@]}"
fi
if [[ -n ${module_config['docfiles']} ]]; then
readarray -t MODULE_DOCFILES <<<"${module_config['docfiles']}"
fi
local -A build_functions=(
['Compiler']=build_modules_compiler
[HDF5_serial]=build_modules_hdf5_serial
[MPI]=build_modules_mpi
[HDF5]=build_modules_hdf5
)
local func=build_modules_other
if [[ -v build_functions[${module_config['group']}] ]]; then
func=${build_functions[${module_config['group']}]}
fi
${func} \
"${module_name}" \
"${module_version}" \
module_config \
"${runtime_deps[@]}" "${build_requires[@]}"
build_sub_packages "${module_config['sub_packages']}"
}
expand_version_key(){
local -n ev_result="$1"
local -- key="$2"
local -- version="$3"
ev_result=()
# loop over comma separated list of keys
for k in ${key//;/ }; do
# do curly brackets expansion {}
local l
local list=()
eval list=( $k )
for l in "${list[@]}"; do
if [[ $l =~ ${version} ]]; then
ev_result+=("${l}")
fi
done
done
}
local -A used_keys=()
local -- yaml_input=''
local -A default_config=()
local -- version_key=''
local -a version_keys=()
local -A shasums=()
check_yaml_keys Yaml_valid_keys_for_module used_keys <<<"${yaml_mod_config}"
[[ -v used_keys['versions'] ]] || \
std::die 3 "No version(s) specified in YAML configuration file."
if [[ -v used_keys['defaults'] ]]; then
yaml_input=$(${yq} '.defaults' <<<"${yaml_mod_config}" 2>/dev/null)
fi
get_config "${yaml_input}" default_config Yaml_default_config
if [[ -v used_keys['shasums'] ]]; then
local yaml_input=$(${yq} '.shasums' <<<"${yaml_mod_config}" 2>/dev/null)
while read key value; do
[[ -z ${key} ]] && continue
SHASUMS[${key//:}]="${value}"
done <<<"${yaml_input}"
fi
if [[ -v used_keys['type'] ]]; then
local -- value=''
get_value "${yaml_mod_config}" value 'type' '!!str'
case "${value,,}" in
'module' )
[[ "${module_type}" == 'sub_package' ]] && \
std::die 3 "Module type is 'module' but was called as 'sub_package'!"
;;
'sub_package' )
[[ "${module_type}" == 'module' ]] && \
std::die 3 "Module type is 'sub_package' but was called as 'module'!"
;;
* )
std::die 3 "Invalid module type -- '${value}'!"
;;
esac
fi
get_matching_version_keys version_keys "${version}" <<<"${yaml_mod_config}"
for version_key in "${version_keys[@]}"; do
local yaml_vk_config=$(get_yaml_vk_config "${version_key}" <<<"${yaml_mod_config}")
# check keys: allowed are 'config' and 'variants'
used_keys=()
check_yaml_keys Yaml_valid_vk_keys used_keys <<<"${yaml_vk_config}"
# read config if set
local -A vk_config=()
yaml_input=''
if [[ -v used_keys['config'] ]]; then
yaml_input=$(${yq} '.config' <<<"${yaml_vk_config}" 2>/dev/null)
debug "vk input: ${yaml_input}"
fi
# reminder: if YAML input is empty, next line copies defaults to 'vk_config'
get_config "${yaml_input}" vk_config default_config
local -- yaml_variants=$(get_variants <<<"${yaml_vk_config}")
local -i num_variants=$(get_num_variants <<<"${yaml_variants}")
local versions=()
expand_version_key versions "${version_key}" "${version}"
local v=''
for v in "${versions[@]}"; do
debug "version: $v"
if (( num_variants == 0 )); then
build_modules_variant \
"${name}" "${v}" \
vk_config
else
local -i n=0
local -- yaml_variant=''
for ((n=0; n<num_variants; n++)); do
yaml_variant=$(get_variant "${n}" <<<"${yaml_variants}")
local -A mod_config=()
get_config "${yaml_variant}" mod_config vk_config
build_modules_variant \
"${name}" "${v}" \
mod_config
done
fi
done
done
} # build_modules_yaml()
build_modules() {
if [[ "${opt_yaml}" == 'yes' ]]; then
build_modules_yaml "$@"
else
build_modules_legacy "$@"
fi
}
debug(){
${echo} "INFO: " "$@" 1>&2
}
#.............................................................................
# main
init_module_environment
parse_args "$@"
pbuild.jobs "${opt_jobs}"
pbuild.force_rebuild "${opt_force_rebuild}"
pbuild.build_target "${opt_build_target}"
pbuild.dry_run "${opt_dry_run}"
pbuild.enable_cleanup_build "${opt_enable_cleanup_build}"
pbuild.enable_cleanup_src "${opt_enable_cleanup_src}"
pbuild.update_modulefiles "${opt_update_modulefiles}"
pbuild.system "${opt_system}"
pbuild.verbose "${opt_verbose}"
#
# read configuration for modbuild
#
pm::read_config
# :FIXME: should dist files go to
# ${pm_root}/var/distfiles
# or
# ${overlay}/var/distfiles
# ?
: ${PMODULES_DISTFILESDIR:="${PMODULES_HOME%%/Tools*}/var/distfiles"}
: ${PMODULES_TMPDIR:=/var/tmp/${USER}}
export PMODULES_DISTFILESDIR PMODULES_TMPDIR
declare -r BUILD_SCRIPT
declare -r BUILDBLOCK_DIR
if [[ -z ${module_name} ]]; then
# the module name is defined by the directory the build script is in
IFS=/ read -r -a fname <<< "${BUILD_SCRIPT:1}"
module_name=${fname[${#fname[@]}-2]}
fi
for version in ${versions_to_build}; do
build_modules "${module_name}" "${version}" "${opt_with_modules[@]}"
done
# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: