Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/scripts/package_release_binary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail

RELEASE_TAG="${RELEASE_TAG:-${1:-}}"
REPO_ROOT="${REPO_ROOT:-$(pwd)}"
OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/dist}"
ASSET_NAME="${ASSET_NAME:-afm-api-macos-arm64.tar.gz}"
ALLOW_SOURCE_FALLBACK="${AFM_ALLOW_SOURCE_FALLBACK:-1}"

if [[ -z "$RELEASE_TAG" ]]; then
echo "ERROR: RELEASE_TAG is required (e.g. v1.2.3)"
exit 1
fi
if [[ ! "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*$ ]]; then
echo "ERROR: RELEASE_TAG must match v<major>.<minor>.<patch>[-suffix]"
exit 1
fi

VERSION="${RELEASE_TAG#v}"

mkdir -p "$OUTPUT_DIR"
rm -rf "$OUTPUT_DIR/stage"
mkdir -p "$OUTPUT_DIR/stage"

ASSET_MODE="binary"
if swift build --package-path "$REPO_ROOT" -c release --product afm-api-server; then
cp "$REPO_ROOT/bin/afm-api" "$OUTPUT_DIR/stage/afm-api"
cp "$REPO_ROOT/.build/release/afm-api-server" "$OUTPUT_DIR/stage/afm-api-server"
chmod +x "$OUTPUT_DIR/stage/afm-api" "$OUTPUT_DIR/stage/afm-api-server"
perl -pi -e "s/__AFM_API_VERSION__/${VERSION}/g" "$OUTPUT_DIR/stage/afm-api"
else
if [[ "$ALLOW_SOURCE_FALLBACK" != "1" ]]; then
echo "ERROR: release binary build failed and source fallback is disabled."
exit 1
fi
ASSET_MODE="source-fallback"
mkdir -p "$OUTPUT_DIR/stage/bin"
cp "$REPO_ROOT/bin/afm-api" "$OUTPUT_DIR/stage/bin/afm-api"
cp "$REPO_ROOT/Package.swift" "$OUTPUT_DIR/stage/Package.swift"
cp -R "$REPO_ROOT/Sources" "$OUTPUT_DIR/stage/Sources"
chmod +x "$OUTPUT_DIR/stage/bin/afm-api"
perl -pi -e "s/__AFM_API_VERSION__/${VERSION}/g" "$OUTPUT_DIR/stage/bin/afm-api"
fi

if [[ "$ASSET_MODE" == "binary" ]]; then
tar -czf "$OUTPUT_DIR/$ASSET_NAME" -C "$OUTPUT_DIR/stage" afm-api afm-api-server
else
tar -czf "$OUTPUT_DIR/$ASSET_NAME" -C "$OUTPUT_DIR/stage" bin Package.swift Sources
fi
shasum -a 256 "$OUTPUT_DIR/$ASSET_NAME" | awk '{print $1}' > "$OUTPUT_DIR/${ASSET_NAME}.sha256"

SHA="$(cat "$OUTPUT_DIR/${ASSET_NAME}.sha256")"

echo "Built asset: $OUTPUT_DIR/$ASSET_NAME"
echo "SHA256: $SHA"
echo "Mode: $ASSET_MODE"

if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
{
echo "asset_path=$OUTPUT_DIR/$ASSET_NAME"
echo "sha_path=$OUTPUT_DIR/${ASSET_NAME}.sha256"
echo "sha256=$SHA"
echo "asset_mode=$ASSET_MODE"
} >> "$GITHUB_OUTPUT"
fi
75 changes: 54 additions & 21 deletions .github/scripts/update_homebrew_tap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ set -euo pipefail

RELEASE_TAG="${RELEASE_TAG:-${1:-}}"
TAP_REPO_PATH="${TAP_REPO_PATH:-${2:-}}"
SOURCE_TARBALL="${SOURCE_TARBALL:-}"
RELEASE_ASSET_FILE="${RELEASE_ASSET_FILE:-}"
RELEASE_ASSET_NAME="${RELEASE_ASSET_NAME:-afm-api-macos-arm64.tar.gz}"
RELEASE_ASSET_URL="${RELEASE_ASSET_URL:-}"
REPO_SLUG="${REPO_SLUG:-tankibaj/apple-foundation-model-api}"
FORMULA_NAME="${FORMULA_NAME:-afm-api}"
UPDATE_VERSIONED_FORMULA="${UPDATE_VERSIONED_FORMULA:-1}"
TEMPLATE_FORMULA_PATH="${TEMPLATE_FORMULA_PATH:-}"
SYNC_TEMPLATE_ALWAYS="${SYNC_TEMPLATE_ALWAYS:-0}"

if [[ -z "${RELEASE_TAG}" || -z "${TAP_REPO_PATH}" ]]; then
echo "Usage: RELEASE_TAG=v1.0.2 TAP_REPO_PATH=/path/to/homebrew-tap $0"
exit 1
fi

if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "ERROR: RELEASE_TAG must match v<major>.<minor>.<patch>, got: ${RELEASE_TAG}"
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*$ ]]; then
echo "ERROR: RELEASE_TAG must match v<major>.<minor>.<patch>[-suffix], got: ${RELEASE_TAG}"
exit 1
fi

