345 lines
16 KiB
YAML
345 lines
16 KiB
YAML
name: Build
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
types: [opened, synchronize, reopened]
|
|
workflow_dispatch:
|
|
inputs:
|
|
pr:
|
|
description: Pull request number to build
|
|
required: true
|
|
type: string
|
|
linux:
|
|
description: Also build and test on Linux
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: build-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr || github.ref }}
|
|
cancel-in-progress: ${{ github.event_name != 'push' }}
|
|
|
|
env:
|
|
CONFIGURE: >-
|
|
-G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
|
|
-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
|
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
|
|
|
jobs:
|
|
eligibility:
|
|
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
sha: ${{ steps.gate.outputs.sha }}
|
|
pr: ${{ steps.gate.outputs.pr }}
|
|
head: ${{ steps.gate.outputs.head }}
|
|
relinker: ${{ steps.gate.outputs.relinker }}
|
|
register-context: ${{ steps.gate.outputs.register-context }}
|
|
write-watch: ${{ steps.gate.outputs.write-watch }}
|
|
full: ${{ steps.gate.outputs.full }}
|
|
steps:
|
|
- uses: actions/github-script@v8
|
|
id: gate
|
|
env:
|
|
PR_NUMBER: ${{ inputs.pr }}
|
|
with:
|
|
script: |
|
|
if (context.eventName === 'push') {
|
|
if (context.ref !== 'refs/heads/main') {
|
|
throw new Error('Automatic full builds only run on main');
|
|
}
|
|
core.setOutput('sha', context.sha);
|
|
core.setOutput('pr', '');
|
|
core.setOutput('head', '');
|
|
core.setOutput('relinker', true);
|
|
core.setOutput('register-context', true);
|
|
core.setOutput('write-watch', true);
|
|
core.setOutput('full', true);
|
|
await core.summary.addRaw(`Building main at ${context.sha}: all builds and tests enabled`).write();
|
|
return;
|
|
}
|
|
const { owner, repo } = context.repo;
|
|
const manual = context.eventName === 'workflow_dispatch';
|
|
if (manual) {
|
|
for (const username of new Set([context.actor, process.env.GITHUB_TRIGGERING_ACTOR])) {
|
|
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username });
|
|
if (!['write', 'maintain', 'admin'].includes(data.permission)) {
|
|
throw new Error('Only collaborators with write access can start or rerun builds');
|
|
}
|
|
}
|
|
if (!/^[1-9][0-9]*$/.test(process.env.PR_NUMBER)) {
|
|
throw new Error('Enter a positive pull request number');
|
|
}
|
|
}
|
|
const pull_number = manual ? Number(process.env.PR_NUMBER) : context.payload.pull_request.number;
|
|
if (!Number.isSafeInteger(pull_number) || pull_number < 1) {
|
|
throw new Error('Invalid pull request number');
|
|
}
|
|
let { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number });
|
|
for (let attempt = 0; pr.mergeable === null && attempt < 10; ++attempt) {
|
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
({ data: pr } = await github.rest.pulls.get({ owner, repo, pull_number }));
|
|
}
|
|
if (pr.state !== 'open' || pr.base.ref !== 'main') {
|
|
throw new Error('The pull request must be open and target main');
|
|
}
|
|
if (!manual && pr.head.sha !== context.payload.pull_request.head.sha) {
|
|
throw new Error('The pull request has newer commits; use the latest run');
|
|
}
|
|
if (pr.mergeable === false) {
|
|
throw new Error('The pull request does not merge cleanly into main; rebase it first');
|
|
}
|
|
let relinker = manual;
|
|
if (!manual) {
|
|
const { data: commits } = await github.rest.repos.listCommits({
|
|
owner, repo, sha: 'main', author: pr.user.login, per_page: 1
|
|
});
|
|
relinker = commits.length > 0;
|
|
}
|
|
const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number, per_page: 100 });
|
|
const paths = files.flatMap((file) => [file.filename, file.previous_filename].filter(Boolean));
|
|
const touches = (...prefixes) => files.length >= 3000 ||
|
|
paths.some((path) => prefixes.some((prefix) => prefix.endsWith('/') ? path.startsWith(prefix) : path === prefix));
|
|
const workflow = '.github/workflows/build.yml';
|
|
const libc = 'core/libs/prx/libc/';
|
|
relinker = relinker && touches(
|
|
'core/relinker/', libc, 'core/libs/prx/libkernel/AppMetadata/', 'core/libs/tests/ParamJsonParser.cpp',
|
|
'core/shader/recompiler/tests/CacheFileReadTests.cpp', 'core/shader/recompiler/ShaderCacheDirectory.hpp',
|
|
'tools/', 'cmake/', 'CMakeLists.txt', workflow
|
|
);
|
|
const registerContext = touches('core/libs/tests/register_context/', 'core/libs/tests/RegisterContext.cpp', libc, workflow);
|
|
const writeWatch = touches('core/libs/tests/write_watch/', 'core/libs/tests/WriteWatch.cpp', libc, workflow);
|
|
const { data: main } = await github.rest.repos.getBranch({ owner, repo, branch: 'main' });
|
|
core.setOutput('sha', main.commit.sha);
|
|
core.setOutput('pr', String(pull_number));
|
|
core.setOutput('head', pr.head.sha);
|
|
core.setOutput('relinker', relinker);
|
|
core.setOutput('register-context', registerContext);
|
|
core.setOutput('write-watch', writeWatch);
|
|
core.setOutput('full', manual);
|
|
core.info(`PR #${pull_number} at ${pr.head.sha} merged into main at ${main.commit.sha}`);
|
|
await core.summary.addRaw(
|
|
`PR #${pull_number} at ${pr.head.sha} merged into main at ${main.commit.sha}: full builds ${manual ? 'enabled' : 'disabled'}, ` +
|
|
`relinker ${relinker ? 'enabled' : 'skipped: no relinker file changed or the author has no commit in main'}, ` +
|
|
`register context tests ${registerContext ? 'enabled' : 'skipped'}, write watch tests ${writeWatch ? 'enabled' : 'skipped'}`
|
|
).write();
|
|
|
|
relinker:
|
|
needs: eligibility
|
|
if: ${{ needs.eligibility.outputs.relinker == 'true' }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 15
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
compiler: g++
|
|
- os: windows-latest
|
|
compiler: g++
|
|
- os: macos-latest
|
|
compiler: clang++
|
|
env:
|
|
WINLIBS_RELEASE: 15.2.0posix-14.0.0-ucrt-r7
|
|
WINLIBS_ZIP: winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ needs.eligibility.outputs.sha }}
|
|
persist-credentials: false
|
|
- name: Merge the pull request into main
|
|
if: ${{ needs.eligibility.outputs.head != '' }}
|
|
shell: bash
|
|
env:
|
|
PR_NUMBER: ${{ needs.eligibility.outputs.pr }}
|
|
PR_HEAD: ${{ needs.eligibility.outputs.head }}
|
|
run: |
|
|
git fetch --no-tags --unshallow origin "refs/pull/$PR_NUMBER/head"
|
|
if [ "$(git rev-parse FETCH_HEAD)" != "$PR_HEAD" ]; then echo "::error::The pull request has newer commits; start a new build"; exit 1; fi
|
|
if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit FETCH_HEAD; then echo "::error::The pull request does not merge cleanly into main; rebase it first"; exit 1; fi
|
|
- if: runner.os == 'Windows'
|
|
id: toolchain
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: C:\winlibs
|
|
key: ${{ env.WINLIBS_ZIP }}
|
|
- if: runner.os == 'Windows' && steps.toolchain.outputs.cache-hit != 'true'
|
|
run: |
|
|
curl -sSfL -o "$RUNNER_TEMP/winlibs.zip" "https://github.com/brechtsanders/winlibs_mingw/releases/download/$WINLIBS_RELEASE/$WINLIBS_ZIP"
|
|
7z x "$RUNNER_TEMP/winlibs.zip" -oC:/winlibs > /dev/null
|
|
- if: runner.os == 'Windows'
|
|
run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH"
|
|
- run: cmake -S . -B build-relinker -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON
|
|
- run: cmake --build build-relinker --parallel 3
|
|
- run: ctest --test-dir build-relinker --output-on-failure --timeout 120
|
|
|
|
register-context:
|
|
needs: eligibility
|
|
if: ${{ needs.eligibility.outputs.register-context == 'true' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-15-intel]
|
|
configuration: [Debug, Release]
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- run: cmake -S core/libs/tests/register_context -B build/register-context -DCMAKE_BUILD_TYPE=${{ matrix.configuration }}
|
|
- run: cmake --build build/register-context --parallel
|
|
- run: ctest --test-dir build/register-context --output-on-failure --repeat until-fail:3 --no-tests=error
|
|
|
|
write-watch:
|
|
needs: eligibility
|
|
if: ${{ needs.eligibility.outputs.write-watch == 'true' }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [macos-15-intel, macos-15]
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- run: cmake -S core/libs/tests/write_watch -B build/write-watch -DCMAKE_BUILD_TYPE=Release
|
|
- run: cmake --build build/write-watch --parallel
|
|
- run: ctest --test-dir build/write-watch --output-on-failure --repeat until-fail:3 --no-tests=error
|
|
|
|
linux:
|
|
needs: eligibility
|
|
if: ${{ (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.linux)) && needs.eligibility.outputs.full == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ needs.eligibility.outputs.sha }}
|
|
persist-credentials: false
|
|
submodules: true
|
|
- name: Merge the pull request into main
|
|
if: ${{ needs.eligibility.outputs.head != '' }}
|
|
shell: bash
|
|
env:
|
|
PR_NUMBER: ${{ needs.eligibility.outputs.pr }}
|
|
PR_HEAD: ${{ needs.eligibility.outputs.head }}
|
|
run: |
|
|
git fetch --no-tags --unshallow origin "refs/pull/$PR_NUMBER/head"
|
|
if [ "$(git rev-parse FETCH_HEAD)" != "$PR_HEAD" ]; then echo "::error::The pull request has newer commits; start a new build"; exit 1; fi
|
|
if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit FETCH_HEAD; then echo "::error::The pull request does not merge cleanly into main; rebase it first"; exit 1; fi
|
|
git submodule update --init --recursive
|
|
- uses: hendrikmuhs/ccache-action@v1.2.24
|
|
with:
|
|
key: linux
|
|
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
evict-old-files: job
|
|
- name: Install lavapipe and the SDL audio and XRandR development files
|
|
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libvulkan1 mesa-vulkan-drivers libpipewire-0.3-dev libpulse-dev libasound2-dev libxrandr-dev
|
|
- id: mesa
|
|
run: |
|
|
echo "version=$(dpkg-query -W -f='${Version}' mesa-vulkan-drivers)" >> "$GITHUB_OUTPUT"
|
|
echo "cpu=$(grep -m1 '^flags' /proc/cpuinfo | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"
|
|
- uses: actions/cache/restore@v6
|
|
with:
|
|
path: .mesa-shader-cache
|
|
key: mesa-${{ steps.mesa.outputs.version }}-${{ steps.mesa.outputs.cpu }}-${{ github.sha }}
|
|
restore-keys: mesa-${{ steps.mesa.outputs.version }}-${{ steps.mesa.outputs.cpu }}-
|
|
- run: cmake -S . -B build $CONFIGURE -DANYPS5_ENABLE_SPIRV_TOOLS=ON
|
|
- name: Check that every test executable is registered with ctest
|
|
run: python3 tools/check_test_registration.py build
|
|
- run: cmake --build build
|
|
- run: cmake --build build --target libs
|
|
- run: ctest --test-dir build --output-on-failure --timeout 120 --parallel 4 --output-log ctest.log --output-junit ctest.xml
|
|
env:
|
|
ANYPS5_REQUIRE_VULKAN: 1
|
|
GALLIUM_OVERRIDE_CPU_CAPS: avx
|
|
MESA_SHADER_CACHE_DIR: ${{ github.workspace }}/.mesa-shader-cache
|
|
- if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && !cancelled() }}
|
|
uses: actions/cache/save@v6
|
|
with:
|
|
path: .mesa-shader-cache
|
|
key: mesa-${{ steps.mesa.outputs.version }}-${{ steps.mesa.outputs.cpu }}-${{ github.sha }}
|
|
- run: python3 tools/check_skips.py build/ctest.xml
|
|
- name: Report tests slower than 30 s
|
|
if: ${{ !cancelled() }}
|
|
shell: bash
|
|
run: |
|
|
[ -f ctest.log ] || exit 0
|
|
awk '/Test +#[0-9]+: / && $NF == "sec" { for (i = 1; i <= NF; i++) if ($i ~ /^#[0-9]+:$/) name = $(i + 1); t = $(NF - 1) + 0; if (t > max[name]) max[name] = t }
|
|
END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' ctest.log
|
|
|
|
windows:
|
|
needs: eligibility
|
|
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.eligibility.outputs.full == 'true' }}
|
|
runs-on: windows-latest
|
|
timeout-minutes: 45
|
|
env:
|
|
WINLIBS_RELEASE: 15.2.0posix-14.0.0-ucrt-r7
|
|
WINLIBS_ZIP: winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip
|
|
SSL_CERT_FILE: C:\Program Files\Git\mingw64\etc\ssl\certs\ca-bundle.crt
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ needs.eligibility.outputs.sha }}
|
|
persist-credentials: false
|
|
submodules: true
|
|
- name: Merge the pull request into main
|
|
if: ${{ needs.eligibility.outputs.head != '' }}
|
|
shell: bash
|
|
env:
|
|
PR_NUMBER: ${{ needs.eligibility.outputs.pr }}
|
|
PR_HEAD: ${{ needs.eligibility.outputs.head }}
|
|
run: |
|
|
git fetch --no-tags --unshallow origin "refs/pull/$PR_NUMBER/head"
|
|
if [ "$(git rev-parse FETCH_HEAD)" != "$PR_HEAD" ]; then echo "::error::The pull request has newer commits; start a new build"; exit 1; fi
|
|
if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit FETCH_HEAD; then echo "::error::The pull request does not merge cleanly into main; rebase it first"; exit 1; fi
|
|
git submodule update --init --recursive
|
|
- id: toolchain
|
|
uses: actions/cache@v6
|
|
with:
|
|
path: C:\winlibs
|
|
key: ${{ env.WINLIBS_ZIP }}
|
|
- if: steps.toolchain.outputs.cache-hit != 'true'
|
|
run: |
|
|
curl -sSfL -o "$RUNNER_TEMP/winlibs.zip" "https://github.com/brechtsanders/winlibs_mingw/releases/download/$WINLIBS_RELEASE/$WINLIBS_ZIP"
|
|
7z x "$RUNNER_TEMP/winlibs.zip" -oC:/winlibs > /dev/null
|
|
- run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH"
|
|
- uses: hendrikmuhs/ccache-action@v1.2.24
|
|
with:
|
|
key: windows
|
|
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
evict-old-files: job
|
|
- shell: cmd
|
|
run: cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown"
|
|
- name: Check that every test executable is registered with ctest
|
|
shell: cmd
|
|
run: python tools/check_test_registration.py build
|
|
- shell: cmd
|
|
run: cmake --build build
|
|
- shell: cmd
|
|
run: cmake --build build --target libs
|
|
- shell: cmd
|
|
run: ctest --test-dir build --output-on-failure --timeout 120 --parallel 4 --output-log ctest.log --output-junit ctest.xml
|
|
- run: python tools/check_skips.py build/ctest.xml --job windows
|
|
- name: Report tests slower than 30 s
|
|
if: ${{ !cancelled() }}
|
|
shell: bash
|
|
run: |
|
|
[ -f ctest.log ] || exit 0
|
|
awk '/Test +#[0-9]+: / && $NF == "sec" { for (i = 1; i <= NF; i++) if ($i ~ /^#[0-9]+:$/) name = $(i + 1); t = $(NF - 1) + 0; if (t > max[name]) max[name] = t }
|
|
END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' ctest.log
|