name: Build on: push: branches: [main] pull_request: branches: [main] types: [opened, synchronize, reopened] workflow_dispatch: inputs: pr: description: Pull request numbers to build together, separated by spaces or commas (up to 40) required: true type: string linux: description: Also build and test on Linux required: false default: true type: boolean full: description: Run the full Linux and Windows builds (off runs only the relinker builds and tests on Linux, Windows and macOS) required: false default: true type: boolean select: description: Build and run only the tests whose build inputs the pull requests change (push builds of main always run every test) required: false default: false type: boolean permissions: contents: read concurrency: group: build-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr || github.ref }} cancel-in-progress: true 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 }} relinker-hosts: ${{ steps.gate.outputs.relinker-hosts }} steps: - uses: actions/github-script@v8 id: gate env: PR_NUMBER: ${{ inputs.pr }} LINUX_BUILD: ${{ inputs.linux }} FULL_BUILD: ${{ inputs.full }} with: script: | const { owner, repo } = context.repo; const workflow = '.github/workflows/build.yml'; const libc = 'core/libs/prx/libc/'; const select = (paths, all) => { const touches = (...prefixes) => all || paths.some((path) => prefixes.some((prefix) => prefix.endsWith('/') ? path.startsWith(prefix) : path === prefix)); return { 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 ), registerContext: touches('core/libs/tests/register_context/', 'core/libs/tests/RegisterContext.cpp', libc, workflow), writeWatch: touches('core/libs/tests/write_watch/', 'core/libs/tests/WriteWatch.cpp', libc, workflow), }; }; const relinkerHosts = (full, linux) => JSON.stringify([ ...(full && linux ? [] : [{ os: 'ubuntu-latest', compiler: 'g++' }]), ...(full ? [] : [{ os: 'windows-latest', compiler: 'g++' }]), { os: 'macos-latest', compiler: 'clang++' }, ]); if (context.eventName === 'push') { if (context.ref !== 'refs/heads/main') { throw new Error('Automatic full builds only run on main'); } let paths = []; let all = true; if (!/^0+$/.test(context.payload.before)) { try { const { data } = await github.rest.repos.compareCommitsWithBasehead({ owner, repo, basehead: `${context.payload.before}...${context.sha}` }); const files = data.files ?? []; all = files.length >= 300; paths = files.flatMap((file) => [file.filename, file.previous_filename].filter(Boolean)); } catch (error) { core.warning(`Could not list the files of this push, so every test job runs: ${error.message}`); } } const run = select(paths, all); core.setOutput('sha', context.sha); core.setOutput('pr', ''); core.setOutput('head', ''); core.setOutput('relinker', run.relinker); core.setOutput('register-context', run.registerContext); core.setOutput('write-watch', run.writeWatch); core.setOutput('full', true); core.setOutput('relinker-hosts', relinkerHosts(true, true)); await core.summary.addRaw( `Building main at ${context.sha}: full builds enabled, ` + `macOS relinker ${run.relinker ? 'enabled' : 'skipped'}, ` + `register context tests ${run.registerContext ? 'enabled' : 'skipped'}, write watch tests ${run.writeWatch ? 'enabled' : 'skipped'}` ).write(); return; } const manual = context.eventName === 'workflow_dispatch'; const linux = process.env.LINUX_BUILD !== 'false'; const full = manual && process.env.FULL_BUILD !== 'false'; 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]*([ ,]+[1-9][0-9]*){0,39}$/.test(process.env.PR_NUMBER.trim())) { throw new Error('Enter up to 40 positive pull request numbers separated by spaces or commas'); } } const numbers = manual ? [...new Set(process.env.PR_NUMBER.trim().split(/[ ,]+/).map(Number))] : [context.payload.pull_request.number]; if (numbers.some((pull_number) => !Number.isSafeInteger(pull_number) || pull_number < 1)) { throw new Error('Invalid pull request number'); } const pulls = []; const paths = []; let truncated = false; for (const pull_number of numbers) { 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 (!manual && pr.state !== 'open') { await core.summary.addRaw(`PR #${pull_number} was ${pr.merged ? 'merged' : 'closed'} before this run started: nothing to build`).write(); return; } if (pr.state !== 'open' || pr.base.ref !== 'main') { throw new Error(`Pull request #${pull_number} 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(`Pull request #${pull_number} does not merge cleanly into main; rebase it first`); } const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number, per_page: 100 }); truncated ||= files.length >= 3000; paths.push(...files.flatMap((file) => [file.filename, file.previous_filename].filter(Boolean))); pulls.push({ number: pull_number, head: pr.head.sha }); } const run = select(paths, truncated); let relinker = run.relinker; if (relinker && !manual) { const author = context.payload.pull_request.user.login; const { data: commits } = await github.rest.repos.listCommits({ owner, repo, sha: 'main', author, per_page: 1 }); relinker = commits.length > 0; if (!relinker) { try { const { data: mergedPRs } = await github.rest.search.issuesAndPullRequests({ q: `repo:${owner}/${repo} is:pr is:merged base:main author:${author}`, per_page: 1 }); if (mergedPRs.incomplete_results) { throw new Error('the search results are incomplete'); } relinker = mergedPRs.total_count > 0; } catch (error) { core.warning(`Skipping the relinker: could not search the merged pull requests of ${author}: ${error.message}`); } } } const registerContext = run.registerContext; const writeWatch = run.writeWatch; const { data: main } = await github.rest.repos.getBranch({ owner, repo, branch: 'main' }); core.setOutput('sha', main.commit.sha); core.setOutput('pr', pulls.map((pull) => pull.number).join(' ')); core.setOutput('head', pulls.map((pull) => pull.head).join(' ')); core.setOutput('relinker', relinker); core.setOutput('register-context', registerContext); core.setOutput('write-watch', writeWatch); core.setOutput('full', full); core.setOutput('relinker-hosts', relinkerHosts(full, linux)); const merged = pulls.map((pull) => `PR #${pull.number} at ${pull.head} merged into main at ${main.commit.sha}`); merged.forEach((line) => core.info(line)); await core.summary.addRaw( `${merged.join(', then ')}: full builds ${full ? 'enabled' : 'disabled'}, ` + `relinker ${relinker ? (full ? (linux ? 'enabled on macOS (the full builds run the Linux and Windows relinker tests)' : 'enabled on Linux and macOS (the full Windows build runs the Windows relinker tests)') : 'enabled') : 'skipped: no relinker file changed, or no commit or merged pull request in main was found for the author'}, ` + `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: ${{ fromJSON(needs.eligibility.outputs.relinker-hosts) }} 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: | read -ra numbers <<< "$PR_NUMBER" read -ra heads <<< "$PR_HEAD" refspecs=() for number in "${numbers[@]}"; do refspecs+=("+refs/pull/$number/head:refs/remotes/pull/$number"); done git fetch --no-tags --unshallow origin "${refspecs[@]}" for i in "${!numbers[@]}"; do if [ "$(git rev-parse "refs/remotes/pull/${numbers[i]}")" != "${heads[i]}" ]; then echo "::error::Pull request #${numbers[i]} 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 "refs/remotes/pull/${numbers[i]}"; then echo "::error::Pull request #${numbers[i]} does not merge cleanly into main and the pull requests before it; rebase it first"; exit 1; fi done - 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: [self-hosted, linux, x64, anyps5] timeout-minutes: 60 env: CCACHE_DIR: /var/cache/anyps5/ccache CCACHE_MAXSIZE: 30G CCACHE_COMPILERCHECK: content MESA_SHADER_CACHE_DIR: /var/cache/anyps5/mesa MESA_SHADER_CACHE_MAX_SIZE: 4G steps: - uses: actions/checkout@v7 with: ref: ${{ needs.eligibility.outputs.sha }} persist-credentials: false submodules: recursive - 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: | read -ra numbers <<< "$PR_NUMBER" read -ra heads <<< "$PR_HEAD" refspecs=() for number in "${numbers[@]}"; do refspecs+=("+refs/pull/$number/head:refs/remotes/pull/$number"); done fetch_options=() if [ "$(git rev-parse --is-shallow-repository)" = true ]; then fetch_options+=(--unshallow); fi git fetch --no-tags "${fetch_options[@]}" origin "${refspecs[@]}" for i in "${!numbers[@]}"; do if [ "$(git rev-parse "refs/remotes/pull/${numbers[i]}")" != "${heads[i]}" ]; then echo "::error::Pull request #${numbers[i]} 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 "refs/remotes/pull/${numbers[i]}"; then echo "::error::Pull request #${numbers[i]} does not merge cleanly into main and the pull requests before it; rebase it first"; exit 1; fi done git submodule update --init --recursive - name: Prepare persistent caches run: | mkdir -p "$CCACHE_DIR" "$MESA_SHADER_CACHE_DIR" test -w "$CCACHE_DIR" && test -w "$MESA_SHADER_CACHE_DIR" ccache --zero-stats ccache --show-config dpkg-query -W mesa-vulkan-drivers grep -m1 '^model name' /proc/cpuinfo - name: Install the wild linker shell: bash env: WILD_VERSION: 0.10.0 WILD_SHA256: 641265506a7c06cfb03181b8916ab663ec8407855db6d4db7f8450667d105283 run: | tools=$RUNNER_TOOL_CACHE/wild mkdir -p "$tools" 2>/dev/null && test -w "$tools" || { tools=$RUNNER_TEMP/wild; mkdir -p "$tools"; } dir=$tools/$WILD_VERSION tmp= probe=$(mktemp -d "$RUNNER_TEMP/wild-probe.XXXXXX") trap 'rm -rf "$tmp" "$probe"' EXIT if [ ! -x "$dir/bin/ld" ]; then tmp=$(mktemp -d "$tools/.wild.XXXXXX") name=wild-linker-$WILD_VERSION-x86_64-unknown-linux-gnu curl -fsSL --retry 3 -o "$tmp/$name.tar.gz" "https://github.com/davidlattimore/wild/releases/download/$WILD_VERSION/$name.tar.gz" echo "$WILD_SHA256 $tmp/$name.tar.gz" | sha256sum -c - tar -xzf "$tmp/$name.tar.gz" -C "$tmp" mkdir -p "$tmp/out/bin" mv "$tmp/$name/wild" "$tmp/out/bin/wild" ln -s wild "$tmp/out/bin/ld" mv -T "$tmp/out" "$dir" 2>/dev/null || test -x "$dir/bin/ld" fi echo 'int main(void) { return 0; }' > "$probe/main.c" gcc "-B$dir/bin" "$probe/main.c" -o "$probe/main" readelf -p .comment "$probe/main" | grep -F "Linker: Wild $WILD_VERSION" echo "LINKER_FLAGS=-B$dir/bin" >> "$GITHUB_ENV" - name: Size parallelism to what the shared host has free run: | cpus=$(nproc) busy=$(awk '{ printf "%d", $1 + 0.5 }' /proc/loadavg) mem_mb=$(awk '/^MemTotal:/ { print int($2 / 1024) }' /proc/meminfo) slots=$(( (mem_mb - 2048) / 1024 )) slots=$(( slots < 1 ? 1 : slots )) tests=$(( cpus - busy )) tests=$(( tests < 1 ? 1 : tests )) slot_dir=$CCACHE_DIR/compile-slots mkdir -p -m 1777 "$slot_dir" test -w "$slot_dir" echo "cpus=$cpus load=$busy mem_total_mb=$mem_mb -> build jobs $cpus, cold compiles $slots per host, tests $tests" echo "CMAKE_BUILD_PARALLEL_LEVEL=$cpus" >> "$GITHUB_ENV" echo "CTEST_PARALLEL_LEVEL=$tests" >> "$GITHUB_ENV" echo "CCACHE_PREFIX=$GITHUB_WORKSPACE/tools/compile_slot.sh" >> "$GITHUB_ENV" echo "ANYPS5_COMPILE_SLOT_DIR=$slot_dir" >> "$GITHUB_ENV" echo "ANYPS5_COMPILE_SLOTS=$slots" >> "$GITHUB_ENV" - run: cmake -S . -B build $CONFIGURE -DANYPS5_ENABLE_SPIRV_TOOLS=ON "-DCMAKE_EXE_LINKER_FLAGS=$LINKER_FLAGS" "-DCMAKE_SHARED_LINKER_FLAGS=$LINKER_FLAGS" "-DCMAKE_MODULE_LINKER_FLAGS=$LINKER_FLAGS" - name: Check that every test executable is registered with ctest run: python3 tools/check_test_registration.py build - if: ${{ inputs.select }} uses: actions/cache/restore@v6 with: path: test-graph.json key: test-graph-${{ runner.os }}-${{ needs.eligibility.outputs.sha }} restore-keys: test-graph-${{ runner.os }}- - name: Select the tests the change reaches if: ${{ inputs.select }} env: BASE: ${{ needs.eligibility.outputs.sha }} run: | git diff --name-only "$BASE" HEAD > changed.txt python3 tools/select_tests.py select build test-graph.json changed.txt - id: build run: | if [ -f build/select.ninja ]; then ninja -C build -f select.ninja -j "$CMAKE_BUILD_PARALLEL_LEVEL" -l "$(nproc)" selected; else cmake --build build -- -l "$(nproc)"; fi - id: libs run: cmake --build build --target libs -- -l "$(nproc)" - run: python3 tools/select_tests.py ctest build --test-load "$(nproc)" --output-on-failure --timeout 120 --output-log ctest.log --output-junit ctest.xml env: ANYPS5_REQUIRE_VULKAN: 1 GALLIUM_OVERRIDE_CPU_CAPS: avx - run: python3 tools/check_skips.py build/ctest.xml - name: Write the test graph of main if: ${{ !cancelled() && github.event_name == 'push' && steps.build.outcome == 'success' && steps.libs.outcome == 'success' }} continue-on-error: true run: python3 tools/select_tests.py dump build test-graph.json - if: ${{ !cancelled() && github.event_name == 'push' && hashFiles('test-graph.json') != '' }} continue-on-error: true uses: actions/cache/save@v6 with: path: test-graph.json key: test-graph-${{ runner.os }}-${{ github.sha }} - name: Report compiler cache statistics if: ${{ !cancelled() }} run: ccache --show-stats - 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 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: | read -ra numbers <<< "$PR_NUMBER" read -ra heads <<< "$PR_HEAD" refspecs=() for number in "${numbers[@]}"; do refspecs+=("+refs/pull/$number/head:refs/remotes/pull/$number"); done git fetch --no-tags --unshallow origin "${refspecs[@]}" for i in "${!numbers[@]}"; do if [ "$(git rev-parse "refs/remotes/pull/${numbers[i]}")" != "${heads[i]}" ]; then echo "::error::Pull request #${numbers[i]} 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 "refs/remotes/pull/${numbers[i]}"; then echo "::error::Pull request #${numbers[i]} does not merge cleanly into main and the pull requests before it; rebase it first"; exit 1; fi done - id: submodules name: Key the submodules on their commits run: | echo "key=$(git ls-files -s | awk '$1 == "160000" { print $2, $4 }' | cat - .gitmodules | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT" echo "ffmpeg=$(git rev-parse HEAD:3rdparty/ffmpeg-core)" >> "$GITHUB_OUTPUT" - id: submodule-cache uses: actions/cache/restore@v6 with: path: ${{ runner.temp }}/submodule-gitdirs.tar key: submodule-gitdirs-${{ runner.os }}-${{ steps.submodules.outputs.key }} - name: Check out the submodules run: | if [ -f "$RUNNER_TEMP/submodule-gitdirs.tar" ]; then tar --force-local -xf "$RUNNER_TEMP/submodule-gitdirs.tar"; fi git submodule update --init --recursive --depth 1 --jobs 8 - if: steps.submodule-cache.outputs.cache-hit != 'true' run: tar --force-local -cf "$RUNNER_TEMP/submodule-gitdirs.tar" .git/modules - if: steps.submodule-cache.outputs.cache-hit != 'true' uses: actions/cache/save@v6 continue-on-error: true with: path: ${{ runner.temp }}/submodule-gitdirs.tar key: submodule-gitdirs-${{ runner.os }}-${{ steps.submodules.outputs.key }} - name: Point the WinLibs CMake at the CA bundle of Git for Windows run: | for prefix in ucrt64 mingw64; do bundle="C:/Program Files/Git/$prefix/etc/ssl/certs/ca-bundle.crt" if [ -f "$bundle" ]; then echo "SSL_CERT_FILE=$bundle" >> "$GITHUB_ENV"; exit 0; fi done echo "::error::No CA bundle found in Git for Windows"; exit 1 - 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 - id: ffmpeg-cache uses: actions/cache/restore@v6 with: path: build/externals key: ffmpeg-${{ runner.os }}-${{ steps.submodules.outputs.ffmpeg }} - id: seed-cache uses: actions/cache/restore@v6 with: path: configure-seed.cmake key: configure-seed-${{ env.WINLIBS_ZIP }}-${{ steps.submodules.outputs.key }}-${{ hashFiles('CMakeLists.txt', 'cmake/**', 'core/libs/CMakeLists.txt', '.github/workflows/build.yml') }} - shell: cmd run: if exist configure-seed.cmake (cmake -C configure-seed.cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown") else (cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown") - if: steps.seed-cache.outputs.cache-hit != 'true' shell: cmd run: python tools/configure_seed.py build/CMakeCache.txt configure-seed.cmake - if: steps.seed-cache.outputs.cache-hit != 'true' uses: actions/cache/save@v6 continue-on-error: true with: path: configure-seed.cmake key: ${{ steps.seed-cache.outputs.cache-primary-key }} - if: steps.ffmpeg-cache.outputs.cache-hit != 'true' uses: actions/cache/save@v6 continue-on-error: true with: path: build/externals key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }} - name: Check that every test executable is registered with ctest shell: cmd run: python tools/check_test_registration.py build - if: ${{ inputs.select }} uses: actions/cache/restore@v6 with: path: test-graph.json key: test-graph-${{ runner.os }}-${{ needs.eligibility.outputs.sha }} restore-keys: test-graph-${{ runner.os }}- - name: Select the tests the change reaches if: ${{ inputs.select }} env: BASE: ${{ needs.eligibility.outputs.sha }} shell: cmd run: | git diff --name-only %BASE% HEAD > changed.txt python tools/select_tests.py select build test-graph.json changed.txt - id: build shell: cmd run: if exist build\select.ninja (ninja -C build -f select.ninja selected) else (cmake --build build) - id: libs shell: cmd run: cmake --build build --target libs - shell: cmd run: python tools/select_tests.py ctest 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: Write the test graph of main if: ${{ !cancelled() && github.event_name == 'push' && steps.build.outcome == 'success' && steps.libs.outcome == 'success' }} continue-on-error: true shell: cmd run: python tools/select_tests.py dump build test-graph.json - if: ${{ !cancelled() && github.event_name == 'push' && hashFiles('test-graph.json') != '' }} continue-on-error: true uses: actions/cache/save@v6 with: path: test-graph.json key: test-graph-${{ runner.os }}-${{ github.sha }} - 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