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