oss-build-scripts/lib/ossbuild-common.bash
Kai Tetzlaff (kai+qnas) 9c9e91848a Add array variable support to ossbuild-project-var-set/get
Use the new functionality for new project specific array variables:
- CONFIGURE_VARS: elements of the form NAME=VALUE will be set as
  environment variables when running autoconfig configure.
- CONFIGURE_OPTS: elements will be added to the options passed to
  autoconfig configure.
2024-05-18 01:49:23 +02:00

615 lines
17 KiB
Bash

# Helper functions for project build scripts.
[ -d "${BSL_PATH:=${HOME}/.local/lib/bash/bsl}" ] || {
echo "[ERR] please set BSL_PATH" >&2
exit 1
}
source "${BSL_PATH}/init.bash"
bsl_load
unset QUIET OSSBUILD_PROJECT
# fail on errors when non-interactive
[ -n "${PS1:-}" ] || set -euo pipefail
#D# Check is current system is running GNU/Linux based QNAP QTS OS.
is-qnap-qts() {
[ -r '/etc/os-release' ] && grep -q '^NAME="QTS"$' '/etc/os-release'
}
#D# Check if variable DRY_RUN has a value greater than ``0``.
is-dry-run() {
! [ ${DRY_RUN:-0} -eq 0 ]
}
#D# Check if variable is an array.
ossbuild-var-array-p() {
declare -p "${1}" 2> /dev/null | grep -Eq -- '^declare -a'
}
#D# Wrapper around pushd.
#
# The wrapper provides the following features:
#
# - return ``1`` if the provided directory does not exist
# - log directory changes
ossbuild-pushd() {
local d="${1}"
bsl_run_cmd_quiet pushd "${d}" || {
[ -d "${d}" ] || {
bsl_logfe "no such dir: '$(realpath -Lm "${d}")'"
return 1
}
bsl_logfe "failed to change directory to '$(realpath -Lm "${d}")'"
return 2
}
bsl_logi "enter directory: '${PWD}'"
}
#D# Logged popd.
ossbuild-popd() {
local dirstack_size
dirstack_size="$(dirs -p | wc -l)"
[ "${dirstack_size}" -gt 1 ] || {
# directory stack is empty (the current directory is always present)
return 0
}
bsl_logi "leave directory: '${PWD}'"
bsl_run_cmd_quiet popd
[ "${dirstack_size}" -le 2 ] || bsl_logi "directory: '${PWD}'"
}
#D# Run a shell command.
#
# This wrapper provides to following features:
#
# - write executed command to stdout (set QUIET=1 to disable the logging)
# - skip the command if func:is-dry-run returns a truthy value
ossbuild-cmd() {
local cmd=("${@}")
if is-dry-run; then
echo "[DRYRUN] ${cmd[@]}" >&2
else
[ -n "${QUIET:-}" ] || echo "${cmd[@]}" >&2
"${cmd[@]}"
fi
}
#D# Writes name of project specific variable to stdout.
ossbuild-project-var() {
local name="${OSSBUILD_PROJECT^^}_${1^^}"
printf '%s' "${name//-/_}"
}
#D# Write value of project specific variable to stdout.
#
# Args:
# name (str): the name of the project specific variable
# default (any): default value to be used if variable does not exist
#
# Returns:
# exit status (int): ``0`` in case of sucecess, ``1`` in case that the
# variable does not exist and no default value was provided
#
# stdout (str): value of the variable
ossbuild-project-var-get() {
#echo "1/ossbuild-project-var-get: ${@}" >&2
local quiet=0 name vname
local -a positional
while [ ${#} -gt 0 ]; do
case "${1}" in
-q | --quiet)
((++quiet))
;;
-v)
local outvar="${2}"
shift
;;
-*)
# ignore unknown option
;;
*)
positional+=("${1}")
;;
esac
shift
done
[ "${#positional[*]}" -gt 0 ] || {
bsl_logfe "missing project variable name"
return 10
}
name="${positional[0]}"
local default
[ "${#positional[*]}" -le 1 ] || default="${positional[1]}"
vname="$(ossbuild-project-var "${name}")"
#echo "2/ossbuild-project-var-get: ${vname}${default:+, default=${default}}" >&2
[[ -v "${vname}" ]] || {
if [ -v default ]; then
#echo "using default: '${vname}=${default}'" >&2
local "${vname}=${default}"
else
[ "${quiet}" -gt 0 ] || bsl_loge "project variable not set: '${vname}'"
return 1
fi
}
#echo "R/ossbuild-project-var-get: ${vname}='${!vname}'" >&2
local -n pvar_ref="${vname}"
if [ -v outvar ]; then
local -n ovar_ref="${outvar}"
if ossbuild-var-array-p "${vname}"; then
ovar_ref=("${pvar_ref[@]}")
else
ovar_ref="${pvar_ref}"
fi
else
if ossbuild-var-array-p "${vname}"; then
for v in "${pvar_ref[@]}"; do
printf '%s\n' "${v}"
done
else
printf '%s' "${pvar_ref}"
fi
fi
}
#D# Set value of project specific variable.
#
# Args:
# name (str): the name of the project specific variable
# value (any): value to be assigned to the variable
#
# Returns:
# exit status (int): ``0`` (the function should never fail)
#
# stdout (str): na
ossbuild-project-var-set() {
#echo "1/ossbuild-project-var-set: ${@}" >&2
local quiet=0 array=0 name vname
local -a positional
while [ ${#} -gt 0 ]; do
case "${1}" in
-q | --quiet)
((++quiet))
;;
-a)
array=1
;;
-*)
# ignore unknown option
;;
*)
positional+=("${1}")
;;
esac
shift
done
[ "${#positional[*]}" -gt 0 ] || {
bsl_logfe "missing project variable name"
return 10
}
name="${positional[0]}"
vname="$(ossbuild-project-var "${name}")"
unset positional[0]
set "${positional[@]}"
[ "${#positional[@]}" -le 1 ] || array=1
unset "${vname}" # make sure that we start with a clean state
local msg='set project variable: '
if [ "${array}" -eq 0 ]; then
local value="${@}"
msg="${msg}${vname}=${value@Q}"
declare -g "${vname}=${value}"
else
local value=("${@}")
msg="${msg}${vname}=(${value[*]@Q})"
declare -ga "${vname}"
declare -n pvar_ref="${vname}"
pvar_ref=("${value[@]}")
fi
[ "${quiet}" -gt 0 ] || bsl_logi "${msg}"
}
#D# Unset (delete) project specific variable.
#
# Args:
# name (str): the name of the project specific variable
#
# Returns:
# exit status (int): ``0`` (the function should never fail)
#
# stdout (str): na
ossbuild-project-var-unset() {
local name="${1}" vname
vname="$(ossbuild-project-var "${name}")"
bsl_logi "unset project variable: ${vname}"
unset "${vname}"
}
#D# Run a git command (see func:ossbuild-cmd).
ossbuild-git() {
ossbuild-cmd git "${@}"
}
#D# Write latest tagged version of a github project to stdout.
ossbuild-gh-version-latest() {
local owner="${1:-$(ossbuild-project-var-get GH_OWNER '')}" \
repo="${2:-$(ossbuild-project-var-get GH_REPO '')}"
[[ -n "${owner}" && -n "${repo}" ]] || return 1
printf '%s' "$(bsl_rtrim -s 'v' "$(bsl_ghapi_releases_tag_latest "${owner}" "${repo}")")"
}
# #D# WiP: Write download URL of ...
# ossbuild-gh-download-url() {
# local owner="${1:-$(ossbuild-project-var-get GH_OWNER '')}" \
# repo="${2:-$(ossbuild-project-var-get GH_REPO '')}"
#
# [[ -n "${owner}" && -n "${repo}" ]] || return 1
# #https://github.com/${owner}/${repo}/releases/download/v23.3/protobuf-23.3.tar.gz
# }
#D# Write the version string of OSSBUILD_PROJECT to stdout.
#
# The first of the following steps which has a non-empty result is used as
# version string:
#
# 1. get value of project variable <PRJ>_VERSION
# 2. get value of project variable <PRJ>_DEFAULT_VERSION
# 3. value returned by ``ossbuild-gh-version-latest`` #d#
# shellcheck disable=SC2120
ossbuild-project-version() {
local version
version="$(ossbuild-project-var-get -q VERSION)" || {
version="$(ossbuild-project-var-get -q DEFAULT_VERSION)" || {
local owner="${1:-$(ossbuild-project-var-get GH_OWNER '')}" \
repo="${2:-$(ossbuild-project-var-get GH_REPO '')}"
version="$(bsl_ltrim -s 'v' "$(ossbuild-gh-version-latest "${owner}" "${repo}")")" || return 10
}
}
printf '%s' "${version}"
}
#D# Initialize a project hosted on github.com.
ossbuild-project-gh-init() {
local owner="${1}" repo="${2}" name="${3:-}"
declare -g OSSBUILD_PROJECT="${name}"
[ -n "${OSSBUILD_PROJECT}" ] || OSSBUILD_PROJECT="${repo}"
bsl_logi "initialize github project: ${OSSBUILD_PROJECT} (${owner}/${repo})"
ossbuild-project-var-set GH_OWNER "${owner}"
ossbuild-project-var-set GH_REPO "${repo}"
ossbuild-project-var-set GH_URL "https://github.com/${owner}/${repo}"
# shellcheck disable=SC2119
ossbuild-project-var-set VERSION "$(ossbuild-project-version)"
}
#D# Write value of source base path (default: ``${HOME}/work/oss``) to stdout.
#
# Returns:
# exit status (int): ``0``
#
# stdout (str): value of OSSBUILD_SRC_BASE_PATH
ossbuild-src-base() {
printf '%s' "${OSSBUILD_SRC_BASE_PATH:-${HOME}/work/oss}"
}
#D# Create OSSBUILD_SRC_BASE_PATH if it does not exist.
#
# Returns:
# exit status (int): ``0``
#
# stdout (str): na
ossbuild-src-base-create() {
local src_base
src_base="$(ossbuild-src-base)"
[ -d "${src_base}" ] || ossbuild-cmd mkdir "${src_base}"
}
#D# Write versioned project name to stdout.
#
# Args:
# version (str): [optional] project version
# name (any): [optional] project name
#
# Returns:
# exit status (int): ``0``
#
# stdout (str): project name inclusing version string
ossbuild-project-namever-gnu() {
# shellcheck disable=SC2119
local version="${1:-$(ossbuild-project-version)}" name="${2:-${OSSBUILD_PROJECT}}"
printf '%s' "${name}${version:+-${version}}"
}
#D# Write project specific archive file name to stdout.
#
# Args:
# version (str): [optional] project version
# name (any): [optional] project name
#
# Returns:
# exit status (int): ``0``
#
# stdout (str): archive file name
ossbuild-project-archive-gnu() {
# shellcheck disable=SC2119
local version="${1:-$(ossbuild-project-version)}" name="${2:-${OSSBUILD_PROJECT}}"
printf '%s' "$(ossbuild-project-namever-gnu "${@}").tar.gz"
}
#D# ...
ossbuild-project-src-path() {
local name="${1}"
printf '%s' "$(ossbuild-src-base)/${name}"
}
#D# Download a file.
ossbuild-dl() {
local url="${1}" file_or_dir="${2:-$(basename "${1}")}"
local dst
if [ -d "${file_or_dir}" ]; then
dst="${file_or_dir}/$(basename "${url}")"
elif bsl_path_relative_p "${file_or_dir}"; then
dst="$(ossbuild-project-src-path "${file_or_dir}")"
else
dst="${file_or_dir}"
fi
[ -e "${dst}" ] || ossbuild-cmd curl -qLs --fail -o "${dst}" "${url}"
[ -n "${QUIET:-}" ] || printf '%s' "${dst}"
}
#D# Download a TAR archive to OSSBUILD_SRC_BASE_PATH and unpack it.
ossbuild-src-tar() {
local url="${1}" file_or_dir="${2:-$(basename "${1}")}"
local archive
archive="$(ossbuild-dl "${url}" "${file_or_dir}")"
bsl_logd "archive:'${archive}'"
ossbuild-cmd tar -xf "${archive}" -C "$(dirname "${archive}")"
}
#D# ...
ossbuild-src-git() {
local url="${1}" prj_name="${2:-$(basename "${1}" .git)}"
local src_tree
src_tre="$(ossbuild-project-src-path "${prj_name}")"
ossbuild-git clone "${url}" "${src_tree}"
}
#D# Check if PATH is writable.
#
# Args:
# path (str): path to be checked
#
# Returns:
# exit status (int): ``0`` if PATH is writable, ``1`` otherwise
#
# stdout (str): na
ossbuild-writable-p() {
local tgt="${1}"
if [ -e "${tgt}" ]; then
test -w "${tgt}"
else
test -w "$(dirname "${p}")"
fi
}
#D# Install a file (automatically uses ``sudo`` if required).
ossbuild-install() {
local src="${1}" dst="${2}"
local cmd=('install')
ossbuild-writable-p "${dst}" || cmd=(sudo "${cmd}")
ossbuild-cmd "${cmd[@]}" "${src}" "${dst}"
}
#D# Create a symlink (automatically uses ``sudo`` if required).
ossbuild-symlink() {
local src="${1}" dst="${2}"
local cmd=('ln' '-s' '-fr')
ossbuild-writable-p "${dst}" || cmd=(sudo "${cmd}")
ossbuild-cmd "${cmd[@]}" "${src}" "${dst}"
}
#D# Check if tests are enabled.
#
# Evaluates global and project specific BUILD_TESTS variable. Tests are enabled
# if:
#
# - the project specific value of BUILD_TESTS > 0 or
# - the global value of BUILD_TESTS > 0 and the project specific value is
# not explicitly set to ``0``.
#
# Returns:
# exit status (int): ``0`` if tests should run, ``1`` otherwise
#
# stdout (str): na
ossbuild-run-tests-p() {
[ "$(ossbuild-project-var-get BUILD_TESTS "${BUILD_TESTS:-0}")" -gt 0 ]
}
#D# Write name of project build step function to stdout (if it exists).
ossbuild-project-build-step-fn() {
local step="${1}"
local step_fn="ossbuild-${step}-${OSSBUILD_PROJECT}"
bsl_run_cmd_nostdout declare -F "${step_fn}" || return 2
printf '%s' "${step_fn}"
}
#D# Run project build step function (if it exists).
ossbuild-project-build-step-run() {
local step="${1}" step_fn
step_fn="$(ossbuild-project-build-step-fn "${step}")" || return "${?}"
shift
bsl_logi "${step} '${OSSBUILD_PROJECT}'"
"${step_fn}" "${@}"
}
#D# Function to be called in case of errors during a project build.
ossbuild-report-error() {
bsl_loge "failed"
bsl_loge "${OSSBUILD_PROJECT}: failed to ${OSSBUILD_STEP}"
}
#D# Initialize a project.
ossbuild-project-init() {
trap ossbuild-report-error ERR
[ -v OSSBUILD_PROJECT ] || {
# use global var for error reporting
declare -g OSSBUILD_STEP='init'
OSSBUILD_PROJECT="$(basename "$(dirname "${BASH_SOURCE[-1]}")")"
}
ossbuild-src-base-create
[ -v PREFIX ] || {
declare -g PREFIX
if is-qnap-qts; then
PREFIX='/opt/local'
else
PREFIX='/usr/local'
fi
}
local version
ossbuild-project-var-get -q -v version VERSION || {
# shellcheck disable=SC2119
version="$(ossbuild-project-version)"
ossbuild-project-var-set VERSION "${version}"
}
}
#D# Check if project is configured to build in the source tree.
ossbuild-project-build-in-tree-p() {
[ "$(ossbuild-project-var-get SRC_TREE)" \
= "${ossbuild-project-var-get BUILD_TREE}" ]
}
ossbuild-archive-prepare() {
local archive_url="${1}" src_tree="${2:-}" archive="${3:-}"
[ -n "${src_tree}" ] || \
src_tree="$(ossbuild-src-base)/$(ossbuild-project-namever-gnu)"
[ -n "${archive}" ] || archive="${src_tree}.tar.gz"
ossbuild-project-var-set ARCHIVE "${archive}"
ossbuild-project-var-set ARCHIVE_URL "${archive_url}"
ossbuild-project-var-set SRC_TREE "${src_tree}"
[ -e "${src_tree}" ] || ossbuild-src-tar "${archive_url}"
[ -d "${src_tree}" ] || {
bsl_loge "failed to create '${src_tree}'"
ossbuild-project-var-unset SRC_TREE
return 2
}
}
ossbuild-archive-gnu-prepare() {
local archive_base_url="${1}"
local src_tree archive archive_url
# shellcheck disable=SC2119
archive="$(ossbuild-project-archive-gnu)"
src_tree="$(ossbuild-src-base)/$(basename "${archive}" .tar.gz)"
archive_url="${archive_base_url}/${archive}"
ossbuild-archive-prepare \
"${archive_url}" \
"${src_tree}" \
"${archive}"
}
#D# ...
ossbuild-autotools-configure-ok-p() {
local build_tree="${1}" \
configlog="${2:-config.log}" \
regexp="${3:-^configure: exit 0$}"
[ -n "${build_tree}" ] || ossbuild-project-var-get -v build_tree BUILD_TREE
configlog="${build_tree}/${configlog}"
[ -e "${configlog}" ] || return 2
grep -qE "${regexp}" "${configlog}" || return 3
bsl_logi "${OSSBUILD_PROJECT}: reuse previous configure results" \
"(found 'configure: exit 0' in '${configlog}')"
}
#D# ...
ossbuild-autotools-build() {
local configure_extra_opts=(${@})
local src_tree build_tree
ossbuild-project-var-get -v src_tree SRC_TREE
ossbuild-project-var-get -q -v build_tree BUILD_TREE || {
build_tree="${src_tree}/ossbuild"
ossbuild-project-var-set BUILD_TREE "${build_tree}"
}
[ -d "${build_tree}" ] || mkdir -p "${build_tree}"
ossbuild-pushd "${build_tree}"
local configure_ok_p_fn
configure_ok_p_fn="$(ossbuild-project-build-step-fn "configure-ok-p")" \
|| configure_ok_p_fn='ossbuild-autotools-configure-ok-p'
if ! "${configure_ok_p_fn}" "${build_tree}"; then
bsl_logi "run configure ..."
local -a configure=(
"${src_tree}/configure"
--prefix="${PREFIX}"
)
local -a vars opts
ossbuild-project-var-get -q -v vars CONFIGURE_VARS || true
ossbuild-project-var-get -q -v opts CONFIGURE_OPTS || true
vars+=("PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig")
[ -n "${opts[*]}" ] || configure+=("${opts[@]}")
ossbuild-cmd env "${vars[@]}" "${configure[@]}"
fi
ossbuild-cmd make
ossbuild-popd
}
#D# ...
ossbuild-autotools-install() {
local build_tree
ossbuild-project-var-get -v build_tree BUILD_tree
ossbuild-pushd "${build_tree}"
ossbuild-cmd sudo make install
ossbuild-popd
}
#D# Run all known project build steps.
ossbuild-project-build-steps-all() {
ossbuild-project-init
local msg=(
"building project: '${OSSBUILD_PROJECT}'"
)
local version
ossbuild-project-var-get -v version VERSION
[ -z "${version}" ] || msg+=("(${version})")
bsl_logi "${msg[@]}"
declare -g OSSBUILD_STEP
for OSSBUILD_STEP in prepare build install; do
[[ ! "${OSSBUILD_SKIP:=}" =~ (^|,)${OSSBUILD_STEP}(,|$) ]] || {
bsl_logi "${OSSBUILD_PROJECT}: skip ${OSSBUILD_SKIP}"
continue
}
ossbuild-project-build-step-run "${OSSBUILD_STEP}" "${@}"
done
}