Continuous Integration¶
Tempo ships reusable GitHub Actions workflows so your project can build, package, run, test and release without writing pipeline code from scratch.
They live in .github/workflows.
TempoSample's
tempo_sample_build_and_package.yml
is a good reference for calling them.
| Workflow | Purpose |
|---|---|
build_and_package.yml |
Build, package, and optionally release your Tempo project. |
test_packaged.yml |
Run client API tests against a packaged artifact. Language-agnostic and reusable. |
publish_engine_mods.yml |
Publish a pre-modded Unreal image to your private GHCR. |
prune_engine_mods.yml |
Keep only recent engine-mod image tags. |
Prerequisites¶
You'll need EPIC_DOCKER_USERNAME and EPIC_DOCKER_TOKEN secrets configured, to pull Epic's base
Unreal image.
Speeding up builds with a pre-modded engine image¶
Tempo modifies the Unreal engine in place via patches in EngineMods/. By default
build_and_package.yml applies these inside each CI run, which adds ~10–15 minutes and
consumes the GitHub Actions cache budget — 10 GB on the free tier, shared with the per-commit
build cache.
For larger projects you can opt into a pre-modded Unreal image hosted on your own private GHCR, which the build workflow pulls instead of re-applying mods every run.
1. Add a publish workflow¶
name: Publish Pre-Modded Engine Image
on:
push:
branches: [main]
paths:
- 'Plugins/Tempo/EngineMods/**'
- 'Plugins/Tempo/Dockerfile'
- 'Plugins/Tempo/Scripts/InstallEngineMods.sh'
workflow_dispatch:
jobs:
publish:
permissions:
contents: read
packages: write
strategy:
matrix:
unreal_version: ["5.7", "5.8"]
fail-fast: false
uses: tempo-sim/Tempo/.github/workflows/publish_engine_mods.yml@main
with:
unreal_version: ${{ matrix.unreal_version }}
image_name: ghcr.io/${{ github.repository_owner }}/tempo-unreal-modded
tempo_root: Plugins/Tempo # adjust to your Tempo submodule path
secrets: inherit
prune:
needs: publish
permissions:
packages: write
uses: tempo-sim/Tempo/.github/workflows/prune_engine_mods.yml@main
with:
package_name: tempo-unreal-modded
package_owner: ${{ github.repository_owner }}
secrets: inherit
2. Point your build workflow at it¶
Pass engine_mods_image: ghcr.io/<your-org>/tempo-unreal-modded to your existing
build_and_package.yml caller, and grant it packages: read.
The build workflow computes the EngineMods hash, attempts a docker pull of the matching tag, and
skips the in-workflow engine-mods install when the pull succeeds. If the pull fails for any
reason — image not yet published for the current hash, permissions misconfigured — the workflow
falls back to the in-workflow path. There is no breakage, just no speedup.
Setup notes¶
- Run your new
publish_engine_modsworkflow manually once viaworkflow_dispatchbefore merging theengine_mods_imagechange to your build workflow, so the first image exists when the build runs. - The published image contains UE-derived content, which the Unreal Engine EULA does not
permit redistributing publicly. Your org's GHCR package-creation policy must be set to
"Private" (under
Settings → Packagesin the org admin), and the package will inherit private visibility on first push. If your org's policy allows public, flip the package to private via its settings page after the first push. - The
prunejob keeps only the most recent tag per Unreal version, which is enough since EngineMods rarely change. Passkeep: 3(or higher) for a longer history.
Running the test suites¶
tempo_build_and_package.yml builds and packages once, uploads the artifact, then fans out to
the reusable test_packaged.yml — parallel jobs per (Unreal version × test group), across both the
Python and Rust clients. Each job downloads the same artifact, so the expensive build runs once
while tests run in parallel.
Things that only bite in CI¶
Clean builds are slow
A fully clean CI run takes about two hours. Cache entries expire after a configurable
retention period (GitHub's default is 7 days; set it per repository under
Settings → Actions → General), and are also evicted once the repository exceeds its cache
size limit. Either way, any fix has to work from a cold start — a change that only works
incrementally will eventually meet a run with nothing cached.
Cold packages need -skipiostore
A clean CI package can crash because iostore cannot find engine content under Saved/Temp.
Pass -skipiostore in CI.