Fix Windows scaffold: BOM-less writes, native Python, configuring cmake for builds

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-11 23:30:47 -07:00
parent 2a4824fcb8
commit fa83aeb064
4 changed files with 183 additions and 50 deletions
+27 -3
View File
@@ -1617,19 +1617,43 @@ def _pack_sysroot_cmake_args(
return [f"-DCMAKE_SYSROOT={sysroot}", "-DOpenGL_GL_PREFERENCE=LEGACY"]
def _read_cmake_cache_generator(cache_file: Path) -> str:
def _read_cmake_cache_entry(cache_file: Path, key: str) -> str:
if not cache_file.is_file():
return ""
try:
text = cache_file.read_text(encoding="utf-8", errors="replace")
except OSError:
return ""
prefix = f"{key}:"
for line in text.splitlines():
if line.startswith("CMAKE_GENERATOR:INTERNAL="):
if line.startswith(prefix) and "=" in line:
return line.split("=", 1)[1].strip()
return ""
def _read_cmake_cache_generator(cache_file: Path) -> str:
return _read_cmake_cache_entry(cache_file, "CMAKE_GENERATOR")
def _cmake_for_build_dir(build_dir: Path) -> Path:
"""The cmake that configured *build_dir* (CMakeCache CMAKE_COMMAND).
Building must use the same cmake as configure. A bare ``cmake`` resolves
through PATH, and on Windows that is often an MSYS2 cmake that rewrites
the Windows build path as ``/f/.../F:/...`` and fails.
"""
cached = _read_cmake_cache_entry(build_dir / "CMakeCache.txt", "CMAKE_COMMAND")
if cached and Path(cached).is_file():
return Path(cached)
found = _which_tool("cmake")
if found is None:
raise RuntimeError(
f"cmake not found: {build_dir} has no configured CMAKE_COMMAND "
"and no cmake is on PATH"
)
return found
def _cmake_configure(
project_root: Path,
build_dir: Path,
@@ -1759,7 +1783,7 @@ def _cmake_build(
os.cpu_count() or 4
)
cmd = [
"cmake",
str(_cmake_for_build_dir(build_dir)),
"--build",
str(build_dir),
"--parallel",
+2 -2
View File
@@ -953,8 +953,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_cli_generate_aot_static.py
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
# psxrecomp_cli.py rebuild: the pre-build mtime clamp works without
# lutimes (Windows), and --cmake-extra wins over the shipping
# PSX_DEBUG_TOOLS=OFF default.
# lutimes (Windows), --cmake-extra wins over the shipping
# PSX_DEBUG_TOOLS=OFF default, and the build runs the configuring cmake.
add_test(
NAME cli_rebuild_host
COMMAND ${Python3_EXECUTABLE}
+102 -45
View File
@@ -80,7 +80,41 @@ function Assert-Cmd([string]$cmd) {
}
}
Assert-Cmd git
Assert-Cmd python
# Windows-native Python only. An MSYS2/Cygwin python (e.g. devkitPro's, often
# first on PATH) ignores the Windows-style PYTHONPATH set below, so every
# sibling helper import fails.
function Resolve-Python {
if ($env:PSXRECOMP_PYTHON) {
if (Test-Path -LiteralPath $env:PSXRECOMP_PYTHON) { return $env:PSXRECOMP_PYTHON }
throw "PSXRECOMP_PYTHON does not exist: $env:PSXRECOMP_PYTHON"
}
if (Get-Command py -ErrorAction SilentlyContinue) {
$exe = (& py -3 -c "import sys; print(sys.executable)" 2>$null)
if ($LASTEXITCODE -eq 0 -and $exe) { return ([string]$exe).Trim() }
}
foreach ($c in @(Get-Command python -All -CommandType Application -ErrorAction SilentlyContinue)) {
if ($c.Source -notmatch '(?i)[\\/](msys64|msys2|msys|cygwin64|cygwin|devkitPro)[\\/]') {
return $c.Source
}
}
throw "No Windows-native Python 3 found (MSYS2/Cygwin pythons cannot import the scaffold helpers). Install Python from python.org or set PSXRECOMP_PYTHON."
}
$Python = Resolve-Python
# Windows PowerShell 5.1 writes a BOM for -Encoding UTF8 and UTF-16LE for
# Tee-Object/Out-File. Block files are spliced mid-CMakeLists.txt, where a BOM
# is a CMake parse error, so every file this script writes goes through here.
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
function Resolve-OutPath([string]$Path) {
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
function Write-TextFile([string]$Path, [string]$Value) {
[System.IO.File]::WriteAllText((Resolve-OutPath $Path), $Value + [Environment]::NewLine, $Utf8NoBom)
}
function Add-TextFile([string]$Path, [string]$Value) {
[System.IO.File]::AppendAllText((Resolve-OutPath $Path), $Value + [Environment]::NewLine, $Utf8NoBom)
}
function Test-Interactive {
return [Environment]::UserInteractive -and -not $Yes -and -not ($env:PSXRECOMP_SETUP_YES -eq "1")
@@ -136,7 +170,7 @@ if ($Players -lt 1 -or $Players -gt 8) {
}
$env:PYTHONPATH = "$ScriptDir" + $(if ($env:PYTHONPATH) { ";$env:PYTHONPATH" } else { "" })
$derivedZip = (python -c "from fill_tokens import derive_zip_prefix; import sys; print(derive_zip_prefix(sys.argv[1]))" $Name).Trim()
$derivedZip = (& $Python -c "from fill_tokens import derive_zip_prefix; import sys; print(derive_zip_prefix(sys.argv[1]))" $Name).Trim()
if (-not $ZipPrefix) {
if ($interactive) {
$ZipPrefix = Prompt-Line "Zip / CI artifact prefix" $derivedZip
@@ -152,7 +186,7 @@ if (-not $GithubOwner) {
$GithubOwner = "RetroPortingToolKit"
}
}
$derivedRepo = (python -c "from fill_tokens import sanitize_github_name; import sys; print(sanitize_github_name(sys.argv[1]))" $Name).Trim()
$derivedRepo = (& $Python -c "from fill_tokens import sanitize_github_name; import sys; print(sanitize_github_name(sys.argv[1]))" $Name).Trim()
if (-not $GithubRepo) {
if ($interactive) {
$GithubRepo = Prompt-Line "GitHub repo name (README download badges)" $derivedRepo
@@ -160,8 +194,8 @@ if (-not $GithubRepo) {
$GithubRepo = $derivedRepo
}
}
$GithubOwner = (python -c "from fill_tokens import sanitize_github_name; import sys; print(sanitize_github_name(sys.argv[1]))" $GithubOwner).Trim()
$GithubRepo = (python -c "from fill_tokens import sanitize_github_name; import sys; print(sanitize_github_name(sys.argv[1]))" $GithubRepo).Trim()
$GithubOwner = (& $Python -c "from fill_tokens import sanitize_github_name; import sys; print(sanitize_github_name(sys.argv[1]))" $GithubOwner).Trim()
$GithubRepo = (& $Python -c "from fill_tokens import sanitize_github_name; import sys; print(sanitize_github_name(sys.argv[1]))" $GithubRepo).Trim()
if (-not $PSBoundParameters.ContainsKey("Description")) {
if ($interactive) { $Description = Prompt-Line "Short game description (optional)" "" }
@@ -282,20 +316,20 @@ $RecompUiBlockFile = Join-Path $env:TEMP "psxrecomp_recomp_ui_block.cmake"
$CodegenArgFile = Join-Path $env:TEMP "psxrecomp_codegen_arg.cmake"
if ($useRecompUi) {
Set-Content -Encoding UTF8 -Path $RecompUiBlockFile -Value "# recomp-ui submodule present (PSX_RECOMP_UI defaults ON)."
Set-Content -Encoding UTF8 -Path $CodegenArgFile -Value ' CODEGEN_SETUP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/codegen_setup.c"'
Write-TextFile $RecompUiBlockFile "# recomp-ui submodule present (PSX_RECOMP_UI defaults ON)."
Write-TextFile $CodegenArgFile ' CODEGEN_SETUP_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/codegen_setup.c"'
} else {
Set-Content -Encoding UTF8 -Path $RecompUiBlockFile -Value @"
Write-TextFile $RecompUiBlockFile @"
set(PSX_RECOMP_UI OFF CACHE BOOL
"No recomp-ui submodule in this scaffold" FORCE)
"@
Set-Content -Encoding UTF8 -Path $CodegenArgFile -Value " # CODEGEN_SETUP_SOURCES -- add with recomp-ui / wizard later"
Write-TextFile $CodegenArgFile " # CODEGEN_SETUP_SOURCES -- add with recomp-ui / wizard later"
}
if ($useNetplay) {
# PSX_NETPLAY defaults RNET_ENABLE_ICE=ON; recomp-net FetchContents
# libjuice via pinned URL (not git) so Retro AppImage builds configure.
Set-Content -Encoding UTF8 -Path $NetplayBlockFile -Value @"
Write-TextFile $NetplayBlockFile @"
if(EXISTS "`${PSXRECOMP_ROOT}/lib/recomp-net/CMakeLists.txt")
set(PSX_NETPLAY ON CACHE BOOL
"Link recomp-net delay-sync (opt-in; needs recomp-net)" FORCE)
@@ -304,7 +338,7 @@ endif()
$NetplayRuntimeArg = " ENABLE_NETPLAY_IF_PRESENT"
$NetplayLobbyUrlArg = " NETPLAY_LOBBY_URL `"$NetplayLobbyWs`""
} else {
Set-Content -Encoding UTF8 -Path $NetplayBlockFile -Value @"
Write-TextFile $NetplayBlockFile @"
# Full netplay UI -- enable after adding recomp-ui + testing:
# if(EXISTS "`${PSXRECOMP_ROOT}/lib/recomp-net/CMakeLists.txt")
# set(PSX_NETPLAY ON CACHE BOOL "..." FORCE)
@@ -315,26 +349,26 @@ endif()
}
if ($useWizard) {
Set-Content -Encoding UTF8 -Path $WizardBlockFile -Value @"
Write-TextFile $WizardBlockFile @"
set(PSX_SETUP_WIZARD ON CACHE BOOL
"Advertise first-run setup wizard + Generate & rebuild in recomp-ui" FORCE)
"@
$WizardRuntimeArg = " ENABLE_SETUP_WIZARD"
} else {
Set-Content -Encoding UTF8 -Path $WizardBlockFile -Value @"
Write-TextFile $WizardBlockFile @"
# First-run setup wizard -- enable after testing:
# set(PSX_SETUP_WIZARD ON CACHE BOOL "..." FORCE)
"@
$WizardRuntimeArg = " # ENABLE_SETUP_WIZARD"
}
Set-Content -Encoding UTF8 -Path $BoxartBlockFile -Value ' # LAUNCHER_BOXART "${CMAKE_CURRENT_SOURCE_DIR}/launcher_assets/img/boxart.tga"'
Write-TextFile $BoxartBlockFile ' # LAUNCHER_BOXART "${CMAKE_CURRENT_SOURCE_DIR}/launcher_assets/img/boxart.tga"'
$HasBoxart = $false
$AppIconBlockFile = Join-Path $env:TEMP "psxrecomp_app_icon_block.cmake"
Set-Content -Encoding UTF8 -Path $AppIconBlockFile -Value ' APP_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/psxrecomp.ico"'
Write-TextFile $AppIconBlockFile ' APP_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/psxrecomp.ico"'
function Fill-Template([string]$src, [string]$dst) {
python $FillTokens $src $dst `
& $Python $FillTokens $src $dst `
--set "PROJECT_CMAKE_NAME=$ProjectCmakeName" `
--set "WINDOW_TITLE=$WindowTitle" `
--set "BOOT_EXE=$BootExe" `
@@ -400,7 +434,7 @@ New-Item -ItemType Directory -Force -Path (Join-Path $Root "tools") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $Root "mods\preloaded\packages") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $Root "assets") | Out-Null
# Empty mod catalog tree (build stages mods/preloaded/packages -> <exe>/mods/bundled).
@'
$PreloadedReadme = @'
# Preloaded mods
Ship reviewed, default-disabled packages here:
@@ -420,7 +454,8 @@ launcher owns and no build ever touches. Install them through the launcher Mods
manager rather than committing them here.
See `psxrecomp/docs/MOD_PACKAGES.md`.
'@ | Set-Content -Encoding utf8 (Join-Path $Root "mods\preloaded\README.md")
'@
Write-TextFile (Join-Path $Root "mods\preloaded\README.md") $PreloadedReadme
New-Item -ItemType File -Force -Path (Join-Path $Root "mods\preloaded\packages\.gitkeep") | Out-Null
Copy-Item (Join-Path $ScriptDir "sync_symbols.py") (Join-Path $Root "tools\sync_symbols.py")
@@ -460,7 +495,9 @@ if (Test-Path "psxrecomp\lib\recomp-net") {
Write-Host "== Framework pins =="
$PinsPath = Join-Path $Root "framework_pins.txt"
if (Test-Path "psxrecomp\tools\ci\record_pins.sh") {
bash psxrecomp/tools/ci/record_pins.sh --root $Root | Tee-Object -FilePath $PinsPath
$pinLines = @(bash psxrecomp/tools/ci/record_pins.sh --root $Root)
Write-TextFile $PinsPath ($pinLines -join [Environment]::NewLine)
$pinLines
} else {
$lines = @("psxrecomp=$(git -C psxrecomp rev-parse HEAD)")
if ($useRecompUi) {
@@ -468,7 +505,8 @@ if (Test-Path "psxrecomp\tools\ci\record_pins.sh") {
} else {
$lines += "recomp-ui=<not added>"
}
$lines | Tee-Object -FilePath $PinsPath
Write-TextFile $PinsPath ($lines -join [Environment]::NewLine)
$lines
}
if ($useNetplay -and -not (Test-Path "psxrecomp\lib\recomp-net\CMakeLists.txt")) {
@@ -484,7 +522,7 @@ if ($useCi) {
$WfDst = Join-Path $Root ".github\workflows\release.yml"
if (Test-Path -LiteralPath $WfSrc) {
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $WfDst) | Out-Null
python $FillTokens $WfSrc $WfDst `
& $Python $FillTokens $WfSrc $WfDst `
--ci-placeholders `
--set "ZIP_PREFIX=$ZipPrefix" `
--set "GAME_TITLE=$WindowTitle" `
@@ -532,10 +570,11 @@ for m in re.finditer(r'FILE\s+"([^"]+)"', text, re.I):
print(" disc staged under disc/ (gitignored -- never commit dumps)")
'@
$pyFile = Join-Path $env:TEMP "psxrecomp_stage_disc.py"
Set-Content -Encoding UTF8 -Path $pyFile -Value $py
python $pyFile $DiscAbs $DiscOut
Write-TextFile $pyFile $py
& $Python $pyFile $DiscAbs $DiscOut
$ProbeCue = Join-Path $DiscOut $DiscBasename
$DiscTomlPath = "disc/$DiscBasename"
$GenDiscHint = $ProbeCue
} else {
Write-Host "== External disc (no full copy) =="
Write-Host " cue remains at: $DiscAbs"
@@ -547,7 +586,7 @@ $SeedCount = 0
if ($DiscBasename -match '\.cue$') {
Write-Host "== Probing disc (identity + seeds + TOC fp) =="
try {
python $ProbeDisc $ProbeCue `
& $Python $ProbeDisc $ProbeCue `
--json-out (Join-Path $Root "disc_probe.json") `
--write-game-toml (Join-Path $Root "game.toml") `
--write-catalog (Join-Path $Root "catalog_identity.json") `
@@ -563,14 +602,14 @@ if ($DiscBasename -match '\.cue$') {
--region $Region
$Probed = $true
$SeedCount = (Select-String -Path (Join-Path $Root "seeds\ghidra_funcs.txt") -Pattern '^0x').Count
$BootFromToml = python -c @"
$BootFromToml = & $Python -c @"
import re,sys
t=open(sys.argv[1],encoding='utf-8').read()
m=re.search(r'boot_exe\s*=\s*\"([^\"]+)\"', t)
print(m.group(1) if m else '')
"@ (Join-Path $Root "game.toml")
$BootFromToml = $BootFromToml.Trim()
$EntryFromToml = python -c @"
$EntryFromToml = & $Python -c @"
import re,sys
t=open(sys.argv[1],encoding='utf-8').read()
m=re.search(r'entry_pc\s*=\s*\"([^\"]+)\"', t)
@@ -599,9 +638,9 @@ print(m.group(1) if m else '')
Write-Warning "-Disc is not a .cue; skipped probe autofill."
}
# Community metadata (https://recomp.fyi/spec) — serial from the probe if it ran.
# Community metadata (https://recomp.fyi/spec) -- serial from the probe if it ran.
Write-Host "== Writing .recomp.json =="
python $WriteRecompJson (Join-Path $Root ".recomp.json") `
& $Python $WriteRecompJson (Join-Path $Root ".recomp.json") `
--game $GameName `
--project $Name `
--region $Region `
@@ -613,23 +652,23 @@ if ($doBoxart) {
$cueHint = if ($DiscBasename) { $DiscBasename } else { $GameName }
$prevPy = $env:PYTHONPATH
try {
python $FetchBoxartPy `
& $Python $FetchBoxartPy `
--out (Join-Path $Root "launcher_assets\img\boxart.tga") `
--cue-stem $cueHint `
--display-name $GameName
if ($LASTEXITCODE -eq 0) {
$HasBoxart = $true
Set-Content -Encoding UTF8 -Path $BoxartBlockFile -Value ' LAUNCHER_BOXART "${CMAKE_CURRENT_SOURCE_DIR}/launcher_assets/img/boxart.tga"'
Write-TextFile $BoxartBlockFile ' LAUNCHER_BOXART "${CMAKE_CURRENT_SOURCE_DIR}/launcher_assets/img/boxart.tga"'
Fill-Template (Join-Path $TemplateDir "CMakeLists.txt.in") (Join-Path $Root "CMakeLists.txt")
Write-Host " wired LAUNCHER_BOXART in CMakeLists.txt"
$env:PYTHONPATH = $ScriptDir
python -c "from pathlib import Path; import sys; from project_studio.readme_metrics import inject_readme_boxart; inject_readme_boxart(Path(sys.argv[1]), sys.argv[2])" (Join-Path $Root "README.md") $GameName
& $Python -c "from pathlib import Path; import sys; from project_studio.readme_metrics import inject_readme_boxart; inject_readme_boxart(Path(sys.argv[1]), sys.argv[2])" (Join-Path $Root "README.md") $GameName
Write-Host " injected boxart into README.md"
} else {
Write-Warning "boxart fetch failed — leave LAUNCHER_BOXART commented."
Write-Warning "boxart fetch failed -- leave LAUNCHER_BOXART commented."
}
} catch {
Write-Warning "boxart fetch failed — leave LAUNCHER_BOXART commented."
Write-Warning "boxart fetch failed -- leave LAUNCHER_BOXART commented."
} finally {
if ($null -eq $prevPy) { Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue } else { $env:PYTHONPATH = $prevPy }
}
@@ -637,9 +676,28 @@ if ($doBoxart) {
Write-Host "== Sync symbols header =="
Push-Location $Root
try { python tools/sync_symbols.py --game $GameName } catch { Write-Warning "sync_symbols failed" }
try { & $Python tools/sync_symbols.py --game $GameName } catch { Write-Warning "sync_symbols failed" }
Pop-Location
# Every emitted text file must be BOM-less UTF-8: a U+FEFF spliced into
# CMakeLists.txt is a configure-time parse error, and UTF-16 pins break tools.
$TextExt = @(".txt", ".toml", ".json", ".md", ".c", ".h", ".cmake", ".sh", ".yml", ".yaml", ".py", ".ps1", ".gitignore")
$Bad = @()
Get-ChildItem -LiteralPath $Root -Recurse -File -Force |
Where-Object { $_.FullName -notmatch '[\\/](psxrecomp|recomp-ui|disc|\.git)[\\/]' -and ($TextExt -contains $_.Extension.ToLower() -or $_.Name -eq "VERSION") } |
ForEach-Object {
$bytes = [System.IO.File]::ReadAllBytes($_.FullName)
$utf16 = $bytes.Length -ge 2 -and (($bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) -or ($bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF))
$bom = $false
for ($i = 0; $i + 2 -lt $bytes.Length; $i++) {
if ($bytes[$i] -eq 0xEF -and $bytes[$i + 1] -eq 0xBB -and $bytes[$i + 2] -eq 0xBF) { $bom = $true; break }
}
if ($utf16 -or $bom) { $Bad += $_.FullName.Substring($Root.Length + 1) }
}
if ($Bad.Count -gt 0) {
throw "Scaffold emitted BOM/UTF-16 text (must be BOM-less UTF-8): $($Bad -join ', ')"
}
git add CMakeLists.txt game.toml codegen_setup.c codegen_setup.h .gitignore VERSION README.md seeds scripts tools mods framework_pins.txt symbols.toml psx_symbols.h 2>$null
if (Test-Path (Join-Path $Root ".github")) {
git add .github 2>$null
@@ -680,8 +738,8 @@ if ($createGithub) {
default { "--private" }
}
$ghSlug = "$GithubOwner/$GithubRepo"
$ghDesc = (python -c "from fill_tokens import GITHUB_ABOUT_DESCRIPTION; print(GITHUB_ABOUT_DESCRIPTION)").Trim()
$ghHome = (python -c "from fill_tokens import GITHUB_ABOUT_HOMEPAGE; print(GITHUB_ABOUT_HOMEPAGE)").Trim()
$ghDesc = (& $Python -c "from fill_tokens import GITHUB_ABOUT_DESCRIPTION; print(GITHUB_ABOUT_DESCRIPTION)").Trim()
$ghHome = (& $Python -c "from fill_tokens import GITHUB_ABOUT_HOMEPAGE; print(GITHUB_ABOUT_HOMEPAGE)").Trim()
try {
gh repo create $ghSlug $vis --source=$Root --remote=origin --description "$ghDesc" --homepage "$ghHome"
Write-Host " created origin ($GithubVisibility); push deferred until end"
@@ -706,7 +764,7 @@ if ($createGithub) {
gh repo edit $ghSlug --description "$ghDesc" --homepage "$ghHome"
Write-Host " GitHub About: R.A.I.D. description + Discord homepage"
} catch {
Write-Warning "gh repo edit (About) failed — set description/homepage by hand."
Write-Warning "gh repo edit (About) failed -- set description/homepage by hand."
}
}
}
@@ -729,7 +787,8 @@ if ($doGenerate) {
$BiosAbs = (Resolve-Path -LiteralPath $Bios).Path
$genArgs += @("--bios", $BiosAbs)
}
python @genArgs
& $Python @genArgs
if ($LASTEXITCODE -ne 0) { throw "psxrecomp_cli generate failed (exit $LASTEXITCODE)" }
$GeneratedOk = $true
# Release CI builds the compiled game from this tree, so it is part
# of the repo, not a local by-product.
@@ -752,8 +811,8 @@ if ($doBuild -and $GeneratedOk) {
Write-Host "== Configure & build psx-runtime =="
Push-Location $Root
try {
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-release --target psx-runtime
& $Python psxrecomp\psxrecomp_cli.py rebuild --config game.toml --project-root . --build-dir build-release --no-pgo
if ($LASTEXITCODE -ne 0) { throw "psxrecomp_cli rebuild failed (exit $LASTEXITCODE)" }
$BuildOk = $true
Write-Host " build OK -> build-release/ (gitignored)"
} catch {
@@ -794,7 +853,7 @@ function Ensure-ActionsRegistersReleaseYml([string]$OwnerRepo) {
Write-Host " Actions has not listed release.yml yet -- nudging with a follow-up commit..."
$stamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mmZ")
Add-Content -Encoding utf8 -Path $wfPath -Value "`n# Actions registration nudge ($stamp UTC)"
Add-TextFile $wfPath "`n# Actions registration nudge ($stamp UTC)"
git add $wfPath 2>$null
try {
git -c user.email=setup@localhost -c user.name=setup `
@@ -848,8 +907,7 @@ $Step2 = if ($BuildOk) {
@"
Build playable runtime:
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-release --target psx-runtime
python psxrecomp\psxrecomp_cli.py rebuild --config game.toml --project-root . --build-dir build-release --no-pgo
"@
} else {
@"
@@ -858,8 +916,7 @@ Build emitters, Generate (and commit it -- release CI builds from generated/), t
bash psxrecomp/tools/ci/build_emitters.sh
python psxrecomp\psxrecomp_cli.py generate --config game.toml --project-root . --disc $GenDiscHint
git add generated; git commit -m "Add generated game C"
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-release --target psx-runtime
python psxrecomp\psxrecomp_cli.py rebuild --config game.toml --project-root . --build-dir build-release --no-pgo
"@
}
$CiNote = if ($useCi) {
+52
View File
@@ -114,5 +114,57 @@ class RebuildConfigureFlagsTest(unittest.TestCase):
[('generate', 'ON'), ('use', 'OFF')])
class CmakeBuildToolTest(unittest.TestCase):
"""Build must run the cmake that configured the tree, never a PATH cmake.
On Windows a PATH ``cmake`` is often MSYS2's, which rewrites the Windows
build directory as ``/f/.../F:/...`` and fails.
"""
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.build = Path(self.tmp.name) / 'build-release'
self.build.mkdir()
self.configured = Path(self.tmp.name) / 'toolchain' / 'bin' / 'cmake.exe'
self.configured.parent.mkdir(parents=True)
self.configured.write_text('')
def tearDown(self):
self.tmp.cleanup()
def build_cmd(self):
calls = []
def run(cmd, **_):
calls.append(cmd)
return mock.Mock(returncode=0, stdout='', stderr='')
with mock.patch.object(cli.subprocess, 'run', side_effect=run):
cli._cmake_build(self.build, 'psx-runtime', mock.Mock())
return calls[0]
def test_uses_cmake_command_from_cache(self):
(self.build / 'CMakeCache.txt').write_text(
'CMAKE_GENERATOR:INTERNAL=Ninja\n'
f'CMAKE_COMMAND:INTERNAL={self.configured}\n')
with mock.patch.object(cli, '_which_tool', return_value=Path('/msys/usr/bin/cmake')):
self.assertEqual(self.build_cmd()[0], str(self.configured))
def test_falls_back_to_path_without_cache(self):
with mock.patch.object(cli, '_which_tool', return_value=Path('/usr/bin/cmake')):
self.assertEqual(self.build_cmd()[0], str(Path('/usr/bin/cmake')))
def test_stale_cache_entry_falls_back_to_path(self):
(self.build / 'CMakeCache.txt').write_text(
f'CMAKE_COMMAND:INTERNAL={self.configured.parent / "gone.exe"}\n')
with mock.patch.object(cli, '_which_tool', return_value=Path('/usr/bin/cmake')):
self.assertEqual(self.build_cmd()[0], str(Path('/usr/bin/cmake')))
def test_no_cmake_anywhere_is_an_error(self):
with mock.patch.object(cli, '_which_tool', return_value=None):
with self.assertRaises(RuntimeError):
cli._cmake_build(self.build, 'psx-runtime', mock.Mock())
if __name__ == '__main__':
unittest.main()