Continuous Integration for Games

A game’s builds for every platform can run on a CI service on each release (or on demand), from the same commands a developer runs by hand. They build from the published engine – the acron wheel of the version the game pins, and the Acron SDKs for C++ – downloaded from the Acron archive; a game never compiles the engine itself.

acronmanager ci init (the launcher’s Set up CI button) writes such a workflow for GitHub Actions or Gitea Actions from the game’s pyproject.toml:

acronmanager ci init                    # .github/workflows/acron.yml
acronmanager ci init --provider gitea   # .gitea/workflows/acron.yml
acronmanager ci init --targets web,android

What is generated

One job per platform the game exports to:

Job

Python game

C++ game

desktop-<os>

acronmanager dist -p <platform> (build_apps), one job per OS: linux, macos, windows

acronmanager dist (the release against the desktop SDK, or the dist command)

web

acronmanager dist --web

the same, when [tool.acron.web] exists

android

acronmanager dist --android

the same, when [tool.acron.android] exists

wii

–

acronmanager dist --wii, when [tool.acron.wii] exists

Each job checks out the repository, sets up uv, runs uv sync (and installs acronmanager from the archive when the game does not depend on it), runs the export, and uploads dist/ as the job’s artifact. On GitHub, the Wii job runs in the devkitpro/devkitppc container; Android jobs install the platform and build tools they need, and the NDK an Android SDK names is installed on first use.

By default the jobs run when started by hand (Actions > Acron > Run workflow) and when a tag v* is pushed.

Secrets

Set these in the repository’s settings (Secrets and variables > Actions):

UV_INDEX_ACRON_ARCHIVE_USERNAME, UV_INDEX_ACRON_ARCHIVE_PASSWORD

Required. An account on archive.acronengine.com with read access and its token: the engine comes from there. The jobs set them as their environment under these same names, which uv (the acron-archive index) and acronmanager (the SDKs) read – as the launcher sets them on a developer’s machine.

ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_ALIAS, ANDROID_KEYSTORE_PASSWORD

Optional: the key the APK is signed with for a store (base64 -w0 release.jks gives the first; see Signing). Without them the APK is signed with a debug key, which is fine for testing but cannot be published. The jobs hand the key to acronmanager as ACRON_ANDROID_KEYSTORE (and _ALIAS, _PASSWORD), and to a packaging script of the game’s own as KEYSTORE, KEYSTORE_ALIAS and KEYSTORE_PASSWORD.

Which engine

A Python game gets the acron wheel of the version it pins (acron==... in [project] dependencies). A C++ game gets each SDK from the archive: the desktop SDK of [tool.acron.desktop] version (pin a release version there, or the job takes the latest), and the web, Android and Wii SDKs as [tool.acron.web/android/wii] sdk name them, else the archive’s latest. A game that lays its SDKs out with a script of its own runs that script in setup (below).

Settings

ci init records its choices in [tool.acron.ci], which every later acronmanager ci update regenerates the workflow from:

[tool.acron.ci]
provider = "github"                      # or "gitea"
targets = ["desktop", "web", "android"]  # default: all the game supports
desktop = ["linux", "macos"]             # default: linux, macos, windows
tags = ["v*"]                            # release tags ([] = none)
branches = []                            # also run on pushes to these
python = "3.14"                          # the Python the engine wheels are built for
timeout = 120                            # minutes per job
runners = { linux = "ubuntu-24.04", wii = ["self-hosted", "Linux"] }
setup = { android = ["tools/fetch-data.sh"] }  # run before the export
artifacts = { android = ["dist/*.apk"] }       # default: dist/
env = { MYGAME_RELEASE = "1" }
# platforms = { linux = "linux_x86_64" }  # Python: build_apps' platform per OS

setup holds commands a job runs before the export (data a game downloads or generates, a program it ships); it is keyed by the target or by the job (desktop-macos).

A Python game’s desktop job builds its own OS’s platform: linux_x86_64, macosx_11_0_arm64 or win_amd64 (platforms changes them). When [tool.acron.build_apps] names platforms itself, each desktop job runs plain acronmanager dist and builds all of them.

Runners

On GitHub the jobs use GitHub’s runners (ubuntu-24.04, macos-14, windows-2022) unless runners names others, such as ["self-hosted", "macOS"]; a self-hosted Wii runner needs devkitPro in /opt/devkitpro, an Android one a JDK.

On Gitea (--provider gitea) the jobs run on self-hosted act_runner machines, by default labelled linux-x64, macos-arm64 and windows-x64. The steps are plain shell (such runners cannot be relied on to run JavaScript actions), so the machines need git, curl and uv (installed by the job when missing); the jobs’ files are kept on the runner, in $ACRON_ARTIFACT_DIR/<repository>/<run>/<job>/.

Your own changes

The generated parts of the workflow sit between marker comments:

# >>> acronmanager:web sha256=... >>>
  web:
    ...
# <<< acronmanager:web <<<

acronmanager ci update rewrites only those, and keeps everything else: comments, and jobs of your own after the last generated one. A generated part changed by hand is not overwritten – put the change into [tool.acron.ci] instead, or outside the markers, or replace it with acronmanager ci update --force.

acronmanager ci check exits with an error when the workflow does not parse, lacks a job for a target, or no longer matches [tool.acron.ci]; it fits a pre-commit hook.

Testing against an engine build

The engine’s own release builds real projects from its new wheels and SDKs before it publishes them, through these variables, which also let a game be built against an engine that is not published:

ACRON_WHEEL

the desktop wheel acronmanager dist freezes instead of the pinned one

ACRON_WEB_WHEEL, ACRON_ANDROID_WHEEL

the web and Android wheels (Python)

ACRON_DESKTOP_SDK, ACRON_WEB_SDK, ACRON_ANDROID_SDK, ACRON_WII_SDK

an SDK directory or .tar.gz instead of the archive’s (C++)