Skip to content

Commit 6666452

Browse files
committed
github(ci): implement dynamic build matrix generation
Signed-off-by: tintinhamans <5984296+tintinhamans@users.noreply.github.com>
1 parent f58c2fd commit 6666452

4 files changed

Lines changed: 223 additions & 344 deletions

File tree

.github/scripts/generate_matrix.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import os
2+
import json
3+
4+
5+
def get_presets(is_pr):
6+
# Base object structure
7+
def p(name, replay=False):
8+
return {"preset": name, "tools": True, "extras": True, "replay": replay}
9+
10+
# 1. Generals (Mixed VC6 + Win32)
11+
if is_pr:
12+
generals = [
13+
p("vc6"),
14+
p("vc6-profile"),
15+
p("vc6-debug"),
16+
p("win32"),
17+
p("win32-profile"),
18+
p("win32-debug"),
19+
]
20+
else:
21+
generals = [
22+
p("vc6"),
23+
p("vc6-profile"),
24+
p("vc6-debug"),
25+
p("win32"),
26+
p("win32-profile"),
27+
p("win32-debug"),
28+
p("win32-vcpkg"),
29+
p("win32-vcpkg-profile"),
30+
p("win32-vcpkg-debug"),
31+
]
32+
33+
# 2. GeneralsMD (replay=True for retail-compatible VC6 builds)
34+
if is_pr:
35+
generalsmd = [
36+
p("vc6", replay=True),
37+
p("vc6-profile"),
38+
p("vc6-debug"),
39+
p("vc6-releaselog", replay=True),
40+
p("win32"),
41+
p("win32-profile"),
42+
p("win32-debug"),
43+
]
44+
else:
45+
generalsmd = [
46+
p("vc6", replay=True),
47+
p("vc6-profile"),
48+
p("vc6-debug"),
49+
p("vc6-releaselog", replay=True),
50+
p("win32"),
51+
p("win32-profile"),
52+
p("win32-debug"),
53+
p("win32-vcpkg"),
54+
p("win32-vcpkg-profile"),
55+
p("win32-vcpkg-debug"),
56+
]
57+
58+
return {
59+
"presets_generals": generals,
60+
"presets_generalsmd": generalsmd,
61+
}
62+
63+
64+
if __name__ == "__main__":
65+
event_name = os.environ.get("GITHUB_EVENT_NAME", "")
66+
is_pr = event_name == "pull_request"
67+
68+
matrix_map = get_presets(is_pr)
69+
70+
github_output = os.environ.get("GITHUB_OUTPUT")
71+
if github_output:
72+
with open(github_output, "a") as f:
73+
for key, value in matrix_map.items():
74+
f.write(f"{key}={json.dumps(value)}\n")
75+
else:
76+
# Debug printing if run locally
77+
print(json.dumps(matrix_map, indent=2))

.github/workflows/build-toolchain.yml

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ name: Build Toolchain
22

33
permissions:
44
contents: read
5-
pull-requests: write
65

76
on:
87
workflow_call:
@@ -25,6 +24,11 @@ on:
2524
default: false
2625
type: boolean
2726
description: "Build extras"
27+
replay:
28+
required: false
29+
default: false
30+
type: boolean
31+
description: "Run replay compatibility check after build"
2832

2933
jobs:
3034
build:
@@ -40,6 +44,8 @@ jobs:
4044
steps:
4145
- name: Checkout Code
4246
uses: actions/checkout@v4
47+
with:
48+
submodules: ${{ inputs.replay }}
4349

4450
- name: Cache VC6 Installation
4551
if: startsWith(inputs.preset, 'vc6')
@@ -54,7 +60,7 @@ jobs:
5460
uses: actions/cache@v4
5561
with:
5662
path: build\${{ inputs.preset }}\_deps
57-
key: cmake-deps-${{ inputs.preset }}-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','**/CMakeLists.txt') }}
63+
key: cmake-deps-${{ inputs.preset }}-${{ hashFiles('CMakePresets.json','cmake/**/*.cmake','Dependencies/**/CMakeLists.txt', 'CMakeLists.txt') }}
5864

5965
- name: Download VC6 Portable from itsmattkc repo
6066
if: ${{ startsWith(inputs.preset, 'vc6') && steps.cache-vc6.outputs.cache-hit != 'true' }}
@@ -106,7 +112,7 @@ jobs:
106112
arch: x86
107113

