Files
mpbb/functions
2025-04-29 22:45:18 +10:00

247 lines
7.5 KiB
Bash

#!/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 mpbb
# 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"
}
## Delete stale failcache entries for a given port
#
# Valid arguments are the first and last parts of a key generated by
# failcache_key in order, i.e. portname and checksum.
# Returns 1 if there were no entries for the given port, 0 otherwise.
failcache_cleanup() {
if ! compgen -G "${option_failcache_dir}/${1} *" > /dev/null; then
return 1
fi
for f in "${option_failcache_dir}/${1} "*; do
if [ "$(basename "$f" | cut -d ' ' -f3)" != "${2}" ]; then
rm -f "${f}"
fi
done
return 0
}
## 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 previously 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
# check that it doesn't exceed NAME_MAX
if [ "$(echo "$key" | wc -c)" -gt 255 ]; then
printf "failcache key too long: %s\n" "${key}"
return 0
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
if [ "$(echo "$key" | wc -c)" -gt 255 ]; then
printf "failcache key too long: %s\n" "${key}"
return 0
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
if [ "$(echo "$key" | wc -c)" -gt 255 ]; then
printf "failcache key too long: %s\n" "${key}"
return 0
fi
mkdir -p "${option_failcache_dir}"
echo "${BUILDBOT_BUILDURL:-unknown}" > "${option_failcache_dir}/${key}"
}
get-maintainers() {
# 'username@macports.org github_username' => 'username@macports.org'
# $option_prefix is set in mpbb
# shellcheck disable=SC2154
"${option_prefix}/bin/port" info --index --maintainers --line "$@" \
| tr ', ' '\n' | fgrep '@' | tr '\n' ',' | sed 's/,$//'
}