name: Update Homebrew Formula
on:
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag (e.g. v0.6.3)'
required: false
workflow_call:
inputs:
release_tag:
description: 'Release tag passed from caller workflow'
required: false
type: string
permissions:
contents: write
jobs:
update-homebrew:
name: Update Homebrew Formula
runs-on: macos-latest
environment: packaging
steps:
- name: Checkout this repository
uses: actions/checkout@v6
- name: Install gnu-sed
run: brew install gnu-sed
- name: Determine version tag
id: get_version
run: |
# Check workflow_dispatch input first, then workflow_call input
INPUT_TAG="${{ github.event.inputs.release_tag || inputs.release_tag }}"
if [ -n "$INPUT_TAG" ]; then
echo "VERSION=$INPUT_TAG" >> $GITHUB_ENV
else
# Get the latest official (non-pre-release) release
TAG=$(gh release view --json tagName --jq .tagName)
echo "VERSION=$TAG" >> $GITHUB_ENV
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Clone Homebrew tap repository
run: |
git clone https://x-access-token:${{ secrets.HOMEBREW_TAP_TOKEN }}@github.com/lablup/homebrew-tap.git
cd homebrew-tap
git config user.name "GitHub Action"
git config user.email "actions@github.com"
- name: Download release artifacts and calculate SHA256
run: |
cd homebrew-tap
set -euo pipefail
RAW_VERSION="${{ env.VERSION }}"
VERSION="${RAW_VERSION#v}" # Remove leading 'v'
echo "VERSION_NO_V=$VERSION" >> $GITHUB_ENV # Export version without 'v' to GitHub env
BASE="https://github.com/lablup/bssh/releases/download/v${VERSION}"
mkdir -p tmp
# `-f` so a missing or renamed asset fails here rather than leaving a
# zero-byte file whose checksum gets committed to the tap, and an
# archive integrity check so a truncated download cannot turn into a
# valid-looking sha256 either.
fetch() { # name url dest
local name="$1" url="$2" dest="$3"
curl -fLs --retry 3 "$url" -o "$dest"
if [ ! -s "$dest" ]; then
echo "::error::Empty download: ${url}"
exit 1
fi
case "$dest" in
*.zip) unzip -qqt "$dest" > /dev/null ;;
*.tar.gz) tar -tzf "$dest" > /dev/null ;;
esac
{
echo "${name}_url=${url}"
echo "${name}_sha=$(shasum -a 256 "$dest" | awk '{print $1}')"
} >> "$GITHUB_ENV"
}
fetch mac "${BASE}/bssh-macos-aarch64.zip" tmp/mac.zip
fetch linux_arm "${BASE}/bssh-linux-aarch64.tar.gz" tmp/linux-arm.tar.gz
fetch linux_x86 "${BASE}/bssh-linux-x86_64.tar.gz" tmp/linux-x86.tar.gz
fetch server_mac "${BASE}/bssh-server-macos-aarch64.zip" tmp/server-mac.zip
fetch server_linux_arm "${BASE}/bssh-server-linux-aarch64.tar.gz" tmp/server-linux-arm.tar.gz
fetch server_linux_x86 "${BASE}/bssh-server-linux-x86_64.tar.gz" tmp/server-linux-x86.tar.gz
fetch keygen_mac "${BASE}/bssh-keygen-macos-aarch64.zip" tmp/keygen-mac.zip
fetch keygen_linux_arm "${BASE}/bssh-keygen-linux-aarch64.tar.gz" tmp/keygen-linux-arm.tar.gz
fetch keygen_linux_x86 "${BASE}/bssh-keygen-linux-x86_64.tar.gz" tmp/keygen-linux-x86.tar.gz
- name: Update formulae
run: |
cd homebrew-tap
set -euo pipefail
VERSION="${{ env.VERSION_NO_V }}" # Use version without 'v'
# Stanzas are located by name, never by line offset. This step used to
# rewrite each checksum with
#
# gsed -i "/ARTIFACT\"/!b;n;c\ sha256 \"...\""
#
# which matched the url line, advanced one line, and replaced whatever
# it landed on. That holds only while sha256 sits directly beneath url
# inside an on_macos / on_linux block. lablup/mlxcel ran the same
# construct against a formula whose url had moved to the top level
# with a `version` stanza in between: it overwrote `version` with the
# new checksum and left the previous release's sha256 below, so the
# formula had no version, two sha256 lines, and failed `brew install`
# on checksum mismatch. It sat broken in the tap for three days
# (lablup/homebrew-tap 9969ec4). Nothing here depends on layout now,
# and the guards abort instead of editing the wrong line.
set_version() { # file version
local file="$1" version="$2" n
n=$(grep -c "^[[:space:]]*version " "$file" || true)
if [ "$n" -ne 1 ]; then
echo "::error::${file}: expected exactly one version stanza, found ${n}."
exit 1
fi
gsed -i "s|^\([[:space:]]*\)version .*|\1version \"${version}\"|" "$file"
}
# Rewrites the url stanza whose value contains ARTIFACT, then the
# first sha256 stanza below it, preserving each line's indentation.
set_artifact() { # file artifact url sha
local file="$1" artifact="$2" url="$3" sha="$4" n
n=$(awk -v a="$artifact" '$0 ~ /^[[:space:]]*url / && index($0, a) { c++ } END { print c+0 }' "$file")
if [ "$n" -ne 1 ]; then
echo "::error::${file}: expected exactly one url stanza for ${artifact}, found ${n}."
exit 1
fi
awk -v a="$artifact" -v u="$url" -v s="$sha" '
!seen && $0 ~ /^[[:space:]]*url / && index($0, a) {
match($0, /^[[:space:]]*/)
printf "%surl \"%s\"\n", substr($0, 1, RLENGTH), u
seen = 1
next
}
seen && !hit && $0 ~ /^[[:space:]]*sha256 / {
match($0, /^[[:space:]]*/)
printf "%ssha256 \"%s\"\n", substr($0, 1, RLENGTH), s
hit = 1
next
}
{ print }
END { exit(hit ? 0 : 1) }
' "$file" > "${file}.new" || {
echo "::error::${file}: no sha256 stanza follows the url for ${artifact}."
rm -f "${file}.new"
exit 1
}
mv "${file}.new" "$file"
}
set_version Formula/bssh.rb "$VERSION"
set_artifact Formula/bssh.rb bssh-macos-aarch64.zip "$mac_url" "$mac_sha"
set_artifact Formula/bssh.rb bssh-linux-aarch64.tar.gz "$linux_arm_url" "$linux_arm_sha"
set_artifact Formula/bssh.rb bssh-linux-x86_64.tar.gz "$linux_x86_url" "$linux_x86_sha"
if [ -f Formula/bssh-server.rb ]; then
set_version Formula/bssh-server.rb "$VERSION"
set_artifact Formula/bssh-server.rb bssh-server-macos-aarch64.zip "$server_mac_url" "$server_mac_sha"
set_artifact Formula/bssh-server.rb bssh-server-linux-aarch64.tar.gz "$server_linux_arm_url" "$server_linux_arm_sha"
set_artifact Formula/bssh-server.rb bssh-server-linux-x86_64.tar.gz "$server_linux_x86_url" "$server_linux_x86_sha"
else
echo "Warning: Formula/bssh-server.rb not found, skipping server formula update"
fi
if [ -f Formula/bssh-keygen.rb ]; then
set_version Formula/bssh-keygen.rb "$VERSION"
set_artifact Formula/bssh-keygen.rb bssh-keygen-macos-aarch64.zip "$keygen_mac_url" "$keygen_mac_sha"
set_artifact Formula/bssh-keygen.rb bssh-keygen-linux-aarch64.tar.gz "$keygen_linux_arm_url" "$keygen_linux_arm_sha"
set_artifact Formula/bssh-keygen.rb bssh-keygen-linux-x86_64.tar.gz "$keygen_linux_x86_url" "$keygen_linux_x86_sha"
else
echo "Warning: Formula/bssh-keygen.rb not found, skipping keygen formula update"
fi
- name: Validate updated formulae
run: |
cd homebrew-tap
set -euo pipefail
VERSION="${{ env.VERSION_NO_V }}"
# `brew style` is the check the tap itself is expected to pass, and it
# only applies formula cops to paths under Formula/. `ruby -c` is not
# sufficient on its own: the mlxcel corruption described above was
# valid Ruby and would have passed a syntax check.
check() { # file expected_sha...
local file="$1"; shift
local n sha
echo "=== ${file} ==="
cat "$file"
ruby -c "$file"
brew style "$file"
if ! grep -q "^[[:space:]]*version \"${VERSION}\"$" "$file"; then
echo "::error::${file}: version stanza is not ${VERSION}."
exit 1
fi
# A substitution that silently no-ops would leave the previous
# release's checksum behind, so require the exact expected set.
n=$(grep -c "^[[:space:]]*sha256 " "$file" || true)
if [ "$n" -ne "$#" ]; then
echo "::error::${file}: expected $# sha256 stanzas, found ${n}."
exit 1
fi
for sha in "$@"; do
if ! grep -q "^[[:space:]]*sha256 \"${sha}\"$" "$file"; then
echo "::error::${file}: sha256 ${sha} was not written."
exit 1
fi
done
}
check Formula/bssh.rb "$mac_sha" "$linux_arm_sha" "$linux_x86_sha"
if [ -f Formula/bssh-server.rb ]; then
check Formula/bssh-server.rb "$server_mac_sha" "$server_linux_arm_sha" "$server_linux_x86_sha"
fi
if [ -f Formula/bssh-keygen.rb ]; then
check Formula/bssh-keygen.rb "$keygen_mac_sha" "$keygen_linux_arm_sha" "$keygen_linux_x86_sha"
fi
- name: Commit and push changes to tap
run: |
cd homebrew-tap
set -euo pipefail
for f in Formula/bssh.rb Formula/bssh-server.rb Formula/bssh-keygen.rb; do
if [ -f "$f" ]; then
git add "$f"
fi
done
# Re-running the workflow for a version already in the tap is not an
# error; `git commit` would fail on an empty index otherwise.
if git diff --cached --quiet; then
echo "Formulae already up to date for v${{ env.VERSION_NO_V }}, nothing to push."
exit 0
fi
git commit -m "bump: bssh, bssh-server, and bssh-keygen to v${{ env.VERSION_NO_V }}"
git push origin main