Expand All @@ -24,54 +29,82 @@ fi

VERSION="${RELEASE_TAG#v}"
MAJOR_MINOR="$(echo "${VERSION}" | cut -d. -f1,2)"
URL="https://github.com/${REPO_SLUG}/archive/refs/tags/${RELEASE_TAG}.tar.gz"
URL="${RELEASE_ASSET_URL:-https://github.com/${REPO_SLUG}/releases/download/${RELEASE_TAG}/${RELEASE_ASSET_NAME}}"
BASE_FORMULA_PATH="${TAP_REPO_PATH}/Formula/${FORMULA_NAME}.rb"
VERSIONED_FORMULA_PATH="${TAP_REPO_PATH}/Formula/${FORMULA_NAME}@${MAJOR_MINOR}.rb"

if [[ -n "${TEMPLATE_FORMULA_PATH}" && -f "${TEMPLATE_FORMULA_PATH}" ]]; then
if [[ "${SYNC_TEMPLATE_ALWAYS}" == "1" || ! -f "${BASE_FORMULA_PATH}" ]]; then
cp "${TEMPLATE_FORMULA_PATH}" "${BASE_FORMULA_PATH}"
fi
fi

if [[ ! -f "${BASE_FORMULA_PATH}" ]]; then
echo "ERROR: base formula not found: ${BASE_FORMULA_PATH}"
exit 1
fi

TMP_TARBALL=""
if [[ -n "${SOURCE_TARBALL}" ]]; then
if [[ ! -f "${SOURCE_TARBALL}" ]]; then
echo "ERROR: SOURCE_TARBALL not found: ${SOURCE_TARBALL}"
TMP_ASSET=""
if [[ -n "${RELEASE_ASSET_FILE}" ]]; then
if [[ ! -f "${RELEASE_ASSET_FILE}" ]]; then
echo "ERROR: RELEASE_ASSET_FILE not found: ${RELEASE_ASSET_FILE}"
exit 1
fi
TMP_TARBALL="${SOURCE_TARBALL}"
TMP_ASSET="${RELEASE_ASSET_FILE}"
else
TMP_TARBALL="$(mktemp -t afm-api-release-XXXXXX.tar.gz)"
curl -fsSL "${URL}" -o "${TMP_TARBALL}"
TMP_ASSET="$(mktemp -t afm-api-release-XXXXXX.tar.gz)"
curl -fsSL "${URL}" -o "${TMP_ASSET}"
fi

SHA256="$(shasum -a 256 "${TMP_TARBALL}" | awk '{print $1}')"
SHA256="$(shasum -a 256 "${TMP_ASSET}" | awk '{print $1}')"

camelize_formula_name() {
echo "$1" | awk -F'[-@.]' '{
out=""
for (i=1; i<=NF; i++) {
if ($i == "") continue
out = out toupper(substr($i,1,1)) substr($i,2)
}
print out
}'
}

FORMULA_CLASS_BASE="AfmApi"
FORMULA_CLASS_VERSIONED="AfmApiAT${MAJOR_MINOR//./}"
FORMULA_CLASS_BASE="$(camelize_formula_name "${FORMULA_NAME}")"
FORMULA_CLASS_VERSIONED="${FORMULA_CLASS_BASE}AT${MAJOR_MINOR//./}"

