From 664827d55aab09463ca61a3ef7114c0a8eea3890 Mon Sep 17 00:00:00 2001 From: "Kai Tetzlaff (qnas)" Date: Thu, 29 Jun 2023 22:08:23 +0200 Subject: [PATCH] Add a lot of new functions to lib/ossbuild-common.bash --- lib/ossbuild-common.bash | 531 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 530 insertions(+), 1 deletion(-) mode change 100644 => 100755 lib/ossbuild-common.bash diff --git a/lib/ossbuild-common.bash b/lib/ossbuild-common.bash old mode 100644 new mode 100755 index cea30d1..bac6906 --- a/lib/ossbuild-common.bash +++ b/lib/ossbuild-common.bash @@ -1,6 +1,535 @@ # Helper functions for project build scripts. -# Check is current system is running GNU/Linux based QNAP QTS OS. +[ -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# 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 "directory: '${PWD}'" +} + +#D# Logged popd. +ossbuild-popd() { + bsl_run_cmd_quiet popd || return 0 + 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]}" + + unset default + [ "${#positional[*]}" -le 1 ] || local 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 + if [ -v outvar ]; then + local -n ovar_ref="${outvar}" + # shellcheck disable=SC2034 + printf -v ovar_ref '%s' "${!vname}" + else + printf '%s' "${!vname}" + 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() { + local name="${1}" value="${2}" vname + vname="$(ossbuild-project-var "${name}")" + bsl_logi "set project variable: ${vname}='${value}'" + declare -g "${vname}=${value}" +} + +#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 VERSION +# 2. get value of project variable 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 stdoutif (it exists). +ossbuild-project-build-step-fn() { + local step_fn="ossbuild-${OSSBUILD_PROJECT}-${1}" + bsl_run_cmd_nostdout declare -F "${step_fn}" || return 1 + printf '%s' "${step_fn}" +} + +#D# Run project build step function (if it exists). +ossbuild-project-build-step-run() { + local step_fn + step_fn="$(ossbuild-project-build-step-fn "${@}")" || return 1 + shift + bsl_logi "${OSSBUILD_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 ] || { + 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-generic-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-generic-prepare \ + "${archive_url}" \ + "${src_tree}" \ + "${archive}" +} + +#D# ... +ossbuild-autotools-configure-ok-p() { + local build_tree="${1}" + [ -n "${build_tree}" ] || ossbuild-project-var-get -v build_tree BUILD_TREE + local config_log="${build_tree}/config.log" + + [ -e "${config_log}" ] && grep -qE '^configure: exit 0$' "${config_log}" +} + +#D# ... +ossbuild-autotools-generic-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}" + + if ossbuild-autotools-configure-ok-p "${build_tree}"; then + bsl_logi "skip configure (found config.status)" + else + bsl_logi "run configure ..." + local configure=( + "${src_tree}/configure" + --prefix="${PREFIX}" + "${configure_extra_opts[@]}" + ) + ossbuild-cmd "${configure[@]}" + fi + ossbuild-cmd make + + ossbuild-popd +} + +#D# ... +ossbuild-autotools-generic-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-project-build-step-run "${OSSBUILD_STEP}" "${@}" + done +}