Skip to content

Build release artifacts #19

Build release artifacts

Build release artifacts #19

name: Build release artifacts
"on":
workflow_dispatch:
inputs:
tag:
description: "Release tag. Defaults to v<macOS app version>."
required: false
type: string
publish_release:
description: "Create or update the GitHub Release"
required: true
default: false
type: boolean
draft:
description: "Create the GitHub Release as a draft"
required: true
default: true
type: boolean
prerelease:
description: "Mark the GitHub Release as a prerelease"
required: true
default: false
type: boolean
permissions:
contents: write
concurrency:
group: build-release-artifacts-${{ github.ref }}
cancel-in-progress: false
jobs:
metadata:
name: Resolve release metadata
runs-on: ubuntu-24.04
timeout-minutes: 10
outputs:
version: ${{ steps.release.outputs.version }}
build: ${{ steps.release.outputs.build }}
tag: ${{ steps.release.outputs.tag }}
artifact_path: ${{ steps.release.outputs.artifact_path }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Resolve macOS app version
id: release
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
version="$(python3 - <<'PY'
import plistlib
from pathlib import Path
with Path("macOS/Sources/ClipDock/Resources/AppInfo.plist").open("rb") as handle:
info = plistlib.load(handle)
print(info.get("CFBundleShortVersionString", "0.1.0"))
PY
)"
build="$(python3 - <<'PY'
import plistlib
from pathlib import Path
with Path("macOS/Sources/ClipDock/Resources/AppInfo.plist").open("rb") as handle:
info = plistlib.load(handle)
print(info.get("CFBundleVersion", "1"))
PY
)"
tag="$INPUT_TAG"
if [[ ! "$version" =~ ^[0-9]+(\.[0-9]+){1,2}$ ]]; then
echo "version must be numeric, such as 0.1.7" >&2
exit 1
fi
if [[ ! "$build" =~ ^[0-9]+$ ]]; then
echo "build must be a positive integer" >&2
exit 1
fi
if [[ -z "$tag" ]]; then
tag="v$version"
fi
if [[ ! "$tag" =~ ^[A-Za-z0-9._/-]+$ ]]; then
echo "tag may only contain letters, numbers, dot, underscore, slash, or dash" >&2
exit 1
fi
artifact_path=".codex/artifacts/release/$version"
{
echo "version=$version"
echo "build=$build"
echo "tag=$tag"
echo "artifact_path=$artifact_path"
} >> "$GITHUB_OUTPUT"
build-macos-app:
name: Build macOS app DMGs
runs-on: macos-15
needs: metadata
timeout-minutes: 75
env:
APP_BUNDLE_NAME: ClipDock
APP_EXECUTABLE_NAME: ClipDock
BUNDLE_IDENTIFIER: com.apkdv.clipdock
APP_ARCHS: arm64 x86_64
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Print toolchain
working-directory: macOS
run: |
sw_vers
xcodebuild -version
swift --version
cargo --version
- name: Build release bundle
working-directory: macOS
env:
APP_VERSION: ${{ needs.metadata.outputs.version }}
APP_BUILD: ${{ needs.metadata.outputs.build }}
RELEASE_DIR: ${{ github.workspace }}/${{ needs.metadata.outputs.artifact_path }}/macos
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY || '-' }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
run: scripts/release-macos.sh
- name: Upload macOS workflow artifact
uses: actions/upload-artifact@v7
with:
name: ClipDock-${{ needs.metadata.outputs.version }}-macos
path: |
.codex/artifacts/release/${{ needs.metadata.outputs.version }}/macos/ClipDock-${{ needs.metadata.outputs.version }}-*.dmg
.codex/artifacts/release/${{ needs.metadata.outputs.version }}/macos/SHA256SUMS
.codex/artifacts/release/${{ needs.metadata.outputs.version }}/macos/ClipDock-release-manifest.txt
if-no-files-found: error
retention-days: 30
build-server:
name: Build server ${{ matrix.artifact_label }}
needs: metadata
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- artifact_label: linux-x86_64
runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- artifact_label: linux-arm64
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
- artifact_label: macos-x86_64
runner: macos-15-intel
target: x86_64-apple-darwin
- artifact_label: macos-arm64
runner: macos-15
target: aarch64-apple-darwin
- artifact_label: windows-x86_64
runner: windows-2025
target: x86_64-pc-windows-msvc
- artifact_label: windows-arm64
runner: windows-11-arm
target: aarch64-pc-windows-msvc
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Print Rust toolchain
shell: bash
run: |
set -euo pipefail
uname -a || true
rustc -vV
cargo --version
- name: Add Rust target
shell: bash
run: rustup target add "${{ matrix.target }}"
- name: Build and package server
shell: bash
run: |
set -euo pipefail
artifact_root="$PWD/${{ needs.metadata.outputs.artifact_path }}"
Server/scripts/package-server.sh \
--target "${{ matrix.target }}" \
--version "${{ needs.metadata.outputs.version }}" \
--output-dir "$artifact_root/server/${{ matrix.artifact_label }}"
- name: Upload server workflow artifact
uses: actions/upload-artifact@v7
with:
name: ClipDock-Server-${{ needs.metadata.outputs.version }}-${{ matrix.artifact_label }}
path: |
.codex/artifacts/release/${{ needs.metadata.outputs.version }}/server/${{ matrix.artifact_label }}/*
if-no-files-found: error
retention-days: 30
publish-release:
name: Publish GitHub Release
if: ${{ inputs.publish_release }}
needs:
- metadata
- build-macos-app
- build-server
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download release artifacts
uses: actions/download-artifact@v7
with:
path: release-artifacts
- name: Generate release notes from commits
env:
RELEASE_VERSION: ${{ needs.metadata.outputs.version }}
RELEASE_BUILD: ${{ needs.metadata.outputs.build }}
RELEASE_TAG: ${{ needs.metadata.outputs.tag }}
RELEASE_NOTES: release-notes.md
run: |
set -euo pipefail
target_commit="$GITHUB_SHA"
if git rev-parse -q --verify "refs/tags/$RELEASE_TAG^{commit}" >/dev/null; then
target_commit="$(git rev-list -n 1 "$RELEASE_TAG")"
previous_tag="$(git describe --tags --abbrev=0 "$RELEASE_TAG^" 2>/dev/null || true)"
else
previous_tag="$(git describe --tags --abbrev=0 "$target_commit" 2>/dev/null || true)"
fi
if [[ -n "$previous_tag" ]]; then
commit_range="$previous_tag..$target_commit"
else
commit_range="$target_commit"
fi
{
echo "## ClipDock $RELEASE_VERSION"
echo
echo "- Build: $RELEASE_BUILD"
echo "- Commit: $target_commit"
if [[ -n "$previous_tag" ]]; then
echo "- Changes since: $previous_tag"
fi
echo
echo "## Commits"
echo
} > "$RELEASE_NOTES"
commit_count="$(git rev-list --count "$commit_range" 2>/dev/null || true)"
if [[ "${commit_count:-0}" =~ ^[0-9]+$ && "$commit_count" -gt 0 ]]; then
git log --format="- %s (%h)" --reverse "$commit_range" >> "$RELEASE_NOTES"
else
git log -1 --format="- %s (%h)" "$target_commit" >> "$RELEASE_NOTES"
fi
cat "$RELEASE_NOTES"
- name: Publish GitHub Release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.metadata.outputs.tag }}
target_commitish: ${{ github.sha }}
name: ClipDock ${{ needs.metadata.outputs.version }}
body_path: release-notes.md
draft: ${{ inputs.draft }}
prerelease: ${{ inputs.prerelease }}
fail_on_unmatched_files: true
overwrite_files: true
files: |
release-artifacts/**/*.dmg
release-artifacts/**/*.tar.gz
release-artifacts/**/*.zip
release-artifacts/**/*.sha256
release-artifacts/**/*manifest.txt