108114
- name: Compute vcpkg cache key parts
109-
if: startsWith(inputs.preset, 'win32')
115+
if: contains(inputs.preset, 'vcpkg')
110116
id: vcpkg_key
111117
shell: pwsh
112118
run: |
@@ -128,7 +134,7 @@ jobs:
128134
Write-Host "vcpkg cache key parts: baseline=$baseline, msvc=$msvcMajorMinor, triplet=$triplet"
129135
130136
- name: Restore vcpkg binary cache
131-
if: startsWith(inputs.preset, 'win32')
137+
if: contains(inputs.preset, 'vcpkg')
132138
id: vcpkg_cache
133139
uses: actions/cache/restore@v4
134140
with:
@@ -139,13 +145,14 @@ jobs:
139145
vcpkg-bincache-v2-${{ runner.os }}-
140146
141147
- name: Setup vcpkg
148+
if: contains(inputs.preset, 'vcpkg')
142149
uses: lukka/run-vcpkg@v11
143150
with:
144151
runVcpkgInstall: false
145152
doNotCache: true
146153

147154
- name: Configure vcpkg to use cached directory
148-
if: startsWith(inputs.preset, 'win32')
155+
if: contains(inputs.preset, 'vcpkg')
149156
shell: pwsh
150157
run: |
151158
$cacheDir = "${{ github.workspace }}\vcpkg-bincache"
@@ -182,7 +189,7 @@ jobs:
182189
183190
- name: Save vcpkg binary cache
184191
# Only one job should save to avoid "Unable to reserve cache" conflicts.
185-
if: ${{ startsWith(inputs.preset, 'win32') && steps.vcpkg_cache.outputs.cache-hit != 'true' && inputs.game == 'Generals' && inputs.preset == 'win32-vcpkg-debug' }}
192+
if: ${{ contains(inputs.preset, 'vcpkg') && steps.vcpkg_cache.outputs.cache-hit != 'true' && inputs.game == 'Generals' && inputs.preset == 'win32-vcpkg-debug' }}
186193
uses: actions/cache/save@v4
187194
with:
188195
path: ${{ github.workspace }}\vcpkg-bincache
@@ -212,3 +219,118 @@ jobs:
212219
path: build\${{ inputs.preset }}\${{ inputs.game }}\artifacts
213220
retention-days: 30
214221
if-no-files-found: error
222+
223+
# Replay Check Steps (only run when replay: true)
224+
- name: Cache Game Data
225+
if: inputs.replay
226+
id: cache-gamedata
227+
uses: actions/cache@v4
228+
with:
229+
path: C:\GameData
230+
key: gamedata-permanent-cache-v4
231+
232+
- name: Download Game Data from Cloudflare R2
233+
if: ${{ inputs.replay && steps.cache-gamedata.outputs.cache-hit != 'true' }}
234+
env:
235+
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
236+
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
237+
AWS_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }}
238+
EXPECTED_HASH_GENERALS: "37A351AA430199D1F05DEB9E404857DCE7B461A6AC272C5D4A0B5652CDB06372"
239+
EXPECTED_HASH_GENERALSMD: "6837FE1E3009A4C239406C39B1598216C0943EE8ED46BB10626767029AC05E21"
240+
shell: pwsh
241+
run: |
242+
if (-not $env:AWS_ACCESS_KEY_ID -or -not $env:AWS_SECRET_ACCESS_KEY -or -not $env:AWS_ENDPOINT_URL) {
243+
Write-Host "One or more required secrets are not set or are empty."
244+
exit 1
245+
}
246+
247+
Write-Host "Downloading Game Data for Generals" -ForegroundColor Cyan
248+
aws s3 cp s3://github-ci/generals108_gamedata_trimmed.7z generals108_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL
249+
$fileHash = (Get-FileHash -Path generals108_gamedata_trimmed.7z -Algorithm SHA256).Hash
250+
if ($fileHash -ne $env:EXPECTED_HASH_GENERALS) {
251+
Write-Error "Hash verification failed for Generals!"
252+
exit 1
253+
}
254+
& 7z x generals108_gamedata_trimmed.7z -o"C:\GameData\Generals"
255+
Remove-Item generals108_gamedata_trimmed.7z
256+
257+
Write-Host "Downloading Game Data for GeneralsMD" -ForegroundColor Cyan
258+
aws s3 cp s3://github-ci/zerohour104_gamedata_trimmed.7z zerohour104_gamedata_trimmed.7z --endpoint-url $env:AWS_ENDPOINT_URL
259+
$fileHash = (Get-FileHash -Path zerohour104_gamedata_trimmed.7z -Algorithm SHA256).Hash
260+
if ($fileHash -ne $env:EXPECTED_HASH_GENERALSMD) {
261+
Write-Error "Hash verification failed for GeneralsMD!"
262+
exit 1
263+
}
264+
& 7z x zerohour104_gamedata_trimmed.7z -o"C:\GameData\GeneralsMD"
265+
Remove-Item zerohour104_gamedata_trimmed.7z
266+
267+
- name: Set Up Replay Check
268+
if: inputs.replay
269+
shell: pwsh
270+
run: |
271+
# Copy game data to build directory
272+
Copy-Item -Path "C:\GameData\${{ inputs.game }}\*" -Destination "build\${{ inputs.preset }}\${{ inputs.game }}\artifacts" -Recurse -Force
273+
274+
# Set Generals InstallPath in Registry (needed by Zero Hour)
275+
$regPath = "HKCU:\SOFTWARE\Electronic Arts\EA Games\Generals"
276+
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
277+
Set-ItemProperty -Path $regPath -Name InstallPath -Value "C:\GameData\Generals\" -Type String
278+
279+
# Move Replays and Maps to User Dir
280+
$replaySource = "GeneralsReplays/GeneralsZH/1.04/Replays"
281+
$replayDest = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Replays"
282+
New-Item -ItemType Directory -Path $replayDest -Force | Out-Null
283+
Copy-Item -Path "$replaySource\*" -Destination $replayDest -Recurse -Force
284+
285+
$mapSource = "GeneralsReplays/GeneralsZH/1.04/Maps"
286+
$mapDest = "$env:USERPROFILE\Documents\Command and Conquer Generals Zero Hour Data\Maps"
287+
New-Item -ItemType Directory -Path $mapDest -Force | Out-Null
288+
Copy-Item -Path "$mapSource\*" -Destination $mapDest -Recurse -Force
289+
290+
- name: Run Replay Compatibility Tests
291+
if: inputs.replay
292+
shell: pwsh
293+
run: |
294+
$exePath = "build\${{ inputs.preset }}\${{ inputs.game }}\artifacts\generalszh.exe"
295+
$arguments = "-jobs 4 -headless -replay *.rep"
296+
$timeoutSeconds = 600
297+
$stdoutPath = "stdout.log"
298+
$stderrPath = "stderr.log"
299+
300+
if (-not (Test-Path $exePath)) {
301+
Write-Host "ERROR: Executable not found at $exePath"
302+
exit 1
303+
}
304+
305+
Remove-Item $stdoutPath, $stderrPath -ErrorAction SilentlyContinue
306+
Write-Host "Run $exePath $arguments"
307+
$process = Start-Process -FilePath $exePath -ArgumentList $arguments -RedirectStandardOutput $stdoutPath -RedirectStandardError $stderrPath -PassThru
308+
$exited = $process.WaitForExit($timeoutSeconds * 1000)
309+
310+
if (-not $exited) {
311+
Write-Host "ERROR: Process timed out after $timeoutSeconds seconds"
312+
Stop-Process -Id $process.Id -Force
313+
}
314+
315+
Write-Host "=== STDOUT ==="
316+
Get-Content $stdoutPath
317+
if ((Test-Path $stderrPath) -and (Get-Item $stderrPath).Length -gt 0) {
318+
Write-Host "`n=== STDERR ==="
319+
Get-Content $stderrPath
320+
}
321+
322+
if (-not $exited) { exit 1 }
323+
if ($process.ExitCode -ne 0) {
324+
Write-Host "ERROR: Process failed with exit code $($process.ExitCode)"
325+
exit $process.ExitCode
326+
}
327+
Write-Host "Success!"
328+
329+
- name: Upload Replay Debug Log
330+
if: ${{ inputs.replay && always() }}
331+
uses: actions/upload-artifact@v4
332+
with:
333+
name: Replay-Debug-Log-${{ inputs.preset }}
334+
path: build\${{ inputs.preset }}\${{ inputs.game }}\artifacts\DebugLogFile*.txt
335+
retention-days: 30
336+
if-no-files-found: ignore

0 commit comments

Comments
 (0)