From a2e057a73f5677e6d6c1c0f7a4c16494d2ab8172 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 16 Sep 2026 13:52:02 +0200 Subject: [PATCH 1/5] [M1-DET-01] Fix Windows CI: quote the trait_compile fixture compiler invocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The G-R8 trait compile-check fixtures (trait_compile_ok / trait_compile_reject_double / trait_compile_reject_unmarked / trait_compile_reject_bad_mark) fail on the Windows x64 (MSVC 2022) job: every one reports 'exit code no such file or directory' with empty compiler output, i.e. the compiler process never launched. Root cause: expect-compile-result.cmake.in interpolates the compiler path into an unquoted execute_process COMMAND line. configure_file substitutes plain text, and unquoted text in a generated script is re-split on whitespace by the CMake parser — so the MSVC compiler path (C:/Program Files/Microsoft Visual Studio/2022/Enterprise/ VC/Tools/MSVC/.../cl.exe) becomes the argument 'C:/Program', a failed process launch that CMake reports as the literal result 'no such file or directory'. The Linux/macOS compilers (/usr/bin/g++, /usr/bin/clang++) contain no spaces, which is why only the Windows job fails. Fix: build the compiler invocation as a CMake list (one process argument per element) in laige_add_compile_check and serialize each element quoted into the generated script; the script runs execute_process(COMMAND ${_cmd}), whose list expansion passes each element as exactly one argument regardless of whitespace. The flag sets themselves are unchanged (same tokens, same order, same semantics as before). Verified: cmake -P end-to-end simulation with a space-containing compiler path (every argument arrives intact); ctest -R trait_compile green in the local g++ and clang++ trees (4/4 each); full ctest green in both trees (55/55); trait_compile green in the ASan and TSan trees (4/4 each). The Windows proof is the PR's ci:windows job. --- tests/laige-sim/CMakeLists.txt | 35 +++++++++++++------ .../laige-sim/expect-compile-result.cmake.in | 11 +++++- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/tests/laige-sim/CMakeLists.txt b/tests/laige-sim/CMakeLists.txt index 92cfaad..975278a 100644 --- a/tests/laige-sim/CMakeLists.txt +++ b/tests/laige-sim/CMakeLists.txt @@ -215,23 +215,36 @@ add_test(NAME determinism_mode # expect-compile-result.cmake.in for the rationale. function(laige_add_compile_check name source expect_ok fragment) if(CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$") - set(_flags "-std=c++20 -Wall -Werror -fno-exceptions -fno-rtti" - " -ffp-contract=off -fno-associative-math") - set(_includes "-I${CMAKE_SOURCE_DIR}/src/laige-sim/include" - "-I${CMAKE_SOURCE_DIR}/src/laige-core/include") + set(_flags -std=c++20 -Wall -Werror -fno-exceptions -fno-rtti + -ffp-contract=off -fno-associative-math) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") - set(_flags "/std:c++20 /W4 /WX /EHs- /EHc- /GR-" - " /fp:precise /D _HAS_EXCEPTIONS=0") - set(_includes "/I${CMAKE_SOURCE_DIR}/src/laige-sim/include" - "/I${CMAKE_SOURCE_DIR}/src/laige-core/include") + set(_flags /std:c++20 /W4 /WX /EHs- /EHc- /GR- + /fp:precise /D _HAS_EXCEPTIONS=0) else() message(FATAL_ERROR "no compile-check flags for '${CMAKE_CXX_COMPILER_ID}'") endif() - set(CXX "${CMAKE_CXX_COMPILER}") - set(CXXFLAGS "${_flags} ${_includes}") + set(_includes "-I${CMAKE_SOURCE_DIR}/src/laige-sim/include" + "-I${CMAKE_SOURCE_DIR}/src/laige-core/include") + # The compiler invocation is a CMake list: exactly one process + # argument per element. The generated script quotes each element + # (CMD_TEXT below) because configure_file substitutes plain text and + # unquoted text in the generated execute_process call would be + # re-split on whitespace — which breaks any argument containing + # spaces. The MSVC compiler path on Windows + # ("C:/Program Files/Microsoft Visual Studio/.../cl.exe") is the + # canonical case: split at the first space it becomes the argument + # "C:/Program", a failed process launch that CMake reports as the + # result "no such file or directory" with no compiler output. + set(_cmd "${CMAKE_CXX_COMPILER}" ${_flags} ${_includes} + "-c" "${CMAKE_CURRENT_SOURCE_DIR}/compile_fail/${source}" + "-o" "${CMAKE_CURRENT_BINARY_DIR}/compile_check_${name}.o") + set(CMD_TEXT "") + foreach(_arg IN LISTS _cmd) + string(REPLACE "\"" "\\\"" _arg_quoted "${_arg}") + string(APPEND CMD_TEXT "\"${_arg_quoted}\" ") + endforeach() set(SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/compile_fail/${source}") - set(OBJ "${CMAKE_CURRENT_BINARY_DIR}/compile_check_${name}.o") set(EXPECT_OK "${expect_ok}") set(FRAGMENT "${fragment}") configure_file( diff --git a/tests/laige-sim/expect-compile-result.cmake.in b/tests/laige-sim/expect-compile-result.cmake.in index a70d9a0..47d099d 100644 --- a/tests/laige-sim/expect-compile-result.cmake.in +++ b/tests/laige-sim/expect-compile-result.cmake.in @@ -15,9 +15,18 @@ # # The script exits non-zero (failing the CTest test) on any mismatch # and prints the full compiler output for diagnosis. +# +# _cmd is a quoted CMake list — one element per process argument. +# Quoting is load-bearing: configure_file substitutes plain text, and +# unquoted text in an execute_process call is re-split on whitespace, +# which would break any argument containing spaces (the MSVC compiler +# path "C:/Program Files/Microsoft Visual Studio/.../cl.exe" on +# Windows). The list expansion below passes each element as exactly +# one argument regardless of its content. +set(_cmd @CMD_TEXT@) execute_process( - COMMAND @CXX@ @CXXFLAGS@ -c "@SOURCE@" -o "@OBJ@" + COMMAND ${_cmd} RESULT_VARIABLE _rc OUTPUT_VARIABLE _out ERROR_VARIABLE _err From 438a4e35c662292d73fdcb6a933ef3d590309c3b Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 16 Sep 2026 14:06:23 +0200 Subject: [PATCH 2/5] [M1-DET-01] Quote the lint fixtures' python interpreter path too Same latent defect as the trait_compile fixture fix, one class below: expect-lint-result.cmake.in and expect-det-lint-result.cmake.in interpolate @PYTHON@ (the find_program result, a full interpreter path) unquoted into the generated execute_process COMMAND. CI never hit it because the windows-2022 runner's python lives in C:/hostedtoolcache (no spaces); a local Windows checkout with python under 'C:/Program Files/Python3xx' would fail the same way. Quoting aligns both templates with the api/detcheck/compile-check scripts. Verified: ctest include-lint-*, determinism-lint-*, api-*, detcheck-*, trait_compile* green in the local g++ tree (27/27); the generated scripts carry the quoted interpreter path. --- tests/tools/expect-det-lint-result.cmake.in | 7 ++++++- tests/tools/expect-lint-result.cmake.in | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/tools/expect-det-lint-result.cmake.in b/tests/tools/expect-det-lint-result.cmake.in index fa71bda..21c1ec3 100644 --- a/tests/tools/expect-det-lint-result.cmake.in +++ b/tests/tools/expect-det-lint-result.cmake.in @@ -13,7 +13,12 @@ # prints the full lint output for diagnosis. execute_process( - COMMAND @PYTHON@ "@LINT@" --root "@ROOT@" + # "@PYTHON@" is quoted like every other generated-script command (the + # rationale is in expect-lint-result.cmake.in): unquoted text in an + # execute_process call is re-split on whitespace, which would break a + # python interpreter path containing spaces on a local Windows + # checkout. + COMMAND "@PYTHON@" "@LINT@" --root "@ROOT@" RESULT_VARIABLE _rc OUTPUT_VARIABLE _out ERROR_VARIABLE _err diff --git a/tests/tools/expect-lint-result.cmake.in b/tests/tools/expect-lint-result.cmake.in index c31bbc1..6ebd14f 100644 --- a/tests/tools/expect-lint-result.cmake.in +++ b/tests/tools/expect-lint-result.cmake.in @@ -13,7 +13,13 @@ # prints the full lint output for diagnosis. execute_process( - COMMAND @PYTHON@ "@LINT@" --root "@ROOT@" + # "@PYTHON@" is quoted like every other generated-script command: + # configure_file substitutes plain text, and unquoted text in an + # execute_process call is re-split on whitespace, which would break a + # python interpreter path containing spaces (e.g. "C:/Program + # Files/Python3xx/python.exe" on a local Windows checkout; the CI + # runner's hostedtoolcache path has none, so this only bites locally). + COMMAND "@PYTHON@" "@LINT@" --root "@ROOT@" RESULT_VARIABLE _rc OUTPUT_VARIABLE _out ERROR_VARIABLE _err From 0a89c023d1c5d4a27a434cf01a0408d785cf8b7b Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 16 Sep 2026 14:12:38 +0200 Subject: [PATCH 3/5] [M1-DET-01] Fix Windows CI: give trait_compile fixtures the MSVC environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the compiler path quoted (previous commit), cl.exe now launches from the generated cmake -P fixture scripts — but it runs outside the VS generator's toolchain setup, so the runner's plain shell has no MSVC environment: cl cannot find the CRT/STL headers and every fixture dies with 'fatal error C1083: Cannot open include file: cstdint'. The Windows Test step in both workflows now loads the VS 2022 development environment in the same step before ctest (vswhere locates the installation; LaunchVsDevEnv.ps1 -Arch amd64 sets INCLUDE/LIB/PATH etc. in the current PowerShell process). ctest, the cmake -P fixture scripts, and cl all inherit it; every other step and test in the job is unchanged (they already worked without the environment — only direct cl invocation needs it). Documented in the ci.yml Windows note and the fixture template header. --- .github/workflows/ci-pull.yml | 14 +++++++++++- .github/workflows/ci.yml | 22 +++++++++++++++++-- .../laige-sim/expect-compile-result.cmake.in | 7 ++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-pull.yml b/.github/workflows/ci-pull.yml index 3203b11..902f761 100644 --- a/.github/workflows/ci-pull.yml +++ b/.github/workflows/ci-pull.yml @@ -198,7 +198,19 @@ jobs: - name: Build run: cmake --build build --config Debug -j - name: Test (unit) - run: ctest --test-dir build -C Debug --output-on-failure + # The trait_compile_* fixtures invoke cl.exe DIRECTLY from a + # generated cmake -P script (execute_process), outside the VS + # generator's toolchain setup, so — unlike every other test in + # this job — they need the MSVC environment (INCLUDE/LIB/PATH) + # to find the CRT/STL headers; the plain runner shell lacks it + # and cl fails with C1083 on etc. LaunchVsDevEnv.ps1 + # (VS 2022, located through vswhere) sets that environment in + # the current PowerShell process; ctest, cmake -P, and cl all + # inherit it. Everything else in this step is unchanged. + run: | + $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath + & (Join-Path $vs "Common7\Tools\LaunchVsDevEnv.ps1") -Arch amd64 + ctest --test-dir build -C Debug --output-on-failure macos-arm64: name: macOS arm64 (AppleClang) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 810661a..1294bb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,13 @@ # would build every configuration from the canonical `cmake --build build # -j`. The Windows job therefore pins the canonical Debug configuration # explicitly on the build and test steps (--config Debug / -C Debug); the -# single-config platforms run the canonical commands verbatim. +# single-config platforms run the canonical commands verbatim. The +# Windows Test step additionally loads the VS 2022 development +# environment (vswhere + LaunchVsDevEnv.ps1, same step, before ctest) +# because the trait_compile_* fixtures invoke cl.exe directly from a +# generated cmake -P script — outside the VS generator's toolchain +# setup — and need the MSVC environment (INCLUDE/LIB/PATH) to find the +# CRT/STL headers. # # AC-6.1 (same source tree, same feature set, no per-OS feature flags): no # build job passes feature flags; every build job builds the identical @@ -221,7 +227,19 @@ jobs: - name: Build run: cmake --build build --config Debug -j - name: Test (unit) - run: ctest --test-dir build -C Debug --output-on-failure + # The trait_compile_* fixtures invoke cl.exe DIRECTLY from a + # generated cmake -P script (execute_process), outside the VS + # generator's toolchain setup, so — unlike every other test in + # this job — they need the MSVC environment (INCLUDE/LIB/PATH) + # to find the CRT/STL headers; the plain runner shell lacks it + # and cl fails with C1083 on etc. LaunchVsDevEnv.ps1 + # (VS 2022, located through vswhere) sets that environment in + # the current PowerShell process; ctest, cmake -P, and cl all + # inherit it. Everything else in this step is unchanged. + run: | + $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath + & (Join-Path $vs "Common7\Tools\LaunchVsDevEnv.ps1") -Arch amd64 + ctest --test-dir build -C Debug --output-on-failure macos-arm64: name: macOS arm64 (AppleClang) diff --git a/tests/laige-sim/expect-compile-result.cmake.in b/tests/laige-sim/expect-compile-result.cmake.in index 47d099d..3a28392 100644 --- a/tests/laige-sim/expect-compile-result.cmake.in +++ b/tests/laige-sim/expect-compile-result.cmake.in @@ -23,6 +23,13 @@ # path "C:/Program Files/Microsoft Visual Studio/.../cl.exe" on # Windows). The list expansion below passes each element as exactly # one argument regardless of its content. +# +# MSVC note: cl launched this way runs outside the VS generator's +# toolchain setup, so the surrounding environment MUST already carry +# the MSVC INCLUDE/LIB/PATH (or cl fails with C1083 on etc.). +# The CI Windows Test step loads the VS development environment before +# ctest (vswhere + LaunchVsDevEnv.ps1 — see .github/workflows/ci.yml); +# a local Windows run needs the same (Developer PowerShell). set(_cmd @CMD_TEXT@) execute_process( From 499af03a8dd6c504dff69f02ff451e5f5456503d Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 16 Sep 2026 14:17:04 +0200 Subject: [PATCH 4/5] [M1-DET-01] Fix Windows CI: use VsDevCmd.bat to import the MSVC environment LaunchVsDevEnv.ps1 (the first attempt at importing the VS development environment in the Windows Test step) does not exist on the windows-2022 runner image used by this project, so the step died with 'not recognized as a name of a cmdlet, function, script file'. Replace it with the canonical, version-stable mechanism: vswhere locates the VS installation, VsDevCmd.bat (the original vcvars, present in every VS 2017+ install under Common7\Tools) is called by relative name from that directory (no path-quoting involved), and its 'set' dump is imported variable by variable into the step's PowerShell process. ctest, the cmake -P fixture scripts, and cl.exe inherit it, so the trait_compile_* fixtures find the CRT/STL headers. Everything else in the job is unchanged. --- .github/workflows/ci-pull.yml | 19 +++++++++--- .github/workflows/ci.yml | 31 +++++++++++++------ .../laige-sim/expect-compile-result.cmake.in | 7 +++-- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-pull.yml b/.github/workflows/ci-pull.yml index 902f761..384cfc2 100644 --- a/.github/workflows/ci-pull.yml +++ b/.github/workflows/ci-pull.yml @@ -203,13 +203,24 @@ jobs: # generator's toolchain setup, so — unlike every other test in # this job — they need the MSVC environment (INCLUDE/LIB/PATH) # to find the CRT/STL headers; the plain runner shell lacks it - # and cl fails with C1083 on etc. LaunchVsDevEnv.ps1 - # (VS 2022, located through vswhere) sets that environment in - # the current PowerShell process; ctest, cmake -P, and cl all + # and cl fails with C1083 on etc. This step imports + # the VS development environment into the current PowerShell + # process: vswhere locates the installation, VsDevCmd.bat (the + # canonical vcvars, present in every VS 2017+ install under + # Common7\Tools — called by relative name from that directory, + # so no path quoting is involved) sets it, and its `set` dump + # is imported variable by variable. ctest, cmake -P, and cl all # inherit it. Everything else in this step is unchanged. run: | $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath - & (Join-Path $vs "Common7\Tools\LaunchVsDevEnv.ps1") -Arch amd64 + Push-Location (Join-Path $vs "Common7\Tools") + $envDump = cmd /c "call VsDevCmd.bat -arch=amd64 -host_arch=amd64 >nul && set" + Pop-Location + foreach ($line in $envDump) { + if ($line -match '^([A-Za-z_][A-Za-z0-9_]*)=(.*)$') { + Set-Item -Path "env:$($matches[1])" -Value $matches[2] + } + } ctest --test-dir build -C Debug --output-on-failure macos-arm64: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1294bb6..f0b09ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,12 +87,12 @@ # -j`. The Windows job therefore pins the canonical Debug configuration # explicitly on the build and test steps (--config Debug / -C Debug); the # single-config platforms run the canonical commands verbatim. The -# Windows Test step additionally loads the VS 2022 development -# environment (vswhere + LaunchVsDevEnv.ps1, same step, before ctest) -# because the trait_compile_* fixtures invoke cl.exe directly from a -# generated cmake -P script — outside the VS generator's toolchain -# setup — and need the MSVC environment (INCLUDE/LIB/PATH) to find the -# CRT/STL headers. +# Windows Test step additionally imports the VS 2022 development +# environment (vswhere + VsDevCmd.bat, same step, before ctest) because +# the trait_compile_* fixtures invoke cl.exe directly from a generated +# cmake -P script — outside the VS generator's toolchain setup — and +# need the MSVC environment (INCLUDE/LIB/PATH) to find the CRT/STL +# headers. # # AC-6.1 (same source tree, same feature set, no per-OS feature flags): no # build job passes feature flags; every build job builds the identical @@ -232,13 +232,24 @@ jobs: # generator's toolchain setup, so — unlike every other test in # this job — they need the MSVC environment (INCLUDE/LIB/PATH) # to find the CRT/STL headers; the plain runner shell lacks it - # and cl fails with C1083 on etc. LaunchVsDevEnv.ps1 - # (VS 2022, located through vswhere) sets that environment in - # the current PowerShell process; ctest, cmake -P, and cl all + # and cl fails with C1083 on etc. This step imports + # the VS development environment into the current PowerShell + # process: vswhere locates the installation, VsDevCmd.bat (the + # canonical vcvars, present in every VS 2017+ install under + # Common7\Tools — called by relative name from that directory, + # so no path quoting is involved) sets it, and its `set` dump + # is imported variable by variable. ctest, cmake -P, and cl all # inherit it. Everything else in this step is unchanged. run: | $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -property installationPath - & (Join-Path $vs "Common7\Tools\LaunchVsDevEnv.ps1") -Arch amd64 + Push-Location (Join-Path $vs "Common7\Tools") + $envDump = cmd /c "call VsDevCmd.bat -arch=amd64 -host_arch=amd64 >nul && set" + Pop-Location + foreach ($line in $envDump) { + if ($line -match '^([A-Za-z_][A-Za-z0-9_]*)=(.*)$') { + Set-Item -Path "env:$($matches[1])" -Value $matches[2] + } + } ctest --test-dir build -C Debug --output-on-failure macos-arm64: diff --git a/tests/laige-sim/expect-compile-result.cmake.in b/tests/laige-sim/expect-compile-result.cmake.in index 3a28392..ad87bf8 100644 --- a/tests/laige-sim/expect-compile-result.cmake.in +++ b/tests/laige-sim/expect-compile-result.cmake.in @@ -27,9 +27,10 @@ # MSVC note: cl launched this way runs outside the VS generator's # toolchain setup, so the surrounding environment MUST already carry # the MSVC INCLUDE/LIB/PATH (or cl fails with C1083 on etc.). -# The CI Windows Test step loads the VS development environment before -# ctest (vswhere + LaunchVsDevEnv.ps1 — see .github/workflows/ci.yml); -# a local Windows run needs the same (Developer PowerShell). +# The CI Windows Test step imports the VS development environment into +# the step before ctest (vswhere + VsDevCmd.bat — see +# .github/workflows/ci.yml); a local Windows run needs the same +# (e.g. a VS Developer PowerShell). set(_cmd @CMD_TEXT@) execute_process( From 67270adadabd3064395c42dfa129fd93d4b51bd3 Mon Sep 17 00:00:00 2001 From: Pascal Severin Date: Wed, 16 Sep 2026 14:21:15 +0200 Subject: [PATCH 5/5] docs: document the G-R8 trait compile checks' Windows environment requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DOC-003: building.md's test list now covers the trait_compile_* CTest fixtures, including the fact that on Windows they invoke cl.exe directly from a generated cmake -P script and therefore need the MSVC environment (VS Developer shell) in a local run — the CI Windows Test step imports it via VsDevCmd.bat. --- docs/getting-started/building.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/getting-started/building.md b/docs/getting-started/building.md index 9be4a34..243a92e 100644 --- a/docs/getting-started/building.md +++ b/docs/getting-started/building.md @@ -83,6 +83,15 @@ Notes: `TSAN_OPTIONS=halt_on_error=1` (wired in `tests//CMakeLists.txt` when `LAIGE_TSAN=ON`), so a data race makes `ctest` fail with a non-zero exit — no extra environment setup needed. +- G-R8 trait compile checks (`trait_compile_*`, `tests/laige-sim`): four + CTest fixtures that compile (not link, not run) one translation unit + each with the engine policy flags and assert both the exit code and — + for the negative fixtures — the actionable G-R8 message in the compiler + output. On Windows they invoke `cl.exe` directly from a generated + `cmake -P` script, outside the VS generator's toolchain setup, so run + them from a VS Developer shell (or any shell with the MSVC environment + loaded: `INCLUDE`/`LIB`/`PATH` set); CI's Windows Test step imports it + via `VsDevCmd.bat` (see `.github/workflows/ci.yml`). ## Build trees and artifacts