mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-09-25 03:38:22 +08:00
The weekly refresh workflow carried its logic inline, compared only the tap SHA against the pin, and duplicated the --sim-tools package list. `brew install --dry-run` never prints "from source", so its bottle check could not fire. Tools/ci/refresh_gz_tap_pin.sh now owns the job and the workflow calls it. It reads the osrf/simulation formulae from macos.sh, walks the tap from HEAD back to the pin, asks Homebrew at each commit whether every formula in the runtime closure has a bottle for the host, HEAD-requests each tarball, and rewrites the pin to the first commit that passes. Formulae with no bottle at the current pin (the gz-harmonic meta-formula, which OSRF never bottles) are not required. peter-evans/create-pull-request opens or updates the bot PR from the script's outputs, so an unmerged bump is refreshed instead of duplicated. The job runs on macos-15, the oldest macOS in compile_macos.yml, because Homebrew pours an older macOS bottle onto a newer one, not the reverse. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Ramon Roche <mrpollo@gmail.com>
187 lines
7.1 KiB
Bash
Executable File
187 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Move Tools/setup/gz-tap-pin.txt to the newest osrf/simulation commit whose
|
|
# Gazebo formulae all have a bottle Homebrew would pour on this machine.
|
|
#
|
|
# macos.sh --sim-tools pins the osrf/simulation tap to gz-tap-pin.txt so
|
|
# Gazebo pours from bottles even while OSRF has them pulled (see that file).
|
|
# The pin is a point in time, so this walks the tap's first-parent history
|
|
# from origin HEAD back to the current pin, checks each commit out, and asks
|
|
# Homebrew whether every osrf/simulation formula macos.sh installs, plus
|
|
# their osrf/simulation runtime dependencies, carries a bottle for this
|
|
# host. Each bottle tarball is then HEAD-requested so a bottle block whose
|
|
# tarball is gone does not count. The first commit that passes becomes the
|
|
# new pin. The tap checkout is restored on exit either way.
|
|
#
|
|
# A formula that has no bottle at the current pin either is not required to
|
|
# have one: in practice that is the gz-harmonic meta-formula, which OSRF
|
|
# never bottles and which builds in seconds. Everything that poured at the
|
|
# pin has to keep pouring.
|
|
#
|
|
# The formula names are read from macos.sh so there is no second list to
|
|
# keep in sync. The bottle tag match is Homebrew's own, which on macOS also
|
|
# accepts a bottle built on an older macOS of the same arch, so a commit
|
|
# that passes on the oldest macOS CI runs on passes on the newer ones too.
|
|
#
|
|
# Exits 0 whether or not a newer bottled commit exists; only errors exit
|
|
# non-zero. Under GitHub Actions the result also lands in $GITHUB_OUTPUT as
|
|
# needs_bump=true|false and old_pin, plus new_pin and new_pin_short when
|
|
# needs_bump is true.
|
|
#
|
|
# Usage: Tools/ci/refresh_gz_tap_pin.sh
|
|
|
|
set -euo pipefail
|
|
|
|
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
|
ROOT_DIR=$(git -C "$DIR" rev-parse --show-toplevel)
|
|
PIN_FILE="${ROOT_DIR}/Tools/setup/gz-tap-pin.txt"
|
|
MACOS_SH="${ROOT_DIR}/Tools/setup/macos.sh"
|
|
TAP="osrf/simulation"
|
|
|
|
log() { echo "[refresh_gz_tap_pin] $*"; }
|
|
die() { log "ERROR: $*" >&2; exit 1; }
|
|
|
|
output() {
|
|
log "$1=$2"
|
|
if [[ -n ${GITHUB_OUTPUT:-} ]]; then
|
|
echo "$1=$2" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
}
|
|
|
|
# Runs inside Homebrew via `brew ruby`. ARGV is the tap name followed by
|
|
# the osrf/simulation formulae macos.sh installs (brew scrubs everything
|
|
# but HOMEBREW_* from the environment, so arguments it is). Prints one line
|
|
# per osrf/simulation formula in their runtime closure, either
|
|
# "bottled <name> <version> <url>" or "no bottle <name> <version>", and
|
|
# exits 3 if any bottle is missing.
|
|
#
|
|
# Formula#bottle is deliberately not used: `brew ruby` runs in a child of
|
|
# the brew process, which has already consumed the HOMEBREW_*_DEFAULT_PREFIX
|
|
# variables Homebrew derives its default cellar from, so under `brew ruby`
|
|
# every bottle without an explicit cellar looks like it was built in a
|
|
# foreign prefix. The tag match and the bottle URL do not depend on that.
|
|
read -r -d '' BOTTLED_RB <<'EOF' || true
|
|
tap = ARGV.shift
|
|
host = Utils::Bottles.tag
|
|
formulae = {}
|
|
ARGV.each do |name|
|
|
root = Formulary.factory(name)
|
|
deps = root.recursive_dependencies do |_, dep|
|
|
next Dependable::PRUNE if dep.build? || dep.test? || dep.optional?
|
|
end
|
|
([root] + deps.map(&:to_formula)).each do |f|
|
|
formulae[f.full_name] = f if f.tap&.name == tap
|
|
end
|
|
end
|
|
missing = 0
|
|
formulae.each_value do |f|
|
|
bottle = f.bottle_for_tag(host) if f.pour_bottle?
|
|
if bottle
|
|
puts "bottled #{f.full_name} #{f.pkg_version} #{bottle.url}"
|
|
else
|
|
puts "no bottle #{f.full_name} #{f.pkg_version}"
|
|
missing += 1
|
|
end
|
|
end
|
|
exit(missing.zero? ? 0 : 3)
|
|
EOF
|
|
|
|
command -v brew > /dev/null || die "Homebrew is required"
|
|
|
|
# The osrf/simulation formulae macos.sh installs. Read from the script so
|
|
# this never drifts from PX4_SIM_BREW_PACKAGES.
|
|
ROOTS=$(grep -oE "${TAP}/[A-Za-z0-9@._+-]+" "$MACOS_SH" | sort -u | tr '\n' ' ')
|
|
[[ -n $ROOTS ]] || die "macos.sh installs nothing from ${TAP}"
|
|
|
|
OLD_PIN=$(grep -v '^#' "$PIN_FILE" | tr -d '[:space:]')
|
|
[[ $OLD_PIN =~ ^[0-9a-f]{40}$ ]] || die "no commit SHA in ${PIN_FILE}"
|
|
|
|
# Keep brew from resetting the tap checkout or nagging mid-run.
|
|
export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ENV_HINTS=1
|
|
|
|
brew tap "$TAP"
|
|
# Homebrew 6.0+ refuses to load formulae from untrusted taps; same guard
|
|
# as macos.sh.
|
|
if brew trust --help &> /dev/null; then
|
|
brew trust "$TAP"
|
|
fi
|
|
TAP_DIR=$(brew --repo "$TAP")
|
|
|
|
git_tap() { git -C "$TAP_DIR" "$@"; }
|
|
|
|
[[ -z $(git_tap status --porcelain) ]] || die "${TAP_DIR} has local changes, refusing to touch it"
|
|
ORIG_REF=$(git_tap symbolic-ref --quiet --short HEAD || git_tap rev-parse HEAD)
|
|
restore_tap() { git_tap checkout --quiet "$ORIG_REF"; }
|
|
trap restore_tap EXIT
|
|
|
|
if [[ $(git_tap rev-parse --is-shallow-repository) == true ]]; then
|
|
git_tap fetch --quiet --unshallow
|
|
fi
|
|
git_tap fetch --quiet origin
|
|
git_tap remote set-head origin --auto > /dev/null
|
|
TAP_HEAD=$(git_tap rev-parse origin/HEAD)
|
|
|
|
git_tap merge-base --is-ancestor "$OLD_PIN" origin/HEAD \
|
|
|| die "pin ${OLD_PIN} is not an ancestor of ${TAP} HEAD ${TAP_HEAD}, bump it by hand"
|
|
|
|
# Bottle report for the checked-out tap commit; exit 3 means at least one
|
|
# formula has no bottle, anything else non-zero is an error.
|
|
bottle_report() {
|
|
# shellcheck disable=SC2086 # word-split on purpose
|
|
brew ruby -e "$BOTTLED_RB" "$TAP" $ROOTS
|
|
}
|
|
|
|
output old_pin "$OLD_PIN"
|
|
git_tap checkout --quiet --detach "$OLD_PIN"
|
|
rc=0
|
|
PIN_REPORT=$(bottle_report) || rc=$?
|
|
[[ $rc -eq 0 || $rc -eq 3 ]] || die "bottle check failed (exit ${rc}) at the pin ${OLD_PIN}"
|
|
EXEMPT=$(printf '%s\n' "$PIN_REPORT" | awk '$1 == "no" { print $3 }')
|
|
[[ -z $EXEMPT ]] || log "not bottled at the pin either, not required: $(tr '\n' ' ' <<< "$EXEMPT")"
|
|
|
|
# Every formula that poured at the pin still has a bottle for this host at
|
|
# the checked-out tap commit, and every bottle tarball is still served.
|
|
bottled_here() {
|
|
local rc=0 report name url
|
|
report=$(bottle_report) || rc=$?
|
|
printf '%s\n' "$report" | sed 's/^/ /'
|
|
[[ $rc -eq 0 || $rc -eq 3 ]] || return "$rc"
|
|
for name in $(printf '%s\n' "$report" | awk '$1 == "no" { print $3 }'); do
|
|
grep -qx "$name" <<< "$EXEMPT" || return 3
|
|
done
|
|
for url in $(printf '%s\n' "$report" | awk '$1 == "bottled" { print $4 }'); do
|
|
curl -sfI -o /dev/null "$url" || { log "bottle tarball missing: ${url}"; return 3; }
|
|
done
|
|
}
|
|
|
|
CANDIDATES=$(git_tap rev-list --first-parent "${OLD_PIN}..origin/HEAD")
|
|
# shellcheck disable=SC2086 # word-split on purpose
|
|
set -- $CANDIDATES
|
|
log "${TAP} HEAD ${TAP_HEAD} is $# commit(s) ahead of the pin, checking newest first for ${ROOTS}"
|
|
|
|
NEW_PIN=""
|
|
for sha in "$@"; do
|
|
log "$(git_tap log -1 --format='%h %cs %s' "$sha")"
|
|
git_tap checkout --quiet --detach "$sha"
|
|
rc=0
|
|
bottled_here || rc=$?
|
|
case $rc in
|
|
0) NEW_PIN=$sha; break ;;
|
|
3) ;;
|
|
*) die "bottle check failed (exit ${rc}) at ${sha}" ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z $NEW_PIN ]]; then
|
|
log "no commit newer than the pin is fully bottled here, keeping ${OLD_PIN}"
|
|
output needs_bump false
|
|
exit 0
|
|
fi
|
|
|
|
awk -v old="$OLD_PIN" -v new="$NEW_PIN" '$0 == old { print new; next } { print }' "$PIN_FILE" > "${PIN_FILE}.tmp"
|
|
mv "${PIN_FILE}.tmp" "$PIN_FILE"
|
|
log "pinned ${TAP} to ${NEW_PIN}, $(git_tap rev-list --count "${NEW_PIN}..origin/HEAD") commit(s) behind HEAD"
|
|
output needs_bump true
|
|
output new_pin "$NEW_PIN"
|
|
output new_pin_short "${NEW_PIN:0:12}"
|