From ed7b25b025996197e7179f8f18b7499095e57f9b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 17 Jun 2026 21:28:09 +0100 Subject: [PATCH] Harden macOS package and macro guards --- content/baseoq4/meson.build | 30 ++++++++ docs-dev/input-key-matrix.md | 6 +- docs-dev/platform-support.md | 7 +- docs-dev/release-completion.md | 4 +- docs-dev/sdl3-linux-macos-migration.md | 4 +- src/framework/BuildDefines.h | 12 ++- src/framework/Common.cpp | 2 +- src/framework/FileSystem.cpp | 2 +- src/framework/KeyInput.cpp | 4 +- src/idlib/math/Simd.cpp | 2 +- src/idlib/precompiled.h | 4 + src/renderer/RenderSystem_init.cpp | 2 +- src/sys/posix/posix_net.cpp | 4 +- tools/tests/macos_metal_bridge.py | 6 +- tools/tests/preprocessor_macro_safety.py | 93 ++++++++++++++++++++++++ tools/validation/openq4_validate.py | 1 + 16 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 tools/tests/preprocessor_macro_safety.py diff --git a/content/baseoq4/meson.build b/content/baseoq4/meson.build index 983e84e6..c6d711e1 100644 --- a/content/baseoq4/meson.build +++ b/content/baseoq4/meson.build @@ -14,6 +14,21 @@ if build_games and build_game_sp install: true, install_dir: install_game_dir, ) + elif host_system == 'darwin' + shared_module( + game_sp_binary_name, + game_sources, + include_directories: game_include_dirs, + link_with: game_idlib_library, + cpp_args: game_common_cpp_args, + c_args: game_common_c_args, + cpp_pch: game_module_pch_headers, + link_args: game_module_link_args, + name_prefix: '', + name_suffix: 'dylib', + install: true, + install_dir: install_game_dir, + ) else shared_module( game_sp_binary_name, @@ -47,6 +62,21 @@ if build_games and build_game_mp install: true, install_dir: install_game_dir, ) + elif host_system == 'darwin' + shared_module( + game_mp_binary_name, + game_sources, + include_directories: game_include_dirs, + link_with: game_idlib_library, + cpp_args: game_common_cpp_args + ['-DGAME_MPAPI'], + c_args: game_common_c_args, + cpp_pch: game_module_pch_headers, + link_args: game_module_link_args, + name_prefix: '', + name_suffix: 'dylib', + install: true, + install_dir: install_game_dir, + ) else shared_module( game_mp_binary_name, diff --git a/docs-dev/input-key-matrix.md b/docs-dev/input-key-matrix.md index 344d51b8..01bd2b02 100644 --- a/docs-dev/input-key-matrix.md +++ b/docs-dev/input-key-matrix.md @@ -38,7 +38,7 @@ For player-facing setup instructions, see [../docs-user/input-settings.md](../do | Mouse buttons | `MOUSE1` through `MOUSE8`, wheel up/down, console/menu routing | Pass | SDL3 maps extended buttons 6-8 onto the engine's existing `K_MOUSE6..K_MOUSE8` range, and console routing treats all eight mouse keys as mouse input. | | Mouse motion | Captured relative motion, menu/console absolute routing, high-DPI/fractional deltas | Pass | SDL3 relative motion is requested unscaled, fractional Linux deltas are accumulated before integer engine events, and menu/console coordinates continue through the existing GUI-space transform. | | Poll contracts | Queue ranges, failed-return outputs, zero/no-op deltas | Pass | SDL3, POSIX/native fallback, dedicated stubs, and legacy Win32 reject invalid key/mouse actions, skip zero movement/wheel deltas, and zero output parameters when no event is returned. Usercmd generation also ignores impossible key IDs. | -| Controllers | SDL3 gamepads, generic joysticks, triggers, D-pad/hat, hotplug, rumble | Pass (SDL3) | Windows/Linux/macOS SDL3 use the stable `JOY1..JOY32` semantic mapping for gamepads, expose fallback joystick buttons through `AUX` keys, poll shaped axes, add trigger-button hysteresis, and release button/axis state on disconnect. Generic raw joysticks keep sane auto axes (`0/1` move, `2/3` look, `4/5` vertical/throttle) with console cvars for devices whose SDL axis order differs. macOS still needs hardware runtime validation. | +| Controllers | SDL3 gamepads, generic joysticks, triggers, D-pad/hat, hotplug, rumble | Pass (SDL3) | Windows/Linux/macOS SDL3 use the stable `JOY1..JOY32` semantic mapping for gamepads, expose fallback joystick buttons through `AUX` keys, poll shaped axes, add trigger-button hysteresis, and release button/axis state on disconnect. Generic raw joysticks keep sane auto axes (`0/1` move, `2/3` look, `4/5` vertical/throttle) with console cvars for devices whose SDL axis order differs. macOS hardware runtime validation remains part of device signoff. | | Native Linux input | X11 keyboard, text chars, mouse buttons/wheel/motion | Pass (fallback) | Unknown keycodes no longer enter the poll buffer, X11 buttons 8-10 map to `MOUSE6..MOUSE8`, and the POSIX polling layer ignores invalid key `0` defensively. | | Native macOS input | Cocoa keyboard/text/modifiers, mouse buttons/wheel/motion | Pass (keyboard/mouse) | Unknown virtual keys no longer become literal `?` binds, Return/Tab/Backspace/Ctrl-letter chars are synthesized for edit fields, Command/Caps modifier changes are tracked, mouse buttons reach `MOUSE8`, and fractional scroll wheel deltas accumulate before wheel events. | | Numpad | KP enter/arrows/home/end/ins/del, KP operators, KP equals | Pass (core) | SDL3 mapping includes KP key families used by id key enums. | @@ -49,11 +49,11 @@ For player-facing setup instructions, see [../docs-user/input-settings.md](../do 1. Legacy keynums still cap bindable printable keys to the engine's 8-bit key range. - SDL3 layout-aware translation restores the intended non-English bind semantics for printable ASCII and the Latin-1 characters with dedicated keynum slots (`¡ ² ´ º à ç è ì ñ ò ù`), including console-toggle lookup. - Other layout-specific keycodes (e.g. German umlauts, non-Latin layouts) bind through their physical US-position scancode fallback; making them bindable by character needs a broader input-system expansion beyond the legacy `< 256` keynum space. -2. macOS SDL3 source selection now reaches the shared controller backend, but still needs compile/link/runtime validation on macOS hardware before it can be marked first-class. +2. macOS SDL3 source selection reaches the shared controller backend and CI covers compile/link/package validation, but real-device runtime validation is still needed for keyboard layouts, trackpads, controllers, and rumble. ## Recommended Next Steps 1. Validate the SDL3 layout-aware path against real AZERTY/QWERTZ/Spanish/Italian keyboards during runtime bring-up and record any layout-specific regressions in this matrix. 2. Validate Linux X11/XWayland/Wayland mouse capture with at least one high-DPI mouse and one device exposing buttons beyond `MOUSE5`. -3. Validate macOS SDL3 keyboard/edit-field behavior, controller hotplug/rumble, multi-button mouse, and trackpad scrolling on hardware; then repeat the native Cocoa fallback keyboard/mouse smoke pass. +3. Continue macOS SDL3 keyboard/edit-field, controller hotplug/rumble, multi-button mouse, and trackpad scrolling signoff on hardware; then repeat the native Cocoa fallback keyboard/mouse smoke pass when that comparison path changes. 4. Decide whether future non-Latin-1 keyboard support should extend the legacy keynum/bind system beyond the current 8-bit printable-key range. diff --git a/docs-dev/platform-support.md b/docs-dev/platform-support.md index 58b6ac48..6b45c8b1 100644 --- a/docs-dev/platform-support.md +++ b/docs-dev/platform-support.md @@ -15,8 +15,8 @@ This document defines the long-term platform direction for openQ4 and how SDL3 + ## Current Baseline (0.1.010 beta line) -- Primary actively validated build target: Windows x64. -- Hosted release automation now covers Windows x64/arm64, Linux x64/arm64, and macOS arm64 package generation. +- Primary actively validated build targets: Windows x64, Linux x64/arm64, and macOS arm64 through hosted CI/package generation. +- Windows arm64 remains package-validated during bring-up, with runtime validation still required on hardware. - Build system: Meson + Ninja. - Dependency model: Meson subprojects/wraps. - Platform backend direction: SDL3-first (legacy Win32 backend is transitional only). @@ -32,6 +32,7 @@ This document defines the long-term platform direction for openQ4 and how SDL3 + - XWayland remains available as an explicit fallback by setting `OPENQ4_FORCE_X11=1` or an SDL video-driver override such as `SDL_VIDEO_DRIVER=x11`. - If a native Wayland compositor has decoration, resize, or window-control issues, `OPENQ4_WAYLAND_PREFER_LIBDECOR=1` asks SDL to prefer libdecor without changing the default path for other sessions. - If a compositor applies window changes too asynchronously for diagnosis, `OPENQ4_WAYLAND_SYNC_WINDOW_OPS=1` asks SDL to synchronize every window operation. Use it only as a troubleshooting option because some compositors may block during window animations. +- macOS arm64 release packages are built and validated in both OpenGL and Metal bridge variants, with `.app` metadata, executable bits, archive contents, runtime dependency roots, and architecture-matched `.dylib` game modules checked before release publication. - Windows arm64 currently uses a custom OpenAL Soft package path during bring-up because the in-repo bundled Windows runtime payload is still x64-only. ## Runtime Baselines @@ -68,7 +69,7 @@ This document defines the long-term platform direction for openQ4 and how SDL3 + 1. Keep Windows x64 stable with SDL3 default backend. 2. Keep Linux on the SDL3 backend by default and validate both x64 and arm64 release paths. 3. Validate Windows arm64 beyond compile/package bring-up, especially runtime audio and in-game coverage. -4. Promote Linux and macOS to first-class once they pass consistent compile/link/runtime validation loops. +4. Continue macOS hardware signoff for input devices, audio devices, and in-game renderer coverage on real Macs while keeping the CI build/package path first-class. ## SDL3 Migration Staging (Linux/macOS) diff --git a/docs-dev/release-completion.md b/docs-dev/release-completion.md index 2254a752..86616624 100644 --- a/docs-dev/release-completion.md +++ b/docs-dev/release-completion.md @@ -15,6 +15,8 @@ Process: - [x] SDL3 input support is aligned across Windows, Linux, and macOS: keyboard/text input, relative mouse capture, high-resolution wheel handling, gamepad/joystick hotplug, stable controller binds, rumble, battery diagnostics, gyro, touchpad, touchscreen routing, and `listControllers` diagnostics now share the same backend contract on SDL3 platforms. - [x] Linux and macOS memory handling now matches the Windows contract more closely: protected allocator blocks use real page-lock and unlock calls instead of no-op success stubs, and reported system RAM totals use the same rounded, overflow-safe megabyte reporting. - [x] macOS renderer startup now validates callable OpenGL entry points after SDL3 creates Apple's fallback compatibility context, resolving the crash reported in GitHub issue #73 comment 4730727130 by downgrading optional VBO uploads or failing cleanly when required ARB program hooks are missing. +- [x] macOS package staging now pins game-module outputs to the `.dylib` names that the app bundle, release archives, and runtime validators require, so OpenGL and Metal bridge packages cannot silently drift to non-macOS module suffixes. +- [x] Platform macro guards were hardened so legacy `MACOS_X`, `__MACH__`, and `__MWERKS__` checks use defined-style preprocessor tests, shared feature switches provide guarded 0/1 defaults, and push validation now blocks those brittle guard forms from returning. - [x] Linux x64 and ARM64 CI builds are unblocked again: SDL3 DRM VRAM detection now uses openQ4's portable string comparison path, keeping sysfs VRAM probing intact without tripping the guarded C string wrappers on GCC/Clang builds. - [x] Multi-display and window behavior is now aligned across Windows, Linux, and macOS SDL3 builds: selected-monitor launches, desktop/exclusive fullscreen, borderless windows, selected-display spanned UI, windowed placement, high-DPI drawable refresh, and display-change recovery use the shared SDL3 path, while macOS now honors the selected display for compatibility calls and falls back through current mode/display bounds when desktop mode is unavailable. - [x] Strogg health stations are usable again after the player becomes Strogg in the single-player campaign: animated in-world GUI models now participate in renderer GUI tracing, so the station panel can receive the stock `heal begin` / `heal end` commands. @@ -497,5 +499,5 @@ Process: ## Carry Forward -- [ ] Linux and macOS bring-up needs full compile/link/runtime validation to reach first-class status. +- [ ] Continue macOS hardware runtime signoff for real input devices, audio devices, display modes, and in-game renderer coverage beyond the hosted CI compile/link/package checks. - [ ] Replace temporary MSVC compatibility flag (`/Zc:strictStrings-`) with full strict-strings codebase compliance. diff --git a/docs-dev/sdl3-linux-macos-migration.md b/docs-dev/sdl3-linux-macos-migration.md index c33df069..a2a55081 100644 --- a/docs-dev/sdl3-linux-macos-migration.md +++ b/docs-dev/sdl3-linux-macos-migration.md @@ -37,12 +37,12 @@ This document defines the implementation plan for migrating non-Windows platform - Validate XWayland and native Wayland behavior explicitly in logs and docs. 4. macOS SDL3 Runtime Bring-Up -- Replace macOS native window/event pump path with SDL3 equivalents. Source selection now reaches the SDL3 wrapper; macOS CI covers configure/build/install, and hardware runtime validation is still required. +- Replace macOS native window/event pump path with SDL3 equivalents. Source selection now reaches the SDL3 wrapper; macOS CI covers OpenGL and Metal bridge configure/build/install/package validation, and real-device runtime validation remains required for hardware-specific input/audio/display signoff. - Validate Cocoa integration assumptions, cursor modes, focus transitions, keyboard/text input, high-resolution scrolling, controller hotplug, gamepad/joystick mapping, and rumble behavior. - Ensure app-bundle execution path behaves correctly. 5. Promotion To First-Class -- Promote the remaining SDL3 paths to default once compile/link/runtime checks pass consistently. +- Keep SDL3 as the default Linux/macOS path and continue broadening hardware runtime evidence after CI compile/link/package checks pass consistently. - Keep native backends available for rollback until at least one release cycle is stable. ## Validation Requirements Per Phase diff --git a/src/framework/BuildDefines.h b/src/framework/BuildDefines.h index b264a890..ab8819b7 100644 --- a/src/framework/BuildDefines.h +++ b/src/framework/BuildDefines.h @@ -22,7 +22,17 @@ #define ID_ALLOW_CHEATS 0 #endif -#define ID_ENABLE_CURL 0 +#ifndef ID_ENABLE_CURL + #define ID_ENABLE_CURL 0 +#endif + +#ifndef ID_ALLOW_D3XP + #define ID_ALLOW_D3XP 0 +#endif + +#ifndef ID_CONSOLE_LOCK + #define ID_CONSOLE_LOCK 0 +#endif // fake a pure client. useful to connect an all-debug client to a server #ifndef ID_FAKE_PURE diff --git a/src/framework/Common.cpp b/src/framework/Common.cpp index 44db6089..d3d5e50a 100644 --- a/src/framework/Common.cpp +++ b/src/framework/Common.cpp @@ -2263,7 +2263,7 @@ void Com_ExecMachineSpec_f( const idCmdArgs &args ) { cvarSystem->SetCVarInteger( "image_useNormalCompression", 1, CVAR_ARCHIVE ); } -#if MACOS_X +#if defined( MACOS_X ) // On low settings, G4 systems & 64MB FX5200/NV34 Systems should default shadows off bool oldArch; int vendorId, deviceId, cpuId; diff --git a/src/framework/FileSystem.cpp b/src/framework/FileSystem.cpp index 9108854e..c29ce80d 100644 --- a/src/framework/FileSystem.cpp +++ b/src/framework/FileSystem.cpp @@ -36,7 +36,7 @@ If you have questions concerning this license or the applicable additional terms #include // for _read #include // for _getcwd #else - #if !__MACH__ && __MWERKS__ + #if !defined( __MACH__ ) && defined( __MWERKS__ ) #include #include #else diff --git a/src/framework/KeyInput.cpp b/src/framework/KeyInput.cpp index 33bf13de..109aa7d5 100644 --- a/src/framework/KeyInput.cpp +++ b/src/framework/KeyInput.cpp @@ -38,7 +38,7 @@ typedef struct { // keys that can be set without a special name static const char unnamedkeys[] = "*,-=./[\\]1234567890abcdefghijklmnopqrstuvwxyz"; -#if MACOS_X +#if defined( MACOS_X ) const char* OSX_GetLocalizedString( const char* ); #endif @@ -393,7 +393,7 @@ const char *idKeyInput::KeyNumToString( int keynum, bool localized ) { if ( !localized || kn->strId[0] != '#' ) { return kn->name; } else { -#if MACOS_X +#if defined( MACOS_X ) switch ( kn->keynum ) { case K_ENTER: diff --git a/src/idlib/math/Simd.cpp b/src/idlib/math/Simd.cpp index 15588188..3cb04893 100644 --- a/src/idlib/math/Simd.cpp +++ b/src/idlib/math/Simd.cpp @@ -180,7 +180,7 @@ typedef struct { double TBToDoubleNano( U64 startTime, U64 stopTime, double ticksPerNanosecond ); -#if __MWERKS__ +#if defined( __MWERKS__ ) asm void GetTB( U64 * ); #else void GetTB( U64 * ); diff --git a/src/idlib/precompiled.h b/src/idlib/precompiled.h index c072459d..5fc218da 100644 --- a/src/idlib/precompiled.h +++ b/src/idlib/precompiled.h @@ -49,6 +49,10 @@ typedef unsigned long long uint64; class ThreadedAlloc; // class that is only used to expand the AutoCrit template to tag allocs/frees called from inside the R_AddModelSurfaces call graph #define PC_CVAR_ARCHIVE CVAR_ARCHIVE +#if defined( __APPLE__ ) && !defined( MACOS_X ) +#define MACOS_X 1 +#endif + #ifdef _WINDOWS // _WIN32 always defined diff --git a/src/renderer/RenderSystem_init.cpp b/src/renderer/RenderSystem_init.cpp index d470e9b4..10079bff 100644 --- a/src/renderer/RenderSystem_init.cpp +++ b/src/renderer/RenderSystem_init.cpp @@ -1157,7 +1157,7 @@ vidmode_t r_vidModes[] = { }; static int s_numVidModes = ( sizeof( r_vidModes ) / sizeof( r_vidModes[0] ) ); -#if MACOS_X +#if defined( MACOS_X ) bool R_GetModeInfo( int *width, int *height, int mode ) { #else static bool R_GetModeInfo( int *width, int *height, int mode ) { diff --git a/src/sys/posix/posix_net.cpp b/src/sys/posix/posix_net.cpp index 73c6dddd..5bcf1281 100644 --- a/src/sys/posix/posix_net.cpp +++ b/src/sys/posix/posix_net.cpp @@ -40,7 +40,7 @@ If you have questions concerning this license or the applicable additional terms #include #include #include -#if MACOS_X +#if defined( MACOS_X ) || defined( __APPLE__ ) #include #endif @@ -450,7 +450,7 @@ void Sys_InitNetworking(void) // haven't been able to clearly pinpoint which standards or RFCs define SIOCGIFCONF, SIOCGIFADDR, SIOCGIFNETMASK ioctls // it seems fairly widespread, in Linux kernel ioctl, and in BSD .. so let's assume it's always available on our targets -#if MACOS_X +#if defined( MACOS_X ) || defined( __APPLE__ ) unsigned int ip, mask; struct ifaddrs *ifap, *ifp; diff --git a/tools/tests/macos_metal_bridge.py b/tools/tests/macos_metal_bridge.py index 28792283..18f9413f 100644 --- a/tools/tests/macos_metal_bridge.py +++ b/tools/tests/macos_metal_bridge.py @@ -386,6 +386,7 @@ def validate_macos_staged_payload_validator_runtime() -> None: def validate_meson_contract() -> None: options = read("meson_options.txt") meson = read("meson.build") + baseoq4_meson = read("content/baseoq4/meson.build") setup_sh = read("tools/build/meson_setup.sh") setup_ps1 = read("tools/build/meson_setup.ps1") @@ -401,6 +402,9 @@ def validate_meson_contract() -> None: require(meson, "-DOPENQ4_MACOS_METAL_BRIDGE=1", "Metal bridge compile define") require(meson, "'macOS graphics bridge': macos_graphics_bridge", "Meson summary") + require(baseoq4_meson, "elif host_system == 'darwin'", "macOS game module source branch") + require(baseoq4_meson, "name_suffix: 'dylib'", "macOS game module dylib suffix") + require(setup_sh, "macos_graphics_bridge", "Bash Meson wrapper option preservation") require(setup_ps1, '"macos_graphics_bridge"', "PowerShell Meson wrapper option preservation") @@ -576,7 +580,7 @@ def validate_docs_and_ci_hooks() -> None: require(release_notes, "Final macOS release archive", "release completion notes") require(platform_support, "Linux and macOS now use the shared SDL3 runtime path", "platform support roadmap") require(platform_support, "macOS SDL3 builds select `src/sys/osx/macosx_sdl3.cpp`", "platform support roadmap") - require(migration, "macOS CI covers configure/build/install", "SDL3 migration plan") + require(migration, "macOS CI covers OpenGL and Metal bridge configure/build/install/package validation", "SDL3 migration plan") require(validator, "macos_metal_bridge.py", "validation runner") require(push, "tools/tests/macos_metal_bridge.py", "push verification workflow") diff --git a/tools/tests/preprocessor_macro_safety.py b/tools/tests/preprocessor_macro_safety.py new file mode 100644 index 00000000..295b5795 --- /dev/null +++ b/tools/tests/preprocessor_macro_safety.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Validate platform preprocessor guards used by shared engine code.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + +SCAN_ROOTS = ( + ROOT / "src" / "framework", + ROOT / "src" / "idlib", + ROOT / "src" / "renderer", + ROOT / "src" / "sys" / "osx", + ROOT / "src" / "sys" / "posix", +) + +UNSAFE_GUARDS = ( + (re.compile(r"^\s*#\s*(if|elif)\s+!?\s*MACOS_X\b"), "use defined( MACOS_X ) for macOS guards"), + (re.compile(r"^\s*#\s*(if|elif)\s+!?\s*__MWERKS__\b"), "use defined( __MWERKS__ ) for compiler guards"), + (re.compile(r"^\s*#\s*(if|elif)\s+!?\s*__MACH__\b"), "use defined( __MACH__ ) for Mach guards"), +) + +BUILD_DEFINE_BOOLEAN_DEFAULTS = ( + "ID_NOLANADDRESS", + "ID_PURE_ALLOWDDS", + "ID_ALLOW_CHEATS", + "ID_ENABLE_CURL", + "ID_ALLOW_D3XP", + "ID_CONSOLE_LOCK", + "ID_FAKE_PURE", + "ID_CLIENTINFO_TAGS", +) + + +def iter_source_files() -> list[Path]: + files: list[Path] = [] + for scan_root in SCAN_ROOTS: + for path in scan_root.rglob("*"): + if path.suffix.lower() in {".c", ".cc", ".cpp", ".h", ".hpp", ".m", ".mm"}: + files.append(path) + return sorted(files) + + +def validate_guard_forms() -> None: + violations: list[str] = [] + for path in iter_source_files(): + text = path.read_text(encoding="utf-8", errors="ignore") + for line_number, line in enumerate(text.splitlines(), start=1): + for pattern, message in UNSAFE_GUARDS: + if pattern.search(line): + violations.append(f"{path.relative_to(ROOT)}:{line_number}: {message}: {line.strip()}") + + if violations: + formatted = "\n".join(f" - {violation}" for violation in violations) + raise SystemExit(f"Unsafe macOS/compiler preprocessor guards found:\n{formatted}") + + +def validate_canonical_macos_define() -> None: + precompiled = (ROOT / "src" / "idlib" / "precompiled.h").read_text(encoding="utf-8", errors="ignore") + required = "#if defined( __APPLE__ ) && !defined( MACOS_X )\n#define MACOS_X 1\n#endif" + if required not in precompiled: + raise SystemExit("src/idlib/precompiled.h must canonicalize __APPLE__ to MACOS_X for non-Meson Apple builds.") + + +def validate_build_define_defaults() -> None: + build_defines = (ROOT / "src" / "framework" / "BuildDefines.h").read_text(encoding="utf-8", errors="ignore") + violations: list[str] = [] + for macro in BUILD_DEFINE_BOOLEAN_DEFAULTS: + default_pattern = re.compile( + rf"#ifndef\s+{re.escape(macro)}\b\s*" + rf"\n\s*#\s*define\s+{re.escape(macro)}\s+[01]\b", + re.MULTILINE, + ) + if not default_pattern.search(build_defines): + violations.append(macro) + + if violations: + formatted = ", ".join(violations) + raise SystemExit(f"BuildDefines.h must provide guarded 0/1 defaults for: {formatted}") + + +def main() -> None: + validate_guard_forms() + validate_canonical_macos_define() + validate_build_define_defaults() + print("preprocessor macro safety checks passed") + + +if __name__ == "__main__": + main() diff --git a/tools/validation/openq4_validate.py b/tools/validation/openq4_validate.py index c1c0aa93..c0eb2d23 100644 --- a/tools/validation/openq4_validate.py +++ b/tools/validation/openq4_validate.py @@ -187,6 +187,7 @@ def run_python_tests(args: argparse.Namespace, root: Path, env: dict[str, str]) root / "tools" / "tests" / "loading_continue_input.py", root / "tools" / "tests" / "macos_renderer_startup_guard.py", root / "tools" / "tests" / "macos_metal_bridge.py", + root / "tools" / "tests" / "preprocessor_macro_safety.py", root / "tools" / "tests" / "posix_memory_management.py", root / "tools" / "tests" / "sdl3_input_parity.py", root / "tools" / "tests" / "sdl3_multidisplay_windowing.py",