Release Build #161
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 Build | |
| permissions: | |
| contents: write | |
| id-token: write | |
| attestations: write | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Dry Run (uncheck to create a release)' | |
| required: false | |
| default: true | |
| type: boolean | |
| release_production: | |
| description: 'Release to latest (production) channel. For stable production releases only!' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| build: | |
| name: Build for ${{ matrix.target }} | |
| runs-on: ${{ matrix.os }} | |
| outputs: | |
| version: ${{ steps.get-version.outputs.version }} | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| artifact_name: git-ai-linux-x64 | |
| use_docker: true | |
| docker_image: ubuntu:20.04 | |
| - os: ubuntu-22.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| artifact_name: git-ai-linux-arm64 | |
| use_docker: true | |
| docker_image: ubuntu:20.04 | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| artifact_name: git-ai-windows-x64 | |
| use_docker: false | |
| - os: windows-11-arm | |
| target: aarch64-pc-windows-msvc | |
| artifact_name: git-ai-windows-arm64 | |
| use_docker: false | |
| - os: macos-latest | |
| target: aarch64-apple-darwin | |
| artifact_name: git-ai-macos-arm64 | |
| use_docker: false | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Get version from Cargo.toml | |
| id: get-version | |
| shell: bash | |
| run: | | |
| VERSION=$(grep '^version = ' Cargo.toml | cut -d'"' -f2) | |
| echo "version=v$VERSION" >> $GITHUB_OUTPUT | |
| - name: Build in Docker (Linux) | |
| if: matrix.use_docker == true | |
| run: | | |
| docker run --rm \ | |
| -v ${{ github.workspace }}:/workspace \ | |
| -w /workspace \ | |
| -e DEBIAN_FRONTEND=noninteractive \ | |
| -e SENTRY_OSS="${{ secrets.SENTRY_OSS }}" \ | |
| -e POSTHOG_API_KEY="${{ secrets.POSTHOG_API_KEY }}" \ | |
| -e OSS_BUILD="1" \ | |
| ${{ matrix.docker_image }} \ | |
| bash -c " | |
| apt-get update && \ | |
| apt-get install -y curl build-essential pkg-config libssl-dev && \ | |
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --target ${{ matrix.target }} && \ | |
| . \$HOME/.cargo/env && \ | |
| cargo build --release --target ${{ matrix.target }} && \ | |
| strip target/${{ matrix.target }}/release/git-ai | |
| " | |
| - name: Install Rust toolchain (non-Docker) | |
| if: matrix.use_docker == false | |
| uses: actions-rs/toolchain@v1 | |
| with: | |
| toolchain: stable | |
| target: ${{ matrix.target }} | |
| override: true | |
| - name: Cache dependencies (non-Docker) | |
| if: matrix.use_docker == false | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-${{ matrix.target }}- | |
| - name: Build release binary (non-Docker, non-Windows-ARM64) | |
| if: matrix.use_docker == false && matrix.os != 'windows-11-arm' | |
| run: | | |
| cargo build --release --target ${{ matrix.target }} | |
| env: | |
| CARGO_INCREMENTAL: 0 | |
| SENTRY_OSS: ${{ secrets.SENTRY_OSS }} | |
| POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| OSS_BUILD: "1" | |
| - name: Build release binary (Windows ARM64 with LLVM strip) | |
| if: matrix.os == 'windows-11-arm' | |
| run: | | |
| cargo build --release --target ${{ matrix.target }} | |
| env: | |
| CARGO_INCREMENTAL: 0 | |
| SENTRY_OSS: ${{ secrets.SENTRY_OSS }} | |
| POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| OSS_BUILD: "1" | |
| RUSTFLAGS: "-C strip=symbols" | |
| - name: Verify binary architecture (Linux) | |
| if: contains(matrix.os, 'ubuntu') | |
| run: | | |
| file target/${{ matrix.target }}/release/git-ai | |
| ldd target/${{ matrix.target }}/release/git-ai || true | |
| echo "Required GLIBC version:" | |
| objdump -T target/${{ matrix.target }}/release/git-ai | grep GLIBC | sed 's/.*GLIBC_/GLIBC_/' | sort -V | uniq | tail -1 | |
| - name: Verify binary architecture (Windows) | |
| if: contains(matrix.os, 'windows') | |
| shell: bash | |
| run: | | |
| file target/${{ matrix.target }}/release/git-ai.exe | |
| - name: Strip binary (Windows x64) | |
| if: matrix.os == 'windows-latest' | |
| run: | | |
| strip target/${{ matrix.target }}/release/git-ai.exe | |
| - name: Strip binary (macOS) | |
| if: matrix.os == 'macos-latest' | |
| run: | | |
| strip target/${{ matrix.target }}/release/git-ai | |
| - name: Create release directory | |
| run: | | |
| mkdir -p release | |
| - name: Copy binary to release directory | |
| if: contains(matrix.os, 'windows') | |
| run: | | |
| cp target/${{ matrix.target }}/release/git-ai.exe release/${{ matrix.artifact_name }}.exe | |
| - name: Copy binary to release directory (non-Windows) | |
| if: ${{ !contains(matrix.os, 'windows') }} | |
| run: | | |
| cp target/${{ matrix.target }}/release/git-ai release/${{ matrix.artifact_name }} | |
| - name: Upload artifact (Windows) | |
| if: contains(matrix.os, 'windows') | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: release/${{ matrix.artifact_name }}.exe | |
| retention-days: 30 | |
| - name: Upload artifact (non-Windows) | |
| if: ${{ !contains(matrix.os, 'windows') }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: release/${{ matrix.artifact_name }} | |
| retention-days: 30 | |
| build-macos-intel: | |
| name: Build for macOS Intel | |
| runs-on: macos-15-intel | |
| outputs: | |
| version: ${{ steps.get-version.outputs.version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Get version from Cargo.toml | |
| id: get-version | |
| shell: bash | |
| run: | | |
| VERSION=$(grep '^version = ' Cargo.toml | cut -d'"' -f2) | |
| echo "version=v$VERSION" >> $GITHUB_OUTPUT | |
| - name: Install Rust toolchain | |
| uses: actions-rs/toolchain@v1 | |
| with: | |
| toolchain: stable | |
| target: x86_64-apple-darwin | |
| override: true | |
| - name: Cache dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-x86_64-apple-darwin-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-x86_64-apple-darwin- | |
| - name: Build release binary | |
| run: | | |
| cargo build --release --target x86_64-apple-darwin | |
| env: | |
| CARGO_INCREMENTAL: 0 | |
| SENTRY_OSS: ${{ secrets.SENTRY_OSS }} | |
| POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| OSS_BUILD: "1" | |
| - name: Verify binary architecture | |
| run: | | |
| file target/x86_64-apple-darwin/release/git-ai | |
| lipo -info target/x86_64-apple-darwin/release/git-ai | |
| - name: Strip binary | |
| run: | | |
| strip target/x86_64-apple-darwin/release/git-ai | |
| - name: Create release directory | |
| run: | | |
| mkdir -p release | |
| - name: Copy binary to release directory | |
| run: | | |
| cp target/x86_64-apple-darwin/release/git-ai release/git-ai-macos-x64 | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: git-ai-macos-x64 | |
| path: release/git-ai-macos-x64 | |
| retention-days: 30 | |
| create-release: | |
| name: Create Release | |
| needs: [build, build-macos-intel] | |
| runs-on: ubuntu-latest | |
| if: success() && inputs.dry_run != true | |
| steps: | |
| - name: Determine release metadata | |
| id: release-meta | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BASE_VERSION="${{ needs.build.outputs.version }}" | |
| SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) | |
| if [[ "${{ inputs.release_production }}" == 'true' ]]; then | |
| RELEASE_TAG="$BASE_VERSION" | |
| CHANNEL="latest" | |
| PRERELEASE="false" | |
| MAKE_LATEST="true" | |
| else | |
| RELEASE_TAG="${BASE_VERSION}-next-${SHORT_SHA}" | |
| CHANNEL="next" | |
| PRERELEASE="true" | |
| MAKE_LATEST="false" | |
| fi | |
| echo "tag_name=$RELEASE_TAG" >> "$GITHUB_OUTPUT" | |
| echo "release_name=Release $RELEASE_TAG" >> "$GITHUB_OUTPUT" | |
| echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT" | |
| echo "make_latest=$MAKE_LATEST" >> "$GITHUB_OUTPUT" | |
| echo "channel_label=$CHANNEL" >> "$GITHUB_OUTPUT" | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Create release directory | |
| run: mkdir -p release | |
| - name: Move artifacts to release directory | |
| run: | | |
| find artifacts -name "git-ai-*" -exec cp {} release/ \; | |
| - name: List available artifacts | |
| run: | | |
| echo "Available artifacts:" | |
| ls -la release/ || echo "No artifacts found" | |
| - name: Create checksums | |
| run: | | |
| cd release | |
| if ls git-ai-* 1> /dev/null 2>&1; then | |
| sha256sum git-ai-* > SHA256SUMS | |
| else | |
| echo "No binaries found to create checksums for" | |
| touch SHA256SUMS | |
| fi | |
| - name: Checkout code for install script | |
| uses: actions/checkout@v4 | |
| with: | |
| path: repo | |
| - name: Generate version-pinned install script | |
| run: | | |
| VERSION="${{ steps.release-meta.outputs.tag_name }}" | |
| REPO="${{ github.repository }}" | |
| # Convert checksums to pipe-separated format for embedding | |
| CHECKSUMS=$(tr '\n' '|' < release/SHA256SUMS | sed 's/|$//') | |
| if [ -z "$CHECKSUMS" ]; then | |
| CHECKSUMS="__CHECKSUMS_PLACEHOLDER__" | |
| fi | |
| # Replace variable assignment lines to embed release-specific values | |
| # Line 15: REPO, Line 22: PINNED_VERSION, Line 27: EMBEDDED_CHECKSUMS | |
| awk -v repo="$REPO" -v version="$VERSION" -v checksums="$CHECKSUMS" ' | |
| NR==15 { sub(/__REPO_PLACEHOLDER__/, repo) } | |
| NR==22 { sub(/__VERSION_PLACEHOLDER__/, version) } | |
| NR==27 { sub(/__CHECKSUMS_PLACEHOLDER__/, checksums) } | |
| { print } | |
| ' repo/install.sh > release/install.sh | |
| chmod +x release/install.sh | |
| echo "Generated version-pinned install.sh for $VERSION from $REPO" | |
| echo "Embedded checksums: $CHECKSUMS" | |
| - name: Generate version-pinned install.ps1 script | |
| run: | | |
| VERSION="${{ steps.release-meta.outputs.tag_name }}" | |
| REPO="${{ github.repository }}" | |
| # Reuse checksums from earlier step | |
| CHECKSUMS=$(tr '\n' '|' < release/SHA256SUMS | sed 's/|$//') | |
| if [ -z "$CHECKSUMS" ]; then | |
| CHECKSUMS="__CHECKSUMS_PLACEHOLDER__" | |
| fi | |
| # Replace variable assignment lines to embed release-specific values | |
| # Line 90: REPO, Line 97: PINNED_VERSION, Line 102: EMBEDDED_CHECKSUMS | |
| awk -v repo="$REPO" -v version="$VERSION" -v checksums="$CHECKSUMS" ' | |
| NR==90 { sub(/__REPO_PLACEHOLDER__/, repo) } | |
| NR==97 { sub(/__VERSION_PLACEHOLDER__/, version) } | |
| NR==102 { sub(/__CHECKSUMS_PLACEHOLDER__/, checksums) } | |
| { print } | |
| ' repo/install.ps1 > release/install.ps1 | |
| echo "Generated version-pinned install.ps1 for $VERSION from $REPO" | |
| - name: Add install scripts to SHA256SUMS | |
| run: | | |
| cd release | |
| sha256sum install.sh install.ps1 >> SHA256SUMS | |
| - name: Generate attestations for release artifacts | |
| uses: actions/attest-build-provenance@v2 | |
| with: | |
| subject-path: | | |
| release/git-ai-* | |
| release/install.sh | |
| release/install.ps1 | |
| release/SHA256SUMS | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.release-meta.outputs.tag_name }} | |
| name: ${{ steps.release-meta.outputs.release_name }} | |
| generate_release_notes: true | |
| body: | | |
| ## git-ai ${{ steps.release-meta.outputs.tag_name }} | |
| ### Release Channel | |
| `${{ steps.release-meta.outputs.channel_label }}` | |
| ${{ steps.release-meta.outputs.prerelease != 'true' && '---' || '' }} | |
| ${{ steps.release-meta.outputs.prerelease != 'true' && '> **Detailed AI-generated changelog is being generated and will appear here shortly...**' || '' }} | |
| ${{ steps.release-meta.outputs.prerelease != 'true' && '---' || '' }} | |
| ### Installation | |
| Install this specific version with checksum verification: | |
| **macOS/Linux:** | |
| ```bash | |
| curl -fsSL https://github.com/${{ github.repository }}/releases/download/${{ steps.release-meta.outputs.tag_name }}/install.sh | bash | |
| ``` | |
| **Windows (PowerShell):** | |
| ```powershell | |
| irm https://github.com/${{ github.repository }}/releases/download/${{ steps.release-meta.outputs.tag_name }}/install.ps1 | iex | |
| ``` | |
| ### Downloads | |
| - **Linux (x64)**: `git-ai-linux-x64` | |
| - **Linux (ARM64)**: `git-ai-linux-arm64` | |
| - **Windows (x64)**: `git-ai-windows-x64.exe` | |
| - **Windows (ARM64)**: `git-ai-windows-arm64.exe` | |
| - **macOS (Intel)**: `git-ai-macos-x64` | |
| - **macOS (Apple Silicon)**: `git-ai-macos-arm64` | |
| ### SHA256 Checksums | |
| ``` | |
| $(cat release/SHA256SUMS) | |
| ``` | |
| ### Attestation Verification | |
| Verify build provenance with the GitHub CLI: | |
| ```bash | |
| gh attestation verify git-ai-<platform> --repo ${{ github.repository }} | |
| ``` | |
| files: | | |
| release/git-ai-* | |
| release/SHA256SUMS | |
| release/install.sh | |
| release/install.ps1 | |
| draft: false | |
| prerelease: ${{ steps.release-meta.outputs.prerelease == 'true' }} | |
| make_latest: ${{ steps.release-meta.outputs.make_latest }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Checkout code for version bump | |
| if: inputs.release_production == true | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - name: Install Rust toolchain | |
| if: inputs.release_production == true | |
| uses: actions-rs/toolchain@v1 | |
| with: | |
| toolchain: stable | |
| override: true | |
| - name: Bump version in Cargo.toml | |
| if: inputs.release_production == true | |
| id: bump-version | |
| run: | | |
| # Extract current version | |
| CURRENT_VERSION=$(grep '^version = ' Cargo.toml | cut -d'"' -f2) | |
| echo "Current version: $CURRENT_VERSION" | |
| # Parse version components | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" | |
| # Bump patch version | |
| NEW_PATCH=$((PATCH + 1)) | |
| NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" | |
| echo "New version: $NEW_VERSION" | |
| # Update Cargo.toml | |
| sed -i.bak "s/^version = \"${CURRENT_VERSION}\"/version = \"${NEW_VERSION}\"/" Cargo.toml | |
| rm Cargo.toml.bak | |
| # Update Cargo.lock | |
| cargo check | |
| echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT | |
| - name: Commit and push version bump | |
| if: inputs.release_production == true | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Cargo.toml Cargo.lock | |
| git commit -m "chore: bump version to ${{ steps.bump-version.outputs.new_version }} [skip ci]" | |
| git push origin main | |
| - name: Trigger release channel update | |
| run: | | |
| curl -X POST \ | |
| -H "Authorization: token ${{ secrets.RELEASE_CHANNEL_TOKEN }}" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| https://api.github.com/repos/git-ai-project/monorepo/actions/workflows/update-release-version.yaml/dispatches \ | |
| -d '{ | |
| "ref": "dev", | |
| "inputs": { | |
| "channel": "${{ steps.release-meta.outputs.channel_label }}", | |
| "tag": "${{ steps.release-meta.outputs.tag_name }}" | |
| } | |
| }' |