#!/bin/sh # build packages one by one, then upload the binary package to the repository server # Details: # https://github.com/archlinux32/builder/wiki/Build-system#build-packages # shellcheck source=conf/default.conf . "${0%/*}/../conf/default.conf" # shellcheck disable=SC2016 usage() { >&2 echo '' >&2 echo 'build-packages: build package(s) on the build-list' >&2 echo '' >&2 echo 'possible options:' >&2 echo ' -h|--help: Show this help and exit.' >&2 echo ' -l|--local pkgname.git-revision.git-mod-revision.repository:' >&2 echo ' Build the given package without asking / reporting to the' >&2 echo ' build master (except -u is given). Cannot be combined with' >&2 echo ' -n, -t or -x.' >&2 echo ' -n count: Build $count packages (if available), then exit.' >&2 echo ' $count=0 is interpreted as infinity.' >&2 echo ' The default is $count=1 or 0 iff -t or -x is given.' >&2 echo ' Cannot be combined with -l.' >&2 echo ' -s|--straw $straw:' >&2 echo ' Use this straw instead of the preconfigured ones. -- May be' >&2 echo ' given multiple times to allow using multiple straws.' >&2 echo ' -t seconds: Do not request new assignment(s) $seconds seconds after start.' >&2 echo ' Cannot be combined with -l.' >&2 echo ' -u|--upload: Upload explicitely built package to build master.' >&2 echo ' Can only be used with -l.' >&2 echo ' -x: If package build fails, do not request new assignment(s).' >&2 echo ' Cannot be combined with -l.' [ -z "$1" ] && exit 1 || exit "$1" } eval set -- "$( getopt -o hl:n:s:t:ux \ --long help \ --long local: \ --long straw: \ --long upload \ -n "$(basename "$0")" -- "$@" || \ echo usage )" unset count unset forced_package unset forced_straws exit_after_failure=false upload_to_build_master=false timeout=0 while true do case "$1" in -h|--help) usage 0 ;; -l|--local) shift if [ -n "${forced_package}" ]; then >&2 echo 'Option -l, --local can be given only once.' usage fi forced_package="$1" ;; -n) shift count="$1" [ "${count}" -eq 0 ] && \ count=-1 ;; -s|--straw) shift forced_straws="${forced_straws} $1" ;; -t) shift timeout="$1" ;; -u|--upload) upload_to_build_master=true ;; -x) exit_after_failure=true ;; --) shift break ;; *) >&2 echo 'Whoops, forgot to implement option "'"$1"'" internally.' exit 42 ;; esac shift done if [ $# -ne 0 ]; then >&2 echo 'Too many arguments.' usage fi if [ -n "${forced_package}" ]; then if [ -n "${count}" ] || \ [ "${timeout}" -ne 0 ] || \ ${exit_after_failure}; then >&2 echo 'Conflicting flags.' usage fi else if ${upload_to_build_master}; then >&2 echo 'Conflicting flags.' usage fi upload_to_build_master=true fi if [ -z "${count}" ]; then if [ "${timeout}" -ne 0 ] || ${exit_after_failure}; then count=-1 else count=1 fi fi if [ -n "${forced_straws}" ]; then straws_that_might_repair_failing_builds="${forced_straws# }" fi if [ "${timeout}" -ne 0 ]; then timeout=$((timeout+$(date +%s))) fi while [ "${count}" -ne 0 ]; do if [ "${timeout}" -ne 0 ] && [ "${timeout}" -lt "$(date +%s)" ]; then break fi err=0 if [ -z "${forced_package}" ]; then package=$( ssh \ -i "${master_build_server_identity}" \ -p "${master_build_server_port}" \ "${master_build_server_user}@${master_build_server}" \ 'get-assignment' ) || err=$? else package=$( echo "${forced_package}" | \ sed ' s|\.\([^.]\+\)\.\([^.]\+\)\.\([^.]\+\)$| \1 \2 \3| ' ) fi case ${err} in # 0: ok, I gave you an assignment 0) [ ${count} -gt 0 ] && \ count=$((count-1)) repository="${package##* }" package="${package% *}" mod_git_revision="${package##* }" package="${package% *}" git_revision="${package##* }" package="${package% *}" if [ "${git_revision##*-}" = 'HEAD' ]; then git_revision=$( repo_name="${git_revision%-*}" eval repo_path='"${repo_paths__'"${repo_name}"'}"' if [ -z "${repo_path}" ]; then >&2 printf 'Unknown git repository "%s".\n' "${repo_name}" exit 2 fi git -C "${repo_path}" rev-parse HEAD ) fi if [ "${mod_git_revision}" = 'work-tree' ]; then mod_git_revision=$( # we can't just create an empty index-file with mktemp, because git doesn't like it tmp_subdir=$(mktemp -d); trap 'rm -rf --one-file-system "${tmp_subdir}"' EXIT export GIT_INDEX_FILE="${tmp_subdir}/index.new" git -C "${repo_paths__archlinux32}" add -A git -C "${repo_paths__archlinux32}" write-tree ) fi # Update git repositories (official packages, community packages and the repository of package customizations). for repo_name in ${repo_names}; do eval repo_path='"${repo_paths__'"${repo_name}"'}"' git -C "${repo_path}" fetch done git_repo=$(find_repository_with_commit "${git_revision}") find_pkgbuilds "${package}" "${repository}" "${git_repo}" "${git_revision}" "${mod_git_revision}" bail_out() { err=$? if [ -n "$1" ]; then err="$1" fi cd "${base_dir}" recursively_umount_and_rm "${tmp_dir}" exit "${err}" } tmp_dir=$(mktemp -d "${work_dir}/tmp.XXXXXX") trap bail_out EXIT extract_source_directory "${git_repo}" "${git_revision}" "${mod_git_revision}" "${tmp_dir}" cd "${tmp_dir}" success=false for straw in ${straws_that_might_repair_failing_builds}; do if echo "${straw}" | \ grep -qF ':mirrored_source:'; then # maybe a missing source is/was the problem? # try to download them from sources.archlinux.org/sources/$repo/$source source_name=$( makepkg --printsrcinfo | \ sed -n ' /^\s*\(epoch\|pkg\(base\|ver\|rel\)\) = /{s|^\s\+||;p} /^pkgname = /q ' | \ sed ' s|^pkgbase = \(.*\)$|0 \1-| s|^epoch = \(.*\)$|1 \1:| s|^pkgver = \(.*\)$|2 \1-| s|^pkgrel = \(.*\)$|3 \1.src.tar.gz| ' | \ sort -k1n,1 | \ sed ' s|^[0-9] || :a N s|\n[0-9] || ta ' ) if ! wget -nc -nd "https://sources.archlinux.org/sources/${git_repo}/${source_name}"; then # we can't improve anything continue fi tar -xz --overwrite -f "${source_name}" --exclude PKGBUILD --strip-components=1 || true fi if echo "${straw}" | \ grep -qF ':mirrored_source_by_hash:'; then # maybe a missing source is/was the problem? # download it from sources.archlinux32.org by its hash if ! download_sources_by_hash "${package}" "${repository}" "${git_revision}" "${mod_git_revision}"; then # we can't improve anything, if no source was downloadable continue fi fi if echo "${straw}" | \ grep -qF ':with_build_support:'; then build_command='staging-with-build-support-i686-build' else build_command='staging-i686-build' fi if echo "${straw}" | \ grep -qF ':clean_chroot:'; then parameters='-c' else parameters='' fi find . -maxdepth 1 -type f \( -name '*.pkg.tar.xz' -o -name '*.pkg.tar.xz.sig' \) -exec \ rm {} \; >&2 printf '%s: building package "%s" (revisions %s %s, repository %s, straw %s) ...' \ "$(date +'%Y-%m-%d %T')" \ "${package}" \ "${git_revision}" \ "${mod_git_revision}" \ "${repository}" \ "${straw}" # by piping the log, we don't see anything in the terminal, # but all ways to duplicate the logs seem pretty elaborate if "${build_command}" ${parameters} > \ "$( date -u --iso-8601=seconds | \ cut -d+ -f1 ).build-log" 2>&1; then # build successful >&2 printf ' ok.\n' tar_content_dir=$(mktemp -d "${tmp_dir}/tar-content.XXXXXX") find . -maxdepth 1 -type f -name '*-debug-*.pkg.tar.xz*' -delete >&2 printf 'generating namcap-log for x86_64 package(s) ...' find . -maxdepth 1 -type f -name '*.pkg.tar.xz' \ -execdir gpg --local-user="${package_key}" --detach-sign '{}' \; \ -execdir mv '{}' '{}.sig' '{}-namcap.log' "${tar_content_dir}/" \; \ -printf '%f\n' | \ sponge | \ while read -r pkg_file; do { pacman -Spdd --print-format '%l' --noconfirm "${pkg_file%-*-*-*}" # shellcheck disable=SC2016 curl -Ss 'https://www.archlinux.org/mirrorlist/?country=all&protocol=https&tier=1&use_mirror_status=on' | \ sed -n ' s/^#Server = // T s/\$repo/'"${repository}"'/g s/\$arch/x86_64/g p ' | \ shuf } | \ sed ' s|/[^/]\+\.pkg\.tar\.xz$|| s|$|/'"${pkg_file}"'| s/-i686\(\.pkg\.tar\.xz\)$/-x86_64\1/ ' | \ while read -r url; do if wget -q -nd "${url}"; then break; fi done done if [ "${repository}" = 'multilib' ]; then x86_64_build_command='multilib-build' else x86_64_build_command='extra-x86_64-build' fi # this is a little hack: makepkg receives '--version', but namcap is run nevertheless # (and it only works with devtools32, because they are running namcap on *.pkg.tar.xz in the base directory, too) "${x86_64_build_command}" -- -- --version > /dev/null 2>&1 || \ "${x86_64_build_command}" -c -- -- --version > /dev/null 2>&1 || \ true # now we generate diffs from the namcap.logs find . "${tar_content_dir}/" -maxdepth 1 -type f -name '*.pkg.tar.xz-namcap.log' -printf '%p\n' | \ while read -r log; do sort_square_bracket_content "${log}" | \ sort | \ sponge "${log}" done find "${tar_content_dir}/" -maxdepth 1 -type f -name '*.pkg.tar.xz-namcap.log' -printf '%f\n' | \ sed ' s|\(^.*\)-i686\(\.pkg\.tar\.xz-namcap\.log\)$|\0 \1-x86_64\2| s|^.*-any\.pkg\.tar\.xz-namcap\.log$|\0 \0| ' | \ while read -r log x86_64_log; do if [ -f "${x86_64_log}" ]; then diff -u "${x86_64_log}" "${tar_content_dir}/${log}" | \ sed ' 1,3d /^[^+-]/d ' | \ sponge "${tar_content_dir}/${log}" else sed -i 's|^|*|' "${tar_content_dir}/${log}" fi done >&2 printf ' done.\n' if ${upload_to_build_master}; then find "${tar_content_dir}/" -maxdepth 1 -name '*.pkg.tar.xz-namcap.log' -execdir gzip '{}' \; else find "${tar_content_dir}/" -maxdepth 1 -name '*.pkg.tar.xz-namcap.log' -execdir grep -HF '' '{}' \; fi # shellcheck disable=SC2046 tar -cf 'package.tar' -C "${tar_content_dir}" -- $( find "${tar_content_dir}/" -maxdepth 1 \( -name '*.pkg.tar.xz' -o -name '*.pkg.tar.xz.sig' -o -name '*.pkg.tar.xz-namcap.log.gz' \) -printf '%f\n' ) while ${upload_to_build_master}; do err=0 # shellcheck disable=SC2029 ssh \ -i "${master_build_server_identity}" \ -p "${master_build_server_port}" \ "${master_build_server_user}@${master_build_server}" \ 'return-assignment' "${package}" "${git_revision}" "${mod_git_revision}" "${repository}" \ < 'package.tar' || \ err=$? case ${err} in 0) # upload successful break ;; 1) >&2 echo '"return-assignment" was running already.' wait_some_time 15 ;; 2) >&2 echo 'I was too slow, the package is outdated. I will continue ...' break ;; 3) >&2 echo "'return-assignment' reports a signature error." bail_out 1 ;; 4) >&2 echo "'return-assignment' reports too many or missing packages." bail_out 1 ;; *) >&2 echo "unknown return code ${err} from 'return-assignment'" bail_out 1 esac done success=true break fi >&2 printf ' failed.\n' done if ! ${success}; then for log in *'.build-log'; do if [ -f "${log}" ]; then if ${upload_to_build_master}; then gzip "${log}" else grep -HF '' "${log}" fi fi done if ${upload_to_build_master} && \ tar -cf 'build-logs.gz.tar' \ -- *'.build-log.gz'; then while ! ssh \ -i "${master_build_server_identity}" \ -p "${master_build_server_port}" \ "${master_build_server_user}@${master_build_server}" \ 'return-assignment' "${package}" "${git_revision}" "${mod_git_revision}" "${repository}" 'ERROR' < \ 'build-logs.gz.tar'; do wait_some_time 15 done fi if ${exit_after_failure}; then >&2 echo 'Build failed, exiting now' bail_out 0 fi fi # clean up tmp_dir cd "${base_dir}" recursively_umount_and_rm "${tmp_dir}" trap - EXIT continue ;; 1) >&2 echo 'get-assignment told me:' >&2 echo ' come back (shortly) later - I was running already' wait_some_time 15 continue ;; 2) >&2 echo 'get-assignment told me:' >&2 echo ' 2: come back later - there are still packages to be built,' >&2 echo ' but currently none has all its dependencies ready' wait_some_time 60 continue ;; 3) >&2 echo 'get-assignment told me:' >&2 echo ' 3: come back after the next run of get-package-updates - currently' >&2 echo ' there are no pending packages' exit 0 ;; *) >&2 echo "ERROR: Unknown exit code ${err} from 'get-assignment'." exit 1 ;; esac done >&2 echo 'Done.'