Files

309 lines
15 KiB
YAML

name: Publish
# Only runs when a version tag is pushed (e.g. `git tag v1.2.3 && git push --tags`).
# `github.ref_name` is passed to Gradle as PUBLISH_VERSION; the root build strips
# the leading `v` before setting each subproject's `version`.
on:
push:
tags:
- 'v*'
# Manual fallback: run with --ref <tag> so github.ref_name is the tag itself
# (PUBLISH_VERSION is derived from it).
workflow_dispatch:
# Never overlap two publish runs for the same tag.
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false
jobs:
publish:
strategy:
fail-fast: false
# Each host runs only the publish tasks Gradle actually generated for it.
# WINDOWS also publishes the shared kotlinMultiplatform metadata modules
# (see its matrix entry) + the jvm jar + the bridge plugin.
# `target` is the Gradle K/N target token (Capitalised) - one per host,
# used for :demo / :apidemo linkReleaseExecutable<Target>. linuxArm64 is
# skipped for the app build because SDL3 static libs on the ubuntu runner
# are host-arch only; cross-compile would fail at link time.
matrix:
include:
- runner: macos-14
host: macos
target: MacosArm64
exe_ext: kexe
tasks: >-
publishMacosArm64PublicationToGitHubPackagesRepository
- runner: ubuntu-22.04
host: linux
target: LinuxX64
exe_ext: kexe
tasks: >-
publishLinuxX64PublicationToGitHubPackagesRepository
publishLinuxArm64PublicationToGitHubPackagesRepository
- runner: windows-2022
host: windows
target: MingwX64
exe_ext: exe
# WINDOWS owns the root KotlinMultiplatform metadata modules: it is
# the ONLY host that declares every target (vHostSupportsMingw is
# host-gated), so only its generated .module files carry the full
# variant table - the macOS-published roots of v0.1.15 had NO
# mingwX64 variants and Windows consumers could not resolve them.
# The host-independent jvm jar (:material-symbols) and the bridge
# plugin ride along on the same job.
tasks: >-
publishMingwX64PublicationToGitHubPackagesRepository
publishKotlinMultiplatformPublicationToGitHubPackagesRepository
publishJvmPublicationToGitHubPackagesRepository
:compose-desktop-native-bridge:publishAllPublicationsToGitHubPackagesRepository
runs-on: ${{ matrix.runner }}
permissions:
contents: write # attach release binaries to the tag's GitHub Release
packages: write # publish Maven artifacts to GitHub Packages
env:
PUBLISH_VERSION: ${{ github.ref_name }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
# SDL3's Linux static build needs the host graphics / audio / input dev
# headers so its CMake configure can pick up wayland / X11 / ALSA / etc.
# libxtst-dev is required whenever X11 is enabled (SDL3 gates XTEST behind
# a hard-error unless -DSDL_X11_XTEST=OFF is passed).
- name: Install Linux SDL build deps
if: matrix.host == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential cmake pkg-config \
libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxi-dev \
libxinerama-dev libxss-dev libxkbcommon-dev libxkbcommon-x11-dev \
libxtst-dev \
libwayland-dev wayland-protocols \
libgl1-mesa-dev libegl1-mesa-dev \
libasound2-dev libpulse-dev libudev-dev \
libdbus-1-dev libibus-1.0-dev \
libfontconfig-dev
# windows-2022 runners pre-install Chocolatey's mingw-w64 at C:\mingw64.
# SDL3 >= 3.4 needs a C++ compiler for its GameInput backend (K/N's
# bundled mingw is C-only) - this pre-install covers that. The setjmp
# intrinsic K/N's LLD can't resolve is aliased to msvcrt's _setjmpex at
# link time via --defsym in sdl3.def.
# Build the static SDL3 lib (scripts/build-sdl/build-sdl.properties). Every
# target renders through Skia now, so all platforms need only base SDL3 for
# the main loop (windowing / input / audio / filesystem) - the from-scratch
# SDL renderer and its SDL3_ttf / SDL3_image / FreeType deps were removed.
# Cached against the manifest + scripts so version bumps invalidate cleanly;
# bump the `v3` key when the toolchain (runner OS, compiler flavour, K/N
# sysroot) changes without a scripts/build-sdl/ edit.
- name: Cache libs/
id: cache-libs
uses: actions/cache@v4
with:
path: libs
key: sdl-libs-v3-${{ matrix.host }}-${{ hashFiles('scripts/build-sdl/**') }}
- name: Build static native deps
if: steps.cache-libs.outputs.cache-hit != 'true'
shell: bash
run: python3 scripts/build-sdl/build-all.py
# src/vendor/ trees are gitignored - a fresh checkout has zero vendored
# androidx.compose.* files. Sync copies them in from the upstream refs
# pinned in scripts/compose-fork/compose.properties. Needs python3 + git;
# both are pre-installed on every runner.
- name: Sync vendored Compose code
shell: bash
run: bash scripts/compose-fork/sync.sh
# GitHub Packages FORBIDS overwriting a file that already exists in a
# version, and maven-publish uploads each publication's files
# SEQUENTIALLY from the first one. So when an upload dies mid-publication
# (the v0.1.13 macOS run lost DNS mid-publish), a blind retry hits 409
# Conflict on the first already-uploaded file and aborts BEFORE reaching
# the missing tail files - the version is poisoned and can never
# complete. A 409 is therefore only benign when the publication had
# already completed; you can't tell from the 409 alone. The fix: between
# attempts, DELETE this host's package versions for the current release
# and re-upload from a clean slate. Each host owns DISJOINT package
# names (roots / -mingwx64 / -jvm / bridge on Windows, -macosarm64 on
# macOS, -linuxx64/-linuxarm64 on Linux), so parallel jobs never touch
# each other's uploads. Uses the run's GITHUB_TOKEN (packages: write);
# a 403 on delete just degrades to the old blind-retry behaviour.
- name: Publish to GitHub Packages
shell: bash
run: |
set -e
VER="${PUBLISH_VERSION#v}"
OWNER="${GITHUB_REPOSITORY%%/*}"
# Keep in sync with the module list in settings.gradle.kts.
MODULES="sdl-core ui ui-graphics ui-text ui-util ui-geometry ui-unit ui-backhandler ui-tooling-preview animation animation-core animation-graphics foundation foundation-layout material3 material-ripple desktop-native-window material-symbols navigation3-ui components-resources"
case "${{ matrix.host }}" in
macos) SUFFIXES="-macosarm64" ;;
linux) SUFFIXES="-linuxx64 -linuxarm64" ;;
windows) SUFFIXES="_ROOT_ -mingwx64" ;;
esac
# Published group per module - mirrors the per-area com.bitsycore groups
# in root build.gradle.kts. Project-only modules (sdl-core, material-symbols)
# fall through to com.bitsycore.compose.sdl; the GitHub Packages Maven
# package name is "<group>.<artifact>".
group_of() {
case "$1" in
ui|ui-graphics|ui-text|ui-util|ui-geometry|ui-unit|ui-backhandler|ui-tooling-preview)
echo "com.bitsycore.compose.ui" ;;
foundation|foundation-layout) echo "com.bitsycore.compose.foundation" ;;
animation|animation-core|animation-graphics) echo "com.bitsycore.compose.animation" ;;
material3) echo "com.bitsycore.compose.material3" ;;
material-ripple) echo "com.bitsycore.compose.material" ;;
components-resources) echo "com.bitsycore.compose.components" ;;
navigation3-ui) echo "com.bitsycore.navigation3" ;;
desktop-native-window) echo "com.bitsycore.compose" ;;
*) echo "com.bitsycore.compose.sdl" ;;
esac
}
host_packages() {
for m in $MODULES; do
g=$(group_of "$m")
for s in $SUFFIXES; do
if [ "$s" = "_ROOT_" ]; then echo "$g.$m"; else echo "$g.$m$s"; fi
done
done
if [ "${{ matrix.host }}" = "windows" ]; then
echo "com.bitsycore.compose.sdl.material-symbols-jvm"
echo "com.bitsycore.compose.sdl.compose-desktop-native-bridge"
echo "com.bitsycore.compose-desktop-native.bridge.com.bitsycore.compose-desktop-native.bridge.gradle.plugin"
fi
}
cleanup_host_versions() {
echo "Deleting this host's partially-published $VER package versions"
host_packages | while read -r pkg; do
vid=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/users/$OWNER/packages/maven/$pkg/versions?per_page=100" \
| jq -r --arg v "$VER" 'if type=="array" then ([.[] | select(.name==$v) | .id] | first // empty) else empty end')
[ -z "$vid" ] && continue
code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/users/$OWNER/packages/maven/$pkg/versions/$vid")
if [ "$code" != "204" ]; then
# Deleting the LAST version of a package is refused (400) -
# a package new in this release: drop the whole package.
code=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"https://api.github.com/users/$OWNER/packages/maven/$pkg")
fi
echo " $pkg@$VER -> HTTP $code"
done
}
for attempt in 1 2 3; do
echo "::group::Publish attempt $attempt"
if ./gradlew ${{ matrix.tasks }} --continue --no-daemon --stacktrace; then
echo "::endgroup::"
exit 0
fi
echo "::endgroup::"
if [ $attempt -lt 3 ]; then
echo "::warning::Publish attempt $attempt failed - cleaning this host's $VER packages and retrying"
cleanup_host_versions || true
sleep 30
fi
done
echo "::error::Publish failed after 3 attempts"
exit 1
# Second target: the self-hosted Reposilite instance. Same publications,
# task names derived from the matrix by swapping the repository name.
# The releases repo has redeployment enabled, so a plain retry after a
# mid-upload failure is safe (no GitHub-Packages-style 409 poisoning).
- name: Publish to maven.bitsycore.com
shell: bash
env:
BITSYCORE_MAVEN_USER: ${{ secrets.BITSYCORE_MAVEN_USER }}
BITSYCORE_MAVEN_TOKEN: ${{ secrets.BITSYCORE_MAVEN_TOKEN }}
run: |
set -e
BITSY_TASKS=$(echo "${{ matrix.tasks }}" | sed 's/GitHubPackagesRepository/BitsycoreRepository/g')
for attempt in 1 2 3; do
echo "::group::Bitsycore publish attempt $attempt"
if ./gradlew $BITSY_TASKS --continue --no-daemon --stacktrace; then
echo "::endgroup::"
exit 0
fi
echo "::endgroup::"
if [ $attempt -lt 3 ]; then
echo "::warning::Bitsycore publish attempt $attempt failed - retrying"
sleep 30
fi
done
echo "::error::Bitsycore publish failed after 3 attempts"
exit 1
# Release binaries for the host target - the binary plus the data.kres
# (bundled composeResources + fonts + subset icon-font). Each app's
# linkReleaseExecutable<Target> auto-runs its data.kres zip task -
# :demo's own copy<Variant>ComposeResources<Target>, :apidemo's
# bridge-plugin package<Variant>ComposeResources<Target> - so data.kres
# is produced next to the .kexe / .exe. On Linux only linuxX64 is built -
# linuxArm64 is skipped since the SDL3 static libs on the ubuntu runner
# are host-arch (x64).
- name: Build demo + apidemo release binaries
shell: bash
run: |
./gradlew \
":demo:linkReleaseExecutable${{ matrix.target }}" \
":apidemo:linkReleaseExecutable${{ matrix.target }}" \
--no-daemon --stacktrace
- name: Package demo + apidemo into zips
shell: bash
run: |
set -euo pipefail
# Lower-case first letter - gradle target tokens are Capitalised but
# the build/bin dir uses the plain target name (macosArm64 etc).
target_dir=$(echo "${{ matrix.target }}" | awk '{print tolower(substr($0,1,1)) substr($0,2)}')
version="${GITHUB_REF_NAME#v}"
mkdir -p dist
for app in demo apidemo; do
src="$app/build/bin/$target_dir/releaseExecutable"
zip_path="$(pwd)/dist/$app-$version-$target_dir.zip"
files=("$src/$app.${{ matrix.exe_ext }}" "$src/data.kres")
# Windows Skia leg ships two runtime sidecars next to the exe - the
# skiko fork DLL and Skia's ICU data (icudtl.dat) - provisioned by the
# bridge plugin after linkReleaseExecutableMingwX64. The exe cannot run
# without them, so include them (and fail loudly if a provision regressed).
if [ "${{ matrix.exe_ext }}" = "exe" ]; then
for sidecar in skiko-windows-x64.dll icudtl.dat; do
if [ ! -f "$src/$sidecar" ]; then
echo "::error::Missing Windows runtime sidecar: $src/$sidecar"; exit 1
fi
files+=("$src/$sidecar")
done
fi
python3 -c 'import sys,os,zipfile; z=zipfile.ZipFile(sys.argv[1],"w",zipfile.ZIP_DEFLATED,compresslevel=9); [z.write(f,os.path.basename(f)) for f in sys.argv[2:]]; z.close()' \
"$zip_path" "${files[@]}"
done
ls -la dist/
- name: Attach demo + apidemo zips to GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: dist/*.zip
fail_on_unmatched_files: true