483 lines
16 KiB
YAML
483 lines
16 KiB
YAML
name: Build Release Artifacts
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
tag:
|
|
required: true
|
|
type: string
|
|
version:
|
|
required: true
|
|
type: string
|
|
deb_revision:
|
|
required: false
|
|
type: string
|
|
default: "1"
|
|
release_id:
|
|
required: false
|
|
type: string
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: "Release tag to build from (e.g. v0.2.0)"
|
|
required: true
|
|
type: string
|
|
version:
|
|
description: "Optional version override (e.g. 0.2.0). If omitted, derived from tag."
|
|
required: false
|
|
type: string
|
|
deb_revision:
|
|
description: "Debian package revision suffix appended to the upstream version (e.g. 1 for 1.2.3-1)."
|
|
required: true
|
|
default: "1"
|
|
type: string
|
|
release_id:
|
|
description: "Optional release id for manual backfills"
|
|
required: false
|
|
type: string
|
|
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
|
|
concurrency:
|
|
group: build-release-artifacts-${{ inputs.tag || github.event.inputs.tag || github.run_id }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
WIXTOOLSET_VERSION: 3.14.1.20250415
|
|
CARGO_WIX_VERSION: 0.3.9
|
|
APPIMAGETOOL_VERSION: 1.9.1
|
|
APPIMAGETOOL_X86_64_SHA256: ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0
|
|
|
|
jobs:
|
|
prepare:
|
|
name: Normalize release metadata
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
outputs:
|
|
tag: ${{ steps.norm.outputs.tag }}
|
|
version: ${{ steps.norm.outputs.version }}
|
|
deb_revision: ${{ steps.norm.outputs.deb_revision }}
|
|
release_id: ${{ steps.norm.outputs.release_id }}
|
|
steps:
|
|
- name: Normalize tag and version
|
|
id: norm
|
|
env:
|
|
INPUT_TAG: ${{ inputs.tag }}
|
|
DISPATCH_TAG: ${{ github.event.inputs.tag }}
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
DISPATCH_VERSION: ${{ github.event.inputs.version }}
|
|
INPUT_DEB_REVISION: ${{ inputs.deb_revision }}
|
|
DISPATCH_DEB_REVISION: ${{ github.event.inputs.deb_revision }}
|
|
INPUT_RELEASE_ID: ${{ inputs.release_id }}
|
|
DISPATCH_RELEASE_ID: ${{ github.event.inputs.release_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
tag="${INPUT_TAG:-${DISPATCH_TAG:-}}"
|
|
version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}"
|
|
deb_revision="${INPUT_DEB_REVISION:-${DISPATCH_DEB_REVISION:-1}}"
|
|
release_id="${INPUT_RELEASE_ID:-${DISPATCH_RELEASE_ID:-}}"
|
|
|
|
tag="$(echo "$tag" | tr -d '[:space:]')"
|
|
version="$(echo "$version" | tr -d '[:space:]')"
|
|
deb_revision="$(echo "$deb_revision" | tr -d '[:space:]')"
|
|
release_id="$(echo "$release_id" | tr -d '[:space:]')"
|
|
|
|
if [ -z "$tag" ]; then
|
|
echo "::error title=Missing tag::Tag is required."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$tag" != v* ]]; then
|
|
tag="v${tag}"
|
|
fi
|
|
|
|
if [ -z "$version" ]; then
|
|
version="${tag#v}"
|
|
fi
|
|
|
|
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then
|
|
echo "::error title=Invalid version::Expected semver like 1.2.3 or 1.2.3-rc.1."
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$tag" != "v${version}" ]; then
|
|
echo "::error title=Tag/version mismatch::Tag '$tag' does not match version '$version'."
|
|
exit 1
|
|
fi
|
|
|
|
if ! [[ "$deb_revision" =~ ^[1-9][0-9]*$ ]]; then
|
|
echo "::error title=Invalid Debian revision::Expected a positive integer like 1, 2, or 3."
|
|
exit 1
|
|
fi
|
|
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "deb_revision=$deb_revision" >> "$GITHUB_OUTPUT"
|
|
echo "release_id=$release_id" >> "$GITHUB_OUTPUT"
|
|
|
|
build_windows:
|
|
name: Build Windows artifacts
|
|
runs-on: windows-latest
|
|
timeout-minutes: 120
|
|
needs: prepare
|
|
env:
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.prepare.outputs.tag }}
|
|
fetch-depth: 0
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Cache Rust artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
|
|
- name: Build release binary
|
|
shell: pwsh
|
|
run: |
|
|
cargo build -p gitcomet --release --locked --features ui-gpui,gix --bins
|
|
|
|
- name: Package portable ZIP
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
New-Item -ItemType Directory -Path dist\portable -Force | Out-Null
|
|
Copy-Item target\release\gitcomet.exe dist\portable\gitcomet.exe -Force
|
|
Copy-Item README.md dist\portable\README.md -Force
|
|
Copy-Item LICENSE-AGPL-3.0 dist\portable\LICENSE-AGPL-3.0 -Force
|
|
Copy-Item NOTICE dist\portable\NOTICE -Force
|
|
|
|
$zipName = "gitcomet-v${env:VERSION}-windows-x86_64-portable.zip"
|
|
if (Test-Path "dist\$zipName") { Remove-Item "dist\$zipName" -Force }
|
|
Compress-Archive -Path dist\portable\* -DestinationPath "dist\$zipName"
|
|
|
|
- name: Build MSI installer (WiX)
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = "Stop"
|
|
choco install wixtoolset --version "${env:WIXTOOLSET_VERSION}" --yes --no-progress
|
|
cargo install cargo-wix --version "${env:CARGO_WIX_VERSION}" --locked
|
|
if (!(Test-Path "crates\gitcomet\wix\main.wxs")) {
|
|
cargo wix init --package gitcomet
|
|
}
|
|
$msiName = "gitcomet-v${env:VERSION}-windows-x86_64.msi"
|
|
cargo wix --package gitcomet --profile release --nocapture --no-build --output "dist\$msiName"
|
|
|
|
- name: Upload Windows artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: windows-release-artifacts
|
|
path: |
|
|
dist/*.zip
|
|
dist/*.msi
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
build_linux:
|
|
name: Build Linux artifacts
|
|
runs-on: ubuntu-22.04
|
|
timeout-minutes: 120
|
|
needs: prepare
|
|
env:
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
DEB_REVISION: ${{ needs.prepare.outputs.deb_revision }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.prepare.outputs.tag }}
|
|
fetch-depth: 0
|
|
|
|
- name: Install Linux UI native dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
desktop-file-utils \
|
|
pkg-config \
|
|
libxcb1-dev \
|
|
libxkbcommon-dev \
|
|
libxkbcommon-x11-dev
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Cache Rust artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
|
|
- name: Build release binary
|
|
run: cargo build -p gitcomet --release --locked --features ui-gpui,gix
|
|
|
|
- name: Normalize desktop entry metadata
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
# Backward compatibility for older tags that used non-standard categories.
|
|
sed 's/VersionControl;/RevisionControl;/g' assets/linux/gitcomet.desktop > dist/gitcomet.desktop
|
|
desktop-file-validate dist/gitcomet.desktop
|
|
|
|
- name: Package tar.gz
|
|
run: |
|
|
set -euo pipefail
|
|
root="gitcomet-v${VERSION}-linux-x86_64"
|
|
mkdir -p "dist/stage/${root}"
|
|
install -m755 target/release/gitcomet "dist/stage/${root}/gitcomet"
|
|
install -m644 README.md "dist/stage/${root}/README.md"
|
|
install -m644 LICENSE-AGPL-3.0 "dist/stage/${root}/LICENSE-AGPL-3.0"
|
|
install -m644 NOTICE "dist/stage/${root}/NOTICE"
|
|
tar -C dist/stage -czf "dist/${root}.tar.gz" "${root}"
|
|
|
|
- name: Package .deb
|
|
run: |
|
|
set -euo pipefail
|
|
deb_upstream_version="${VERSION/-rc./~rc.}"
|
|
deb_revision="${DEB_REVISION}"
|
|
deb_version="${deb_upstream_version}-${deb_revision}"
|
|
pkg_root="dist/deb-root"
|
|
mkdir -p "${pkg_root}/DEBIAN"
|
|
mkdir -p "${pkg_root}/usr/bin"
|
|
mkdir -p "${pkg_root}/usr/share/applications"
|
|
mkdir -p "${pkg_root}/usr/share/icons/hicolor/512x512/apps"
|
|
|
|
install -m755 target/release/gitcomet "${pkg_root}/usr/bin/gitcomet"
|
|
install -m644 dist/gitcomet.desktop "${pkg_root}/usr/share/applications/gitcomet.desktop"
|
|
install -m644 assets/gitcomet-512.png "${pkg_root}/usr/share/icons/hicolor/512x512/apps/gitcomet.png"
|
|
|
|
{
|
|
echo "Package: gitcomet"
|
|
echo "Version: ${deb_version}"
|
|
echo "Architecture: amd64"
|
|
echo "Maintainer: AutoExplore Oy <info@autoexplore.ai>"
|
|
echo "Depends: git"
|
|
echo "Section: utils"
|
|
echo "Priority: optional"
|
|
echo "Description: Fast, resource-efficient Git GUI written in Rust."
|
|
} > "${pkg_root}/DEBIAN/control"
|
|
|
|
dpkg-deb --build "${pkg_root}" "dist/gitcomet_${deb_version}_amd64.deb"
|
|
|
|
- name: Package AppImage
|
|
run: |
|
|
set -euo pipefail
|
|
appdir="dist/AppDir"
|
|
mkdir -p "${appdir}/usr/bin"
|
|
install -m755 target/release/gitcomet "${appdir}/usr/bin/gitcomet"
|
|
install -m644 dist/gitcomet.desktop "${appdir}/gitcomet.desktop"
|
|
install -m644 assets/gitcomet-512.png "${appdir}/gitcomet.png"
|
|
|
|
{
|
|
echo '#!/usr/bin/env bash'
|
|
echo 'set -euo pipefail'
|
|
echo 'HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"'
|
|
echo 'exec "${HERE}/usr/bin/gitcomet" "$@"'
|
|
} > "${appdir}/AppRun"
|
|
chmod +x "${appdir}/AppRun"
|
|
|
|
curl -fsSL \
|
|
"https://github.com/AppImage/appimagetool/releases/download/${APPIMAGETOOL_VERSION}/appimagetool-x86_64.AppImage" \
|
|
-o /tmp/appimagetool.AppImage
|
|
echo "${APPIMAGETOOL_X86_64_SHA256} /tmp/appimagetool.AppImage" | sha256sum -c -
|
|
chmod +x /tmp/appimagetool.AppImage
|
|
ARCH=x86_64 /tmp/appimagetool.AppImage --appimage-extract-and-run \
|
|
"${appdir}" "dist/gitcomet-v${VERSION}-linux-x86_64.AppImage"
|
|
|
|
- name: Upload Linux artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: linux-release-artifacts
|
|
path: |
|
|
dist/*.tar.gz
|
|
dist/*.AppImage
|
|
dist/*.deb
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
build_macos:
|
|
name: Build macOS artifacts (${{ matrix.arch }})
|
|
runs-on: ${{ matrix.runs_on }}
|
|
timeout-minutes: 120
|
|
needs: prepare
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: arm64
|
|
runs_on: macos-latest
|
|
- arch: x86_64
|
|
runs_on: macos-15-intel
|
|
env:
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
ARCH: ${{ matrix.arch }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.prepare.outputs.tag }}
|
|
fetch-depth: 0
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@stable
|
|
|
|
- name: Cache Rust artifacts
|
|
uses: Swatinem/rust-cache@v2
|
|
|
|
- name: Package macOS release assets
|
|
run: |
|
|
set -euo pipefail
|
|
scripts/package-macos.sh \
|
|
--version "${VERSION}" \
|
|
--arch "${ARCH}" \
|
|
--release \
|
|
--out-dir dist
|
|
|
|
- name: Upload macOS artifacts
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: macos-release-artifacts-${{ matrix.arch }}
|
|
path: |
|
|
dist/*.tar.gz
|
|
dist/*.dmg
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
publish_release_assets:
|
|
name: Attach assets and checksums to GitHub release
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
needs: [prepare, build_windows, build_linux, build_macos]
|
|
env:
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
RELEASE_ID: ${{ needs.prepare.outputs.release_id }}
|
|
ENABLE_KEYLESS_SIGNING: ${{ vars.ENABLE_KEYLESS_SIGNING }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ needs.prepare.outputs.tag }}
|
|
fetch-depth: 0
|
|
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: windows-release-artifacts
|
|
path: dist/windows
|
|
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: linux-release-artifacts
|
|
path: dist/linux
|
|
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: macos-release-artifacts-*
|
|
path: dist/macos
|
|
merge-multiple: true
|
|
|
|
- name: Assemble release payload
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist/release
|
|
find dist/windows -maxdepth 1 -type f -exec cp -f {} dist/release/ \;
|
|
find dist/linux -maxdepth 1 -type f -exec cp -f {} dist/release/ \;
|
|
find dist/macos -maxdepth 1 -type f -exec cp -f {} dist/release/ \;
|
|
file_count="$(find dist/release -maxdepth 1 -type f | wc -l | tr -d '[:space:]')"
|
|
if [ "${file_count}" = "0" ]; then
|
|
echo "::error title=No release artifacts::No files were assembled into dist/release."
|
|
exit 1
|
|
fi
|
|
ls -lah dist/release
|
|
|
|
- name: Generate Homebrew formula
|
|
run: |
|
|
set -euo pipefail
|
|
arm_tar="gitcomet-v${VERSION}-macos-arm64.tar.gz"
|
|
intel_tar="gitcomet-v${VERSION}-macos-x86_64.tar.gz"
|
|
linux_tar="gitcomet-v${VERSION}-linux-x86_64.tar.gz"
|
|
arm_path="dist/release/${arm_tar}"
|
|
intel_path="dist/release/${intel_tar}"
|
|
linux_path="dist/release/${linux_tar}"
|
|
|
|
if [ ! -f "${arm_path}" ] || [ ! -f "${intel_path}" ] || [ ! -f "${linux_path}" ]; then
|
|
echo "::error title=Missing release tarballs::Expected ${arm_tar}, ${intel_tar}, and ${linux_tar} in dist/release."
|
|
exit 1
|
|
fi
|
|
|
|
scripts/generate-homebrew-formula.sh \
|
|
--version "${VERSION}" \
|
|
--github-repo "${GITHUB_REPOSITORY}" \
|
|
--arm-tar "${arm_path}" \
|
|
--intel-tar "${intel_path}" \
|
|
--linux-tar "${linux_path}" \
|
|
--output "dist/release/gitcomet.rb"
|
|
|
|
- name: Generate checksums
|
|
run: |
|
|
set -euo pipefail
|
|
pushd dist/release >/dev/null
|
|
md5_file="gitcomet-v${VERSION}-checksums.md5"
|
|
sha_file="gitcomet-v${VERSION}-checksums.sha256"
|
|
|
|
find . -maxdepth 1 -type f \
|
|
! -name "*checksums*" \
|
|
-printf "%P\n" \
|
|
| LC_ALL=C sort > /tmp/release_files.txt
|
|
|
|
xargs -I{} md5sum "{}" < /tmp/release_files.txt > "$md5_file"
|
|
xargs -I{} sha256sum "{}" < /tmp/release_files.txt > "$sha_file"
|
|
popd >/dev/null
|
|
|
|
- name: Install cosign
|
|
if: ${{ env.ENABLE_KEYLESS_SIGNING == 'true' }}
|
|
uses: sigstore/cosign-installer@v3
|
|
|
|
- name: Sign SHA256 checksums with keyless signing
|
|
if: ${{ env.ENABLE_KEYLESS_SIGNING == 'true' }}
|
|
run: |
|
|
set -euo pipefail
|
|
sha_file="dist/release/gitcomet-v${VERSION}-checksums.sha256"
|
|
sig_file="dist/release/gitcomet-v${VERSION}-checksums.sha256.sig"
|
|
cert_file="dist/release/gitcomet-v${VERSION}-checksums.sha256.pem"
|
|
cosign sign-blob --yes \
|
|
--output-signature "$sig_file" \
|
|
--output-certificate "$cert_file" \
|
|
"$sha_file"
|
|
|
|
- name: Upload payload artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: release-payload
|
|
path: dist/release/*
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
- name: Upload assets to GitHub release
|
|
if: ${{ env.RELEASE_ID != '' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null
|
|
gh release upload "$TAG" dist/release/* --clobber --repo "$GITHUB_REPOSITORY"
|
|
|
|
- name: Emit release summary
|
|
run: |
|
|
set -euo pipefail
|
|
uploaded="no"
|
|
if [ -n "${RELEASE_ID}" ]; then
|
|
uploaded="yes"
|
|
fi
|
|
{
|
|
echo "### Built release artifacts"
|
|
echo ""
|
|
echo "- Tag: \`$TAG\`"
|
|
echo "- Version: \`$VERSION\`"
|
|
echo "- Release upload attempted: \`${uploaded}\`"
|
|
echo ""
|
|
echo "Files:"
|
|
ls -1 dist/release | sed 's/^/- `/;s/$/`/'
|
|
} >> "$GITHUB_STEP_SUMMARY"
|