Skip to content

[M0-CORE-08] Fix Windows CI: <algorithm> missing from budget_harness.h (MSVC C2039/C3861 on std::sort) - #11

Merged
offdev merged 6 commits into
masterfrom
fix/windows-msvc-budget-harness-include
Sep 12, 2026
Merged

offdev merged 6 commits into
masterfrom
fix/windows-msvc-budget-harness-include

Conversation

@offdev

@offdev offdev commented Sep 12, 2026 •

Copy link
Copy Markdown
Owner

Problem

The windows-msvc job (cmake --build build --config Debug -j) failed with:

  • budget_harness.h(242,10): error C2039: 'sort': is not a member of 'std'

That was only the first of four distinct Windows-only defects; each was
unmasked by fixing the previous one (the build halts at the first failure).
All four are fixed here, verified on real windows-2022 runs.

Fixes (one commit each)

  1. fe0f5a5 — missing <algorithm> include (the reported error).
    Histogram::stats() calls std::copy_n/std::sort, but
    budget_harness.h included only <chrono>/<cmath>/<cstddef>/<cstdint>/ <string>/<string_view>/<vector>. GCC/Clang pull <algorithm> in
    transitively (linux lanes + local builds pass); MSVC standard headers do
    not. Added #include <algorithm> (self-contained per CPP-010) and
    regenerated the checked-in laige-api.json (the manifest records
    per-symbol line numbers; 58 entries shift by one line, signatures
    unchanged — the api-real-tree drift check would otherwise fail).

  2. 6b3d7fe — unguarded getenv/fopen (C4996, fatal under /WX).
    laige-bench.cpp (LAIGE_BENCH_MACHINE, LAIGE_BUDGETS_PATH, report
    file) and budget_harness_tests.cpp (LAIGE_BUDGETS_PATH) used plain
    std::getenv/std::fopen. Fixed with the repo's established CPP-009
    platform-boundary pattern (#if defined(_MSC_VER) → getenv_s /
    _fsopen(_SH_DENYNO), same semantics as on other compilers).
    kEnvValueMaxBytes lives inside the MSVC branches — outside them it is an
    unused constant, fatal under -Wunused-const-variable on Clang (-Werror).

  3. 89eae70 — api CTest tests cannot find the scanner (multi-config layout).
    The six api-* CTest tests drove the scanner from generated cmake -P
    scripts with a configure-time path ${CMAKE_BINARY_DIR}/bin/laige-api-scanner.
    Under the VS multi-config generator the executable lives in
    bin/<Config>/laige-api-scanner.exe, so execute_process died with
    "no such file or directory". Fixed by carrying the scanner path in each
    test's ENVIRONMENT property as $<TARGET_FILE:laige-api-scanner> —
    CMake expands it per configuration in the generated CTestTestfile.cmake —
    and reading it at test time via $ENV{LAIGE_API_SCANNER} (configure_file
    copies the reference verbatim; a configure-time expansion bakes in a wrong
    value). Verified by experiment on CMake 4.4.3 (Ninja Multi-Config +
    Unix Makefiles).

  4. b5bbacb — api-check-fresh stale on CRLF fixture (0 differences).
    The canonical manifest form is LF (serializer emits LF; .gitattributes
    pins eol=lf). But CMake's file(WRITE) uses a text-mode stream on
    Windows, so the CMake-generated test fixtures land on disk with CRLF
    endings. The scanner's --check reads in binary mode → byte compare sees
    CRLF vs LF while the symbol diff is 0 (CRLF is JSON whitespace) →
    "stale (0 difference(s))". runCheck now folds CRLF pairs to LF before
    the byte compare (a lone \r is NOT folded, so a genuinely corrupted file
    still fails, CORE-008); regeneration stays byte-identical LF (NFR-13.4);
    contract text updated in the same change (CORE-006).

Verification

  • windows-msvc job (this PR, real runs): build + all 22 CTest entries pass
    on the final commit (run of b5bbacb, with ci:windows label).
  • macOS arm64 + macOS Intel: build + 22/22 pass on the final commit
    (ci:macos label run).
  • include-graph lint + API-manifest drift: pass on every run.
  • Linux lanes passed on CI for fe0f5a5; for the final commit all six
    canonical local trees pass 22/22 (build, build-asan, build-tsan,
    build-clang, build-shared, build-clang-shared).
  • Scanner --check case matrix verified locally: LF → 0, CRLF → 0,
    lone-CR → 1 (stale), stale fixture → 1 with the 2 symbol diffs, missing
    file → 2.
  • laige-bench smoke-tested with LAIGE_BENCH_MACHINE/LAIGE_BUDGETS_PATH
    set and --report append (report file written correctly).

Note: this branch has two empty trigger commits (the PR workflow is
label-gated: ci:windows/ci:macos select which OS lanes run per PR); a
squash merge collapses them.

…h (MSVC C2039/C3861 on std::sort)

Histogram::stats() calls std::copy_n and std::sort in budget_harness.h,
but the header included only <chrono>/<cmath>/<cstddef>/<cstdint>/<string>/
<string_view>/<vector>. GCC and Clang pull <algorithm> in transitively
(via <vector> and friends), so the linux lanes and local builds pass,
but MSVC standard headers do not: the windows-msvc job died with
C2039 (sort: is not a member of std) / C3861 while compiling
budget_harness.cpp.

