Files
EVEngine/.github/workflows/ci.yml
T
2026-09-12 06:53:52 +08:00

1718 lines
71 KiB
YAML

# Continuous integration: daily Debug on the development branch (push/PR), plus a weekly run.
# Reusable via workflow_call (build_type: debug | release | both) for releases.
# Each platform runs as its own parallel job:
# windows → win32 debug (+ unit tests); windows-release when asked
# android → android debug; android-release when asked
# macos → macosx debug (+ unit tests); macos-release when asked
# ios → ios debug; ios-release when asked
# linux → linux debug (+ unit tests); linux-release when asked
# webgpu → webgpu (Emscripten/WASM) release
#
# iOS signing (optional repo secrets):
# IOS_CERTIFICATE_P12_BASE64, IOS_CERTIFICATE_PASSWORD, IOS_DEVELOPMENT_TEAM
# IOS_PROVISIONING_PROFILE_BASE64 (optional)
# Without them, iOS builds unsigned (compile-only; cannot install to a device).
name: CI
on:
workflow_call:
inputs:
build_type:
description: debug | release | both
type: string
default: debug
ref:
description: git ref to check out (release branch or moved tag)
type: string
default: ""
push:
branches: [dev]
pull_request:
branches: [dev]
schedule:
# Weekly: Monday 00:00 CST ≈ Sunday 16:00 UTC
- cron: "0 16 * * 0"
workflow_dispatch:
inputs:
build_type:
description: debug | release | both
type: choice
default: debug
options: [debug, release, both]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# Single source of truth: repository variables (Settings -> Secrets and
# variables -> Actions -> Variables), falling back to these defaults.
VULKAN_VERSION: ${{ vars.VULKAN_VERSION || '1.4.304.1' }}
# Aligned to the NDK preinstalled on the windows-latest / macos-latest runner
# images (27.3.13750724), so setup-android downloads nothing extra.
ANDROID_NDK_VERSION: ${{ vars.ANDROID_NDK_VERSION || '27.3.13750724' }}
ANDROID_API: ${{ vars.ANDROID_API || '34' }}
jobs:
change-scope:
name: Classify CI change scope
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
windows: ${{ steps.scope.outputs.windows }}
android: ${{ steps.scope.outputs.android }}
macos: ${{ steps.scope.outputs.macos }}
ios: ${{ steps.scope.outputs.ios }}
linux: ${{ steps.scope.outputs.linux }}
asan: ${{ steps.scope.outputs.asan }}
fuzz: ${{ steps.scope.outputs.fuzz }}
webgpu: ${{ steps.scope.outputs.webgpu }}
tools: ${{ steps.scope.outputs.tools }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0
- name: Classify changed paths
id: scope
env:
EVENT_NAME: ${{ github.event_name }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
python3 scripts/ci_change_scope.py \
--base "$BASE_SHA" --head "$HEAD_SHA" --github-output "$GITHUB_OUTPUT"
else
python3 scripts/ci_change_scope.py --all --github-output "$GITHUB_OUTPUT"
fi
# Fast source-only gate for formatting, module boundaries, architecture
# contracts and the Python scripts that make later CI results trustworthy.
source-quality:
name: Source quality
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0
- name: Install source-quality tools
run: |
# This job needs Ubuntu packages only. Leave the runner's Chrome
# installation and repository configuration intact for browser jobs.
apt_empty_parts="$(mktemp -d)"
chmod 755 "$apt_empty_parts"
trap 'rmdir "$apt_empty_parts"' EXIT
apt_sources=(
-o "Dir::Etc::sourcelist=$GITHUB_WORKSPACE/.github/apt/ubuntu-noble.sources"
-o "Dir::Etc::sourceparts=$apt_empty_parts"
)
sudo apt-get "${apt_sources[@]}" update
sudo apt-get "${apt_sources[@]}" install -y clang-format-18
sudo ln -sf /usr/bin/clang-format-18 /usr/local/bin/clang-format
python3 -m pip install --user --quiet pillow ruff
- name: Check changed lines are clang-format clean
run: bash .github/scripts/check-format.sh "${{ github.event.pull_request.base.sha || 'origin/dev' }}"
- name: Check module dependency layering
timeout-minutes: 5
run: python3 scripts/module_depgraph.py --check
- name: Check script bindings are documented (all modules)
timeout-minutes: 5
run: python3 scripts/check_bindings.py --strict
- name: Check binding gap ownership and budget metadata
timeout-minutes: 5
run: python3 scripts/check_binding_gap_metadata.py
- name: Check declared module layers vs includes
timeout-minutes: 5
run: python3 scripts/module_depgraph.py --check-layers
- name: Report dependency graph
timeout-minutes: 5
if: always()
run: python3 scripts/module_depgraph.py
- name: Check test source manifest
timeout-minutes: 5
run: python3 scripts/check_test_manifest.py
- name: Check critical return-value diagnostics
timeout-minutes: 5
run: python3 scripts/check_nodiscard.py
- name: Check release version consistency
timeout-minutes: 5
run: python3 scripts/release.py check-versions
- name: Check bounded quality debt and profile matrix
timeout-minutes: 5
run: make check/quality
- name: Audit top-level architecture contracts and changed API shapes
timeout-minutes: 5
run: |
python3 scripts/check_architecture_contracts.py \
--base "${{ github.event.pull_request.base.sha || github.event.before || 'HEAD^' }}"
- name: Ruff lint CI and repository scripts
timeout-minutes: 5
run: python3 -m ruff check scripts
- name: Test CI and repository scripts
timeout-minutes: 10
run: python3 -m unittest discover -s scripts/tests -p "test_*.py" -v
profile-matrix:
name: Profile ${{ matrix.profile }}
# Pull requests already run the toolchain-free profile contract check in
# source-quality. Full configure/build/smoke coverage belongs to dev
# pushes, scheduled/manual runs and release workflow calls.
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && github.event_name != 'pull_request' }}
runs-on: ubuntu-24.04
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
profile: [minimal, 2d, 3d, procgen-core-only, physics-core-only, asset-core-only, headless, server]
env:
PROFILE: ${{ matrix.profile }}
PROFILE_BUILD_ROOT: /tmp/evengine-profile-matrix
PROFILE_JOBS: 2
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install profile toolchain
timeout-minutes: 15
run: |
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- -o Acquire::Retries=2
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build libvulkan-dev \
libx11-dev libxi-dev libxcursor-dev libxrandr-dev libxinerama-dev \
libxext-dev libxss-dev libwayland-dev libxkbcommon-dev
- name: Configure profile
timeout-minutes: 30
run: make profile/configure
- name: Build independent profile smoke target
timeout-minutes: 35
run: make profile/build
- name: Run independent capability smoke probes
timeout-minutes: 10
run: make profile/smoke
tool-tests:
name: Standalone Python tools
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.tools == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install Python test dependencies
timeout-minutes: 10
run: |
python3 -m pip install --user --quiet \
pytest pydantic requests numpy opencv-python-headless pillow ffmpy ruff
- name: Ruff lint standalone tools
timeout-minutes: 5
run: python3 -m ruff check tools
- name: Test asset-pipeline-agent
timeout-minutes: 5
run: cd tools/asset-pipeline-agent && python3 -m unittest discover -s tests -v
- name: Test scene-qc-agent
timeout-minutes: 5
run: cd tools/scene-qc-agent && python3 -m unittest discover -s tests -v
- name: Test creative-brain
timeout-minutes: 5
run: cd tools/creative-brain && python3 -m pytest -q
- name: Test normal-crafter (client)
timeout-minutes: 5
run: cd tools/normal-crafter && python3 -m pytest -q test
- name: Test story-scene-agent
timeout-minutes: 5
run: PYTHONPATH=tools/creative-brain python3 -m pytest -q tools/story-scene-agent/tests
- name: Test vision-prefilter
timeout-minutes: 5
run: cd tools/vision-prefilter && python3 -m pytest -q test
windows:
name: Windows
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.windows == 'true' && (github.event_name != 'workflow_call' || inputs.build_type == 'debug' || inputs.build_type == 'both') }}
runs-on: windows-latest
# This lane builds and tests both Vulkan and native Dawn configurations.
# A cold Windows runner needs more than 90 minutes for the two independent
# unit_test builds; keep the per-step limits below as the hang guards.
timeout-minutes: 180
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-windows-x64
# Pull-request cache entries are scoped to refs/pull/*/merge and cannot
# warm other PRs. Let PRs consume the dev cache without duplicating it.
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
defaults:
run:
shell: bash
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install build tools
timeout-minutes: 15
shell: pwsh
run: |
choco install make ninja -y --no-progress
echo "VS_GENERATOR=Visual Studio 17 2022" >> $env:GITHUB_ENV
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Identify third-party toolchain
id: third-party-toolchain
timeout-minutes: 5
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$vsRoot = & $vswhere -latest -products * `
-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
-property installationPath
$toolsetFile = Join-Path $vsRoot "VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"
$toolset = (Get-Content $toolsetFile -Raw).Trim()
$sdkVersion = (& cmd.exe /d /s /c `
'cmake\with-msvc.cmd cmd.exe /d /c echo %WindowsSDKVersion%').Trim().TrimEnd('\')
$cmakeVersion = ((cmake --version | Select-Object -First 1) -split '\s+')[-1]
"id=msvc-$toolset-winsdk-$sdkVersion-cmake-$cmakeVersion" >> $env:GITHUB_OUTPUT
- name: Restore win32 third-party cache
id: third-party-cache
timeout-minutes: 15
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: build/third-party-binary/win32-debug
key: third-party-v1-${{ runner.os }}-${{ runner.arch }}-win32-debug-${{ steps.third-party-toolchain.outputs.id }}-${{ hashFiles('cmake/third_party_pin.cmake', 'cmake/third_party_cache_version.txt', 'cmake/patch_third_party.cmake', 'cmake/patches/**', 'cmake/write_third_party_version.cmake') }}
- name: Verify restored win32 third-party cache
if: steps.third-party-cache.outputs.cache-hit == 'true'
timeout-minutes: 5
run: bash .github/scripts/verify-third-party-cache.sh build/third-party-binary/win32-debug win32 Debug
- name: Install Vulkan SDK
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
install_runtime: true
# Headless Windows runners have no GPU ICD; SwiftShader provides
# VK_KHR_surface so SDL_WINDOW_VULKAN / unit tests can run.
install_swiftshader: true
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup software Vulkan ICD (SwiftShader)
timeout-minutes: 5
run: bash .github/scripts/setup-software-vulkan.sh
- name: Build win32 debug
# A PR that changes the third-party patch/cache key must do a full cold
# dependency plus engine/test build. Recent windows-2025 runners can
# exceed 40 minutes without any compiler failure, so retain a bounded
# hang guard while allowing the cold build to finish.
timeout-minutes: 60
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tp_args=""
if [ "${{ steps.third-party-cache.outputs.cache-hit }}" = "true" ]; then
tp_args="-DEVENGINE_THIRD_PARTY_BINARY_DIR=build/third-party-binary/win32-debug"
fi
make build/win32-debug JOBS="${NUMBER_OF_PROCESSORS:-8}" \
CMAKE_EXTRA_ARGS="-DEVENGINE_ENABLE_STRICT_WARNINGS=ON \
-DEVENGINE_COMPILER_CACHE=OFF $tp_args"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Verify built win32 third-party cache
timeout-minutes: 5
run: bash .github/scripts/verify-third-party-cache.sh build/third-party-binary/win32-debug win32 Debug
- name: Save win32 third-party cache
if: >-
steps.third-party-cache.outputs.cache-hit != 'true' &&
(github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')
timeout-minutes: 15
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: build/third-party-binary/win32-debug
key: ${{ steps.third-party-cache.outputs.cache-primary-key }}
- name: Run win32 unit tests
timeout-minutes: 40
# Headless runners have no WASAPI endpoint (0x80070490); OpenAL Soft null backend.
env:
ALSOFT_DRIVERS: "null"
run: make test/win32-debug
- name: Capture Vulkan parity artifacts
timeout-minutes: 10
env:
ALSOFT_DRIVERS: "null"
EVENGINE_RENDER_PARITY_DIR: ${{ github.workspace }}/artifacts/render-parity
run: ctest --test-dir build/win32-debug -R '^graphics\.backendParity\.' --output-on-failure
- name: Configure native Dawn parity lane
timeout-minutes: 15
shell: pwsh
run: |
cmake\with-msvc.cmd cmake.exe -S . -B build/webgpu-native -G Ninja `
-DCMAKE_BUILD_TYPE=Debug `
-DCMAKE_C_COMPILER="$PWD/cmake/msvc-cl.cmd" `
-DCMAKE_CXX_COMPILER="$PWD/cmake/msvc-cl.cmd" `
-DEVENGINE_BUILD_PLATFORM=webgpu -DBUILD_PLATFORM=webgpu -DBUILD_TESTING=ON `
-DEVENGINE_COMPILER_CACHE=SCCACHE `
-DEVENGINE_THIRD_PARTY_BINARY_DIR="$PWD/build/third-party-binary/win32-debug" `
-DEVENGINE_DOWNLOAD_SPINE_MODELS=OFF `
-DEVENGINE_DOWNLOAD_SKINNED_CHARACTER=OFF
- name: Verify native Dawn platform selection
timeout-minutes: 5
shell: pwsh
run: |
$config = Get-Content build/webgpu-native/src/engine/common/config.h -Raw
$sources = Get-Content build/webgpu-native/src/modules/graphics_src.txt -Raw
if ($config -notmatch '(?m)^#define EVENGINE_WEBGPU\s*$') {
throw 'native Dawn build lost EVENGINE_WEBGPU during configuration'
}
if ($sources -notmatch 'graphics[/\\]webgpu[/\\]' -or
$sources -match 'graphics[/\\]vulkan[/\\]') {
throw 'native Dawn graphics source list does not exclusively select webgpu'
}
- name: Build native Dawn parity lane
# A cold Dawn/Tint build compiles roughly 2,400 targets and reached
# 95% only when the former 40-minute guard fired on windows-latest. The
# full test link reached 2,401/2,403 at 60 minutes as the module matrix
# grew, so retain a bounded 15-minute link/cache variance margin.
timeout-minutes: 75
shell: pwsh
run: cmake\with-msvc.cmd cmake.exe --build build/webgpu-native --target unit_test -j 8
- name: Report native Dawn compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Run native Dawn parity and PCG compute
# Dawn initialization takes roughly two minutes per isolated test on
# the Windows software adapter. Keep per-case process isolation while
# allowing all parity cases plus the PCG compute parity case to complete.
timeout-minutes: 40
shell: pwsh
env:
ALSOFT_DRIVERS: "null"
EVENGINE_RENDER_PARITY_DIR: ${{ github.workspace }}/artifacts/render-parity
run: >-
ctest --test-dir build/webgpu-native
-R '^(graphics\.backendParity\..*|gpgpu\.procgen\.transformParity)$'
--output-on-failure
- name: Compare Vulkan and WebGPU parity images
timeout-minutes: 5
shell: pwsh
run: |
python -m pip install --quiet pillow
python scripts/compare_render_backends.py `
artifacts/render-parity/vulkan artifacts/render-parity/webgpu `
--result-json artifacts/render-parity/ci-result.json
- name: Upload render parity artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: render-parity-windows
path: artifacts/render-parity
if-no-files-found: warn
windows-release:
name: Windows (Release)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.windows == 'true' && (inputs.build_type == 'release' || inputs.build_type == 'both') }}
runs-on: windows-latest
# A cold Release build took 55 minutes and the process-isolated 3510-case
# suite reached 3060/3510 at the former 30-minute test guard. Preserve
# bounded hang detection while budgeting setup, build, and the full suite.
timeout-minutes: 120
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-windows-x64-release
SCCACHE_GHA_RW_MODE: ${{ (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
defaults:
run:
shell: bash
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install build tools
timeout-minutes: 15
shell: pwsh
run: |
choco install make ninja -y --no-progress
echo "VS_GENERATOR=Visual Studio 17 2022" >> $env:GITHUB_ENV
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Install Vulkan SDK
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
install_runtime: true
install_swiftshader: true
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup software Vulkan ICD (SwiftShader)
timeout-minutes: 5
run: bash .github/scripts/setup-software-vulkan.sh
- name: Build win32 release
timeout-minutes: 60
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
make build/win32 JOBS="${NUMBER_OF_PROCESSORS:-8}"
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE -DEVENGINE_ENABLE_ASSERTS=ON"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Run win32 release unit tests
timeout-minutes: 45
env:
ALSOFT_DRIVERS: "null"
run: make test/win32
android:
name: Android
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.android == 'true' && (github.event_name != 'workflow_call' || inputs.build_type == 'debug' || inputs.build_type == 'both') }}
runs-on: windows-latest
# A cold NDK build can consume almost the entire 45-minute build-step budget
# before Gradle packages the finished native library. Keep the step timeout
# below as the hang guard while leaving enough job budget for setup/cleanup.
timeout-minutes: 90
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-android-arm64-debug
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
defaults:
run:
shell: bash
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install build tools
timeout-minutes: 15
shell: pwsh
run: choco install make ninja -y --no-progress
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Setup Java 17
timeout-minutes: 10
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: "17"
- name: Setup Android SDK
timeout-minutes: 20
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
with:
packages: >-
platform-tools
platforms;android-${{ env.ANDROID_API }}
build-tools;${{ env.ANDROID_API }}.0.0
ndk;${{ env.ANDROID_NDK_VERSION }}
- name: Resolve Android SDK / NDK paths
timeout-minutes: 5
run: bash .github/scripts/setup-android-sdk.sh
- name: Write Gradle local.properties
timeout-minutes: 5
run: |
printf 'sdk.dir=%s\n' "$ANDROID_HOME" > platform/android/apk/local.properties
echo "ANDROID_HOME=$ANDROID_HOME"
echo "ANDROID_NDK=$ANDROID_NDK"
test -f "$ANDROID_NDK/build/cmake/android.toolchain.cmake"
- name: Build android debug
timeout-minutes: 60
run: |
test -f "$ANDROID_NDK/build/cmake/android.toolchain.cmake"
make build/android-debug \
ANDROID_SDK="$ANDROID_SDK" \
ANDROID_NDK="$ANDROID_NDK" \
JAVA_HOME="$JAVA_HOME" \
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE" \
ANDROID_JOBS="${NUMBER_OF_PROCESSORS:-8}"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
android-release:
name: Android (Release)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.android == 'true' && (inputs.build_type == 'release' || inputs.build_type == 'both') }}
runs-on: windows-latest
timeout-minutes: 60
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-android-arm64-release
SCCACHE_GHA_RW_MODE: ${{ (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
defaults:
run:
shell: bash
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install build tools
timeout-minutes: 15
shell: pwsh
run: choco install make ninja -y --no-progress
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Setup Java 17
timeout-minutes: 10
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: temurin
java-version: "17"
- name: Setup Android SDK
timeout-minutes: 20
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
with:
packages: >-
platform-tools
platforms;android-${{ env.ANDROID_API }}
build-tools;${{ env.ANDROID_API }}.0.0
ndk;${{ env.ANDROID_NDK_VERSION }}
- name: Resolve Android SDK / NDK paths
timeout-minutes: 5
run: bash .github/scripts/setup-android-sdk.sh
- name: Write Gradle local.properties
timeout-minutes: 5
run: |
printf 'sdk.dir=%s\n' "$ANDROID_HOME" > platform/android/apk/local.properties
test -f "$ANDROID_NDK/build/cmake/android.toolchain.cmake"
- name: Build android release
timeout-minutes: 60
run: |
test -f "$ANDROID_NDK/build/cmake/android.toolchain.cmake"
make build/android \
ANDROID_SDK="$ANDROID_SDK" \
ANDROID_NDK="$ANDROID_NDK" \
JAVA_HOME="$JAVA_HOME" \
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE -DEVENGINE_ENABLE_ASSERTS=ON" \
ANDROID_JOBS="${NUMBER_OF_PROCESSORS:-8}"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
macos:
name: macOS
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.macos == 'true' && (github.event_name != 'workflow_call' || inputs.build_type == 'debug' || inputs.build_type == 'both') }}
runs-on: macos-latest
timeout-minutes: 120
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-macos-arm64-debug
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install tools
timeout-minutes: 10
run: brew install ninja || true
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Install Vulkan SDK (MoltenVK + iOS)
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
destination: ${{ runner.temp }}/VulkanSDK
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve VULKAN_SDK
timeout-minutes: 5
run: bash .github/scripts/resolve-vulkan-macos.sh "${{ runner.temp }}/VulkanSDK"
- name: Build macosx debug
timeout-minutes: 40
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
make build/macosx-debug JOBS="$(sysctl -n hw.ncpu)"
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE"
- name: Run macosx unit tests
timeout-minutes: 30
env:
ALSOFT_DRIVERS: "null"
run: make test/macosx-debug
- name: Configure native Dawn Metal lane
timeout-minutes: 20
run: |
cmake -S . -B build/webgpu-macos -G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DEVENGINE_BUILD_PLATFORM=webgpu -DBUILD_PLATFORM=webgpu -DBUILD_TESTING=ON \
-DEVENGINE_COMPILER_CACHE=SCCACHE \
-DEVENGINE_THIRD_PARTY_BINARY_DIR="$PWD/build/third-party-binary/macosx-debug" \
-DEVENGINE_DOWNLOAD_SPINE_MODELS=OFF \
-DEVENGINE_DOWNLOAD_SKINNED_CHARACTER=OFF
- name: Verify native Dawn Metal platform selection
timeout-minutes: 5
run: |
grep -Eq '^#define EVENGINE_WEBGPU[[:space:]]*$' \
build/webgpu-macos/src/engine/common/config.h
grep -Eq 'graphics[/\\]webgpu[/\\]' \
build/webgpu-macos/src/modules/graphics_src.txt
if grep -Eq 'graphics[/\\]vulkan[/\\]' \
build/webgpu-macos/src/modules/graphics_src.txt; then
echo 'native Dawn graphics source list still contains Vulkan' >&2
exit 1
fi
- name: Build native Dawn Metal lane
timeout-minutes: 40
run: cmake --build build/webgpu-macos --target unit_test -j "$(sysctl -n hw.ncpu)"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Test native Dawn Metal surface and rendering
timeout-minutes: 20
env:
ALSOFT_DRIVERS: "null"
run: |
ctest --test-dir build/webgpu-macos \
-R '^(graphics\.backendParity\.|RenderSystem3D\.(textureCheckerPixels|lightingAffectsPixels))' \
--output-on-failure
macos-release:
name: macOS (Release)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.macos == 'true' && (inputs.build_type == 'release' || inputs.build_type == 'both') }}
runs-on: macos-latest
timeout-minutes: 60
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-macos-arm64-release
SCCACHE_GHA_RW_MODE: ${{ (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install tools
timeout-minutes: 10
run: brew install ninja || true
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Install Vulkan SDK (MoltenVK + iOS)
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
destination: ${{ runner.temp }}/VulkanSDK
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve VULKAN_SDK
timeout-minutes: 5
run: bash .github/scripts/resolve-vulkan-macos.sh "${{ runner.temp }}/VulkanSDK"
- name: Build macosx release
timeout-minutes: 40
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
make build/macosx JOBS="$(sysctl -n hw.ncpu)"
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE -DEVENGINE_ENABLE_ASSERTS=ON"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Run macosx release unit tests
timeout-minutes: 30
env:
ALSOFT_DRIVERS: "null"
run: make test/macosx
ios:
name: iOS
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.ios == 'true' && (github.event_name != 'workflow_call' || inputs.build_type == 'debug' || inputs.build_type == 'both') }}
runs-on: macos-latest
timeout-minutes: 60
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install tools
timeout-minutes: 10
run: brew install ninja || true
- name: Install Vulkan SDK (MoltenVK + iOS)
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
destination: ${{ runner.temp }}/VulkanSDK
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve VULKAN_SDK
timeout-minutes: 5
run: bash .github/scripts/resolve-vulkan-macos.sh "${{ runner.temp }}/VulkanSDK"
- name: Setup iOS signing (optional)
timeout-minutes: 5
env:
IOS_CERTIFICATE_P12_BASE64: ${{ secrets.IOS_CERTIFICATE_P12_BASE64 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
IOS_DEVELOPMENT_TEAM: ${{ secrets.IOS_DEVELOPMENT_TEAM }}
IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
run: bash .github/scripts/setup-ios-signing.sh
- name: Build ios debug
timeout-minutes: 40
run: |
set -euo pipefail
JOBS="$(sysctl -n hw.ncpu)"
if [ "${IOS_SIGNING:-0}" = "1" ] && [ -n "${IOS_DEVELOPMENT_TEAM:-}" ]; then
echo "Building signed iOS (team=$IOS_DEVELOPMENT_TEAM)"
make build/ios-debug \
IOS_DEVELOPMENT_TEAM="$IOS_DEVELOPMENT_TEAM" \
ANDROID_JOBS="$JOBS"
else
echo "Building unsigned iOS"
make build/ios-debug \
IOS_DEVELOPMENT_TEAM= \
ANDROID_JOBS="$JOBS"
fi
ios-release:
name: iOS (Release)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.ios == 'true' && (inputs.build_type == 'release' || inputs.build_type == 'both') }}
runs-on: macos-latest
timeout-minutes: 60
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install tools
timeout-minutes: 10
run: brew install ninja || true
- name: Install Vulkan SDK (MoltenVK + iOS)
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
destination: ${{ runner.temp }}/VulkanSDK
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve VULKAN_SDK
timeout-minutes: 5
run: bash .github/scripts/resolve-vulkan-macos.sh "${{ runner.temp }}/VulkanSDK"
- name: Setup iOS signing (optional)
timeout-minutes: 5
env:
IOS_CERTIFICATE_P12_BASE64: ${{ secrets.IOS_CERTIFICATE_P12_BASE64 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
IOS_DEVELOPMENT_TEAM: ${{ secrets.IOS_DEVELOPMENT_TEAM }}
IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
run: bash .github/scripts/setup-ios-signing.sh
- name: Build ios release
timeout-minutes: 40
run: |
set -euo pipefail
JOBS="$(sysctl -n hw.ncpu)"
if [ "${IOS_SIGNING:-0}" = "1" ] && [ -n "${IOS_DEVELOPMENT_TEAM:-}" ]; then
echo "Building signed iOS (team=$IOS_DEVELOPMENT_TEAM)"
make build/ios \
IOS_DEVELOPMENT_TEAM="$IOS_DEVELOPMENT_TEAM" \
ANDROID_JOBS="$JOBS"
else
echo "Building unsigned iOS"
make build/ios \
IOS_DEVELOPMENT_TEAM= \
ANDROID_JOBS="$JOBS"
fi
linux:
name: Linux
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.linux == 'true' && (github.event_name != 'workflow_call' || inputs.build_type == 'debug' || inputs.build_type == 'both') }}
runs-on: ubuntu-24.04
# Full debug build + per-case unit tests + all-example smoke exceeded the
# previous 60-minute job cap even though each step stayed within its guard.
timeout-minutes: 90
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-linux-x64
# Populate shared caches only from long-lived branches; PR-scoped writes
# are dead stores after that PR closes and quickly evict reusable entries.
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install system packages
# apt update may consume most of ten minutes while retrying a slow
# Ubuntu mirror; keep enough time for the package download itself.
timeout-minutes: 20
run: |
# GitHub-hosted ubuntu-24.04 images sometimes pin the retired
# azure.archive.ubuntu.com mirror, which hangs instead of failing.
# Normalize to the canonical mirror so apt has a reachable source.
sudo sed -i -E 's#https?://[^/]*archive\.ubuntu\.com/ubuntu#https://archive.ubuntu.com/ubuntu#g' \
/etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true
printf 'Acquire::Retries "2";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' | \
sudo tee /etc/apt/apt.conf.d/90eve-timeouts >/dev/null
sudo pkill -f "unattended-upgrade" || true
sudo systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
apt_ok=0
for i in 1 2 3; do
timeout 120 sudo apt-get update -o Acquire::Retries=2 \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- \
-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 && { apt_ok=1; break; }
[ "$i" -lt 3 ] && sleep 20
done
[ "$apt_ok" -eq 1 ] || exit 1
# Package downloads can exceed the old ten-minute step budget.
# Keep bounded transport retries and signature/hash verification.
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build \
libx11-dev libxxf86vm-dev libxi-dev libxcursor-dev \
libxrandr-dev libxinerama-dev libxext-dev libxss-dev \
libgl1-mesa-dev \
libbz2-dev libpng-dev \
libwayland-dev libxkbcommon-dev libasound2-dev \
mesa-vulkan-drivers vulkan-tools \
xvfb ffmpeg python3-pil
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Identify third-party toolchain
id: third-party-toolchain
timeout-minutes: 5
run: |
compiler_target="$(g++ -dumpmachine)"
compiler_version="$(g++ -dumpfullversion -dumpversion)"
cmake_version="$(cmake --version | head -n 1 | awk '{print $3}')"
echo "id=gcc-$compiler_target-$compiler_version-cmake-$cmake_version" >> "$GITHUB_OUTPUT"
- name: Restore linux third-party cache
id: third-party-cache
timeout-minutes: 15
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: build/third-party-binary/linux-debug
key: third-party-v1-ubuntu-24.04-${{ runner.arch }}-linux-debug-${{ steps.third-party-toolchain.outputs.id }}-${{ hashFiles('cmake/third_party_pin.cmake', 'cmake/third_party_cache_version.txt', 'cmake/patch_third_party.cmake', 'cmake/patches/**', 'cmake/write_third_party_version.cmake') }}
- name: Verify restored linux third-party cache
if: steps.third-party-cache.outputs.cache-hit == 'true'
timeout-minutes: 5
run: bash .github/scripts/verify-third-party-cache.sh build/third-party-binary/linux-debug linux Release
- name: Install Vulkan SDK
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup software Vulkan ICD (Lavapipe)
timeout-minutes: 5
run: bash .github/scripts/setup-software-vulkan.sh
- name: Build linux debug
timeout-minutes: 40
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tp_args=""
if [ "${{ steps.third-party-cache.outputs.cache-hit }}" = "true" ]; then
tp_args="-DEVENGINE_THIRD_PARTY_BINARY_DIR=build/third-party-binary/linux-debug"
fi
make build/linux-debug JOBS="$(nproc)" \
CMAKE_EXTRA_ARGS="-DEVENGINE_ENABLE_STRICT_WARNINGS=ON \
-DEVENGINE_COMPILER_CACHE=SCCACHE $tp_args"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Verify built linux third-party cache
timeout-minutes: 5
run: bash .github/scripts/verify-third-party-cache.sh build/third-party-binary/linux-debug linux Release
- name: Save linux third-party cache
if: >-
steps.third-party-cache.outputs.cache-hit != 'true' &&
(github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')
timeout-minutes: 15
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: build/third-party-binary/linux-debug
key: ${{ steps.third-party-cache.outputs.cache-primary-key }}
- name: Verify resource format fixture integrity
timeout-minutes: 5
run: python3 scripts/resource_format_suite.py --report build/resource-format-inventory.json
- name: Run linux unit tests
timeout-minutes: 30
env:
ALSOFT_DRIVERS: "null"
EVENGINE_FORMAT_EXPORT_DIR: "${{ github.workspace }}/build/resource-format-exports"
run: |
# CTEST_JOBS=2: Lavapipe renders several window/GPU tests in
# parallel; -j4 hits a pre-existing race (voxel.render.sceneHillsIsland
# SEGFAULT) that is not reproducible in isolation.
xvfb-run -a -s "-screen 0 1280x800x24" make test/linux-debug CTEST_JOBS=2
- name: Verify resource test results without skipped or warning-only cases
timeout-minutes: 20
env:
ALSOFT_DRIVERS: "null"
EVENGINE_FORMAT_EXPORT_DIR: "${{ github.workspace }}/build/resource-format-exports"
run: |
xvfb-run -a -s "-screen 0 1280x800x24" python3 scripts/resource_format_suite.py \
--build-dir build/linux-debug --report build/resource-format-report.json
- name: Independently decode resource image exports
timeout-minutes: 5
run: |
/usr/bin/python3 scripts/check_resource_format_exports.py build/resource-format-exports \
--report build/resource-format-export-report.json
- name: Run headless GPU tests without Xvfb
timeout-minutes: 15
env:
ALSOFT_DRIVERS: "null"
EVENGINE_VIEW_SECONDS: "0.3"
EVENGINE_PERF_FRAMES: "30"
run: |
# The headless GPU suites must run without any display server,
# proving the headless-vs-window policy in docs/dev/测试覆盖.md §0.
# (These files never create an SDL window: canvas/device readback only.)
ctest --test-dir build/linux-debug --output-on-failure -j 2 \
-R '^bundle/(graphics_font|TextureSampler|gpgpu|tensor|virtualgeometry_gpu|graphics_headless)\.cpp$'
- name: Smoke all examples (headless)
timeout-minutes: 20
env:
ALSOFT_DRIVERS: "null"
run: |
xvfb-run -a -s "-screen 0 1280x800x24" bash scripts/smoke_examples.sh
- name: DevLab developer-experience test (hot reload + DevTools)
timeout-minutes: 5
env:
ALSOFT_DRIVERS: "null"
run: |
xvfb-run -a -s "-screen 0 1280x800x24" bash scripts/test_devlab.sh "$PWD/build/linux-debug/src/engine/eve"
linux-release:
name: Linux (Release)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.linux == 'true' && (inputs.build_type == 'release' || inputs.build_type == 'both') }}
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-linux-x64-release
SCCACHE_GHA_RW_MODE: ${{ (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install system packages
# Keep parity with the debug lane's full native dependency install.
timeout-minutes: 15
run: |
# GitHub-hosted ubuntu-24.04 images sometimes pin the retired
# azure.archive.ubuntu.com mirror, which hangs instead of failing.
# Normalize to the canonical mirror so apt has a reachable source.
sudo sed -i -E 's#https?://[^/]*archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' \
/etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true
printf 'Acquire::Retries "2";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' | \
sudo tee /etc/apt/apt.conf.d/90eve-timeouts >/dev/null
sudo pkill -f "unattended-upgrade" || true
sudo systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
apt_ok=0
for i in 1 2 3; do
timeout 120 sudo apt-get update -o Acquire::Retries=2 \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- \
-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 && { apt_ok=1; break; }
[ "$i" -lt 3 ] && sleep 20
done
[ "$apt_ok" -eq 1 ] || exit 1
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build \
libx11-dev libxxf86vm-dev libxi-dev libxcursor-dev \
libxrandr-dev libxinerama-dev libxext-dev libxss-dev \
libgl1-mesa-dev \
libbz2-dev libpng-dev \
libwayland-dev libxkbcommon-dev libasound2-dev \
mesa-vulkan-drivers vulkan-tools \
xvfb
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Install Vulkan SDK
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup software Vulkan ICD (Lavapipe)
timeout-minutes: 5
run: bash .github/scripts/setup-software-vulkan.sh
- name: Build linux release
timeout-minutes: 40
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
make build/linux JOBS="$(nproc)"
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE -DEVENGINE_ENABLE_ASSERTS=ON"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Run linux release unit tests
timeout-minutes: 30
env:
ALSOFT_DRIVERS: "null"
run: xvfb-run -a -s "-screen 0 1280x800x24" make test/linux
linux-asan:
name: Linux (ASan+UBSan)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.asan == 'true' && (github.event_name != 'workflow_call' || inputs.build_type == 'debug' || inputs.build_type == 'both') }}
runs-on: ubuntu-24.04
# Allow the 100-minute cold optimized sanitizer build and 90-minute test
# guards plus dependency/toolchain setup and cache reporting.
timeout-minutes: 270
env:
# Leak detection off: the zeroerr discovery run during the build (and a
# few tests) allocates process-lifetime state that LeakSanitizer flags;
# the sanitizer job's value is UAF/OOB/UB, not leaks.
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1"
UBSAN_OPTIONS: "halt_on_error=1:print_stacktrace=1"
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-linux-x64-asan
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install system packages
# Sanitizer jobs use the same full dependency set as the debug lane.
timeout-minutes: 20
run: |
sudo sed -i -E 's#https?://[^/]*archive\.ubuntu\.com/ubuntu#https://archive.ubuntu.com/ubuntu#g' \
/etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true
printf 'Acquire::Retries "2";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' | \
sudo tee /etc/apt/apt.conf.d/90eve-timeouts >/dev/null
sudo pkill -f "unattended-upgrade" || true
sudo systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
apt_ok=0
for i in 1 2 3; do
timeout 120 sudo apt-get update -o Acquire::Retries=2 \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- \
-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 && { apt_ok=1; break; }
[ "$i" -lt 3 ] && sleep 20
done
[ "$apt_ok" -eq 1 ] || exit 1
# Package downloads can exceed the old ten-minute step budget.
# Keep bounded transport retries and signature/hash verification.
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build \
libx11-dev libxxf86vm-dev libxi-dev libxcursor-dev \
libxrandr-dev libxinerama-dev libxext-dev libxss-dev \
libgl1-mesa-dev \
libbz2-dev libpng-dev \
libwayland-dev libxkbcommon-dev libasound2-dev \
mesa-vulkan-drivers vulkan-tools \
xvfb
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Install Vulkan SDK
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup software Vulkan ICD (Lavapipe)
timeout-minutes: 5
run: bash .github/scripts/setup-software-vulkan.sh
- name: Build linux RelWithDebInfo (ASan+UBSan)
# PRs cannot populate the shared sccache. A new compiler flag set is
# therefore a cold build; run 34596121051 reached 67% after 55 minutes.
timeout-minutes: 100
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
make build/linux-asan JOBS="$(nproc)"
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Run linux unit tests (ASan+UBSan)
# Keep every case process-isolated, but use all CPUs exposed by the
# GitHub runner instead of hard-coding a runner-size assumption.
timeout-minutes: 90
env:
ALSOFT_DRIVERS: "null"
run: |
xvfb-run -a -s "-screen 0 1280x800x24" make test/linux-asan CTEST_JOBS="$(nproc)"
linux-coverage:
name: Linux (Coverage)
# Coverage is a trend signal rather than a per-PR gate; run weekly and on
# demand so it does not double CI minutes for every push.
needs: [source-quality]
if: ${{ needs.source-quality.result == 'success' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') }}
runs-on: ubuntu-24.04
timeout-minutes: 90
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-linux-x64-coverage
SCCACHE_GHA_RW_MODE: ${{ (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/v') || startsWith(inputs.ref, 'v')) && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Install system packages
# Keep parity with the other full native Linux dependency installs.
timeout-minutes: 15
run: |
sudo sed -i -E 's#https?://[^/]*archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' \
/etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true
printf 'Acquire::Retries "2";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' | \
sudo tee /etc/apt/apt.conf.d/90eve-timeouts >/dev/null
sudo pkill -f "unattended-upgrade" || true
sudo systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
apt_ok=0
for i in 1 2 3; do
timeout 120 sudo apt-get update -o Acquire::Retries=2 \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- \
-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 && { apt_ok=1; break; }
[ "$i" -lt 3 ] && sleep 20
done
[ "$apt_ok" -eq 1 ] || exit 1
sudo apt-get install -y --no-install-recommends \
build-essential cmake ninja-build \
libx11-dev libxxf86vm-dev libxi-dev libxcursor-dev \
libxrandr-dev libxinerama-dev libxext-dev libxss-dev \
libgl1-mesa-dev \
libbz2-dev libpng-dev \
libwayland-dev libxkbcommon-dev libasound2-dev \
mesa-vulkan-drivers vulkan-tools \
xvfb gcovr
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Install Vulkan SDK
timeout-minutes: 20
uses: jakoch/install-vulkan-sdk-action@37effcfa045411f8bfbbda26df2fd1b3bf3436fa # v1
with:
vulkan_version: ${{ env.VULKAN_VERSION }}
cache: true
cache_save_if: ${{ github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup software Vulkan ICD (Lavapipe)
timeout-minutes: 5
run: bash .github/scripts/setup-software-vulkan.sh
- name: Build linux debug (coverage)
timeout-minutes: 40
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
make build/linux-coverage JOBS="$(nproc)"
CMAKE_EXTRA_ARGS="-DEVENGINE_COMPILER_CACHE=SCCACHE"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Run linux unit tests (coverage)
timeout-minutes: 40
env:
ALSOFT_DRIVERS: "null"
run: |
xvfb-run -a -s "-screen 0 1280x800x24" make test/linux-coverage CTEST_JOBS=2
- name: Generate coverage report
timeout-minutes: 10
run: |
gcovr -r . --object-directory build/linux-coverage \
--exclude 'build/' --exclude 'external/' --exclude 'third-party/' --exclude 'test/' \
--gcov-ignore-parse-errors \
--gcov-ignore-errors=no_working_dir_found \
-x build/linux-coverage/coverage.xml
gcovr -r . --object-directory build/linux-coverage \
--exclude 'build/' --exclude 'external/' --exclude 'third-party/' --exclude 'test/' \
--gcov-ignore-parse-errors \
--gcov-ignore-errors=no_working_dir_found \
--html --html-details -o build/linux-coverage/coverage.html
- name: Upload coverage artifact
timeout-minutes: 5
uses: actions/upload-artifact@v7
with:
name: linux-coverage
path: |
build/linux-coverage/coverage.xml
build/linux-coverage/coverage.html
linux-fuzz:
name: Linux (zeroerr fuzz)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.fuzz == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 20
env:
# zeroerr's structured custom mutator retains IRObject allocations for
# the process lifetime. Keep ASan's UAF/OOB checks without treating that
# upstream runner behavior as an engine leak.
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=1"
UBSAN_OPTIONS: "halt_on_error=1:print_stacktrace=1"
EVENGINE_FUZZ_RUNS: ${{ github.event_name == 'schedule' && '50000' || '2000' }}
EVENGINE_FUZZ_SEED: "1"
SCCACHE_GHA_ENABLED: "true"
SCCACHE_GHA_VERSION: evengine-cpp-v1-linux-x64-fuzz
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main') && 'READ_WRITE' || 'READ_ONLY' }}
SCCACHE_DIRECT: "false"
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install fuzz toolchain
timeout-minutes: 5
run: |
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
sudo apt-get update \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- -o Acquire::Retries=2
sudo apt-get install -y --no-install-recommends clang ninja-build
- name: Setup C/C++ compilation cache
timeout-minutes: 10
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- name: Build zeroerr JSON fuzz harness
timeout-minutes: 5
run: >-
make build/linux-fuzz JOBS="$(nproc)"
CMAKE_EXTRA_ARGS="-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache"
- name: Report C/C++ compilation cache
if: always()
timeout-minutes: 5
run: sccache --show-stats
- name: Fuzz shared JSON parser
timeout-minutes: 5
run: make fuzz/linux JOBS="$(nproc)"
- name: Upload reproducer
if: failure()
uses: actions/upload-artifact@v7
with:
name: json-fuzz-reproducer
path: |
build/linux-fuzz/crash-*
build/linux-fuzz/leak-*
build/linux-fuzz/oom-*
build/linux-fuzz/timeout-*
if-no-files-found: ignore
webgpu:
name: WebGPU (WASM)
needs: [source-quality, change-scope]
if: ${{ needs.source-quality.result == 'success' && needs.change-scope.outputs.webgpu == 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
EMSCRIPTEN_VERSION: "6.0.9"
EMDAWNWEBGPU_RELEASE: "v20260423.175430"
# Do not add sccache via EM_COMPILER_WRAPPER here: emsdk's Clang commands
# carry -Xclang flags that sccache v0.16 treats as non-cacheable, adding
# wrapper overhead without storing any of the engine's object files.
steps:
- name: Checkout
timeout-minutes: 10
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref || github.ref }}
submodules: recursive
- name: Install system packages
timeout-minutes: 10
run: |
# GitHub-hosted ubuntu-24.04 images sometimes pin the retired
# azure.archive.ubuntu.com mirror, which hangs instead of failing.
# Normalize to the canonical mirror so apt has a reachable source.
sudo sed -i -E 's#https?://[^/]*archive\.ubuntu\.com/ubuntu#http://archive.ubuntu.com/ubuntu#g' \
/etc/apt/apt-mirrors.txt /etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true
printf 'Acquire::Retries "2";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' | \
sudo tee /etc/apt/apt.conf.d/90eve-timeouts >/dev/null
sudo pkill -f "unattended-upgrade" || true
sudo systemctl stop apt-daily.service apt-daily-upgrade.service 2>/dev/null || true
# Only Ubuntu indexes are needed for this toolchain.
test -s /etc/apt/sources.list.d/ubuntu.sources
apt_ok=0
for i in 1 2 3; do
timeout 120 sudo apt-get update -o Acquire::Retries=2 \
-o Dir::Etc::sourcelist=/etc/apt/sources.list.d/ubuntu.sources \
-o Dir::Etc::sourceparts=- \
-o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 && { apt_ok=1; break; }
[ "$i" -lt 3 ] && sleep 20
done
[ "$apt_ok" -eq 1 ] || exit 1
sudo apt-get install -y --no-install-recommends \
git ninja-build python3 python3-pip
- name: Install CMake 3.29 (Emscripten is incompatible with CMake 4.x)
timeout-minutes: 10
run: |
python3 -m pip install --user cmake==3.29.6
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Restore Emscripten SDK cache
id: emsdk-cache
timeout-minutes: 5
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: $HOME/emsdk
key: emsdk-${{ runner.os }}-${{ runner.arch }}-${{ env.EMSCRIPTEN_VERSION }}-${{ env.EMDAWNWEBGPU_RELEASE }}-v1
- name: Install Emscripten SDK
timeout-minutes: 30
run: |
if [ "${{ steps.emsdk-cache.outputs.cache-hit }}" = "true" ]; then
echo "Using cached Emscripten SDK"
else
git clone --depth 1 https://github.com/emscripten-core/emsdk.git "$HOME/emsdk"
fi
"$HOME/emsdk/emsdk" install "$EMSCRIPTEN_VERSION"
"$HOME/emsdk/emsdk" activate "$EMSCRIPTEN_VERSION"
- name: Acquire and verify immutable WebGPU port
timeout-minutes: 10
run: |
set -euo pipefail
mkdir -p artifacts/webgpu-dependencies
archive="artifacts/webgpu-dependencies/emdawnwebgpu.zip"
url="https://github.com/google/dawn/releases/download/${EMDAWNWEBGPU_RELEASE}/emdawnwebgpu_pkg-${EMDAWNWEBGPU_RELEASE}.zip"
if ! curl --fail --location --retry 4 --retry-all-errors --retry-delay 5 \
--output "$archive" "$url"; then
python3 -c 'from pathlib import Path; from scripts.ci_result import CIResult, FailureKind; CIResult.failure(FailureKind.INFRASTRUCTURE, "WEBGPU_DEPENDENCY_DOWNLOAD_FAILED", "webgpu-dependencies", "failed to download the locked emdawnwebgpu archive after retries").write(Path("artifacts/webgpu-dependencies/ci-result.json"))'
exit 1
fi
python3 scripts/verify_webgpu_dependencies.py \
--lock cmake/webgpu_dependencies.json \
--archive "$archive" \
--emscripten-version "$EMSCRIPTEN_VERSION" \
--result-json artifacts/webgpu-dependencies/ci-result.json
- name: Prewarm Emscripten WebGPU port
timeout-minutes: 10
run: |
set -euo pipefail
source "$HOME/emsdk/emsdk_env.sh"
for attempt in 1 2 3; do
em++ --use-port=emdawnwebgpu -x c++ -c /dev/null \
-o artifacts/webgpu-dependencies/port-probe.o && exit 0
[ "$attempt" -lt 3 ] && sleep $((attempt * 10))
done
exit 1
- name: Upload WebGPU dependency result
if: always()
uses: actions/upload-artifact@v7
with:
name: webgpu-dependency-result
path: artifacts/webgpu-dependencies/ci-result.json
if-no-files-found: warn
- name: Configure webgpu (Emscripten/WASM)
timeout-minutes: 15
run: |
export PATH="$HOME/.local/bin:$PATH"
source "$HOME/emsdk/emsdk_env.sh"
emcmake cmake -B build/webgpu-web -G Ninja -DCMAKE_BUILD_TYPE=Release
- name: Build webgpu (Emscripten/WASM)
timeout-minutes: 40
run: |
export PATH="$HOME/.local/bin:$PATH"
source "$HOME/emsdk/emsdk_env.sh"
cmake --build build/webgpu-web --parallel "$(nproc)"
- name: Verify webgpu artifacts
timeout-minutes: 5
run: |
test -f build/webgpu-web/src/engine/eve.html
test -f build/webgpu-web/src/engine/eve.wasm
ls -lh build/webgpu-web/src/engine/eve.*
- name: Capture browser WebGPU frame
timeout-minutes: 10
run: |
python3 -m pip install --quiet pillow
cd build/webgpu-web/src/engine
python3 -m http.server 8090 >webgpu-http.log 2>&1 &
server_pid=$!
trap 'kill "$server_pid"' EXIT
google-chrome --headless=new --no-sandbox --disable-gpu-sandbox \
--enable-unsafe-webgpu --enable-features=Vulkan \
--use-vulkan=swiftshader --window-size=800,600 \
--timeout=20000 --screenshot=webgpu-browser.png \
http://127.0.0.1:8090/eve.html
python3 "$GITHUB_WORKSPACE/scripts/validate_render_frame.py" webgpu-browser.png
- name: Upload browser WebGPU frame
if: always()
uses: actions/upload-artifact@v7
with:
name: webgpu-browser-frame
path: |
build/webgpu-web/src/engine/webgpu-browser.png
build/webgpu-web/src/engine/webgpu-http.log
if-no-files-found: warn
- name: Save Emscripten SDK cache
# Only refresh on the default branch so per-PR runs restore (free) but
# never rewrite the cache, keeping churn/eviction of hot caches low.
if: ${{ steps.emsdk-cache.outputs.cache-hit != 'true' && github.ref == 'refs/heads/dev' }}
timeout-minutes: 15
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: $HOME/emsdk
key: ${{ steps.emsdk-cache.outputs.cache-primary-key }}