# 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 _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 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 }