Skip to content

Release Build

Release Build #161

Workflow file for this run

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 }}"
}
}'