update_formula_file() {
local file_path="$1"
local class_name="$2"

ruby - "${file_path}" "${class_name}" "${URL}" "${SHA256}" <<'RUBY'
path, class_name, url, sha = ARGV
ruby - "${file_path}" "${class_name}" "${URL}" "${SHA256}" "${VERSION}" <<'RUBY'
path, class_name, url, sha, version = ARGV
content = File.read(path)
content.sub!(/^class\s+\S+\s+<\s+Formula$/, "class #{class_name} < Formula")
if class_name && !class_name.empty?
content.sub!(/^class\s+\S+\s+<\s+Formula$/, "class #{class_name} < Formula")
end
content.sub!(/^\s*url\s+".*"$/, " url \"#{url}\"")
content.sub!(/^\s*sha256\s+".*"$/, " sha256 \"#{sha}\"")
if content.match?(/^\s*version\s+"/)
content.sub!(/^\s*version\s+".*"$/, " version \"#{version}\"")
else
content.sub!(/^\s*url\s+".*"$/, " url \"#{url}\"\n version \"#{version}\"")
end
File.write(path, content)
RUBY
}

update_formula_file "${BASE_FORMULA_PATH}" "${FORMULA_CLASS_BASE}"

if [[ ! -f "${VERSIONED_FORMULA_PATH}" ]]; then
cp "${BASE_FORMULA_PATH}" "${VERSIONED_FORMULA_PATH}"
if [[ "${UPDATE_VERSIONED_FORMULA}" == "1" ]]; then
if [[ ! -f "${VERSIONED_FORMULA_PATH}" ]]; then
cp "${BASE_FORMULA_PATH}" "${VERSIONED_FORMULA_PATH}"
fi
update_formula_file "${VERSIONED_FORMULA_PATH}" "${FORMULA_CLASS_VERSIONED}"
fi
update_formula_file "${VERSIONED_FORMULA_PATH}" "${FORMULA_CLASS_VERSIONED}"

echo "Updated: ${BASE_FORMULA_PATH}"
echo "Updated: ${VERSIONED_FORMULA_PATH}"
if [[ "${UPDATE_VERSIONED_FORMULA}" == "1" ]]; then
echo "Updated: ${VERSIONED_FORMULA_PATH}"
fi
echo "Release: ${RELEASE_TAG}"
echo "SHA256: ${SHA256}"
96 changes: 96 additions & 0 deletions .github/workflows/build-rc-pre-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Build RC Pre-release

on:
push:
branches:
- "feature/**"
workflow_dispatch:
inputs:
release_tag:
description: "Optional RC tag override (e.g. v1.2.0-rc.feature-x)"
required: false
type: string

permissions:
actions: write
contents: write

jobs:
build-and-publish-rc:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Resolve RC tag
id: vars
shell: bash
run: |
if [[ -n "${{ inputs.release_tag }}" ]]; then
TAG="${{ inputs.release_tag }}"
else
BRANCH_SLUG="$(echo "${GITHUB_REF_NAME}" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
SHORT_SHA="$(git rev-parse --short "${GITHUB_SHA}")"
TAG="v0.0.0-rc.${BRANCH_SLUG}.r${GITHUB_RUN_NUMBER}.${SHORT_SHA}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

- name: Build release asset
id: package
env:
RELEASE_TAG: ${{ steps.vars.outputs.tag }}
ASSET_NAME: afm-api-macos-arm64.tar.gz
run: |
./.github/scripts/package_release_binary.sh

- name: Publish RC pre-release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.vars.outputs.tag }}"
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" \
--target "$GITHUB_SHA" \
--title "$TAG" \
--notes "Automated pre-release for branch ${GITHUB_REF_NAME}." \
--prerelease
fi

gh release edit "$TAG" --draft=false --prerelease

gh release upload "$TAG" \
"${{ steps.package.outputs.asset_path }}" \
"${{ steps.package.outputs.sha_path }}" \
--clobber

- name: Checkout Homebrew prerelease tap
uses: actions/checkout@v4
with:
repository: tankibaj/homebrew-tap-prerelease
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-tap-prerelease

- name: Update prerelease tap formula
env:
RELEASE_TAG: ${{ steps.vars.outputs.tag }}
TAP_REPO_PATH: homebrew-tap-prerelease
REPO_SLUG: tankibaj/apple-foundation-model-api
FORMULA_NAME: afm-api-rc
RELEASE_ASSET_NAME: afm-api-macos-arm64.tar.gz
UPDATE_VERSIONED_FORMULA: "0"
TEMPLATE_FORMULA_PATH: Formula/afm-api-rc.rb
SYNC_TEMPLATE_ALWAYS: "1"
run: |
./.github/scripts/update_homebrew_tap.sh

- name: Commit and push prerelease tap updates
working-directory: homebrew-tap-prerelease
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Formula/afm-api-rc.rb
git diff --cached --quiet && echo "No prerelease formula changes" && exit 0
git commit -m "feat: afm-api-rc ${{ steps.vars.outputs.tag }}"
git push origin HEAD
65 changes: 65 additions & 0 deletions .github/workflows/build-release-binary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Build Release Binary

on:
release:
types: [published]
workflow_dispatch:
inputs:
release_tag:
description: "Release tag (e.g. v1.2.3)"
required: true
type: string

permissions:
actions: write
contents: write

jobs:
build-and-publish:
if: github.event_name != 'release' || github.event.release.prerelease == false
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Resolve release tag
id: vars
run: |
if [ "${{ github.event_name }}" = "release" ]; then
TAG="${GITHUB_REF_NAME}"
else
TAG="${{ inputs.release_tag }}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

- name: Checkout release tag
run: |
git fetch --tags --force
git checkout "${{ steps.vars.outputs.tag }}"

- name: Build release asset
id: package
env:
RELEASE_TAG: ${{ steps.vars.outputs.tag }}
ASSET_NAME: afm-api-macos-arm64.tar.gz
run: |
./.github/scripts/package_release_binary.sh

- name: Upload release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ steps.vars.outputs.tag }}" \
"${{ steps.package.outputs.asset_path }}" \
"${{ steps.package.outputs.sha_path }}" \
--clobber

- name: Trigger Homebrew tap update
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run "Update Homebrew Tap" \
--ref main \
-f release_tag="${{ steps.vars.outputs.tag }}"
9 changes: 0 additions & 9 deletions .github/workflows/release-on-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,6 @@ jobs:
--title "$tag" \
--generate-notes

- name: Trigger Homebrew tap update
if: steps.version.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run "Update Homebrew Tap" \
--ref main \
-f release_tag="${{ steps.version.outputs.next_tag }}"

- name: Summary
run: |
echo "should_release=${{ steps.version.outputs.should_release }}"
Expand Down
Loading