You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/09/17 08:10:36 UTC
(skywalking-ai-sessionizer) 01/01: Keep every release installable with Homebrew, and add the binary-distribution skill
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch binary-distribution
in repository https://gitbox.apache.org/repos/asf/skywalking-ai-sessionizer.git
commit 5e10afbd0465d2a271e15b772d49aaa59cb012d7
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Thu Sep 17 16:10:29 2026 +0800
Keep every release installable with Homebrew, and add the binary-distribution skill
A formula names one version, so once asz.rb moves to the next release the
previous one could no longer be installed. Each release now also gets
asz@VERSION and asz-claude-code@VERSION, which Formula/ keeps. They are
keg-only, as Homebrew wants for an alternate version, so installing one
beside the current formula does not replace its binary on PATH.
Homebrew relates asz@VERSION to asz only when VERSION is digits and dots.
With the CI version 0.0.0-ci it did not, linked asz@0.0.0-ci over asz, and
the install failed. Every Apache release is digits and dots, so the
generator writes versioned formulae only for such a version, the check
refuses any other, and CI builds its packages as 0.0.0. The check also
removes a formula whose install stopped part way, which it left behind.
tools/homebrew-formula.sh [--from DIR] [--check] VERSION... writes the
formulae of released versions into Formula/. It downloads each version's
packages from downloads.apache.org, or archive.apache.org for an older one,
holds them to their sha512, runs tools/homebrew-check.sh first with --check,
and moves asz.rb and asz-claude-code.rb only forward, so adding an older
version never moves them back. Tested with two local versions and a re-add
of the older one, and against the real sites with 0.3.0, whose old package
layout it refuses, and 0.2.9, which was never released.
.claude/skills/binary-distribution runs it for one version or several,
checks each version is released first, and opens the pull request.
---
.claude/skills/binary-distribution/SKILL.md | 94 +++++++++++++++++++++
.github/workflows/ci.yaml | 4 +-
docs/en/changes/changes.md | 5 ++
docs/en/guides/how-to-release.md | 26 +++---
docs/en/setup/install.md | 4 +
tools/homebrew-check.sh | 22 ++++-
tools/homebrew-formula.sh | 124 ++++++++++++++++++++++++++++
tools/install-manifests.sh | 27 ++++++
8 files changed, 289 insertions(+), 17 deletions(-)
diff --git a/.claude/skills/binary-distribution/SKILL.md b/.claude/skills/binary-distribution/SKILL.md
new file mode 100644
index 0000000..7188fad
--- /dev/null
+++ b/.claude/skills/binary-distribution/SKILL.md
@@ -0,0 +1,94 @@
+---
+name: binary-distribution
+description: Put released versions of Apache SkyWalking AI Sessionizer into Homebrew by updating Formula/ on main in apache/skywalking-ai-sessionizer, the tap. Writes asz@VERSION and asz-claude-code@VERSION for each version, moves asz and asz-claude-code to the newest, checks the formulae with Homebrew, and opens the pull request. Use after a version is published, or to add older released versions.
+user-invocable: true
+---
+
+# Binary distribution: Homebrew
+
+`Formula/` on main is the Homebrew tap. It holds, for every released version,
+`asz@VERSION.rb` and `asz-claude-code@VERSION.rb`, and `asz.rb` and
+`asz-claude-code.rb` for the newest version. Users install with:
+
+```sh
+brew tap apache/skywalking-ai-sessionizer https://github.com/apache/skywalking-ai-sessionizer
+brew install apache/skywalking-ai-sessionizer/asz
+brew install apache/skywalking-ai-sessionizer/asz-claude-code@0.4.0
+```
+
+The user names one version or several. A version must be released: voted,
+moved to the download site, and its GitHub release promoted, because the
+formulae download the packages from that GitHub release.
+
+## 1. Check the versions are released
+
+Each version must answer 200 on one of the two Apache sites, and its GitHub
+release must carry the packages. A local proxy can break HTTPS to Apache hosts,
+so these commands go around it:
+
+```sh
+for v in <versions>; do
+ for u in "https://downloads.apache.org/skywalking/ai-sessionizer/$v/" "https://archive.apache.org/dist/skywalking/ai-sessionizer/$v/"; do
+ curl --noproxy '*' -s -o /dev/null -w "$v $u %{http_code}\n" "$u"
+ done
+ gh release view "v$v" --repo apache/skywalking-ai-sessionizer --json isPrerelease,assets \
+ --jq '"v'"$v"' prerelease=\(.isPrerelease) assets=\(.assets|length)"'
+done
+```
+
+Stop and tell the user about any version that is not released, or whose release
+is still a prerelease.
+
+## 2. Branch from main
+
+Work in a worktree so another session's changes in the main clone are left
+alone:
+
+```sh
+git fetch origin
+git worktree add -b brew-<versions> <scratch dir> origin/main
+cd <scratch dir>
+```
+
+## 3. Write and check the formulae, on macOS
+
+```sh
+env -u http_proxy -u https_proxy -u all_proxy tools/homebrew-formula.sh --check <versions>
+```
+
+For each version it downloads the six packages and their `.sha512` from the
+Apache sites, holds each package to its `.sha512`, and runs the formulae
+through `brew style`, `brew audit --strict`, `brew install` and `brew test`,
+removing what it installed. Only then does it write `Formula/asz@VERSION.rb`
+and `Formula/asz-claude-code@VERSION.rb`, and move `Formula/asz.rb` and
+`Formula/asz-claude-code.rb` to the newest version. Adding an older version
+never moves them back. With `--from DIR` it reads `DIR/VERSION/` instead, such
+as the release manager's `dist/`.
+
+If `asz` or `asz-claude-code` is already installed on this machine, the check
+refuses to run rather than replace it. Say so and stop; do not uninstall the
+user's own install.
+
+## 4. Look at the change
+
+Read what it printed, and `git status --short`. Only files under `Formula/` may
+change.
+
+## 5. Commit and open the pull request
+
+```sh
+git add Formula
+git commit -m "Homebrew formulae for <versions>"
+git push -u origin brew-<versions>
+gh pr create --repo apache/skywalking-ai-sessionizer --base main --title "Homebrew formulae for <versions>"
+```
+
+The commit message and the pull request carry no AI attribution: no
+Co-Authored-By line and no "Generated with" line. Say in the pull request which
+versions were added, which version `asz` and `asz-claude-code` now install, and
+that the check passed.
+
+## 6. After the merge
+
+Anyone who tapped runs `brew update`, then `brew upgrade asz asz-claude-code`.
+Tell the user the pull request link, and remove the worktree once it is merged.
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index e6627de..da69a1c 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -411,8 +411,8 @@ jobs:
echo "::notice::this tag has no tools/homebrew-check.sh, so the formulae are not run"
exit 0
fi
- make binaries checksums VERSION=0.0.0-ci
- tools/homebrew-check.sh 0.0.0-ci dist
+ make binaries checksums VERSION=0.0.0
+ tools/homebrew-check.sh 0.0.0 dist
lint:
name: Lint
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 744f5b8..8115545 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -100,6 +100,11 @@
then `brew install apache/skywalking-ai-sessionizer/asz` and `.../asz-claude-code`.
`tools/homebrew-check.sh` runs both formulae through `brew style`, `brew audit --strict`,
`brew install` and `brew test`, and CI's `homebrew` job runs it on macOS on every change.
+- **Every release stays installable with Homebrew.** Each release adds `asz@VERSION` and
+ `asz-claude-code@VERSION`, keg-only, beside the current formulae. `tools/homebrew-formula.sh`
+ writes them into `Formula/` from the released packages, checks them with Homebrew first, and
+ moves `asz` and `asz-claude-code` only forward. The `binary-distribution` skill runs it and opens
+ the pull request.
- **Two install scripts, one for each install.** asz, the collector, and the Claude Code plugin are
installed apart. `install/asz.sh` and `install/asz.ps1` install `asz`, and
[Install](../setup/install.md#install-script) runs them from the version's tag in one command. The
diff --git a/docs/en/guides/how-to-release.md b/docs/en/guides/how-to-release.md
index 849db6f..667f4b9 100644
--- a/docs/en/guides/how-to-release.md
+++ b/docs/en/guides/how-to-release.md
@@ -929,22 +929,24 @@ Each installs `LICENSE`, `NOTICE` and `licenses/`, and its test runs its binary.
Homebrew/homebrew-core takes a formula only when it builds from source, and these install a binary
built for each platform.
-The tap is this repository. Homebrew looks for a tap's formulae in `Formula/` first, so after
-Publish, open a pull request to main that replaces `Formula/asz.rb` and `Formula/asz-claude-code.rb`
-with the two files from `dist/$VERSION/install/homebrew/`. The formulae name the sha256 of the voted
-packages, so they cannot be committed before the vote, and the tag of a version never holds its own
-formulae. `.gitattributes` keeps `Formula/` out of the source package, which would otherwise carry
-the formulae of the version before it. Users add the tap and install by the full name, as
-[Install](../setup/install.md#homebrew-on-macos-and-linux) shows.
-
-Before the pull request, run the formulae through Homebrew on the voted packages. It writes them into
-a local tap, runs `brew style` and `brew audit --strict`, installs and tests both, and removes
-everything it installed:
+The tap is this repository: `Formula/` on main. It keeps `asz@VERSION.rb` and
+`asz-claude-code@VERSION.rb` for every release, keg-only so they do not clash, and `asz.rb` and
+`asz-claude-code.rb` for the newest. The formulae name the sha256 of the voted packages, so they
+cannot be committed before the vote, and the tag of a version never holds its own formulae.
+`.gitattributes` keeps `Formula/` out of the source package.
+
+After Publish, on macOS, on a branch from main:
```sh
-tools/homebrew-check.sh $VERSION dist/$VERSION
+tools/homebrew-formula.sh --from dist --check $VERSION
```
+It runs each version's formulae through `brew style`, `brew audit --strict`, `brew install` and
+`brew test`, and only then writes them into `Formula/`, moving `asz.rb` and `asz-claude-code.rb` to
+the newest version. Without `--from` it downloads released packages from the Apache sites, which is
+how older versions are added. Open a pull request with the change to `Formula/`. The
+`binary-distribution` skill in `.claude/skills/` does all of this with Claude Code.
+
CI's `homebrew` job runs the same script on every change, on packages it builds with a version of
its own, so a change that breaks the formulae fails before a release.
diff --git a/docs/en/setup/install.md b/docs/en/setup/install.md
index 57acd89..4a00834 100644
--- a/docs/en/setup/install.md
+++ b/docs/en/setup/install.md
@@ -37,6 +37,10 @@ brew install apache/skywalking-ai-sessionizer/asz-claude-code
plugin into Claude Code, run the two commands that `brew info asz-claude-code` prints. Upgrade with
`brew upgrade asz asz-claude-code`.
+For an exact version, install `apache/skywalking-ai-sessionizer/asz@<version>` and
+`apache/skywalking-ai-sessionizer/asz-claude-code@<version>`. Homebrew keeps them off your `PATH`;
+`brew info` shows where they are.
+
## Binary package
The [downloads page](https://skywalking.apache.org/downloads/) links one package per platform:
diff --git a/tools/homebrew-check.sh b/tools/homebrew-check.sh
index fc3fbd7..64215bb 100755
--- a/tools/homebrew-check.sh
+++ b/tools/homebrew-check.sh
@@ -40,6 +40,9 @@ step() { printf '\n== %s\n' "$*"; }
[ "$#" -eq 2 ] || { echo "usage: tools/homebrew-check.sh VERSION PKG_DIR" >&2; exit 2; }
version=$1
+# Homebrew takes asz@VERSION as a version of asz only when VERSION is digits
+# and dots. With any other VERSION it links the versioned formula over asz.
+printf '%s' "$version" | grep -Eq '^[0-9]+(\.[0-9]+)*$' || fail "$version is not digits and dots, which a Homebrew versioned formula needs"
[ -d "$2" ] || fail "$2 is not a directory"
pkg_dir=$(cd "$2" && pwd)
tree=$(cd "$(dirname "$0")/.." && pwd)
@@ -48,11 +51,13 @@ command -v perl >/dev/null 2>&1 || fail "perl is not on PATH"
export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ANALYTICS=1 HOMEBREW_NO_INSTALL_CLEANUP=1 HOMEBREW_NO_ENV_HINTS=1
tap=aszcheck/formulae
-formulae="asz asz-claude-code"
+formulae="asz asz-claude-code asz@$version asz-claude-code@$version"
work=$(mktemp -d)
installed=""
cleanup() {
- for f in $installed; do brew uninstall --formula "$f" >/dev/null 2>&1 || true; done
+ # A formula whose install stopped part way is installed but not recorded,
+ # so every one this check could have installed is removed.
+ for f in $formulae $installed; do brew uninstall --formula "$tap/$f" >/dev/null 2>&1 || true; done
brew untap "$tap" >/dev/null 2>&1 || true
rm -rf "$work"
}
@@ -98,6 +103,17 @@ brew info --formula "$tap/asz-claude-code" | grep -qF "claude plugin marketplace
[ ! -e "$prefix/bin/asz-claude-plugin" ] || [ -e "$(brew --prefix asz-claude-code)/bin/asz-claude-plugin" ] ||
fail "asz-claude-plugin does not come from asz-claude-code"
[ ! -e "$(brew --prefix asz)/bin/asz-claude-plugin" ] || fail "asz installs asz-claude-plugin too, which is the plugin's own formula"
+# A versioned formula is keg-only: its binary is in its own prefix, and the
+# one on PATH stays the current formula's.
+for f in asz asz-claude-code; do
+ b=asz
+ [ "$f" = asz ] || b=asz-claude-plugin
+ "$(brew --prefix "$f@$version")/bin/$b" version | grep -qF "$version" || fail "$f@$version has no $b that reports $version"
+ case "$(readlink "$prefix/bin/$b")" in
+ *"/Cellar/$f/"*) ;;
+ *) fail "$b on Homebrew's PATH is not the one $f installed: $(readlink "$prefix/bin/$b")" ;;
+ esac
+done
step "Done"
-echo "brew install asz and brew install asz-claude-code work for $version"
+echo "brew install asz, asz-claude-code, asz@$version and asz-claude-code@$version work"
diff --git a/tools/homebrew-formula.sh b/tools/homebrew-formula.sh
new file mode 100755
index 0000000..1a45538
--- /dev/null
+++ b/tools/homebrew-formula.sh
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Writes the Homebrew formulae of released versions into Formula/, the tap
+# this repository is.
+#
+# tools/homebrew-formula.sh [--from DIR] [--check] VERSION...
+#
+# For each VERSION it takes the six binary packages and their .sha512 from
+# DIR/VERSION, or else downloads them from downloads.apache.org, or from
+# archive.apache.org for a version the download site no longer holds.
+# tools/install-manifests.sh holds each package to its .sha512 and writes the
+# formulae.
+#
+# Every VERSION gets Formula/asz@VERSION.rb and Formula/asz-claude-code@VERSION.rb.
+# Formula/asz.rb and Formula/asz-claude-code.rb follow the newest version among
+# those given and the one Formula/asz.rb names already, so adding an older
+# version never moves them back. The script changes Formula/ only, and leaves
+# the commit and the pull request to the person who runs it.
+#
+# With --check, each version's formulae go through tools/homebrew-check.sh,
+# brew style, brew audit, brew install and brew test, before any of them is
+# written into Formula/. It needs Homebrew, and runs on macOS.
+
+set -euo pipefail
+
+me=homebrew-formula
+fail() { printf '%s: %s\n' "$me" "$*" >&2; exit 1; }
+say() { printf '%s: %s\n' "$me" "$*"; }
+
+from=""
+check=false
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ --from)
+ [ -n "${2:-}" ] || fail "--from needs a directory"
+ from=$(cd "$2" && pwd) || fail "$2 is not a directory"
+ shift 2 ;;
+ --check) check=true; shift ;;
+ --*) fail "unknown option $1" ;;
+ *) break ;;
+ esac
+done
+[ "$#" -ge 1 ] || { echo "usage: tools/homebrew-formula.sh [--from DIR] [--check] VERSION..." >&2; exit 2; }
+for v in "$@"; do
+ # Homebrew takes asz@VERSION as a version of asz only when VERSION is
+ # digits and dots, which every Apache release is.
+ printf '%s' "$v" | grep -Eq '^[0-9]+(\.[0-9]+)*$' || fail "$v is not a released version: digits and dots"
+done
+
+tree=$(cd "$(dirname "$0")/.." && pwd)
+formula=$tree/Formula
+work=$(mktemp -d)
+trap 'rm -rf "$work"' EXIT
+
+# The version the current formulae install, read from the release path in
+# their download URL.
+current=""
+if [ -f "$formula/asz.rb" ]; then
+ current=$(grep -Eo '/releases/download/v[0-9.]+/' "$formula/asz.rb" | head -1 | sed -E 's#/releases/download/v([0-9.]+)/#\1#')
+fi
+
+packages() {
+ local v=$1 dir=$2 base=apache-skywalking-ai-sessionizer-$1-bin
+ mkdir -p "$dir"
+ for p in darwin-arm64.tgz darwin-amd64.tgz linux-arm64.tgz linux-amd64.tgz windows-amd64.zip windows-arm64.zip; do
+ for f in "$base-$p" "$base-$p.sha512"; do
+ local site=https://downloads.apache.org/skywalking/ai-sessionizer/$v/$f
+ local archive=https://archive.apache.org/dist/skywalking/ai-sessionizer/$v/$f
+ local code
+ code=$(curl -sSL -o "$dir/$f" -w '%{http_code}' "$site") || fail "cannot reach downloads.apache.org for $f"
+ case "$code" in
+ 200) ;;
+ 404) curl -fsSL -o "$dir/$f" "$archive" || fail "neither downloads.apache.org nor archive.apache.org has $f. Is $v released?" ;;
+ *) fail "downloads.apache.org answered $code for $f" ;;
+ esac
+ done
+ done
+}
+
+newest=$current
+for v in "$@"; do
+ if [ -n "$from" ]; then
+ dir=$from/$v
+ [ -d "$dir" ] || fail "$dir does not exist"
+ else
+ say "downloading the packages of $v"
+ dir=$work/packages-$v
+ packages "$v" "$dir"
+ fi
+ bash "$tree/tools/install-manifests.sh" "$v" "$dir" "$work/manifests-$v" >/dev/null
+ if [ "$check" = true ]; then
+ say "checking the formulae of $v with Homebrew"
+ bash "$tree/tools/homebrew-check.sh" "$v" "$dir" || fail "the formulae of $v did not pass tools/homebrew-check.sh, so none of $v was written"
+ fi
+ mkdir -p "$formula"
+ cp "$work/manifests-$v/homebrew/asz@$v.rb" "$work/manifests-$v/homebrew/asz-claude-code@$v.rb" "$formula/"
+ say "wrote Formula/asz@$v.rb and Formula/asz-claude-code@$v.rb"
+ newest=$(printf '%s\n%s\n' "$newest" "$v" | sed '/^$/d' | sort -V | tail -1)
+done
+
+if [ "$newest" != "$current" ]; then
+ cp "$work/manifests-$newest/homebrew/asz.rb" "$work/manifests-$newest/homebrew/asz-claude-code.rb" "$formula/"
+ say "Formula/asz.rb and Formula/asz-claude-code.rb now install $newest${current:+, not $current}"
+else
+ say "Formula/asz.rb and Formula/asz-claude-code.rb stay at $current, the newest version"
+fi
diff --git a/tools/install-manifests.sh b/tools/install-manifests.sh
index bf37c18..ffd8dd8 100755
--- a/tools/install-manifests.sh
+++ b/tools/install-manifests.sh
@@ -32,6 +32,8 @@
#
# homebrew/asz.rb a formula that installs asz from the macOS and Linux packages
# homebrew/asz-claude-code.rb a formula that installs the Claude Code plugin's binary
+# homebrew/asz@VERSION.rb, asz-claude-code@VERSION.rb
+# the same, keg-only, kept for every release
# scoop/skywalking-ai-sessionizer.json a Scoop manifest for the Windows packages
# winget/manifests/a/Apache/SkyWalkingAISessionizer/VERSION/
# the three winget manifests
@@ -378,6 +380,30 @@ end
EOF
} | fill > "$out_dir/homebrew/asz-claude-code.rb"
+# A versioned formula of each, asz@VERSION and asz-claude-code@VERSION, which
+# Formula/ keeps for every release, so an exact older version stays
+# installable after the current formulae move on. It is keg-only, as
+# Homebrew wants for an alternate version, so it does not clash with the
+# current formula's binary. The class name is Homebrew's for the file name:
+# asz@0.4.0 is AszAT040.
+homebrew_class() {
+ printf '%s' "$1" | perl -pe '$_ = ucfirst; s/[-_.\s]([a-zA-Z0-9])/\U$1/g; tr/+/x/; s/(.)@(\d)/$1AT$2/'
+}
+# Homebrew takes asz@VERSION as a version of asz only when VERSION is digits
+# and dots, which every Apache release is. A candidate's version with a
+# suffix gets no versioned formula.
+homebrew_versioned=asz
+printf '%s' "$version" | grep -Eq '^[0-9]+(\.[0-9]+)*$' || homebrew_versioned=
+for f in ${homebrew_versioned:+asz asz-claude-code}; do
+ current=$(homebrew_class "$f")
+ versioned=$(homebrew_class "$f@$version")
+ CURRENT=$current VERSIONED=$versioned perl -pe \
+ 's/^class \Q$ENV{CURRENT}\E < Formula$/class $ENV{VERSIONED} < Formula/; s/^( license "Apache-2\.0"\n)/$1\n keg_only :versioned_formula\n/' \
+ "$out_dir/homebrew/$f.rb" > "$out_dir/homebrew/$f@$version.rb"
+ grep -q "^class $versioned < Formula$" "$out_dir/homebrew/$f@$version.rb" && grep -q '^ keg_only :versioned_formula$' "$out_dir/homebrew/$f@$version.rb" ||
+ fail "could not write $f@$version.rb from $f.rb"
+done
+
# --------------------------------------------------------------------- Scoop
# The notes name the plugin's tag as VERSION for the reader to fill in. Scoop
# replaces only $dir, $original_dir and $persist_dir in notes, and
@@ -699,6 +725,7 @@ if grep -rn '@[A-Z0-9_]*@' "$out_dir" >&2; then fail "a placeholder above was no
cat <<DONE
wrote the manifests for $version into $out_dir:
homebrew/asz.rb, asz-claude-code.rb darwin and linux, arm64 and amd64, sha256
+ homebrew/asz@$version.rb, asz-claude-code@$version.rb
scoop/skywalking-ai-sessionizer.json windows-amd64 and windows-arm64, sha512
$winget_dir/ schema $winget_schema, sha256
README.md