#!/bin/bash # -*- coding: utf-8; mode: sh; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=sh:et:sw=4:ts=4:sts=4 # Helper functions for mp-buildbot # Print $0 and arguments to standard error. # Unset IFS to ensure "$*" uses spaces as separators. msg() (unset IFS; printf >&2 '%s: %s\n' "$0" "$*") err() { msg error: "$@"; } warn() { msg warning: "$@"; } unset GETOPT_COMPATIBLE if getopt -T >/dev/null; then # http://frodo.looijaard.name/project/getopt err "Cannot find an enhanced getopt(1)" return 3 fi # TODO Documentation, obviously :) parseopt() { # Be stricter about this than getopt(1) is. if ! [[ ${1-} =~ ^[[:alnum:]-]+:{0,2}(,[[:alnum:]-]+:{0,2})*$ ]]; then err 'Invalid argument given to parseopt' return 3 fi # Use "--options +" to prevent arguments from being rearranged. local opts opts=$(getopt --name "$0" --opt + --longopt "$1" -- "${@:2}") case $? in 0) ;; 1) # getopt(1) will print the bad argument to standard error. echo >&2 "Try \`$0 help' for more information." return 2 ;; *) err 'getopt encountered an internal error' return 3 ;; esac readonly opts local -a validopts IFS=, read -ra validopts <<<"$1" readonly validopts=("${validopts[@]/#/--}") eval set -- "$opts" local opt validopt # getopt(1) ensures that the options are always terminated with "--". while [[ $1 != -- ]]; do opt=$1 shift # XXX Do NOT touch anything below unless you know exactly what # you're doing (http://mywiki.wooledge.org/BashFAQ/006#eval). for validopt in "${validopts[@]}"; do if [[ $validopt == "$opt:" || $validopt == "$opt::" ]]; then opt=${opt#--} # $1 is null for omitted optional arguments. eval option_"${opt//-/_}"'=$1' shift continue 2 fi if [[ $validopt == "$opt" ]]; then opt=${opt#--} eval option_"${opt//-/_}"'=1' continue 2 fi done # Unreachable unless there is a bug in this function or in getopt(1). err 'parseopt encountered an internal error' return 3 done # shellcheck disable=SC2034 args=("${@:2}") } ## Compute a failcache hash for the given port # # Computes and prints a hash uniquely identifying a specific state of a port's # definition files, including the Portfile's hash as well as the port's # patchfiles. To build the hash, this function executes the following # algorithm: # - For the Portfile, and each file in files/ (if any), calculate a SHA256 # hash # - Sort the hash values alphabetically # - Hash the result using SHA256 # This means a failcache entry will not match if a patchfile changes. A common # case where this is the desired behavior is a port committed without # a required patchfile. # # Valid arguments are all arguments accepted by "port dir". compute_failcache_hash() { local portdir local -a filelist portdir=$("${option_prefix}/bin/port" dir "$@") if [ $? -ne 0 ] || [ -z "$portdir" ]; then err "Could not compute failcache hash: port dir" "$@" "failed" return 1 fi if [ ! -d "$portdir" ]; then err "Port directory $portdir does not exist" return 2 fi filelist=("$portdir/Portfile") if [ -d "$portdir/files" ]; then filelist+=("$portdir/files") fi find "${filelist[@]}" -type f -exec openssl dgst -sha256 {} \; |\ cut -d' ' -f2 |\ sort |\ openssl dgst -sha256 |\ cut -d' ' -f2 } ## Compute a key that uniquely identifies a (port, variants, portfile-hash) tuple # # Valid arguments are a port name, optionally followed by a variant # specification. Invokes "port dir" to find the Portfile and patchfiles and # computes a checksum of these files that will become part of the hash. failcache_key() { local port=$1 if [ -z "$port" ]; then err "failcache_key expects a port argument, but none was given." return 1 fi local checksum checksum=$(compute_failcache_hash "$port") if [ $? -ne 0 ]; then err "compute_failcache_hash $port failed" return 2 fi local canonical_variants canonical_variants=$("${option_prefix}/bin/port-tclsh" "${thisdir}/tools/canonical-variants.tcl" "$@") if [ $? -ne 0 ]; then err "tools/canonical-variants.tcl" "$@" "failed" return 4 fi echo "$port $canonical_variants $checksum" } ## Test whether a given port with variants has previously failed. # # Valid arguments are a port name, optionally followed by a variant # specification. Succeeds if the port did not previsouly fail to build, # fails if the port is known to fail. failcache_test() { local key key=$(failcache_key "$@") if [ $? -ne 0 ]; then err "Could not determine failcache key for" "$@" return 1 fi if [ -f "${option_failcache_dir}/${key}" ]; then printf "port %s previously failed in build %s\n" "${key}" "$(<"${option_failcache_dir}/${key}")" return 1 else return 0 fi } ## Mark a build of a given port with variants as successful. # # Valid arguments are a port name, optionally followed by a variant # specification. Removes any database entries that marked a port as failed. failcache_success() { local key key=$(failcache_key "$@") if [ $? -ne 0 ]; then err "Could not determine failcache key for" "$@" return 1 fi # Only remove the entry for the successful configuration, leaving # other entries in case they are explicitly requested later. These # can be removed manually if desired. rm -f "${option_failcache_dir}/${key}" } ## Mark a build of a given port with variants as failed. # # Valid arguments are a port name, optionally followed by a variant # specification. Creates or updates the timestamp of a database entry that # marks a port as failed. failcache_failure() { local key key=$(failcache_key "$@") if [ $? -ne 0 ]; then err "Could not determine failcache key for" "$@" return 1 fi mkdir -p "${option_failcache_dir}" echo "${BUILDBOT_BUILDURL:-unknown}" > "${option_failcache_dir}/${key}" }