mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
Make check_platform.py skip the branches that only a Windows build compiles, so that it passes on the runtime and guards it again.
tools/check_platform.py scanned the raw text of each shared runtime file and knew nothing of preprocessor branches, so the Win32 use that input.c, mods_probe.c, rtss_limit.c, vk_cache_file.h, vk_dlaa.c, vk_dlssg.c, vk_dump.c, vk_ffx.cpp, vk_ffx4.cpp, vk_fg.cpp, vk_fg4.cpp and vk_pipeline.c keep behind #ifdef _WIN32 made it fail on every run, at f1a2a64e already: a check that always fails guards nothing. It now leaves out the lines of a Windows-only branch (#ifdef _WIN32, #if defined(_WIN32), #if _WIN32 up to their #else, #elif or #endif, the #else of #ifndef _WIN32 or #if !defined(_WIN32), and conditionals nested in them), found on the text with its comments blanked. Every other branch is still scanned, #elif defined(_WIN32) and #if defined(_WIN32) && X among them, which can fail the check but never lets a use through. docs/building.md lists the check and its test under "Checks".
Reproduced before the change: the check exited with 1 and these 12 FAIL lines on 2f640fb8; it now exits with 0. Checks that passed: the new tests/check_platform_test.py (guarded uses pass, unguarded ones fail, the repository's runtime passes; it fails before the change) and tools/check_content.py --staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -444,6 +444,9 @@ Before a commit:
|
||||
- `python tools/check_content.py --staged`: nothing game-derived and no compiled shader is staged. CI runs the same check;
|
||||
`git config core.hooksPath .githooks` runs it before each local commit.
|
||||
- `build\<ver>\nx2navi.exe --selftest`: layout, dispatch table and host-function registry, without game files.
|
||||
- `python tools\check_platform.py` after a runtime change outside `runtime/platform/`: no shared file uses Win32 outside
|
||||
a branch that only a Windows build compiles (`#ifdef _WIN32`, the `#else` of `#ifndef _WIN32`).
|
||||
`python tests\check_platform_test.py` checks the scanner itself on made-up files.
|
||||
|
||||
A change to the in-game menu's look (`runtime/ui/menu_skin.cpp`, `option_rows.cpp`, `perf_panel.cpp`) can be looked at
|
||||
without game files: `python tools/menu_preview.py` builds the menu's own drawing code with the host's clang++ and
|
||||
|
||||
70
tests/check_platform_test.py
Normal file
70
tests/check_platform_test.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""tools/check_platform.py on made-up runtime/ trees: a Win32 use behind a Windows-only preprocessor branch passes, an
|
||||
unguarded one fails, and the repository's own runtime/ passes. No game files; stdlib only.
|
||||
|
||||
Run: python tests/check_platform_test.py
|
||||
"""
|
||||
import contextlib
|
||||
import io
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / 'tools'))
|
||||
|
||||
import check_platform # noqa: E402
|
||||
|
||||
|
||||
class CheckPlatformTest(unittest.TestCase):
|
||||
def check(self, files, runtime=None):
|
||||
"""(exit code, output) of check_platform.py on a runtime/ folder holding files ({name: text}), or on
|
||||
runtime."""
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
if runtime is None:
|
||||
runtime = Path(d) / 'runtime'
|
||||
runtime.mkdir()
|
||||
for name, text in files.items():
|
||||
(runtime / name).write_text(text, encoding='utf-8')
|
||||
out = io.StringIO()
|
||||
with mock.patch.object(sys, 'argv', ['check_platform.py', '--runtime', str(runtime)]), \
|
||||
contextlib.redirect_stdout(out):
|
||||
code = check_platform.main()
|
||||
return code, out.getvalue()
|
||||
|
||||
def test_windows_only_branches_pass(self):
|
||||
code, out = self.check({
|
||||
'a.c': '#ifdef _WIN32\n#include <windows.h>\n#endif\nint a;\n',
|
||||
'b.c': '#ifndef _WIN32\n#include <dlfcn.h>\nvoid f(int h) { (void)h; }\n#else\n'
|
||||
'void f(HANDLE h) { CloseHandle(h); }\n#endif\n',
|
||||
'c.cpp': '#if defined(_WIN32) // the DLL\n# if X\nstatic DWORD d;\n# endif\n'
|
||||
'static void g() { FreeLibrary(0); }\n#elif Y\nint y;\n#else\nint z;\n#endif\n',
|
||||
'd.h': '#if !defined(_WIN32)\nint p;\n#elif Z\nint q;\n#else\n#include <windows.h>\n#endif\n',
|
||||
'e.c': '/* CloseHandle in a comment */\nconst char *s = "GetLastError";\n',
|
||||
})
|
||||
self.assertEqual(code, 0, out)
|
||||
self.assertNotIn('FAIL', out)
|
||||
|
||||
def test_unguarded_uses_fail(self):
|
||||
code, out = self.check({
|
||||
'a.c': '#include <windows.h>\nint a;\n',
|
||||
'b.c': '#ifndef _WIN32\nint b;\n#endif\nvoid f(void *h) { CloseHandle(h); }\n',
|
||||
'c.c': '#ifdef _WIN32\nint c;\n#else\nstatic DWORD d;\n#endif\n',
|
||||
'd.c': '#ifdef _WIN32\nint d;\n#elif 1\nvoid g(void) { Sleep(1); }\n#endif\n',
|
||||
'e.c': '#if defined(_WIN32) && X\nvoid h(void) { Sleep(1); }\n#endif\n', # scanned: never let through
|
||||
'f.c': '#ifdef _WIN32\n#endif\n\n#include <windows.h>\n',
|
||||
'ok.c': '#ifdef _WIN32\nvoid f(void *h) { CloseHandle(h); }\n#endif\n',
|
||||
})
|
||||
self.assertEqual(code, 1)
|
||||
for name in 'abcdef':
|
||||
self.assertIn(f'FAIL runtime/{name}.c', out)
|
||||
self.assertNotIn('runtime/ok.c', out)
|
||||
|
||||
def test_the_runtime_passes(self):
|
||||
code, out = self.check({}, ROOT / 'runtime')
|
||||
self.assertEqual(code, 0, out)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
@@ -3,12 +3,15 @@
|
||||
|
||||
Runtime code goes through runtime/platform/platform.h. The Win32 back ends of the window system are Win32 by design and
|
||||
are not scanned: files named *_win.c / *_win.cpp (runtime/ and runtime/ui/), whose SDL counterparts (*_sdl.*) serve
|
||||
other hosts. Any other file that still uses Win32 goes into ALLOWED with the reason; a file in ALLOWED that no longer
|
||||
other hosts. Nor are the branches of a shared file that only a Windows build compiles (windows_only_lines): a file
|
||||
with a portable path of its own may reach a Windows-only library there (the upscalers' DLLs, RivaTuner's shared
|
||||
memory). Any other file that still uses Win32 goes into ALLOWED with the reason; a file in ALLOWED that no longer
|
||||
matches is noted, so the entry gets removed.
|
||||
|
||||
Usage: python tools/check_platform.py [--root DIR] (DIR holds runtime/; default: the repository root)
|
||||
"""
|
||||
import argparse
|
||||
import bisect
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -54,11 +57,49 @@ def strip(text):
|
||||
return re.sub(r'"(?:\\.|[^"\\\n])*"', blank, text)
|
||||
|
||||
|
||||
DIRECTIVE = re.compile(r"#\s*(if|ifdef|ifndef|elif|else|endif)\b")
|
||||
WIN_IF = re.compile(r"#\s*(ifdef\s+_WIN32|if\s+defined\s*\(\s*_WIN32\s*\)|if\s+defined\s+_WIN32|if\s+_WIN32)$")
|
||||
WIN_IFNOT = re.compile(r"#\s*(ifndef\s+_WIN32|if\s+!\s*defined\s*\(\s*_WIN32\s*\)|if\s+!\s*defined\s+_WIN32)$")
|
||||
|
||||
|
||||
def windows_only_lines(code):
|
||||
"""The numbers (from 0) of the lines of code (comments blanked: strip) that only a Windows build compiles: the
|
||||
branch of #ifdef _WIN32, #if defined(_WIN32) or #if _WIN32 up to its #else, #elif or #endif, the #else branch of
|
||||
#ifndef _WIN32 or #if !defined(_WIN32), and whatever a conditional inside such a branch holds. The directives
|
||||
themselves are not among them. Any other branch counts as compiled everywhere, #elif defined(_WIN32) and
|
||||
#if defined(_WIN32) && X too: such a branch is scanned, which can fail the check but never lets a use through."""
|
||||
lines, stack = set(), [] # stack: [kind ('win', 'notwin', 'other'), whether its open branch is Windows-only]
|
||||
for n, line in enumerate(code.split("\n")):
|
||||
s = line.strip()
|
||||
m = DIRECTIVE.match(s)
|
||||
if not m:
|
||||
if any(w for _, w in stack):
|
||||
lines.add(n)
|
||||
continue
|
||||
d = m.group(1)
|
||||
if d in ("if", "ifdef", "ifndef"):
|
||||
kind = "win" if WIN_IF.match(s) else "notwin" if WIN_IFNOT.match(s) else "other"
|
||||
stack.append([kind, kind == "win"])
|
||||
elif d == "else" and stack:
|
||||
stack[-1][1] = stack[-1][0] == "notwin"
|
||||
elif d == "elif" and stack:
|
||||
stack[-1][1] = False
|
||||
elif d == "endif" and stack:
|
||||
stack.pop()
|
||||
return lines
|
||||
|
||||
|
||||
def scan(path):
|
||||
raw = path.read_text(encoding="utf-8", errors="replace")
|
||||
hits = [("include", m.group(0).strip()) for m in INCLUDE.finditer(raw)]
|
||||
code = strip(raw)
|
||||
hits += [("call", m.group(1)) for m in CALL_RE.finditer(code)]
|
||||
code = strip(raw) # the same lines as raw
|
||||
skip = windows_only_lines(code)
|
||||
starts = [0] + [i + 1 for i, c in enumerate(raw) if c == "\n"]
|
||||
|
||||
def guarded(pos):
|
||||
return bisect.bisect_right(starts, pos) - 1 in skip
|
||||
# an include's match can begin on the line before it (^\s* takes that line's newline): its end is on its own line
|
||||
hits = [("include", m.group(0).strip()) for m in INCLUDE.finditer(raw) if not guarded(m.end() - 1)]
|
||||
hits += [("call", m.group(1)) for m in CALL_RE.finditer(code) if not guarded(m.start())]
|
||||
return hits
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user