Release #104
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| # Auto-release after CI passes on main. | |
| # | |
| # This used to trigger directly on push, which meant the release built and published | |
| # concurrently with CI Pipeline and did not depend on it — a red pipeline still shipped a | |
| # binary. workflow_run defers the release until CI Pipeline has concluded, and the job | |
| # guard below requires that conclusion to be success. | |
| # | |
| # To be clear about what this does not fix: b674 shipped a Gemma 4 regression while CI | |
| # was green on that commit, because no job loads gemma-4-e2b-it-4bit. Gating stops a | |
| # release when CI fails; it cannot stop one when CI passes on an untested path. | |
| # | |
| # The opencode job is no longer continue-on-error, so a failure there now flips CI | |
| # Pipeline to failure and stops the release. That coupling is intentional: the job tests | |
| # the request shape opencode sends rather than shelling out to its npm CLI, so a failure | |
| # means SwiftLM misbehaved. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| create_release: | |
| description: 'Create new release' | |
| required: true | |
| type: boolean | |
| workflow_run: | |
| workflows: ["CI Pipeline"] | |
| types: [completed] | |
| branches: | |
| - main | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-and-release: | |
| runs-on: macos-15 | |
| # A manual dispatch always runs; an automatic one only after CI succeeded. | |
| if: >- | |
| github.event_name == 'workflow_dispatch' | |
| || github.event.workflow_run.conclusion == 'success' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # Full history for build number | |
| submodules: recursive | |
| # Build exactly the commit CI validated. workflow_run would otherwise check out | |
| # the branch tip, which may already have moved on to an untested commit. | |
| ref: ${{ github.event.workflow_run.head_sha || github.ref }} | |
| - name: Install Metal Toolchain | |
| run: xcodebuild -downloadComponent MetalToolchain || true | |
| # workflow_run carries no path filter, so the old `paths:` list is enforced here. | |
| # Without this a docs-only commit would publish a release, because CI runs on every | |
| # push to main. Submodule pointer moves count as source: the mlx-swift-lm bump is | |
| # how the Gemma 4 fix reached users. | |
| - name: Check for source changes since the last release | |
| id: changes | |
| run: | | |
| LAST_TAG="$(git tag --list 'b[0-9]*' --sort=-creatordate | head -1)" | |
| if [ -z "$LAST_TAG" ]; then | |
| echo "No previous release tag; treating as source change." | |
| echo "source=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| CHANGED="$(git diff --name-only "$LAST_TAG"..HEAD || true)" | |
| echo "Changed since $LAST_TAG:"; echo "$CHANGED" | head -40 | |
| if echo "$CHANGED" | grep -qE '\.(swift|c|cpp|h|hpp|m|mm|metal)$|^Package\.(swift|resolved)$|^\.github/workflows/release\.yml$|^mlx-swift(-lm)?$'; then | |
| echo "source=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No source changes since $LAST_TAG — build will run, publish will be skipped." | |
| echo "source=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Determine tag name | |
| id: tag | |
| run: | | |
| BUILD_NUMBER="$(git rev-list --count HEAD)" | |
| SHORT_HASH="$(git rev-parse --short=7 HEAD)" | |
| echo "name=b${BUILD_NUMBER}" >> $GITHUB_OUTPUT | |
| echo "full=b${BUILD_NUMBER}-${SHORT_HASH}" >> $GITHUB_OUTPUT | |
| echo "Build: b${BUILD_NUMBER} (${SHORT_HASH})" | |
| - name: Generate changelog | |
| id: changelog | |
| run: | | |
| # Find the previous release tag | |
| PREV_TAG=$(git tag --sort=-creatordate | head -1 2>/dev/null || echo "") | |
| if [ -z "$PREV_TAG" ]; then | |
| # First release — all commits | |
| CHANGELOG=$(git log --pretty=format:"- %s (%h)" --no-merges -20) | |
| else | |
| CHANGELOG=$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s (%h)" --no-merges) | |
| fi | |
| # Write to file (multi-line safe) | |
| echo "$CHANGELOG" > /tmp/changelog.txt | |
| echo "Generated changelog with $(echo "$CHANGELOG" | wc -l | tr -d ' ') entries" | |
| - name: Cache Swift packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: .build | |
| key: ${{ runner.os }}-spm-release-${{ hashFiles('Package.resolved') }} | |
| restore-keys: | | |
| ${{ runner.os }}-spm-release- | |
| - name: Build (Release) | |
| run: | | |
| swift package resolve | |
| swift build -c release | |
| - name: Install MLX Metal library | |
| run: | | |
| python3 -m venv /tmp/mlx_venv | |
| /tmp/mlx_venv/bin/pip install --quiet mlx | |
| MLX_LIB=$(find /tmp/mlx_venv -name "mlx.metallib" | head -n 1) | |
| cp "$MLX_LIB" .build/release/mlx.metallib | |
| - name: Verify binary | |
| run: | | |
| ls -lh .build/release/SwiftLM | |
| file .build/release/SwiftLM | |
| .build/release/SwiftLM --help || true | |
| - name: Package binary | |
| run: | | |
| mkdir -p release | |
| cp .build/release/SwiftLM release/ | |
| cp .build/release/mlx.metallib release/ | |
| cp LICENSE README.md release/ | |
| cd release | |
| # Verify the tarball will be self-contained before archiving | |
| ls -lh | |
| tar -czvf ../SwiftLM-${{ steps.tag.outputs.name }}-macos-arm64.tar.gz . | |
| - name: Build macOS DMG Wrapper | |
| run: | | |
| # Previously hardcoded 1.0/1 inside generate_xcodeproj.py, so every | |
| # release ever built reported the same app version regardless of | |
| # which commit produced it. SWIFTBUDDY_BUILD_NUMBER reuses the same | |
| # `git rev-list --count HEAD` counter this workflow's bNNN release | |
| # tags already use (step "Determine tag name" above), so the app's | |
| # build number and its release tag always agree. Marketing version | |
| # is date-based (always distinct, no manual "when do we bump this" | |
| # decision needed for a pipeline that releases roughly daily). | |
| export SWIFTBUDDY_MARKETING_VERSION="$(date -u +%Y.%m.%d)" | |
| export SWIFTBUDDY_BUILD_NUMBER="$(git rev-list --count HEAD)" | |
| echo "App version: ${SWIFTBUDDY_MARKETING_VERSION} (${SWIFTBUDDY_BUILD_NUMBER})" | |
| cd SwiftBuddy && python3 generate_xcodeproj.py | |
| cd .. | |
| xcodebuild clean build \ | |
| -project SwiftBuddy/SwiftBuddy.xcodeproj \ | |
| -scheme SwiftBuddy \ | |
| -destination "generic/platform=macOS" \ | |
| -configuration Release \ | |
| ARCHS=arm64 ONLY_ACTIVE_ARCH=NO \ | |
| CODE_SIGN_IDENTITY="" \ | |
| CODE_SIGNING_REQUIRED=NO \ | |
| CODE_SIGN_ENTITLEMENTS="" \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| TARGET_BUILD_DIR="$RUNNER_TEMP/build" \ | |
| BUILT_PRODUCTS_DIR="$RUNNER_TEMP/build" | |
| brew install create-dmg | |
| chmod +x scripts/build_dmg.sh | |
| ./scripts/build_dmg.sh "$RUNNER_TEMP/build/SwiftBuddy.app" | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: SwiftLM-${{ steps.tag.outputs.name }}-macos-arm64 | |
| path: SwiftLM-${{ steps.tag.outputs.name }}-macos-arm64.tar.gz | |
| retention-days: 90 | |
| - name: Prepare release notes | |
| id: notes | |
| run: | | |
| CHANGELOG=$(cat /tmp/changelog.txt) | |
| cat > /tmp/release_notes.md << 'RELEASE_EOF' | |
| ## SwiftLM ${{ steps.tag.outputs.full }} | |
| <details open> | |
| ${{ github.event.head_commit.message }} | |
| </details> | |
| ### Changelog | |
| RELEASE_EOF | |
| cat /tmp/changelog.txt >> /tmp/release_notes.md | |
| cat >> /tmp/release_notes.md << 'RELEASE_EOF' | |
| ### Download | |
| - **CLI Server**: [macOS Apple Silicon (arm64)](https://github.com/SharpAI/SwiftLM/releases/download/${{ steps.tag.outputs.name }}/SwiftLM-${{ steps.tag.outputs.name }}-macos-arm64.tar.gz) | |
| - **GUI Desktop App**: Download the attached DMG file below! | |
| ### Quick Start | |
| **For GUI Users (SwiftBuddy)**: | |
| 1. Download the attached DMG and open it. | |
| 2. Drag `SwiftBuddy.app` into your Applications folder natively or run directly. | |
| 3. When launched, click "Model Options" to select or download an MLX local model to chat with. | |
| **For CLI Users (SwiftLM)**: | |
| Please refer to the [Getting Started](https://github.com/SharpAI/SwiftLM#getting-started) section in the README. | |
| > **Note:** `mlx.metallib` is bundled in the tar archive. Keep it in the same directory as the `SwiftLM` binary — Metal GPU compute will fail if it is missing. | |
| RELEASE_EOF | |
| - name: Create release | |
| if: >- | |
| ${{ (github.event_name == 'workflow_run' && steps.changes.outputs.source == 'true') | |
| || github.event.inputs.create_release == 'true' }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.name }} | |
| name: "SwiftLM ${{ steps.tag.outputs.name }}" | |
| body_path: /tmp/release_notes.md | |
| files: | | |
| SwiftLM-${{ steps.tag.outputs.name }}-macos-arm64.tar.gz | |
| output/*.dmg | |
| draft: false | |
| prerelease: false |