- Add #include <algorithm> to the header (self-contained per CPP-010;
  alphabetically first in the include block).
- Regenerate the checked-in laige-api.json with laige-api-scanner: the
  manifest records per-symbol line numbers, and the added include shifts
  every budget_harness.h entry by one line (58 lines; signatures and
  summaries unchanged). Without it the api-real-tree drift check fails
  in every P0 job, including windows-msvc, right after the build fix.

Verified: full build + all 22 CTest entries pass in build;
include-lint passes (17 source files scanned, 78 system headers,
1 vendored dependency); audited every CI-built TU (src/tests/tools) for
std symbols used without a direct include - no other gaps.
…r /WX)

After the <algorithm> include fix, the windows-msvc build proceeds
past laige-core and fails on the remaining unguarded C4996 deprecations
(fatal under the engine /WX policy, NFR-8.10):

- tools/bench/laige-bench.cpp: getenv (LAIGE_BENCH_MACHINE,
  LAIGE_BUDGETS_PATH) and fopen (report file, mode "a")
- tests/laige-core/budget_harness_tests.cpp: getenv (LAIGE_BUDGETS_PATH)

Same CPP-009 platform-boundary pattern the repo already uses in
logging.cpp / budget_harness.cpp / logging_tests.cpp: #if defined(_MSC_VER)
switches to the CRT replacements (getenv_s, _fsopen with _SH_DENYNO) with
the same lookup/open semantics; other compilers keep the standard calls.

- envValue() returns the value as std::string (empty when unset); a value
  beyond the named kEnvValueMaxBytes (4096) is treated as unset, and the
  documented fallback applies (CORE-005 named bound, CORE-008 loud
  default instead of a truncated value).
- BudgetsFilePath() now returns std::string (single call site, passed
  into loadBudgets(std::string_view) within one full expression - the
  temporary outlives the call).

Audited every CI-built TU (src/tests/tools) for the C4996-prone CRT calls
(getenv, fopen, tmpnam, tmpfile, strcpy, strcat, sprintf, gets, getwd,
mkstemps, scandir): these were the only unguarded sites.

Verified: full build + all 22 CTest entries pass; laige-bench smoke test
with LAIGE_BENCH_MACHINE/LAIGE_BUDGETS_PATH set and --report append
writes the report file correctly.
…under multi-config layout

The six api-* CTest tests drive laige-api-scanner from generated
cmake -P check scripts with a configure-time path
(${CMAKE_BINARY_DIR}/bin/laige-api-scanner). That layout only matches
single-config generators: under the Windows VS multi-config generator
the executable lives in bin/<Config>/laige-api-scanner.exe, so
execute_process dies with "no such file or directory" (and
api-fixture-scan additionally fails its file(READ) on the manifest that
the never-running scanner was supposed to write).

Fix (verified by experiment on CMake 4.4.3, Ninja Multi-Config and
Unix Makefiles):

- The scanner path is carried in each test's ENVIRONMENT property as
  $<TARGET_FILE:laige-api-scanner>; CMake expands it per configuration
  in the generated CTestTestfile.cmake (Debug -> bin/Debug/..., Release
  -> bin/Release/..., single-config -> bin/...).
- The check script reads it at test time via $ENV{LAIGE_API_SCANNER}
  (configure_file copies the reference verbatim; a configure-time
  expansion would bake in an empty/wrong value) and fails loudly when
  unset (CORE-008).
- kEnvValueMaxBytes moved inside the MSVC #if branches in laige-bench.cpp
  and budget_harness_tests.cpp: outside them it is an unused
  namespace-scope constant, which -Wunused-const-variable makes fatal
  under -Werror on Clang (NFR-8.10, caught in the ASan tree).

Verified: all 22 CTest entries pass in build, build-asan, build-tsan,
build-clang, build-shared, and build-clang-shared; the multi-config
experiment confirmed per-config resolution and single-config parity.
…ormalize CRLF to LF in --check)

After the scanner-path fix, the Windows windows-msvc job's ctest left
one failure: api-check-fresh reported "stale (0 difference(s))".

Root cause: the canonical manifest form is LF (the serializer emits LF;
.gitattributes pins eol=lf on every checkout). But CMake's file(WRITE)
opens a text-mode stream on Windows (cmFileCommand: ofstream without
ios::binary), so the CMake-generated test fixtures
(expected-fixture-api.json, ...) land on disk with CRLF endings. The
api-fixture-scan test survived because CMake's file(READ) normalizes
CRLF->LF on read; the scanner's --check reads the file in binary mode,
so its byte compare saw CRLF vs LF, while the symbol-level diff was 0
(CRLF is JSON whitespace) - hence "stale (0 difference(s))".

Fix: in runCheck, fold CRLF pairs to LF in the read file before the
byte compare (a lone \r is NOT folded, so a genuinely corrupted file
still fails, CORE-008). The regeneration itself is unchanged and stays
byte-identical LF (NFR-13.4); contract text (header comment, --help)
updated in the same change (CORE-006).

Verified locally (CMake 4.4.3): LF copy -> exit 0, CRLF copy -> exit 0,
lone-CR copy -> exit 1 "stale", stale fixture -> exit 1 with the 2
symbol diffs, missing file -> exit 2; all 22 CTest entries pass in
build, build-asan, build-tsan, build-clang, build-shared, and
build-clang-shared.
@offdev
offdev merged commit 9e31b83 into master Sep 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant