Skip to content

Release

Release #6

Workflow file for this run

# Top-level release orchestrator: one run, one graph, source -> Tier-1 ->
# engine -> app.
#
# It reads the pinned versions from source (bol/config.py + third_party/), and
# for each Tier-1 component decides reuse-or-build:
# - if the pinned, attested release already exists (and, where a SHA is
# committed, its published sidecar matches the pin) -> REUSE it,
# - otherwise -> BUILD it from source and publish it (attested).
# The engine and app then always assemble from those (cheap), verifying every
# input against the committed pins. It NEVER mutates a pin; it only asserts them.
#
# Triggers:
# - workflow_dispatch: cut a versioned release (publish=true), or dry-run
# (publish=false) to build + verify the whole chain without cutting the app
# release. A dry run still publishes the reusable building blocks it needs
# (a missing Tier-1, and the engine) so the app can be verified against them;
# only the user-facing app release is withheld. force_rebuild rebuilds every
# tier from source even if a release exists.
# - schedule: nightly. Reuses the pinned engine/Tier-1 and rebuilds the app
# from the default branch, publishing a rolling "nightly" prerelease.
#
# Reproducibility drift is best caught by a separate periodic job that runs the
# engine with rebuild_from_source=true; this orchestrator is about releasing.
name: Release
on:
workflow_dispatch:
inputs:
publish:
description: "Publish the engine + app releases (off = dry run)"
type: boolean
default: true
force_rebuild:
description: "Rebuild every tier from source even if a release exists"
type: boolean
default: false
schedule:
- cron: "0 7 * * *" # nightly ~07:00 UTC
permissions:
contents: read
jobs:
resolve:
runs-on: ubuntu-24.04
env:
GH_TOKEN: ${{ github.token }}
outputs:
build_xcurl: ${{ steps.probe.outputs.build_xcurl }}
build_winegdk: ${{ steps.probe.outputs.build_winegdk }}
build_vkd3d: ${{ steps.probe.outputs.build_vkd3d }}
channel: ${{ steps.probe.outputs.channel }}
publish: ${{ steps.probe.outputs.publish }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Resolve pins and decide reuse-or-build per tier
id: probe
run: |
set -euo pipefail
repo="${{ github.repository }}"
force="${{ inputs.force_rebuild || 'false' }}"
cfg=bol/config.py
xrev="$(grep -m1 '^OPENSSL_XCURL_REV = ' $cfg | cut -d'"' -f2)"
xsha="$(grep -m1 '^OPENSSL_XCURL_ARCHIVE_SHA256 = ' $cfg | cut -d'"' -f2)"
commit="$(grep -m1 '^WINEGDK_SOURCE_COMMIT = ' $cfg | cut -d'"' -f2)"
wshort="${commit:0:12}"
wpre="$(grep -m1 '^WINEGDK_PREFIX_SHA256 = ' $cfg | cut -d'"' -f2)"
vtag="vkd3d-3.0.1-nv-dgc"
# Reuse a release when it exists and (if a SHA is committed) its
# published sidecar matches the pin. Missing or drifted -> rebuild.
needs_build() {
local tag="$1" base="$2" pin="$3" got
[ "$force" = "true" ] && { echo true; return; }
gh release view "$tag" -R "$repo" >/dev/null 2>&1 || { echo true; return; }
if [ -n "$pin" ]; then
rm -rf /tmp/pb; mkdir -p /tmp/pb
gh release download "$tag" -R "$repo" -p "${base}.sha256" -D /tmp/pb 2>/dev/null \
|| { echo true; return; }
got="$(cut -d' ' -f1 "/tmp/pb/${base}.sha256" 2>/dev/null | head -1)"
[ "$got" = "$pin" ] && echo false || echo true
else
echo false
fi
}
bx="$(needs_build "xcurl-${xrev}" "openssl-xcurl-set-${xrev}.tar.gz" "$xsha")"
bw="$(needs_build "winegdk-${wshort}" "winegdk-prefix-${wshort}.tar.gz" "$wpre")"
# vkd3d has no single committed tarball SHA (its wrapper is not the
# integrity boundary; the DLL hashes in OUTPUT-SHA256SUMS are). So it
# reuses on existence; build-engine verifies the DLLs against that
# committed file and fails fast on drift. force_rebuild heals a stale one.
bv="$(needs_build "$vtag" "vkd3d-universal-3.0.1-nv-dgc.tar.gz" "")"
if [ "${{ github.event_name }}" = "schedule" ]; then
channel=nightly; publish=true
else
channel=release; publish="${{ inputs.publish }}"
fi
{
echo "build_xcurl=$bx"
echo "build_winegdk=$bw"
echo "build_vkd3d=$bv"
echo "channel=$channel"
echo "publish=$publish"
} >> "$GITHUB_OUTPUT"
echo "resolved: xcurl=$bx winegdk=$bw vkd3d=$bv channel=$channel publish=$publish"
# ---- Tier 1: built (and published, so it can be reused) only when needed ----
xcurl:
needs: resolve
if: ${{ needs.resolve.outputs.build_xcurl == 'true' }}
permissions:
contents: write
id-token: write
attestations: write
uses: ./.github/workflows/build-xcurl.yml
with:
publish: true
winegdk:
needs: resolve
if: ${{ needs.resolve.outputs.build_winegdk == 'true' }}
permissions:
contents: write
id-token: write
attestations: write
uses: ./.github/workflows/build-winegdk.yml
with:
publish: true
vkd3d:
needs: resolve
if: ${{ needs.resolve.outputs.build_vkd3d == 'true' }}
permissions:
contents: write
id-token: write
attestations: write
uses: ./.github/workflows/build-vkd3d.yml
with:
publish: true
# ---- Engine: always reuses the now-present Tier-1, verifies + asserts ----
engine:
needs: [resolve, xcurl, winegdk, vkd3d]
if: ${{ !cancelled() && !failure() }}
permissions:
contents: write
id-token: write
attestations: write
uses: ./.github/workflows/build-engine.yml
with:
rebuild_from_source: false
# The engine is a building block (like Tier-1): publish it on the release
# channel so the app job can reuse it in the same run, including a dry run
# (publish=false) that still needs to verify the app against it. Only the
# app *release* is gated by publish. On nightly the engine is unchanged
# (pinned) and already published, so verify-only.
publish: ${{ needs.resolve.outputs.channel == 'release' }}
# ---- App: reuses engine + xcurl, publishes with the pinned bill of materials
app:
needs: [resolve, engine]
if: ${{ !cancelled() && !failure() }}
permissions:
contents: write
id-token: write
attestations: write
uses: ./.github/workflows/build-app.yml
with:
publish: ${{ needs.resolve.outputs.publish == 'true' }}
channel: ${{ needs.resolve.outputs.channel }}