windows.yml 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. name: Windows Build (tags only)
  2. on:
  3. push:
  4. tags:
  5. - 'v*' # run only on tags like v1.2.3
  6. permissions:
  7. contents: write
  8. jobs:
  9. build-windows:
  10. runs-on: windows-latest
  11. env:
  12. BUILD_TYPE: Release
  13. APP_NAME: standard_of_iron
  14. APP_DIR: build\bin
  15. QML_DIR: ui\qml
  16. QT_VERSION: 6.8.2
  17. QT_ARCH: win64_msvc2022_64
  18. QT_ROOT: D:\a\Standard-of-Iron\Qt
  19. steps:
  20. - uses: actions/checkout@v4
  21. - uses: actions/setup-python@v5
  22. with:
  23. python-version: '3.12'
  24. - uses: ilammy/msvc-dev-cmd@v1
  25. # Fast path: official installer (allowed to fail)
  26. - name: Install Qt (fast path)
  27. id: fastqt
  28. continue-on-error: true
  29. uses: jurplel/install-qt-action@v4
  30. with:
  31. version: ${{ env.QT_VERSION }}
  32. host: windows
  33. arch: ${{ env.QT_ARCH }}
  34. cache: true
  35. aqtversion: '==3.3.0'
  36. modules: 'qt5compat qtmultimedia'
  37. tools-only: false
  38. # Fallback: install with aqt directly if fast path fails
  39. - name: Install Qt (fallback via aqt)
  40. if: ${{ steps.fastqt.outcome != 'success' }}
  41. shell: pwsh
  42. run: |
  43. python -m pip install --upgrade pip
  44. python -m pip install "aqtinstall==3.3.0" "py7zr==1.0.*"
  45. $qtDir = Join-Path $env:QT_ROOT "$env:QT_VERSION\$env:QT_ARCH"
  46. New-Item -ItemType Directory -Force -Path $qtDir | Out-Null
  47. try {
  48. python -m aqt install-qt windows desktop $env:QT_VERSION $env:QT_ARCH `
  49. --outputdir "$env:QT_ROOT" `
  50. -m qtmultimedia qt5compat qtdeclarative
  51. } catch {
  52. Write-Warning "Explicit module install failed. Falling back to '-m all'. Error: $($_.Exception.Message)"
  53. python -m aqt install-qt windows desktop $env:QT_VERSION $env:QT_ARCH `
  54. --outputdir "$env:QT_ROOT" -m all
  55. }
  56. if (!(Test-Path "$qtDir\lib\cmake\Qt6\Qt6Config.cmake")) {
  57. Write-Error "Qt6Config.cmake not found in $qtDir after fallback install."
  58. exit 1
  59. }
  60. # Export env and PATH
  61. echo "Qt6_DIR=$qtDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
  62. echo "$qtDir\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
  63. # Normalize env for both paths
  64. - name: Normalize Qt env
  65. shell: pwsh
  66. run: |
  67. $candidates = @()
  68. if ($env:Qt6_DIR) { $candidates += $env:Qt6_DIR }
  69. if ($env:QT_ROOT_DIR) { $candidates += $env:QT_ROOT_DIR }
  70. $candidates += (Join-Path $env:QT_ROOT "$env:QT_VERSION\$env:QT_ARCH")
  71. $qtDir = $null
  72. foreach ($c in $candidates | Select-Object -Unique) {
  73. if ($c -and (Test-Path (Join-Path $c 'lib\cmake\Qt6\Qt6Config.cmake'))) {
  74. $qtDir = $c
  75. break
  76. }
  77. }
  78. if (-not $qtDir) {
  79. Write-Error "Could not locate Qt6. Checked: $($candidates -join ', ')"
  80. exit 1
  81. }
  82. echo "Qt6_DIR=$qtDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
  83. echo "$qtDir\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
  84. Write-Host "Qt6_DIR=$qtDir"
  85. - name: Verify Qt installation
  86. shell: pwsh
  87. run: |
  88. Write-Host "Qt6_DIR=$env:Qt6_DIR"
  89. if (!(Test-Path "$env:Qt6_DIR\lib\cmake\Qt6\Qt6Config.cmake")) {
  90. Write-Error "Qt6Config.cmake not found in $env:Qt6_DIR"
  91. exit 1
  92. }
  93. if (!(Get-Command windeployqt -ErrorAction SilentlyContinue)) {
  94. Write-Error "windeployqt not found on PATH"
  95. exit 1
  96. }
  97. - name: Configure (CMake + Ninja)
  98. shell: pwsh
  99. run: |
  100. cmake -S . -B build -G "Ninja" `
  101. -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} `
  102. -DDEFAULT_LANG=en `
  103. -DCMAKE_PREFIX_PATH="$env:Qt6_DIR"
  104. - name: Build
  105. run: cmake --build build
  106. # Code signing (optional: signs .exe with Authenticode)
  107. - name: Sign executable
  108. if: ${{ secrets.WINDOWS_CERTIFICATE != '' && secrets.WINDOWS_CERTIFICATE_PASSWORD != '' }}
  109. shell: pwsh
  110. env:
  111. CERT_DATA: ${{ secrets.WINDOWS_CERTIFICATE }}
  112. CERT_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
  113. run: |
  114. # Validate that the executable exists
  115. $exePath = "$env:APP_DIR\${{ env.APP_NAME }}.exe"
  116. if (!(Test-Path $exePath)) {
  117. Write-Error "Executable not found at: $exePath"
  118. exit 1
  119. }
  120. # Decode certificate from base64 secret and save to temp file
  121. $certPath = "$env:TEMP\cert.pfx"
  122. $certBytes = [System.Convert]::FromBase64String($env:CERT_DATA)
  123. [System.IO.File]::WriteAllBytes($certPath, $certBytes)
  124. try {
  125. # Find signtool.exe - check multiple potential locations
  126. $searchPaths = @(
  127. "$env:ProgramFiles(x86)\Windows Kits",
  128. "$env:ProgramFiles\Windows Kits"
  129. )
  130. $signtool = $null
  131. foreach ($searchPath in $searchPaths) {
  132. if (Test-Path $searchPath) {
  133. $signtool = Get-ChildItem $searchPath -Recurse -Filter "signtool.exe" -ErrorAction SilentlyContinue |
  134. Where-Object { $_.FullName -match "x64" } |
  135. Select-Object -First 1
  136. if ($signtool) { break }
  137. }
  138. }
  139. if (-not $signtool) {
  140. Write-Error "signtool.exe not found in Windows Kits directories"
  141. exit 1
  142. }
  143. Write-Host "Using signtool: $($signtool.FullName)"
  144. # Prepare signing arguments - use environment variable for password
  145. $signArgs = @(
  146. "sign",
  147. "/f", $certPath,
  148. "/p", $env:CERT_PASSWORD,
  149. "/tr", "http://timestamp.digicert.com",
  150. "/td", "SHA256",
  151. "/fd", "SHA256",
  152. "/v",
  153. $exePath
  154. )
  155. Write-Host "Signing $exePath..."
  156. & $signtool.FullName $signArgs
  157. if ($LASTEXITCODE -ne 0) {
  158. Write-Error "Code signing failed with exit code $LASTEXITCODE"
  159. exit 1
  160. }
  161. Write-Host "Successfully signed $exePath"
  162. # Verify the signature
  163. Write-Host "Verifying signature..."
  164. & $signtool.FullName verify /pa /v $exePath
  165. if ($LASTEXITCODE -ne 0) {
  166. Write-Error "Signature verification failed with exit code $LASTEXITCODE"
  167. exit 1
  168. }
  169. Write-Host "Signature verification successful"
  170. } finally {
  171. # Clean up certificate file
  172. if (Test-Path $certPath) {
  173. Remove-Item $certPath -Force
  174. }
  175. }
  176. # Deploy Qt DLLs (dynamic linking ensures LGPL v3 compliance)
  177. - name: Deploy Qt
  178. shell: pwsh
  179. run: |
  180. $mode = if ("${{ env.BUILD_TYPE }}" -eq "Debug") { "--debug" } else { "--release" }
  181. windeployqt $mode --compiler-runtime --qmldir "$env:QML_DIR" "$env:APP_DIR\${{ env.APP_NAME }}.exe"
  182. # SAFE: write qt.conf
  183. - name: Write qt.conf
  184. shell: pwsh
  185. run: |
  186. @(
  187. '[Paths]'
  188. 'Plugins = .'
  189. 'Imports = qml'
  190. 'Qml2Imports = qml'
  191. 'Translations = translations'
  192. ) | Set-Content -Encoding ASCII "$env:APP_DIR\qt.conf"
  193. # Copy Windows launcher and debug scripts from dist/windows/
  194. - name: Copy launcher and debug scripts
  195. shell: pwsh
  196. run: |
  197. Write-Host "Copying Windows launcher and debug scripts..."
  198. $scripts = @(
  199. "LAUNCH.bat",
  200. "run_debug.cmd",
  201. "run_debug_angle.cmd",
  202. "run_debug_softwaregl.cmd"
  203. )
  204. foreach ($script in $scripts) {
  205. $src = "dist\windows\$script"
  206. $dst = "$env:APP_DIR\$script"
  207. if (!(Test-Path $src)) {
  208. Write-Error "Source script not found: $src"
  209. exit 1
  210. }
  211. Copy-Item $src $dst -Force
  212. Write-Host "✓ Copied $script"
  213. }
  214. Write-Host "All launcher scripts deployed successfully."
  215. # Add comprehensive README for users
  216. - name: Add README.txt for distribution
  217. shell: pwsh
  218. run: |
  219. @(
  220. '==============================================================================='
  221. ' STANDARD OF IRON'
  222. ' Windows Distribution'
  223. '==============================================================================='
  224. ''
  225. 'QUICK START'
  226. '-----------'
  227. '1. Double-click "LAUNCH.bat" to start the game'
  228. ' OR'
  229. '2. Double-click "standard_of_iron.exe" directly'
  230. ''
  231. 'If the game does not start, see TROUBLESHOOTING below.'
  232. ''
  233. ''
  234. 'TROUBLESHOOTING'
  235. '---------------'
  236. 'If the game does not appear or crashes on startup, try these steps in order:'
  237. ''
  238. '1. Run "run_debug.cmd"'
  239. ' - This will start the game with diagnostic logging'
  240. ' - Look for error messages in the console window'
  241. ' - A log file "runlog.txt" will be created'
  242. ''
  243. '2. Run "run_debug_angle.cmd" (RECOMMENDED for compatibility)'
  244. ' - Uses ANGLE rendering (OpenGL via DirectX)'
  245. ' - Works on most systems, even with outdated GPU drivers'
  246. ' - Creates "runlog_angle.txt" log file'
  247. ''
  248. '3. Run "run_debug_softwaregl.cmd" (FALLBACK - works everywhere)'
  249. ' - Uses CPU-based software rendering'
  250. ' - Guaranteed to work but with reduced performance'
  251. ' - Creates "runlog_softwaregl.txt" log file'
  252. ''
  253. ''
  254. 'COMMON ISSUES'
  255. '-------------'
  256. 'Q: I double-click the EXE and nothing happens'
  257. 'A: Your system may lack proper graphics drivers. Run "run_debug_softwaregl.cmd"'
  258. ''
  259. 'Q: The game crashes immediately'
  260. 'A: Try "run_debug_angle.cmd" for better GPU driver compatibility'
  261. ''
  262. 'Q: I see OpenGL errors in the log'
  263. 'A: Run "run_debug_softwaregl.cmd" to bypass GPU drivers entirely'
  264. ''
  265. 'Q: Performance is slow'
  266. 'A: Make sure you are NOT using "run_debug_softwaregl.cmd"'
  267. ' Try "run_debug.cmd" or "run_debug_angle.cmd" instead'
  268. ''
  269. ''
  270. 'GRAPHICS MODES EXPLAINED'
  271. '------------------------'
  272. 'Desktop OpenGL (run_debug.cmd):'
  273. ' - Best performance'
  274. ' - Requires proper GPU drivers'
  275. ' - Use this if your GPU drivers are up to date'
  276. ''
  277. 'ANGLE (run_debug_angle.cmd):'
  278. ' - Good performance'
  279. ' - Compatible with most systems'
  280. ' - Translates OpenGL to DirectX'
  281. ' - RECOMMENDED for older or problematic GPUs'
  282. ''
  283. 'Software OpenGL (run_debug_softwaregl.cmd):'
  284. ' - CPU-based rendering'
  285. ' - Works on ALL systems'
  286. ' - Reduced performance'
  287. ' - Use as last resort'
  288. ''
  289. ''
  290. 'LOG FILES'
  291. '---------'
  292. 'When using the debug scripts, log files are created:'
  293. ' - runlog.txt (Desktop OpenGL mode)'
  294. ' - runlog_angle.txt (ANGLE mode)'
  295. ' - runlog_softwaregl.txt (Software OpenGL mode)'
  296. ''
  297. 'These files contain diagnostic information to help identify problems.'
  298. 'Include these files when reporting issues.'
  299. ''
  300. ''
  301. 'SYSTEM REQUIREMENTS'
  302. '-------------------'
  303. 'Minimum:'
  304. ' - Windows 10 or later'
  305. ' - 4 GB RAM'
  306. ' - Any GPU (software rendering supported)'
  307. ''
  308. 'Recommended:'
  309. ' - Windows 10/11'
  310. ' - 8 GB RAM'
  311. ' - GPU with OpenGL 3.3+ support'
  312. ' - Updated graphics drivers'
  313. ''
  314. ''
  315. 'UPDATING GRAPHICS DRIVERS'
  316. '-------------------------'
  317. 'For best performance, update your graphics drivers:'
  318. ''
  319. 'NVIDIA: https://www.nvidia.com/Download/index.aspx'
  320. 'AMD: https://www.amd.com/en/support'
  321. 'Intel: https://www.intel.com/content/www/us/en/download-center/home.html'
  322. ''
  323. ''
  324. 'REPORTING ISSUES'
  325. '----------------'
  326. 'If you encounter problems:'
  327. ''
  328. '1. Run one of the debug scripts (run_debug.cmd or run_debug_angle.cmd)'
  329. '2. Save the generated log file (runlog.txt or runlog_angle.txt)'
  330. '3. Report the issue at: https://github.com/djeada/Standard-of-Iron/issues'
  331. '4. Include the log file and your system information:'
  332. ' - Windows version'
  333. ' - GPU model'
  334. ' - Which startup method you tried'
  335. ''
  336. ''
  337. 'MORE INFORMATION'
  338. '----------------'
  339. 'Website: https://github.com/djeada/Standard-of-Iron'
  340. 'License: See LICENSE file (MIT License)'
  341. ''
  342. 'This software uses Qt Framework (LGPL v3)'
  343. 'See THIRD_PARTY_LICENSES.md for details'
  344. ''
  345. '==============================================================================='
  346. ) | Set-Content -Encoding ASCII "$env:APP_DIR\README.txt"
  347. - name: Add GL/ANGLE fallbacks (mandatory)
  348. shell: pwsh
  349. run: |
  350. $qtbin = Join-Path $env:Qt6_DIR "bin"
  351. $required = @("d3dcompiler_47.dll","opengl32sw.dll","libEGL.dll","libGLESv2.dll")
  352. $missing = @()
  353. foreach ($f in $required) {
  354. $src = Join-Path $qtbin $f
  355. if (Test-Path $src) {
  356. Copy-Item $src "$env:APP_DIR" -Force
  357. Write-Host "✓ Copied $f"
  358. } else {
  359. Write-Error "✗ Missing required fallback DLL: $f"
  360. $missing += $f
  361. }
  362. }
  363. if ($missing.Count -gt 0) {
  364. Write-Error "CRITICAL: Missing graphics fallback DLLs: $($missing -join ', ')"
  365. Write-Error "These DLLs are required for compatibility on systems without proper GPU drivers."
  366. Write-Error "Without them, the app will fail silently on many user machines."
  367. exit 1
  368. }
  369. Write-Host "All graphics fallback DLLs successfully deployed."
  370. - name: Copy assets
  371. shell: pwsh
  372. run: |
  373. if (!(Test-Path "$env:APP_DIR\assets")) {
  374. New-Item -ItemType Directory -Path "$env:APP_DIR\assets" | Out-Null
  375. }
  376. robocopy assets "$env:APP_DIR\assets" /E /NFL /NDL /NJH /NJS /nc /ns /np
  377. if ($LASTEXITCODE -ge 8) { exit $LASTEXITCODE } else { exit 0 }
  378. - name: Sanity check deployed files
  379. shell: pwsh
  380. run: |
  381. Write-Host "=== Verifying Runtime Completeness ==="
  382. # Critical Qt runtime files
  383. $req = @(
  384. "$env:APP_DIR\platforms\qwindows.dll",
  385. "$env:APP_DIR\Qt6Core.dll",
  386. "$env:APP_DIR\Qt6Gui.dll",
  387. "$env:APP_DIR\Qt6Qml.dll"
  388. )
  389. # Graphics fallback DLLs (now mandatory)
  390. $graphics_fallbacks = @(
  391. "$env:APP_DIR\d3dcompiler_47.dll",
  392. "$env:APP_DIR\opengl32sw.dll",
  393. "$env:APP_DIR\libEGL.dll",
  394. "$env:APP_DIR\libGLESv2.dll"
  395. )
  396. $missing = @()
  397. Write-Host "`nChecking Qt runtime DLLs..."
  398. foreach ($f in $req) {
  399. if (!(Test-Path $f)) {
  400. $missing += $f
  401. Write-Error "✗ Missing: $f"
  402. } else {
  403. Write-Host "✓ Found: $(Split-Path -Leaf $f)"
  404. }
  405. }
  406. Write-Host "`nChecking graphics fallback DLLs..."
  407. foreach ($f in $graphics_fallbacks) {
  408. if (!(Test-Path $f)) {
  409. $missing += $f
  410. Write-Error "✗ Missing: $f"
  411. } else {
  412. Write-Host "✓ Found: $(Split-Path -Leaf $f)"
  413. }
  414. }
  415. if ($missing.Count -gt 0) {
  416. Write-Error "`nDEPLOYMENT FAILED - Missing files:`n$($missing -join "`n")"
  417. exit 1
  418. }
  419. Write-Host "`n✓ All required runtime files are present."
  420. Write-Host "Deployment is complete and ready for distribution."
  421. - name: Dependency check (dumpbin)
  422. shell: pwsh
  423. run: |
  424. Write-Host "Checking EXE dependencies with dumpbin..."
  425. # Find dumpbin.exe in Visual Studio installation
  426. $dumpbin = Get-ChildItem "C:\Program Files\Microsoft Visual Studio" -Recurse -Filter "dumpbin.exe" -ErrorAction SilentlyContinue |
  427. Where-Object { $_.FullName -match "Hostx64\\x64" } |
  428. Select-Object -First 1
  429. if (-not $dumpbin) {
  430. Write-Warning "dumpbin.exe not found, skipping dependency check"
  431. exit 0
  432. }
  433. Write-Host "Using dumpbin: $($dumpbin.FullName)"
  434. # Get dependencies
  435. $exePath = "$env:APP_DIR\${{ env.APP_NAME }}.exe"
  436. $output = & $dumpbin.FullName /DEPENDENTS $exePath 2>&1 | Out-String
  437. Write-Host "Dependencies:"
  438. Write-Host $output
  439. # Parse DLL names from dumpbin output
  440. $dllPattern = '^\s+([a-zA-Z0-9_\-\.]+\.dll)\s*$'
  441. $dependencies = @()
  442. foreach ($line in $output -split "`n") {
  443. if ($line -match $dllPattern) {
  444. $dependencies += $matches[1]
  445. }
  446. }
  447. # Known system DLLs that don't need to be bundled
  448. # These include Windows API sets (api-ms-win-*) and core system DLLs
  449. $knownSystemDlls = @(
  450. 'KERNEL32.dll',
  451. 'USER32.dll',
  452. 'ADVAPI32.dll',
  453. 'SHELL32.dll',
  454. 'ole32.dll',
  455. 'OLEAUT32.dll',
  456. 'OPENGL32.dll',
  457. 'GDI32.dll',
  458. 'WS2_32.dll',
  459. 'NETAPI32.dll'
  460. )
  461. # Check if each dependency exists in APP_DIR or is a known system DLL
  462. $systemDirs = @(
  463. "$env:SystemRoot\System32",
  464. "$env:SystemRoot\SysWOW64"
  465. )
  466. $missingDeps = @()
  467. foreach ($dll in $dependencies) {
  468. # Skip Windows API set DLLs (api-ms-win-*, ext-ms-*)
  469. if ($dll -match '^(api-ms-win-|ext-ms-)') {
  470. Write-Host "Skipping Windows API set: $dll"
  471. continue
  472. }
  473. # Skip known system DLLs
  474. if ($knownSystemDlls -contains $dll) {
  475. Write-Host "Skipping known system DLL: $dll"
  476. continue
  477. }
  478. $foundInApp = Test-Path (Join-Path $env:APP_DIR $dll)
  479. $foundInSystem = $false
  480. foreach ($sysDir in $systemDirs) {
  481. if (Test-Path (Join-Path $sysDir $dll)) {
  482. $foundInSystem = $true
  483. break
  484. }
  485. }
  486. if (-not $foundInApp -and -not $foundInSystem) {
  487. $missingDeps += $dll
  488. Write-Warning "Missing dependency: $dll"
  489. } else {
  490. $location = if ($foundInApp) { "app directory" } else { "system directory" }
  491. Write-Host "Found $dll in $location"
  492. }
  493. }
  494. if ($missingDeps.Count -gt 0) {
  495. Write-Error "Missing DLL dependencies: $($missingDeps -join ', ')"
  496. exit 1
  497. }
  498. Write-Host "All required dependencies are present"
  499. - name: Smoke-run (software GL, capture exit)
  500. shell: pwsh
  501. run: |
  502. $ErrorActionPreference = 'Stop'
  503. # Exit code constants for better readability
  504. $EXIT_ACCESS_VIOLATION = -1073741819 # 0xC0000005 - Access violation (common OpenGL crash)
  505. $EXIT_ORDINAL_NOT_FOUND = -1073741510 # 0xC000007A - Ordinal not found
  506. $EXIT_DLL_NOT_FOUND = -1073741701 # 0xC0000135 - DLL not found
  507. $EXIT_APPINIT_FAILURE = -1073741515 # 0xC0000139 - Entry point not found
  508. $env:QT_DEBUG_PLUGINS = "1"
  509. $env:QT_LOGGING_RULES = "qt.*=true;qt.qml=true;qqml.*=true;qt.rhi.*=true"
  510. $env:QT_OPENGL = "software"
  511. $env:QT_QPA_PLATFORM = "windows"
  512. Write-Host "=== Smoke Test Configuration ==="
  513. Write-Host "QT_OPENGL: $env:QT_OPENGL"
  514. Write-Host "QT_QPA_PLATFORM: $env:QT_QPA_PLATFORM"
  515. Write-Host "================================"
  516. Push-Location "$env:APP_DIR"
  517. try {
  518. $logFile = "smoke_test.log"
  519. $errFile = "smoke_test_err.log"
  520. Write-Host "Starting application..."
  521. $process = Start-Process -FilePath ".\${{ env.APP_NAME }}.exe" `
  522. -RedirectStandardOutput $logFile `
  523. -RedirectStandardError $errFile `
  524. -PassThru -NoNewWindow
  525. $timeoutSeconds = 8
  526. $checkInterval = 1
  527. $elapsed = 0
  528. $exited = $false
  529. $successMarkers = @()
  530. Write-Host "Monitoring startup for $timeoutSeconds seconds..."
  531. while ($elapsed -lt $timeoutSeconds) {
  532. Start-Sleep -Seconds $checkInterval
  533. $elapsed += $checkInterval
  534. # Check if process exited
  535. if ($process.HasExited) {
  536. $exited = $true
  537. break
  538. }
  539. # Check logs for success markers
  540. if ((Test-Path $logFile)) {
  541. $content = Get-Content $logFile -Raw
  542. if ($content -match "QGuiApplication created successfully") {
  543. $successMarkers += "QGuiApplication initialized"
  544. }
  545. if ($content -match "GameEngine created") {
  546. $successMarkers += "GameEngine created"
  547. }
  548. if ($content -match "QML engine.*loading") {
  549. $successMarkers += "QML loading"
  550. }
  551. }
  552. Write-Host " [$elapsed/$timeoutSeconds s] Process running, found markers: $($successMarkers.Count)"
  553. }
  554. # Process still running after timeout - this is GOOD
  555. if (-not $exited) {
  556. Write-Host "`n✓ SUCCESS: Application is running after $timeoutSeconds seconds"
  557. Write-Host " Found initialization markers: $($successMarkers -join ', ')"
  558. # Kill process gracefully
  559. $process.Kill()
  560. $process.WaitForExit(3000)
  561. # Show relevant log excerpts
  562. if (Test-Path $logFile) {
  563. Write-Host "`n=== Application Output (last 30 lines) ==="
  564. Get-Content $logFile -Tail 30 | Write-Host
  565. }
  566. Write-Host "`n✓ Smoke test PASSED - application starts and runs correctly"
  567. exit 0
  568. }
  569. # Process exited early - investigate why
  570. $code = $process.ExitCode
  571. Write-Host "`n⚠ Process exited early with code: $code (after $elapsed seconds)"
  572. # Show logs to help diagnose
  573. if (Test-Path $logFile) {
  574. Write-Host "`n=== Application Output ==="
  575. Get-Content $logFile | Write-Host
  576. }
  577. if (Test-Path $errFile) {
  578. Write-Host "`n=== Error Output ==="
  579. Get-Content $errFile | Write-Host
  580. }
  581. # Categorize exit codes
  582. $expectedOnHeadless = @($EXIT_ACCESS_VIOLATION, $EXIT_ORDINAL_NOT_FOUND)
  583. $deploymentIssues = @($EXIT_DLL_NOT_FOUND, $EXIT_APPINIT_FAILURE)
  584. if ($deploymentIssues -contains $code) {
  585. Write-Error "✗ DEPLOYMENT ISSUE: Missing DLL or symbol (exit code $code)"
  586. Write-Error "This indicates incomplete deployment or missing dependencies"
  587. exit 1
  588. }
  589. if ($expectedOnHeadless -contains $code) {
  590. Write-Warning "⚠ Headless-expected exit code ($code)"
  591. Write-Warning "This is expected on CI without display. Checking for fatal errors..."
  592. # Check if we got far enough in initialization
  593. if ($successMarkers.Count -ge 2) {
  594. Write-Host "✓ Found $($successMarkers.Count) success markers before exit"
  595. Write-Host " This suggests the app initializes correctly on systems with displays"
  596. exit 0
  597. }
  598. Write-Warning "Only found $($successMarkers.Count) success markers"
  599. Write-Warning "Treating as PASS for headless CI, but verify on real systems"
  600. exit 0
  601. }
  602. if ($code -eq 0) {
  603. Write-Host "✓ App exited cleanly with code 0"
  604. if ($successMarkers.Count -ge 2) {
  605. Write-Host "✓ Found adequate success markers: $($successMarkers -join ', ')"
  606. exit 0
  607. }
  608. Write-Warning "App exited cleanly but found only $($successMarkers.Count) success markers"
  609. Write-Warning "This may indicate early/incomplete initialization"
  610. exit 0
  611. }
  612. Write-Error "✗ UNEXPECTED EXIT: Exit code $code with only $($successMarkers.Count) success markers"
  613. Write-Error "This suggests a real deployment or runtime issue"
  614. exit 1
  615. } catch {
  616. Write-Error "✗ Failed to start process: $_"
  617. exit 1
  618. } finally {
  619. Pop-Location
  620. }
  621. # Test ANGLE backend to ensure compatibility mode works
  622. - name: Verify ANGLE backend (compatibility test)
  623. shell: pwsh
  624. run: |
  625. Write-Host "=== ANGLE Backend Verification ==="
  626. Write-Host "Testing ANGLE (OpenGL ES via D3D11) for maximum compatibility"
  627. Push-Location "$env:APP_DIR"
  628. try {
  629. $env:QT_DEBUG_PLUGINS = "0"
  630. $env:QT_OPENGL = "angle"
  631. $env:QT_ANGLE_PLATFORM = "d3d11"
  632. $env:QT_QPA_PLATFORM = "windows"
  633. $logFile = "angle_test.log"
  634. $process = Start-Process -FilePath ".\${{ env.APP_NAME }}.exe" `
  635. -RedirectStandardOutput $logFile `
  636. -RedirectStandardError "${logFile}.err" `
  637. -PassThru -NoNewWindow
  638. # Give ANGLE mode 5 seconds to initialize
  639. Start-Sleep -Seconds 5
  640. if (-not $process.HasExited) {
  641. Write-Host "✓ ANGLE mode: Process running successfully"
  642. $process.Kill()
  643. $process.WaitForExit(2000)
  644. } else {
  645. $code = $process.ExitCode
  646. # ANGLE might fail on headless CI, that's OK
  647. Write-Warning "ANGLE mode exited with code: $code"
  648. Write-Warning "This is expected on headless CI without D3D11"
  649. }
  650. Write-Host "✓ ANGLE backend verification complete"
  651. } catch {
  652. Write-Warning "ANGLE test failed (expected on headless CI): $_"
  653. } finally {
  654. Pop-Location
  655. }
  656. # Final readiness check
  657. - name: Final runtime readiness verification
  658. shell: pwsh
  659. run: |
  660. Write-Host "=== Final Runtime Readiness Check ==="
  661. Write-Host ""
  662. $criticalFiles = @(
  663. "$env:APP_DIR\standard_of_iron.exe",
  664. "$env:APP_DIR\LAUNCH.bat",
  665. "$env:APP_DIR\README.txt",
  666. "$env:APP_DIR\run_debug.cmd",
  667. "$env:APP_DIR\run_debug_angle.cmd",
  668. "$env:APP_DIR\run_debug_softwaregl.cmd",
  669. "$env:APP_DIR\opengl32sw.dll",
  670. "$env:APP_DIR\libEGL.dll",
  671. "$env:APP_DIR\libGLESv2.dll",
  672. "$env:APP_DIR\d3dcompiler_47.dll"
  673. )
  674. $allPresent = $true
  675. foreach ($file in $criticalFiles) {
  676. if (Test-Path $file) {
  677. Write-Host "✓ $(Split-Path -Leaf $file)"
  678. } else {
  679. Write-Error "✗ MISSING: $(Split-Path -Leaf $file)"
  680. $allPresent = $false
  681. }
  682. }
  683. if (-not $allPresent) {
  684. Write-Error "Deployment is incomplete!"
  685. exit 1
  686. }
  687. Write-Host ""
  688. Write-Host "═══════════════════════════════════════════════════════"
  689. Write-Host " ✓ BUILD IS SELF-CONTAINED AND RUNTIME READY"
  690. Write-Host "═══════════════════════════════════════════════════════"
  691. Write-Host ""
  692. Write-Host "Included components:"
  693. Write-Host " • Main executable with fallback rendering"
  694. Write-Host " • User-friendly launcher (LAUNCH.bat)"
  695. Write-Host " • Comprehensive documentation (README.txt)"
  696. Write-Host " • Debug/troubleshooting scripts"
  697. Write-Host " • Software OpenGL fallback (CPU rendering)"
  698. Write-Host " • ANGLE fallback (OpenGL ES via DirectX)"
  699. Write-Host " • All required Qt and system libraries"
  700. Write-Host ""
  701. Write-Host "Users can:"
  702. Write-Host " 1. Double-click LAUNCH.bat for guided startup"
  703. Write-Host " 2. Use debug scripts if issues occur"
  704. Write-Host " 3. Fallback to software rendering if GPU fails"
  705. Write-Host " 4. Read README.txt for detailed troubleshooting"
  706. Write-Host ""
  707. - name: Zip
  708. shell: pwsh
  709. run: |
  710. $zip = "standard_of_iron-win-x64-${{ env.BUILD_TYPE }}.zip"
  711. if (Test-Path $zip) { Remove-Item $zip -Force }
  712. Compress-Archive -Path "$env:APP_DIR\*" -DestinationPath $zip -Force
  713. - uses: actions/upload-artifact@v4
  714. with:
  715. name: windows-${{ env.BUILD_TYPE }}
  716. path: standard_of_iron-win-x64-${{ env.BUILD_TYPE }}.zip
  717. - name: Attach to GitHub Release
  718. uses: softprops/action-gh-release@v2
  719. if: startsWith(github.ref, 'refs/tags/')
  720. with:
  721. files: standard_of_iron-win-x64-${{ env.BUILD_TYPE }}.zip