mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
707 lines
36 KiB
YAML
707 lines
36 KiB
YAML
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 jobOfLine = (text) => {
|
|
let inJobs = false;
|
|
let job = '';
|
|
return text.split('\n').map((line) => {
|
|
if (/^\S/.test(line)) {
|
|
inJobs = /^jobs:\s*$/.test(line);
|
|
job = '';
|
|
} else if (inJobs) {
|
|
job = /^ ([\w-]+):\s*$/.exec(line)?.[1] ?? job;
|
|
}
|
|
return job;
|
|
});
|
|
};
|
|
const patchedJobs = (patch, text) => {
|
|
const jobs = jobOfLine(text);
|
|
const touched = new Set();
|
|
let line = 1;
|
|
for (const row of patch.split('\n')) {
|
|
const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)/.exec(row);
|
|
if (hunk) {
|
|
line = Number(hunk[1]);
|
|
} else if (row.startsWith('+')) {
|
|
touched.add(jobs[line - 1] ?? '');
|
|
line += 1;
|
|
} else if (row.startsWith('-')) {
|
|
touched.add(jobs[line - 2] ?? '').add(jobs[line - 1] ?? '');
|
|
} else if (row.startsWith(' ')) {
|
|
line += 1;
|
|
}
|
|
}
|
|
return touched;
|
|
};
|
|
const workflowJobs = new Set();
|
|
const readWorkflow = async (files, ref) => {
|
|
const file = files.find((file) => file.filename === workflow || file.previous_filename === workflow);
|
|
if (!file) {
|
|
return;
|
|
}
|
|
if (file.filename !== workflow || !file.patch) {
|
|
workflowJobs.add('*');
|
|
return;
|
|
}
|
|
try {
|
|
const { data } = await github.rest.repos.getContent({ owner, repo, path: workflow, ref });
|
|
const text = Buffer.from(data.content, 'base64').toString('utf8');
|
|
patchedJobs(file.patch, text).forEach((job) => workflowJobs.add(job));
|
|
} catch (error) {
|
|
core.warning(`Could not read ${workflow} at ${ref}, so every test job runs: ${error.message}`);
|
|
workflowJobs.add('*');
|
|
}
|
|
};
|
|
const select = (paths, all) => {
|
|
const touches = (...prefixes) => all ||
|
|
paths.some((path) => prefixes.some((prefix) => prefix.endsWith('/') ? path.startsWith(prefix) : path === prefix));
|
|
const edits = (job) => ['*', '', 'eligibility', job].some((name) => workflowJobs.has(name));
|
|
return {
|
|
relinker: edits('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'
|
|
),
|
|
registerContext: edits('register-context') ||
|
|
touches('core/libs/tests/register_context/', 'core/libs/tests/RegisterContext.cpp', libc),
|
|
writeWatch: edits('write-watch') || touches('core/libs/tests/write_watch/', 'core/libs/tests/WriteWatch.cpp', libc),
|
|
};
|
|
};
|
|
const relinkerHosts = (full, linux) => JSON.stringify([
|
|
...(full && linux ? [] : [{ os: 'ubuntu-latest', compiler: 'g++' }]),
|
|
...(full ? [] : [{ os: 'windows-latest', compiler: 'g++' }]),
|
|
{ os: 'macos-latest', compiler: 'clang++' },
|
|
{ os: 'macos-latest', compiler: 'clang++', architectures: 'x86_64' },
|
|
]);
|
|
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));
|
|
await readWorkflow(files, context.sha);
|
|
} 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)));
|
|
await readWorkflow(files, pr.head.sha);
|
|
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"
|
|
- if: ${{ matrix.architectures == 'x86_64' }}
|
|
run: sudo softwareupdate --install-rosetta --agree-to-license
|
|
- run: cmake -S . -B build-relinker -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON -DCMAKE_OSX_ARCHITECTURES=${{ matrix.architectures }}
|
|
- run: cmake --build build-relinker --parallel 3
|
|
- run: ctest --test-dir build-relinker --output-on-failure --timeout 120 --parallel 4
|
|
|
|
relinker-asan:
|
|
needs: eligibility
|
|
if: ${{ needs.eligibility.outputs.relinker == 'true' }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
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 != '' }}
|
|
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
|
|
- run: cmake -S . -B build-relinker-asan -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ -DANYPS5_RELINKER_ONLY=ON -DANYPS5_ENABLE_ASAN=ON -DBUILD_TESTING=ON
|
|
- run: cmake --build build-relinker-asan --parallel 4
|
|
- run: ctest --test-dir build-relinker-asan --output-on-failure --timeout 120 --label-regex '^tls_function_coverage$' --no-tests=error
|
|
|
|
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
|
|
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
|
|
- 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
|
|
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
|
|
- 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: ${{ github.event_name == 'workflow_dispatch' && 'dispatch-windows' || 'windows' }}
|
|
restore-keys: ${{ github.event_name == 'workflow_dispatch' && 'windows' || '' }}
|
|
save: ${{ github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
|
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
|
|
|