mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
9e4dc48165
* Release: sign the Windows binaries and MSI through SignPath Foundation When the SIGNPATH_API_TOKEN secret exists, each Windows job sends the three .exe files to SignPath before packaging, then the MSI built from them, using the GitHub artifact flow from SignPath's docs. The policy is test-signing unless the signpath_signing_policy dispatch input or the SIGNPATH_SIGNING_POLICY repository variable says otherwise. Without the secret the jobs build unsigned exactly as before. package.ps1 gains -Phase stage|package so signing can sit between the build and the MSI; the artifact configuration to paste into SignPath is packaging/windows/signpath-artifact-configuration.xml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * SignPath: drop the description attributes the Foundation plan doesn't allow SignPath Foundation projects get an automatic signature description (its watermark); the artifact configuration may not override it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
725 lines
35 KiB
YAML
725 lines
35 KiB
YAML
# Release pipeline, after PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
|
#
|
|
# Push to `release` to build packages and create/update a DRAFT GitHub Release. The version
|
|
# comes from [workspace.package] in Cargo.toml. An agent verifies the artifacts and publishes
|
|
# the draft separately; see docs/releasing.md. Tags and pushes to main do not trigger releases.
|
|
# At midnight Eastern, changed main commits build and publish a nightly pre-release.
|
|
# A version with a suffix (0.2.0-rc.1) is a pre-release.
|
|
# The macOS app is signed and notarized when the APPLE_* secrets exist. The Windows binaries and
|
|
# MSI are Authenticode-signed through SignPath Foundation when the SIGNPATH_API_TOKEN secret exists
|
|
# (see the windows job). Nothing else is signed yet.
|
|
#
|
|
# Linux x86_64, aarch64 AppImage (+ .zsync), .deb, .rpm, .flatpak, .snap, .tar.gz packaging/linux
|
|
# FreeBSD x86_64 .tar.gz (built in a FreeBSD VM) packaging/freebsd
|
|
# macOS universal .dmg, and the CLI as a .zip (both archs built in parallel) packaging/macos
|
|
# Windows x64, x86, arm64 .msi and a portable .zip; x64 also the plain .exe packaging/windows
|
|
# Web a .zip of the static site packaging/web
|
|
#
|
|
# The main installers are also attached without the version in their names
|
|
# (solidcraft-windows-x64.msi, solidcraft-linux-x86_64.AppImage, ...) so that
|
|
# releases/latest/download/<name> links in the README keep working across releases.
|
|
#
|
|
# A dry run: dispatch with draft=false (the default). Only the release branch may create drafts.
|
|
# A dry run from any branch also test-signs the Windows files when SIGNPATH_API_TOKEN exists:
|
|
# gh workflow run release.yml --ref <branch> -f draft=false [-f signpath_signing_policy=test-signing]
|
|
name: Release
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 0 * * *'
|
|
timezone: America/New_York
|
|
push:
|
|
branches: [release]
|
|
workflow_dispatch:
|
|
inputs:
|
|
draft:
|
|
description: "Create/update a draft (release branch only; off: build artifacts only)"
|
|
type: boolean
|
|
default: false
|
|
signpath_signing_policy:
|
|
description: "SignPath signing policy for the Windows files (empty: the repository variable, else test-signing)"
|
|
type: string
|
|
default: ""
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
CARGO_INCREMENTAL: 0
|
|
|
|
jobs:
|
|
version:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
build: ${{ steps.changed.outputs.build }}
|
|
version: ${{ steps.v.outputs.version }}
|
|
date: ${{ steps.v.outputs.date }}
|
|
prerelease: ${{ steps.v.outputs.prerelease }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
- id: changed
|
|
name: Skip unchanged nightly builds
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: bash packaging/nightly.sh
|
|
- id: v
|
|
if: steps.changed.outputs.build == 'true'
|
|
name: Resolve version
|
|
env:
|
|
REQUEST_DRAFT: ${{ inputs.draft || false }}
|
|
run: |
|
|
. packaging/env.sh
|
|
if [ "$GITHUB_EVENT_NAME" = schedule ]; then
|
|
# Unique SemVer without committing daily version bumps to main.
|
|
VERSION="${VERSION%%-*}-nightly.$(TZ=America/New_York date +%Y%m%d).$GITHUB_RUN_NUMBER.$GITHUB_RUN_ATTEMPT"
|
|
fi
|
|
if ! printf '%s' "$VERSION" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?$'; then
|
|
echo "::error::Cargo.toml must contain a version like 1.2.3 or 1.2.3-rc.1"
|
|
exit 1
|
|
fi
|
|
if [ "$REQUEST_DRAFT" = true ] && [ "$GITHUB_REF" != refs/heads/release ]; then
|
|
echo "::error::Draft releases must be built from the release branch"
|
|
exit 1
|
|
fi
|
|
case "$VERSION" in *-*) pre=true ;; *) pre=false ;; esac
|
|
{ echo "version=$VERSION"; echo "date=$(date -u +%Y-%m-%d)"; echo "prerelease=$pre"; } >>"$GITHUB_OUTPUT"
|
|
echo "### SolidCraft v$VERSION (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
|
|
|
|
# The app icon in every size, drawn by solidcraft-cli (no image files are committed), for the
|
|
# Windows jobs: the ARM64 build is cross-compiled and can't run there to draw its own.
|
|
icons:
|
|
needs: version
|
|
if: needs.version.outputs.build == 'true'
|
|
runs-on: ubuntu-22.04
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: release-icons
|
|
- run: cargo build --release --locked -p solidcraft-cli
|
|
- run: |
|
|
. packaging/env.sh
|
|
make_icons target/release/solidcraft-cli icons
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: icons
|
|
path: icons
|
|
if-no-files-found: error
|
|
|
|
linux:
|
|
name: Linux ${{ matrix.arch }}
|
|
needs: version
|
|
if: needs.version.outputs.build == 'true'
|
|
# The oldest GitHub-hosted image, so the binaries only need glibc >= 2.35.
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { arch: x86_64, runner: ubuntu-22.04, deb: amd64 }
|
|
- { arch: aarch64, runner: ubuntu-22.04-arm, deb: arm64 }
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
|
NFPM_VERSION: 2.47.0
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- name: Install packaging tools
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends desktop-file-utils appstream zsync rpm
|
|
curl -fsSL -o "$RUNNER_TEMP/nfpm.deb" \
|
|
"https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_${{ matrix.deb }}.deb"
|
|
sudo dpkg -i "$RUNNER_TEMP/nfpm.deb"
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: release-linux-${{ matrix.arch }}
|
|
- name: Build and package (AppImage, deb, rpm, tar.gz)
|
|
run: packaging/linux/package.sh
|
|
- name: Check the packages
|
|
run: |
|
|
dpkg-deb --info dist/release/*.deb
|
|
dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/solidcraft$|usr/bin/solidcraft-cli$|metainfo|applications|THIRD-PARTY-LICENSES.txt'
|
|
# Compatibility shims (docs/compatibility.md): the old CLI name and the package rename.
|
|
dpkg-deb --contents dist/release/*.deb | grep -E 'usr/bin/solvecraft-cli$'
|
|
dpkg-deb --field dist/release/*.deb Replaces Conflicts Provides | grep -F solvecraft
|
|
rpm -qip dist/release/*.rpm
|
|
rpm -qlp dist/release/*.rpm | grep -E '/usr/bin/solidcraft$|THIRD-PARTY-LICENSES.txt'
|
|
tar -tzf dist/release/*.tar.gz | grep -E '/bin/solidcraft$|THIRD-PARTY-LICENSES.txt'
|
|
ldd target/release/solidcraft
|
|
for appimage in dist/release/*.AppImage; do
|
|
chmod +x "$appimage"
|
|
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
|
|
"$appimage" --appimage-updateinformation | grep -F 'gh-releases-zsync|'
|
|
test -s "$appimage.zsync"
|
|
done
|
|
# Legacy AppImage update alias (docs/compatibility.md).
|
|
ls dist/release/solvecraft-*-linux-*.AppImage.zsync
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-linux-${{ matrix.arch }}
|
|
path: dist/release/*
|
|
if-no-files-found: error
|
|
|
|
flatpak:
|
|
name: Flatpak ${{ matrix.arch }}
|
|
needs: [version, linux]
|
|
if: needs.version.outputs.build == 'true'
|
|
# Repackages the Linux job's tarball (the same binaries as the AppImage/deb/rpm); no Rust
|
|
# build here. 24.04 for a newer flatpak-builder than 22.04 ships.
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { arch: x86_64, runner: ubuntu-24.04 }
|
|
- { arch: aarch64, runner: ubuntu-24.04-arm }
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
with:
|
|
name: release-linux-${{ matrix.arch }}
|
|
path: dist/linux
|
|
- name: Install flatpak-builder
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder librsvg2-common xvfb xauth weston dbus-x11
|
|
# Ubuntu 24.04's AppArmor blocks the unprivileged user namespaces bubblewrap needs.
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
- name: Build the Flatpak bundle
|
|
run: packaging/linux/flatpak-bundle.sh "dist/linux/solidcraft-$SOLIDCRAFT_VERSION-linux-${{ matrix.arch }}.tar.gz"
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-flatpak-${{ matrix.arch }}
|
|
path: dist/release/*.flatpak
|
|
if-no-files-found: error
|
|
|
|
|
|
|
|
snap:
|
|
name: Snap ${{ matrix.arch }}
|
|
needs: [version, linux]
|
|
if: needs.version.outputs.build == 'true'
|
|
# The Snap Store hasn't accepted SolidCraft yet, so a snap failure never holds up a release.
|
|
continue-on-error: true
|
|
# Repackages the Linux job's tarball, like the Flatpak. On 24.04 because the core24 base is
|
|
# Ubuntu 24.04, which lets snapcraft build on the runner itself (--destructive-mode).
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { arch: x86_64, runner: ubuntu-24.04 }
|
|
- { arch: aarch64, runner: ubuntu-24.04-arm }
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
with:
|
|
name: release-linux-${{ matrix.arch }}
|
|
path: dist/linux
|
|
- name: Install snapcraft and the smoke-test tools
|
|
run: |
|
|
sudo snap install snapcraft --classic
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends xvfb xauth weston dbus-x11
|
|
- name: Build the snap
|
|
run: |
|
|
sudo --preserve-env=SOLIDCRAFT_VERSION,SOLIDCRAFT_BUILD_DATE env "PATH=$PATH" \
|
|
packaging/linux/snap-pack.sh --destructive-mode "dist/linux/solidcraft-$SOLIDCRAFT_VERSION-linux-${{ matrix.arch }}.tar.gz"
|
|
sudo chown -R "$(id -u):$(id -g)" dist target
|
|
- name: Install it and start the app on headless X11 and Wayland
|
|
run: |
|
|
snap_file="dist/release/solidcraft-$SOLIDCRAFT_VERSION-linux-${{ matrix.arch }}.snap"
|
|
sudo snap install --dangerous "$snap_file"
|
|
snap info --verbose "$snap_file" | head -n 12
|
|
snap connections solidcraft
|
|
solidcraft.cli --version
|
|
# Snaps expect the login session's runtime directory, which runners don't create.
|
|
sudo install -d -m 700 -o "$(id -u)" -g "$(id -g)" "/run/user/$(id -u)"
|
|
python3 packaging/linux/snap-smoke.py --out snap-smoke
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: snap-smoke-${{ matrix.arch }}
|
|
path: snap-smoke/*.png
|
|
if-no-files-found: error
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-snap-${{ matrix.arch }}
|
|
path: dist/release/*.snap
|
|
if-no-files-found: error
|
|
|
|
freebsd:
|
|
name: FreeBSD x86_64
|
|
needs: version
|
|
if: needs.version.outputs.build == 'true'
|
|
# GitHub has no FreeBSD runners: build in a FreeBSD 14.3 VM.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 180
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- name: Build and package in a FreeBSD VM (tar.gz)
|
|
# vmactions/freebsd-vm v1.5.9, pinned by commit (it runs our build).
|
|
uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc
|
|
with:
|
|
release: "14.3"
|
|
usesh: true
|
|
cpu: 4
|
|
mem: 12288
|
|
# Passed into the VM by name; never templated into the script below.
|
|
envs: "SOLIDCRAFT_VERSION SOLIDCRAFT_BUILD_DATE"
|
|
prepare: |
|
|
pkg install -y bash git curl pkgconf libxkbcommon wayland libX11 libXcursor libXrandr libXi libxcb mesa-libs vulkan-loader
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
|
|
run: |
|
|
set -e
|
|
. "$HOME/.cargo/env"
|
|
git config --global --add safe.directory "$PWD"
|
|
# Build outside the checkout so only dist/ is copied back out of the VM. 4 jobs: more
|
|
# runs the VM out of memory.
|
|
export CARGO_TARGET_DIR=/var/tmp/solidcraft-target CARGO_BUILD_JOBS=4
|
|
bash packaging/freebsd/package.sh
|
|
tar -tzf dist/release/solidcraft-*-freebsd-x86_64.tar.gz \
|
|
| grep -E '/bin/solidcraft$|/bin/solidcraft-cli$|/share/applications/ai\.storyteller\.solvecraft\.desktop$|/share/doc/solidcraft/THIRD-PARTY-LICENSES\.txt$'
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-freebsd-x86_64
|
|
path: dist/release/*
|
|
if-no-files-found: error
|
|
|
|
# The two halves of the universal binary build in parallel on their own runners (each alone
|
|
# took about half of the old single job's 45 minutes); `macos` below joins and packages them.
|
|
macos-build:
|
|
name: macOS build ${{ matrix.arch }}
|
|
needs: version
|
|
if: needs.version.outputs.build == 'true'
|
|
runs-on: macos-14
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { arch: aarch64, target: aarch64-apple-darwin }
|
|
- { arch: x86_64, target: x86_64-apple-darwin }
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: release-macos-${{ matrix.arch }}
|
|
- name: Build
|
|
run: packaging/macos/package.sh --build-only ${{ matrix.target }}
|
|
# Not named release-*: the release job collects every release-* artifact as an asset.
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: macos-bin-${{ matrix.arch }}
|
|
path: |
|
|
target/${{ matrix.target }}/release/solidcraft
|
|
target/${{ matrix.target }}/release/solidcraft-cli
|
|
target/${{ matrix.target }}/release/solvecraft-cli
|
|
if-no-files-found: error
|
|
|
|
macos:
|
|
name: macOS universal
|
|
needs: [version, macos-build]
|
|
if: needs.version.outputs.build == 'true'
|
|
runs-on: macos-14
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
with:
|
|
name: macos-bin-aarch64
|
|
path: target/aarch64-apple-darwin/release
|
|
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
with:
|
|
name: macos-bin-x86_64
|
|
path: target/x86_64-apple-darwin/release
|
|
- name: Set up Developer ID signing and notarization
|
|
# Optional until the APPLE_* secrets exist (docs/releasing.md, "macOS signing"). Without
|
|
# them (forks, dry runs) the app keeps its ad-hoc signature and is not notarized.
|
|
env:
|
|
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
run: packaging/macos/signing.sh --setup
|
|
- name: Join, sign, package and check (dmg, CLI zip; the dmg is mounted and checked)
|
|
run: |
|
|
# Artifacts don't keep the executable bit.
|
|
chmod +x target/*-apple-darwin/release/solidcraft target/*-apple-darwin/release/solidcraft-cli target/*-apple-darwin/release/solvecraft-cli
|
|
packaging/macos/package.sh --arch universal --skip-build ${MACOS_SIGN_IDENTITY:+--sign}
|
|
- name: Remove the signing keychain and API key
|
|
if: always()
|
|
run: packaging/macos/signing.sh --cleanup
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-macos
|
|
path: dist/release/*
|
|
if-no-files-found: error
|
|
|
|
windows:
|
|
name: Windows ${{ matrix.arch }}
|
|
needs: [version, icons]
|
|
if: needs.version.outputs.build == 'true'
|
|
# Must stay a GitHub-hosted runner, like every job before it: SignPath's open-source policy
|
|
# only signs builds whose jobs all ran on GitHub-hosted runners.
|
|
runs-on: windows-latest
|
|
# actions: read lets the SignPath action read this job's details and fetch the uploaded
|
|
# artifact with GITHUB_TOKEN (the workflow default above grants contents: read only).
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { arch: x64, target: x86_64-pc-windows-msvc }
|
|
- { arch: x86, target: i686-pc-windows-msvc }
|
|
# Windows on ARM: cross-compiled on the x64 runner.
|
|
- { arch: arm64, target: aarch64-pc-windows-msvc }
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
# Authenticode signing through SignPath Foundation (docs/releasing.md, "Windows signing").
|
|
# Only when the API token secret exists: forks and repositories without it build unsigned,
|
|
# exactly as before. `if:` can't read secrets, so the check goes through this variable.
|
|
SIGN_WINDOWS: ${{ secrets.SIGNPATH_API_TOKEN != '' }}
|
|
# The one switch between the self-signed test certificate (test-signing) and the real one
|
|
# (release-signing): a dispatch input, else the SIGNPATH_SIGNING_POLICY repository variable,
|
|
# else test-signing.
|
|
SIGNPATH_SIGNING_POLICY: ${{ inputs.signpath_signing_policy || vars.SIGNPATH_SIGNING_POLICY || 'test-signing' }}
|
|
SIGNPATH_ORGANIZATION_ID: fa603ff4-c702-404b-8588-a524371c7a4e
|
|
SIGNPATH_PROJECT_SLUG: solidcraft
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
with:
|
|
name: icons
|
|
path: icons
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: ${{ matrix.target }}
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: release-windows-${{ matrix.arch }}
|
|
- name: Install WiX
|
|
shell: pwsh
|
|
run: |
|
|
dotnet tool install --global wix --version 5.0.2
|
|
wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2
|
|
- name: Build and package (msi, portable zip)
|
|
if: env.SIGN_WINDOWS != 'true'
|
|
shell: pwsh
|
|
env:
|
|
SOLIDCRAFT_ICONS: ${{ github.workspace }}/icons
|
|
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }}
|
|
|
|
# ---- signed path ---------------------------------------------------------------------------
|
|
# SignPath's GitHub integration (docs.signpath.io/trusted-build-systems/github): the files go
|
|
# up as a workflow artifact, SignPath fetches that artifact itself (so it can verify it came
|
|
# from this workflow run), signs it, and the action downloads the signed copy. GitHub stores
|
|
# artifacts as zips, so packaging/windows/signpath-artifact-configuration.xml (pasted into the
|
|
# SignPath project) describes a zip holding the files at its top level.
|
|
# Two rounds: the .exe files first, so the MSI, the portable zip and the plain .exe all carry
|
|
# signed binaries; then the MSI built from them.
|
|
- name: Build and stage the binaries for signing
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
shell: pwsh
|
|
env:
|
|
SOLIDCRAFT_ICONS: ${{ github.workspace }}/icons
|
|
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }} -Phase stage
|
|
- name: Upload the unsigned binaries
|
|
id: unsigned-exes
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: signpath-unsigned-exes-windows-${{ matrix.arch }}
|
|
path: target/windows-package/${{ matrix.arch }}/*.exe
|
|
if-no-files-found: error
|
|
retention-days: 3
|
|
- name: Sign the binaries (SignPath, ${{ env.SIGNPATH_SIGNING_POLICY }})
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ env.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: ${{ env.SIGNPATH_PROJECT_SLUG }}
|
|
signing-policy-slug: ${{ env.SIGNPATH_SIGNING_POLICY }}
|
|
github-artifact-id: ${{ steps.unsigned-exes.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
# release-signing requests wait for a person to approve them in SignPath; give that an
|
|
# hour instead of the default 10 minutes. test-signing requests finish in seconds.
|
|
wait-for-completion-timeout-in-seconds: 3600
|
|
output-artifact-directory: target/windows-package/signed-exes-${{ matrix.arch }}
|
|
- name: Put the signed binaries in place
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$stage = "target/windows-package/${{ matrix.arch }}"
|
|
foreach ($exe in 'solidcraft.exe', 'solidcraft-cli.exe', 'solvecraft-cli.exe') {
|
|
$signed = "target/windows-package/signed-exes-${{ matrix.arch }}/$exe"
|
|
# The artifact configuration matches each file optionally, so check that every file
|
|
# actually came back signed rather than trusting the request's success.
|
|
$sig = Get-AuthenticodeSignature $signed
|
|
if (-not $sig.SignerCertificate) { throw "$exe came back from SignPath without a signature ($($sig.Status))" }
|
|
# The test certificate is self-signed, so Status is not Valid until release-signing.
|
|
Write-Output "$exe signed by $($sig.SignerCertificate.Subject) (status: $($sig.Status))"
|
|
Copy-Item -Force $signed "$stage/$exe"
|
|
}
|
|
- name: Package the signed binaries (msi, portable zip)
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
shell: pwsh
|
|
env:
|
|
SOLIDCRAFT_ICONS: ${{ github.workspace }}/icons
|
|
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }} -Phase package
|
|
- name: Upload the unsigned MSI
|
|
id: unsigned-msi
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: signpath-unsigned-msi-windows-${{ matrix.arch }}
|
|
path: dist/release/*.msi
|
|
if-no-files-found: error
|
|
retention-days: 3
|
|
- name: Sign the MSI (SignPath, ${{ env.SIGNPATH_SIGNING_POLICY }})
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ env.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: ${{ env.SIGNPATH_PROJECT_SLUG }}
|
|
signing-policy-slug: ${{ env.SIGNPATH_SIGNING_POLICY }}
|
|
github-artifact-id: ${{ steps.unsigned-msi.outputs.artifact-id }}
|
|
wait-for-completion: true
|
|
wait-for-completion-timeout-in-seconds: 3600
|
|
output-artifact-directory: target/windows-package/signed-msi-${{ matrix.arch }}
|
|
- name: Put the signed MSI in place
|
|
if: env.SIGN_WINDOWS == 'true'
|
|
shell: pwsh
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$msi = Get-Item "dist/release/*.msi"
|
|
$signed = "target/windows-package/signed-msi-${{ matrix.arch }}/$($msi.Name)"
|
|
$sig = Get-AuthenticodeSignature $signed
|
|
if (-not $sig.SignerCertificate) { throw "$($msi.Name) came back from SignPath without a signature ($($sig.Status))" }
|
|
Write-Output "$($msi.Name) signed by $($sig.SignerCertificate.Subject) (status: $($sig.Status))"
|
|
Copy-Item -Force $signed $msi.FullName
|
|
"Windows ${{ matrix.arch }}: Authenticode-signed through SignPath (policy $env:SIGNPATH_SIGNING_POLICY, $($sig.SignerCertificate.Subject))." >> $env:GITHUB_STEP_SUMMARY
|
|
- name: Note the unsigned build
|
|
if: env.SIGN_WINDOWS != 'true'
|
|
shell: pwsh
|
|
run: '"Windows ${{ matrix.arch }}: unsigned (no SIGNPATH_API_TOKEN secret)." >> $env:GITHUB_STEP_SUMMARY'
|
|
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-windows-${{ matrix.arch }}
|
|
path: dist/release/*
|
|
if-no-files-found: error
|
|
|
|
web:
|
|
name: Web (wasm)
|
|
needs: version
|
|
if: needs.version.outputs.build == 'true'
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
|
TRUNK_VERSION: 0.21.14
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: wasm32-unknown-unknown
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: release-web
|
|
- name: Install trunk
|
|
run: |
|
|
curl -fsSL "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
|
|
| tar -xz -C "$HOME/.cargo/bin" trunk
|
|
- name: Build and zip
|
|
run: packaging/web/package.sh
|
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-web
|
|
path: dist/release/*
|
|
if-no-files-found: error
|
|
|
|
release:
|
|
name: GitHub Release or dry-run artifacts
|
|
needs: [version, linux, flatpak, snap, freebsd, macos, windows, web]
|
|
if: needs.version.outputs.build == 'true'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
env:
|
|
VERSION: ${{ needs.version.outputs.version }}
|
|
PRERELEASE: ${{ needs.version.outputs.prerelease }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GH_REPO: ${{ github.repository }}
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
with:
|
|
pattern: release-*
|
|
path: artifacts
|
|
merge-multiple: true
|
|
- name: Version-less copies for permanent download links
|
|
# The README links to releases/latest/download/<name>, which must not change between
|
|
# releases: solidcraft-<version>-windows-x64.msi is also uploaded as
|
|
# solidcraft-windows-x64.msi, and so on for the main installers.
|
|
working-directory: artifacts
|
|
run: |
|
|
shopt -s nullglob
|
|
for f in "solidcraft-$VERSION-"*; do
|
|
case "$f" in
|
|
*.msi | *-portable.zip | *.dmg | *.AppImage | *.deb | *.rpm | *.flatpak | *.snap)
|
|
cp -- "$f" "solidcraft-${f#"solidcraft-$VERSION-"}" ;;
|
|
esac
|
|
done
|
|
for f in solidcraft-windows-x64.msi solidcraft-macos-universal.dmg \
|
|
solidcraft-linux-x86_64.AppImage solidcraft-linux-x86_64.deb solidcraft-linux-x86_64.rpm; do
|
|
test -s "$f" || { echo "::error::missing version-less copy $f"; exit 1; }
|
|
done
|
|
ls -l solidcraft-[!0-9]*
|
|
- name: Checksums and notes
|
|
run: |
|
|
(cd artifacts && sha256sum -- * | tee SHA256SUMS.txt)
|
|
sed "s/{version}/$VERSION/g" .github/release-notes.md >notes.md
|
|
- name: Sign the checksum list for in-app updates
|
|
# Optional until the UPDATE_SIGNING_KEY secret exists (docs/releasing.md, "Update
|
|
# signing"). The trusted comment names the release, which the app checks.
|
|
env:
|
|
UPDATE_SIGNING_KEY: ${{ secrets.UPDATE_SIGNING_KEY }}
|
|
run: |
|
|
# The key the app checks against (crates/ui-egui/src/update/install.rs, PUBLIC_KEY).
|
|
public_key=$(sed -n 's/^pub const PUBLIC_KEY: Option<&str> = Some("\([^"]*\)");/\1/p' crates/ui-egui/src/update/install.rs)
|
|
if [ -z "$UPDATE_SIGNING_KEY" ]; then
|
|
if [ -n "$public_key" ]; then
|
|
echo "::error::the app expects signed updates (PUBLIC_KEY is set) but the UPDATE_SIGNING_KEY secret is missing"
|
|
exit 1
|
|
fi
|
|
echo "UPDATE_SIGNING_KEY isn't set: SHA256SUMS.txt stays unsigned"
|
|
exit 0
|
|
fi
|
|
sudo apt-get update -q
|
|
sudo apt-get install -y --no-install-recommends minisign
|
|
umask 077
|
|
printf '%s\n' "$UPDATE_SIGNING_KEY" >"$RUNNER_TEMP/update.key"
|
|
minisign -S -s "$RUNNER_TEMP/update.key" -m artifacts/SHA256SUMS.txt \
|
|
-x artifacts/SHA256SUMS.txt.minisig -t "SolidCraft v$VERSION" </dev/null
|
|
rm -f "$RUNNER_TEMP/update.key"
|
|
if [ -n "$public_key" ]; then
|
|
minisign -V -P "$public_key" -m artifacts/SHA256SUMS.txt -x artifacts/SHA256SUMS.txt.minisig
|
|
fi
|
|
- name: Publish nightly pre-release
|
|
if: github.event_name == 'schedule'
|
|
working-directory: artifacts
|
|
run: |
|
|
tag="v$VERSION"
|
|
sha256sum --check SHA256SUMS.txt
|
|
# Stage all packages before publishing. Published tags are never replaced.
|
|
gh release create "$tag" --draft --prerelease --latest=false \
|
|
--title "SolidCraft v$VERSION (nightly)" --target "$GITHUB_SHA" \
|
|
--notes-file ../notes.md --generate-notes -- *
|
|
gh release edit "$tag" --draft=false --prerelease --latest=false
|
|
echo "### Published nightly: $tag (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
|
|
- name: Create or update the draft release
|
|
if: github.ref == 'refs/heads/release' && (github.event_name == 'push' || inputs.draft)
|
|
working-directory: artifacts
|
|
run: |
|
|
tag="v$VERSION"
|
|
# Refuse existing tags: a draft must point at this build, never an older tag.
|
|
# matching-refs returns [] for an absent tag and fails on API/auth errors.
|
|
existing_refs="$(gh api "repos/$GH_REPO/git/matching-refs/tags/$tag" \
|
|
--jq '.[].ref')"
|
|
if printf '%s\n' "$existing_refs" | grep -Fxq "refs/tags/$tag"; then
|
|
echo "::error::Tag $tag already exists. Choose a new version; never move a release tag."
|
|
exit 1
|
|
fi
|
|
pre=()
|
|
if [ "$PRERELEASE" = true ]; then pre=(--prerelease); fi
|
|
if draft="$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null)"; then
|
|
if [ "$draft" != true ]; then
|
|
echo "::error::Release $tag is already published. Bump the version before releasing."
|
|
exit 1
|
|
fi
|
|
# Replace the complete draft asset set, including assets removed in this build.
|
|
assets="$(gh release view "$tag" --json assets --jq '.assets[].name')"
|
|
while IFS= read -r asset; do
|
|
if [ -n "$asset" ]; then gh release delete-asset "$tag" "$asset" --yes; fi
|
|
done <<<"$assets"
|
|
gh release edit "$tag" --draft --title "SolidCraft v$VERSION" \
|
|
--target "$GITHUB_SHA" --prerelease="$PRERELEASE"
|
|
gh release upload "$tag" --clobber -- *
|
|
else
|
|
gh release create "$tag" --draft --title "SolidCraft v$VERSION" \
|
|
--target "$GITHUB_SHA" --notes-file ../notes.md --generate-notes "${pre[@]}" -- *
|
|
fi
|
|
{
|
|
echo "Draft **SolidCraft v$VERSION** at \`${GITHUB_SHA::9}\`:"
|
|
echo
|
|
sed 's/^/ /' SHA256SUMS.txt
|
|
} >>"$GITHUB_STEP_SUMMARY"
|
|
# Snap Store: nightlies go to the edge channel and release drafts to candidate, only after
|
|
# every package built and the GitHub release was staged. Publishing the GitHub release
|
|
# promotes candidate to stable (snap-store.yml). Dry runs never upload. Skipped until the
|
|
# owner adds the SNAPCRAFT_STORE_CREDENTIALS secret (docs/releasing.md).
|
|
- id: snapstore
|
|
name: Check for Snap Store credentials
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
run: |
|
|
if [ -n "$SNAPCRAFT_STORE_CREDENTIALS" ]; then
|
|
echo "configured=true" >>"$GITHUB_OUTPUT"
|
|
else
|
|
echo "No SNAPCRAFT_STORE_CREDENTIALS secret: skipping the Snap Store upload."
|
|
fi
|
|
- name: Upload the snaps to the Snap Store
|
|
if: >-
|
|
steps.snapstore.outputs.configured == 'true' && (github.event_name == 'schedule' ||
|
|
(github.ref == 'refs/heads/release' && (github.event_name == 'push' || inputs.draft)))
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
working-directory: artifacts
|
|
run: |
|
|
sudo snap install snapcraft --classic
|
|
channel=candidate
|
|
if [ "$GITHUB_EVENT_NAME" = schedule ]; then channel=edge; fi
|
|
for arch in x86_64 aarch64; do
|
|
snapcraft upload --release="$channel" "solidcraft-$VERSION-linux-$arch.snap"
|
|
done
|
|
echo "Uploaded the snaps to the Snap Store's $channel channel." >>"$GITHUB_STEP_SUMMARY"
|
|
- name: Keep the files (dry run)
|
|
if: github.event_name == 'workflow_dispatch' && !inputs.draft
|
|
run: cp notes.md artifacts/release-notes.md
|
|
- if: github.event_name == 'workflow_dispatch' && !inputs.draft
|
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-assets
|
|
path: artifacts/*
|