#!/bin/sh # report back on a build assignment # either on success via: # "$0 $package $revision $mod_revision $repository" and tar'ed packages and logs # (= a tar of package(s), signature(s) and log(s)) on stdin # or on failure via: # "$0 $package $revision $mod_revision $repository ERROR" and tar'ed logs # exit codes: # 0: ok # 1: another instance was already running # 2: outdated package # 3: signature error # 4: package error (e.g. wrong packages sent) # TODO: sign database # shellcheck disable=SC2119,SC2120 # shellcheck source=../conf/default.conf . "${0%/*}/../conf/default.conf" if [ -s "${work_dir}/build-master-sanity" ]; then >&2 echo 'Build master is not sane.' exit 1 fi # Create a lock file and a trap. exec 9> "${build_list_lock_file}" if ! flock -n 9; then >&2 echo 'come back (shortly) later - I cannot lock build list.' exit 1 fi exec 8> "${sanity_check_lock_file}" if ! flock -s -n 8; then >&2 echo 'come back (shortly) later - sanity-check running.' exit 1 fi clean_up_lock_file() { rm -f "${build_list_lock_file}" } trap clean_up_lock_file EXIT if [ "$5" = 'ERROR' ]; then # the build failed on the build slave # shellcheck disable=SC2016 infos=$( { printf 'SELECT DISTINCT `build_assignments`.`id`,IF(`build_assignments`.`is_broken`,"true","false") FROM `build_slaves`' mysql_join_build_slaves_build_assignments mysql_join_build_assignments_package_sources mysql_join_package_sources_upstream_repositories mysql_join_build_assignments_binary_packages mysql_join_binary_packages_repositories printf ' WHERE `build_slaves`.`id`=from_base64("%s")' \ "$( # shellcheck disable=SC2154 printf '%s' "${slave_id}" | \ base64 -w0 )" printf ' AND `package_sources`.`%s`=from_base64("%s")' \ 'pkgbase' "$(printf '%s' "$1" | base64 -w0)" \ 'git_revision' "$(printf '%s' "$2" | base64 -w0)" \ 'mod_git_revision' "$(printf '%s' "$3" | base64 -w0)" printf ' AND `upstream_repositories`.`name`=from_base64("%s")' \ "$(printf '%s' "$4" | base64 -w0)" printf ' AND `repositories`.`name`="build-list"' } | \ mysql_run_query | \ tr '\t' ' ' ) if [ -z "${infos}" ]; then >&2 echo 'You do not build this package (anymore) - move on.' exit 2 fi was_broken_before="${infos##* }" build_assignment_id="${infos%% *}" # save sent build logs saved_build_logs=$( tar -vx \ -C "${build_log_directory}/error" \ --wildcards \ --no-wildcards-match-slash \ --transform="s|^|$1.$2.$3.$4.|" \ '*.build-log.gz' ) # shellcheck disable=SC2016 { if [ -n "${saved_build_logs}" ]; then printf 'CREATE TEMPORARY TABLE `failures` (' printf '`%s` %s,' \ 'date' 'DATETIME' \ 'reason' 'SMALLINT' \ 'log_file' 'VARCHAR(512)' | \ sed 's/,$//' printf ');\n' fail_reason_identifiers=$( { printf 'SELECT `fail_reasons`.`id`,replace(to_base64(`fail_reasons`.`identifier`),"\\n","")' printf ' FROM `fail_reasons` ORDER BY `fail_reasons`.`severity`' } | \ mysql_run_query ) for saved_build_log in ${saved_build_logs}; do printf '%s\n' "${fail_reason_identifiers}" | \ while read -r reason_id identifier; do if zgrep -q "^$( printf '%s' "${identifier}" | \ base64 -d )\$" \ "${build_log_directory}/error/$1.$2.$3.$4.${saved_build_log}"; then printf ' (from_base64("%s"),%s,from_base64("%s")),' \ "$( printf '%s' "${saved_build_log}" | \ sed 's|\.build-log\.gz$||;s|^.*\.||' | \ base64 -w0 )" \ "${reason_id}" \ "$( printf '%s' "$1.$2.$3.$4.${saved_build_log}" | \ base64 -w0 )" break fi done done | \ sed ' 1 s/^/INSERT INTO `failures` (`date`,`reason`,`log_file`) VALUES / s/,$/;\n/ ' printf 'INSERT INTO `failed_builds` (`build_slave`,`build_assignment`,`date`,`reason`,`log_file`)' printf ' SELECT ' printf 'from_base64("%s"),' \ "$(printf '%s' "${slave_id}" | base64 -w0)" \ "$(printf '%s' "${build_assignment_id}" | base64 -w0)" printf '`failures`.`%s`,' \ 'date' 'reason' 'log_file' | \ sed 's/,$//' printf ' FROM `failures`;\n' printf 'DROP TEMPORARY TABLE `failures`;\n' printf 'COMMIT;\n' fi printf 'UPDATE `build_assignments`' printf ' SET `build_assignments`.`is_broken`=1' printf ' WHERE `build_assignments`.`id`=from_base64("%s");\n' \ "$( printf '%s' "${build_assignment_id}" | \ base64 -w0 )" printf 'COMMIT;\n' printf 'UPDATE `build_slaves`' printf ' SET `build_slaves`.`currently_building`=NULL,' printf ' `build_slaves`.`logged_lines`=NULL' printf ' WHERE `build_slaves`.`id`=from_base64("%s");\n' \ "$( printf '%s' "${slave_id}" | \ base64 -w0 )" } | \ mysql_run_query # release lock on build-list - otherwise seed-build-list won't run flock -u 9 if ! ${was_broken_before}; then haskell_rebuild_packages=$( find "${build_log_directory}/error" -type f \ -name "$1.$2.$3.$4.*.build-log.gz" \ -exec zgrep -qFx ' The following packages are broken because other packages they depend on are missing. These broken packages must be rebuilt before they can be used.' {} \; \ -exec zcat {} \; | \ sed -n ' s/^installed package \(.*\) is broken due to missing package .*$/\1/ T p ' | \ tr ' ' '\n' | \ sed ' s/^/-p ^(haskell-)?/ s/-[0-9.]\+$/\$/ ' | \ sort -u ) rescheduled_packages=$( if [ -n "${haskell_rebuild_packages}" ]; then # shellcheck disable=SC2086 "${base_dir}/bin/seed-build-list" ${haskell_rebuild_packages} | \ sed 's/ .*$//' fi ) # shellcheck disable=SC2119 { # shellcheck disable=SC2154 printf '%s is broken (says %s).' \ "$1" \ "${slave}" if [ -n "${rescheduled_packages}" ]; then printf -- ' - I rescheduled:' # shellcheck disable=SC2086 printf ' %s,' ${rescheduled_packages} | \ sed 's/,$/./' fi printf '\n' } | \ irc_say fi exit 0 fi # the build was successful on the build slave # so we also need a lock on the package database exec 7> "${package_database_lock_file}" if ! flock -n 7; then >&2 echo 'come back (shortly) later - I cannot lock package database.' exit 1 fi clean_up_lock_file() { rm -f "${build_list_lock_file}" rm -f "${package_database_lock_file}" } # shellcheck disable=SC2016 infos=$( { printf 'SELECT DISTINCT `build_assignments`.`id`,`upstream_repositories`.`id` FROM `build_slaves`' mysql_join_build_slaves_build_assignments mysql_join_build_assignments_package_sources mysql_join_package_sources_upstream_repositories mysql_join_build_assignments_binary_packages mysql_join_binary_packages_repositories printf ' WHERE `build_slaves`.`id`=from_base64("%s")' \ "$( # shellcheck disable=SC2154 printf '%s' "${slave_id}" | \ base64 -w0 )" printf ' AND `package_sources`.`%s`=from_base64("%s")' \ 'pkgbase' "$(printf '%s' "$1" | base64 -w0)" \ 'git_revision' "$(printf '%s' "$2" | base64 -w0)" \ 'mod_git_revision' "$(printf '%s' "$3" | base64 -w0)" printf ' AND `upstream_repositories`.`name`=from_base64("%s")' \ "$(printf '%s' "$4" | base64 -w0)" printf ' AND `repositories`.`name`="build-list"' printf ' AND `binary_packages`.`sub_pkgrel`=from_base64("%s");\n' \ "$(printf '%s' "$5" | base64 -w0)" } | \ mysql_run_query ) if [ -z "${infos}" ]; then >&2 echo 'Sorry, the sent package is outdated.' exit 2 fi build_assignment_id="${infos%% *}" upstream_repository_id="${infos##* }" clean_up_tmp_dir() { cd "${base_dir}" rm -rf --one-file-system "${tmp_dir}" clean_up_lock_file } tmp_dir=$(mktemp -d "${work_dir}/tmp.return-assignment.XXXXXXXXXX") cd "${tmp_dir}" trap clean_up_tmp_dir EXIT export TMPDIR="${tmp_dir}" # extract package(s) tar -x \ --wildcards \ --no-wildcards-match-slash \ '*.pkg.tar.xz' \ '*.pkg.tar.xz.sig' \ '*.pkg.tar.xz-namcap.log.gz' \ '*.pkg.tar.xz.so.needs.gz' \ '*.pkg.tar.xz.so.provides.gz' # check if all packages come with: # - a package file # - a signature # - a namcap log # - a list of needed libraries # - a list of provided libraries missing_files=$( find . -maxdepth 1 \( \ \( \ -name '*.pkg.tar.xz' \ -printf '%f package\n' \ \) -o \ \( \ -name '*.pkg.tar.xz.sig' \ -printf '%f signature\n' \ \) -o \ \( \ -name '*.pkg.tar.xz-namcap.log.gz' \ -printf '%f namcap\n' \ \) -o \ \( \ -name '*.pkg.tar.xz.so.needs.gz' \ -printf '%f needed-libraries\n' \ \) -o \ \( \ -name '*.pkg.tar.xz.so.provides.gz' \ -printf '%f provided-libraries\n' \ \) \ \) | \ sed ' s/\(\.pkg\.tar\.xz\)\(\.sig\|\(-namcap\.log\|\.so\.\(provides\|needs\)\)\.gz\) /\1 / ' | \ sort -k1,1 -k2,2 | \ sed ' :a $!N s/^\(\(\S\+\) [^\n]\+\)\n\2 /\1 / ta P D ' | \ sed -n ' s/$/ / / package /!{ h s/^\(\S\+\) .*$/Package "\1" is missing./ p g } / signature /!{ h s/^\(\S\+\) .*$/Signature of "\1" is missing./ p g } / namcap /!{ h s/^\(\S\+\) .*$/Namcap log of "\1" is missing./ p g } / needed-libraries /!{ h s/^\(\S\+\) .*$/List of libraries needed by "\1" is missing./ p g } / provided-libraries /!{ h s/^\(\S\+\) .*$/List of libraries provided by "\1" is missing./ p g } ' ) if [ -n "${missing_files}" ]; then >&2 echo 'The following packages lack a signature, namcap log, package file or list of needed/provided libraries:' >&2 echo "${missing_files}" exit 3 fi # check if the signatures are valid signatures=$( find . -maxdepth 1 -name '*.pkg.tar.xz' \ -printf 'package file %f\n' \ -exec gpg --batch --status-fd 1 -q --homedir /etc/pacman.d/gnupg --verify '{}.sig' '{}' \; 2> /dev/null ) if [ -z "$( printf '%s\n' "${signatures}" | \ cut -d' ' -f2 | \ grep -x 'file\|TRUST_FULLY' | \ sort | \ uniq -c | \ awk '{print $1}' | \ uniq -d )" ]; then >&2 echo 'Signature(s) is/are not fully trusted:' >&2 printf '%s\n' "${signatures}" exit 3 fi # check if the sent packages are the expected ones packages=$( find . -maxdepth 1 -name '*.pkg.tar.xz' -printf '%f\n' ) # shellcheck disable=SC2016 { printf 'SELECT' printf ' `binary_packages`.`id`,' mysql_package_name_query printf ' FROM `binary_packages`' mysql_join_binary_packages_architectures mysql_join_binary_packages_repositories printf ' WHERE `binary_packages`.`build_assignment`=from_base64("%s")' \ "$( printf '%s' "${build_assignment_id}" | \ base64 -w0 )" printf ' AND `repositories`.`name`="build-list"' printf ';\n' } | \ mysql_run_query | \ tr '\t' ' ' | \ sort -k2 > \ "${tmp_dir}/package-ids" package_errors=$( { printf '%s\n' "${packages}" | \ sed ' s|^|was_built: | ' sed ' s|^[0-9]\+ |expected: | ' "${tmp_dir}/package-ids" } | \ sort -k2 | \ uniq -u -f1 ) if [ -n "${package_errors}" ]; then >&2 echo 'The following packages should have been built but are missing or vice versa:' >&2 echo "${package_errors}" exit 4 fi if [ ! -s "${tmp_dir}/package-ids" ]; then >&2 echo 'No package was expected, no package was built.' >&2 echo 'That should not happen!' exit 4 fi # shellcheck disable=SC2016 infos=$( { printf 'SELECT `t`.`id`,`t`.`name`' printf ' FROM `repository_moves`' printf ' JOIN `repositories` as `%s` ON `%s`.`id`=`repository_moves`.`%s_repository`' \ 't' 't' 'to' \ 'f' 'f' 'from' printf ' WHERE `f`.`name`="build-list"' printf ' AND `repository_moves`.`upstream_package_repository`=from_base64("%s");\n' \ "$( printf '%s' "${upstream_repository_id}" | \ base64 -w0 )" } | \ mysql_run_query | \ tr '\t' ' ' ) if [ -z "${infos}" ]; then >&2 echo 'I cannot determine, where this package should be published.' >&2 echo 'This is some internal error and not (necessarily) your fault.' # We give a temporary error (although resolving this needs manual # intervention), because there is nothing wrong with the sent package # whence it does not neet to be built again, but can simply be sent again. exit 1 fi destination="${infos##* }" destination_id="${infos%% *}" while read -r package_id package_name; do # move namcap.logs mv \ "${tmp_dir}/${package_name}-namcap.log.gz" \ "${build_log_directory}/success/" # generate checksum sha512sum "${tmp_dir}/${package_name}" | \ awk '{print "'"${package_id}"'\t" $1}' >> \ "${tmp_dir}/sha512sums" # generate list of required/provided libraries for lib in 'provides' 'needs'; do zcat "${tmp_dir}/${package_name}.so.${lib}.gz" | \ sed ' s/^/'"${package_id}"'\t/ ' >> "${tmp_dir}/so.${lib}" done done < \ "${tmp_dir}/package-ids" # move packages ${master_mirror_rsync_command} \ "${master_mirror_rsync_directory}/i686/${destination}/${destination}.db."* \ "${master_mirror_rsync_directory}/i686/${destination}/${destination}.files."* \ . # shellcheck disable=SC2086 repo-add "${destination}.db.tar.gz" ${packages} # repo-add -v -s -k "${repo_key}" "${destination}.db.tar.gz" ${packages} ${master_mirror_rsync_command} \ "${destination}.db."* \ "${destination}.files."* \ ./*".pkg.tar.xz" \ ./*".pkg.tar.xz.sig" \ "${master_mirror_rsync_directory}/i686/${destination}/" trigger_mirror_refreshs # shellcheck disable=SC2016 { # insert checksums into database printf 'CREATE TEMPORARY TABLE `pkg_hashes` (`pkgid` BIGINT, `sha512sum` VARCHAR(128));\n' printf 'LOAD DATA LOCAL INFILE "%s" INTO TABLE `pkg_hashes`;\n' \ "${tmp_dir}/sha512sums" printf 'UPDATE `binary_packages`' printf ' JOIN `pkg_hashes` ON `pkg_hashes`.`pkgid`=`binary_packages`.`id`' printf ' SET `binary_packages`.`sha512sum`=`pkg_hashes`.`sha512sum`;\n' printf 'COMMIT;\n' # insert provided/needed libraries into database for lib_link in 'pl:provides' 'nl:needs'; do printf 'CREATE TEMPORARY TABLE `%s` (`pkgid` BIGINT, `lib` VARCHAR(128));\n' \ "${lib_link%:*}" printf 'LOAD DATA LOCAL INFILE "%s" INTO TABLE `%s`;\n' \ "${tmp_dir}/so.${lib_link#*:}" "${lib_link%:*}" printf 'INSERT IGNORE INTO `install_targets` (`name`)' printf ' SELECT DISTINCT `%s`.`lib` FROM `%s`;\n' \ "${lib_link%:*}" "${lib_link%:*}" printf 'COMMIT;\n' if [ "${lib_link%:*}" = 'pl' ]; then printf 'INSERT IGNORE INTO `install_target_providers` (`package`,`install_target`)' else printf 'INSERT IGNORE INTO `dependencies` (`dependent`,`depending_on`,`dependency_type`)' fi printf ' SELECT `%s`.`pkgid`,`install_targets`.`id`' \ "${lib_link%:*}" if [ "${lib_link%:*}" = 'nl' ]; then printf ',`dependency_types`.`id`' fi printf ' FROM `install_targets`' if [ "${lib_link%:*}" = 'nl' ]; then printf ' JOIN `dependency_types` ON `dependency_types`.`name`="link"' fi printf ' JOIN `%s` ON `%s`.`lib`=`install_targets`.`name`' \ "${lib_link%:*}" "${lib_link%:*}" printf ';\n' printf 'COMMIT;\n' done # remove build_assignment's markers printf 'UPDATE `build_assignments`' printf ' SET' printf ' `build_assignments`.`is_broken`=0,' printf ' `build_assignments`.`priority`=0,' printf ' `build_assignments`.`return_date`=NOW()' printf ' WHERE `build_assignments`.`id`=from_base64("%s");\n' \ "$( printf '%s' "${build_assignment_id}" | \ base64 -w0 )" printf 'COMMIT;\n' # remove from build-list printf 'UPDATE `binary_packages`' printf ' SET' printf ' `binary_packages`.`repository`=from_base64("%s")' \ "$( printf '%s' "${destination_id}" | \ base64 -w0 )" printf ' `binary_packages`.`has_issues`=0,' printf ' `binary_packages`.`is_tested`=0,' printf ' `binary_packages`.`last_moved`=NOW()' printf ' WHERE `binary_packages`.`id` IN (' cut -d' ' -f1 < \ "${tmp_dir}/package-ids" | \ base64_encode_each | \ sed ' s/^/from_base64("/ s/$/"),/ $ s/,$// ' printf ');\n' printf 'COMMIT;\n' # remove from build slave's `currently_building` printf 'UPDATE `build_slaves`' printf ' SET `build_slaves`.`currently_building`=NULL,' printf ' `build_slaves`.`logged_lines`=NULL' printf ' WHERE `build_slaves`.`currently_building`=from_base64("%s");\n' \ "$( printf '%s' "${build_assignment_id}" | \ base64 -w0 )" printf 'COMMIT;\n' # remove broken loops printf 'CREATE TEMPORARY TABLE `loops_to_delete` (`loop` MEDIUMINT);\n' printf 'INSERT INTO `loops_to_delete`' printf ' SELECT `build_dependency_loops`.`loop` FROM `build_dependency_loops`' mysql_join_build_dependency_loops_binary_packages mysql_join_binary_packages_repositories printf ' WHERE NOT `repositories`.`name` = "build-list";\n' printf 'COMMIT;\n' printf 'DELETE FROM `build_dependency_loops` WHERE EXISTS (' printf 'SELECT 1 FROM `loops_to_delete`' printf ' WHERE `loops_to_delete`.`loop`=`build_dependency_loops`.`loop`' printf ');\n' printf 'DROP TEMPORARY TABLE `loops_to_delete`;\n' } | \ mysql_run_query cut -d' ' -f1 < \ "${tmp_dir}/package-ids" | \ while read -r package_id; do remove_old_package_versions "${package_id}" done