[M1-HEAD-01] Fix Windows CI: MSVC C4996 fopen fatal under /WX in laige-run #77
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # Laige CI — P0 platform matrix on pull request (M0-CI-01) | |
| # | |
| # Roadmap: roadmap/M0-foundations.md, step M0-CI-01 | |
| # PRD refs: §6 (P0 platforms, AC-6.1), §14 (cadence), §8.1 (build budget) | |
| # | |
| # Cadence (PRD §14: "one per PR, all per merge"): each pull request runs | |
| # exactly ONE P0 OS, selected by the PR's ci:* label; the default (no | |
| # label) is Linux. Merges to master run ALL P0 OSes via ci.yml, which is | |
| # the "all per merge" half of the cadence. | |
| # | |
| # Label selector (apply at most one per PR; if several are present the | |
| # highest-priority label wins, so exactly one P0 OS always runs): | |
| # | |
| # ci:macos → macos-arm64 + macos-intel jobs | |
| # ci:windows → windows-msvc job | |
| # ci:linux → linux-gcc + linux-clang jobs (also the no-label default) | |
| # | |
| # NOTE: the labels must be created once in the repository settings | |
| # (GitHub does not create labels from workflows). A PR with an unknown | |
| # or missing ci:* label simply falls back to the Linux default. | |
| # | |
| # Jobs, runner images, steps, and the 10-minute budget timeout are the | |
| # same as in ci.yml (see its header for the full matrix documentation | |
| # and the Windows multi-config note). | |
| # | |
| # Sanitizer lanes (M0-CI-02): linux-asan (LAIGE_ASAN=ON) and linux-tsan | |
| # (LAIGE_TSAN=ON) run under the same condition as the default Linux | |
| # jobs — i.e. on every PR that does not select another P0 OS — because | |
| # they are part of the Linux P0 check (PRD §14: one P0 OS per PR). See | |
| # the ci.yml header for the lane semantics (fatal sanitizer reports, | |
| # report archiving, toolchain choice). | |
| # | |
| # Fuzz lane (PRD §14 "every commit (bounded)"; M0-TEST-01): no separate | |
| # fuzz job — the `fuzz_json_parse` ctest entry (1000 deterministic runs | |
| # of laige-fuzz on the json_parse target) runs inside every build | |
| # job's ctest, instrumented in the ASan tree. Seed and nightly-long-run | |
| # conventions: docs/testing.md. | |
| # | |
| # Include-graph lint (M0-CI-03; NFR-8.11, NFR-8.13): the `include-lint` | |
| # job runs on EVERY pull request, independent of the ci:* label selector | |
| # — it is a platform-independent repository check (Python 3 stdlib only), | |
| # not a P0 OS build, so it does not interact with the "one P0 OS per PR" | |
| # cadence. It enforces the PRD §10.1 include rules over src/** (laige-core | |
| # depends on nothing internal; arrows only downward in the module stack; | |
| # vendored deps only included from their deps.lock `owner`) and reports | |
| # the vendored-dependency count, which must stay ≤ 10 (PRD §11). | |
| # | |
| # Fork PRs: this workflow uses the pull_request event, which runs on the | |
| # merge ref with a read-only token; checkout + build + ctest need nothing | |
| # beyond contents:read. On such PRs the artifact upload (needs | |
| # actions:write) fails with a 403, so the upload steps carry | |
| # continue-on-error: true — the reports then remain in the job log and | |
| # the tee'd output instead of the artifact. | |
| # ============================================================================ | |
| name: CI (pull request) | |
| on: | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| linux-gcc: | |
| name: Linux x64 (g++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++ | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| linux-clang: | |
| name: Linux x64 (clang++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| linux-asan: | |
| name: Linux x64 ASan+UBSan (clang++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| # Job-scoped token: the archive step below needs actions:write for | |
| # PRs from the base repository (fork PR tokens stay read-only, | |
| # whatever this declares — see header). | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure (ASan+UBSan) | |
| run: cmake -S . -B build-asan -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DLAIGE_ASAN=ON | |
| - name: Build (ASan+UBSan) | |
| run: cmake --build build-asan -j | |
| - name: Test (unit, ASan+UBSan) | |
| # Every sanitizer report is fatal to the test process: ASan via | |
| # abort_on_error=1:halt_on_error=1 here, UBSan via | |
| # -fno-sanitize-recover=all (wired by CMake). log_path keeps one | |
| # report file per test process for the artifact upload below. | |
| env: | |
| ASAN_OPTIONS: "abort_on_error=1:halt_on_error=1:detect_leaks=1:log_path=${{ github.workspace }}/asan-reports/asan" | |
| run: | | |
| set -o pipefail | |
| ctest --test-dir build-asan --output-on-failure 2>&1 | tee asan-ctest-output.txt | |
| - name: Archive sanitizer reports | |
| # continue-on-error: fork PRs run with a read-only token, so the | |
| # upload 403s there (see header); the reports stay in the job log | |
| # and the tee'd output. The job-level actions:write covers PRs | |
| # from the base repository. | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-asan-reports | |
| path: | | |
| asan-ctest-output.txt | |
| asan-reports/ | |
| build-asan/Testing/Temporary/LastTest.log | |
| linux-tsan: | |
| name: Linux x64 TSan (clang++) | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:windows') | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| # Job-scoped token: the archive step below needs actions:write (see | |
| # the linux-asan job comment). | |
| permissions: | |
| contents: read | |
| actions: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure (TSan) | |
| run: cmake -S . -B build-tsan -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++ -DLAIGE_TSAN=ON | |
| - name: Build (TSan) | |
| run: cmake --build build-tsan -j | |
| - name: Test (unit, TSan) | |
| # TSAN_OPTIONS=halt_on_error=1 is applied per test by tests/ | |
| # (M0-BUILD-01), so the first data race report kills the test | |
| # process and ctest fails. The race report is printed to stderr | |
| # and captured by the tee below. | |
| run: | | |
| set -o pipefail | |
| ctest --test-dir build-tsan --output-on-failure 2>&1 | tee tsan-ctest-output.txt | |
| - name: Archive sanitizer reports | |
| # continue-on-error and job-level permissions: same rationale as | |
| # the linux-asan job (fork PRs read-only, base-repo PRs can | |
| # upload). | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-tsan-reports | |
| path: | | |
| tsan-ctest-output.txt | |
| build-tsan/Testing/Temporary/LastTest.log | |
| windows-msvc: | |
| name: Windows x64 (MSVC 2022) | |
| if: >- | |
| contains(github.event.pull_request.labels.*.name, 'ci:windows') && | |
| !contains(github.event.pull_request.labels.*.name, 'ci:macos') | |
| runs-on: windows-2022 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| # CMAKE_BUILD_TYPE is ignored by the multi-config VS generator; the | |
| # Debug configuration is pinned on the build/test steps below. | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build --config Debug -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build -C Debug --output-on-failure | |
| macos-arm64: | |
| name: macOS arm64 (AppleClang) | |
| if: contains(github.event.pull_request.labels.*.name, 'ci:macos') | |
| runs-on: macos-15 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| macos-intel: | |
| name: macOS Intel (AppleClang) | |
| if: contains(github.event.pull_request.labels.*.name, 'ci:macos') | |
| runs-on: macos-14 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Test (unit) | |
| run: ctest --test-dir build --output-on-failure | |
| include-lint: | |
| # M0-CI-03: runs on every PR (no ci:* condition). Platform-independent: | |
| # the lint is Python 3 stdlib only (no setup step needed) and checks | |
| # the repository layout, not a compiler-specific build. The CTest | |
| # suite runs it against the real tree in every P0 job as well | |
| # (tests/tools, test `include-lint-real-tree`), so a broken include | |
| # fails even a PR that selects another P0 OS. | |
| name: Include-graph lint + dependency count | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Lint include graph + report dependency count | |
| run: python3 tools/laige-include-lint | |
| api-manifest: | |
| # M0-TOOL-01: runs on every PR (no ci:* condition). The checked-in | |
| # public API manifest (laige-api.json, PRD §9.4, NFR-13.1) must stay | |
| # in sync with the public headers: this job regenerates it from the | |
| # current headers with laige-api-scanner and fails on any drift, so | |
| # adding a public symbol without regenerating the manifest fails CI. | |
| # The CTest suite runs the same check against the real tree in every | |
| # P0 job as well (tests/api, test `api-real-tree`). | |
| name: Public API manifest drift | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build scanner | |
| run: cmake --build build --target laige-api-scanner -j | |
| - name: Check manifest drift | |
| run: ./build/bin/laige-api-scanner --root . --check laige-api.json | |
| detcheck: | |
| # M0-TOOL-02: runs on every PR (no ci:* condition). The determinism | |
| # checker skeleton (FR-11.5): the built-in synthetic scenario | |
| # self-check runs here so a broken checker lands red before the real | |
| # scenarios exist. The real-scenario comparison (two build | |
| # configurations of M1-SAMPLE-01's hello) is skipped until that | |
| # sample lands; M1-DET-04 activates it (see the job in ci.yml for | |
| # the full note). | |
| name: Determinism check | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Configure | |
| run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug | |
| - name: Build | |
| run: cmake --build build -j | |
| - name: Synthetic scenario self-check | |
| run: ./build/bin/laige-detcheck --scenario=synthetic | |
| - name: Real scenario (M1-SAMPLE-01) | |
| # Skipped until M1-SAMPLE-01 lands the hello scenario binary; | |
| # M1-DET-04 replaces this step with the two-configuration | |
| # comparison (see the job in ci.yml). | |
| run: | | |
| if [ -x samples/hello/bin/hello ]; then | |
| ./build/bin/laige-detcheck \ | |
| --run-a=samples/hello/bin/hello \ | |
| --run-b=samples/hello/bin/hello | |
| else | |
| echo "skipped: no real scenario yet (M1-SAMPLE-01)" | |
| fi |