From 5d81a6c25dc4f37c0f318de79043e23b87df7901 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 9 Jul 2026 16:59:17 -0500 Subject: [PATCH 1/6] Add a public-header gate CI job; make CompliantByDefaultFilterApi self-contained Consumers that embed the SDK (e.g. ONNX Runtime / Foundry Local) compile their own translation units -- which include our public headers -- under strict warning flags: -Wall -Wextra -Werror on GCC/Clang (plus -Wshorten-64-to-32 on Clang) and /W4 /WX on MSVC, suppressing third-party headers via -isystem / /external:W0. When that suppression is defeated (include order, PCH, or NO_SYSTEM_FROM_IMPORTED), any warning or missing include in our headers breaks the consumer build. This adds a CI gate that compiles every public header on its own, with no -isystem suppression, under those flags on GCC, Clang, and MSVC, so header issues surface here instead of at integration time: - tests/headers/check_public_headers.sh (GCC + Clang) - tests/headers/check_public_headers.cmd (MSVC) - .github/workflows/public-header-gate.yml The gate caught one real self-containment bug: CompliantByDefaultFilterApi.hpp uses std::vector but did not include , so it only compiled when something else pulled in first. Added the include. VariantType.hpp is excluded: it is an implementation fragment included by Variant.hpp (which defines VariantMap/VariantArray and the needed std headers first), not a standalone header. Validated locally: all 40 public headers pass (39 compiled + VariantType excluded) on g++, clang++, and MSVC cl /W4 /WX. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/public-header-gate.yml | 49 +++++++++++ .../public/CompliantByDefaultFilterApi.hpp | 1 + tests/headers/check_public_headers.cmd | 67 +++++++++++++++ tests/headers/check_public_headers.sh | 83 +++++++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 .github/workflows/public-header-gate.yml create mode 100644 tests/headers/check_public_headers.cmd create mode 100644 tests/headers/check_public_headers.sh diff --git a/.github/workflows/public-header-gate.yml b/.github/workflows/public-header-gate.yml new file mode 100644 index 000000000..cd065c2ef --- /dev/null +++ b/.github/workflows/public-header-gate.yml @@ -0,0 +1,49 @@ +name: Public header gate + +on: + push: + branches: + - master + - main + - dev + - dev/* + - release/* + - buildme/* + + pull_request: + branches: + - master + - main + - dev + +# Least-privilege GITHUB_TOKEN scope: this workflow only checks out source and +# compiles the public headers. Explicit block satisfies CodeQL rule +# actions/missing-workflow-permissions if Actions analysis is enabled. +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + linux: + name: Public headers (GCC/Clang) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install clang + run: sudo apt-get update && sudo apt-get install -y clang + - name: Compile each public header standalone under strict flags + run: bash tests/headers/check_public_headers.sh + + windows: + name: Public headers (MSVC) + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Compile each public header standalone under /W4 /WX + shell: cmd + run: tests\headers\check_public_headers.cmd diff --git a/lib/include/public/CompliantByDefaultFilterApi.hpp b/lib/include/public/CompliantByDefaultFilterApi.hpp index 642aa4c9e..85e779c3f 100644 --- a/lib/include/public/CompliantByDefaultFilterApi.hpp +++ b/lib/include/public/CompliantByDefaultFilterApi.hpp @@ -7,6 +7,7 @@ #include "ctmacros.hpp" +#include #include namespace MAT_NS_BEGIN { namespace Modules { namespace Filtering diff --git a/tests/headers/check_public_headers.cmd b/tests/headers/check_public_headers.cmd new file mode 100644 index 000000000..2de67e386 --- /dev/null +++ b/tests/headers/check_public_headers.cmd @@ -0,0 +1,67 @@ +@echo off +REM Copyright (c) Microsoft Corporation. All rights reserved. +REM SPDX-License-Identifier: Apache-2.0 +REM +REM Public header gate (MSVC). Compiles each public SDK header on its own under +REM /W4 /WX, mirroring how ONNX Runtime / Foundry Local compile their own C++ +REM translation units on Windows. STL headers are treated as external +REM (/external:W0) so only the SDK's headers are gated. Exits non-zero if any +REM header fails to compile or emits a warning. +setlocal enabledelayedexpansion + +set "SCRIPT_DIR=%~dp0" +set "REPO_ROOT=%SCRIPT_DIR%..\.." +set "PUB=%REPO_ROOT%\lib\include\public" + +REM Enter the MSVC x64 developer environment via vswhere (portable across runners). +set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" +if not exist "%VSWHERE%" ( + echo error: vswhere.exe not found 1>&2 + exit /b 2 +) +set "VSPATH=" +for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -property installationPath`) do set "VSPATH=%%i" +if not defined VSPATH ( + echo error: no Visual Studio installation found 1>&2 + exit /b 2 +) +call "%VSPATH%\VC\Auxiliary\Build\vcvars64.bat" >nul +if errorlevel 1 ( + echo error: failed to initialize the MSVC environment 1>&2 + exit /b 2 +) + +set "WORK=%TEMP%\pubhdrgate" +if exist "%WORK%" rmdir /s /q "%WORK%" +mkdir "%WORK%" + +REM /W4 /WX matches ORT; /external:W0 suppresses STL warnings so only our headers gate. +set "FLAGS=/nologo /std:c++17 /permissive- /W4 /WX /EHsc /experimental:external /external:anglebrackets /external:W0" +set "FAIL=0" +set "OKC=0" + +echo == cl (c++17, /W4 /WX) == +for %%h in ("%PUB%\*.hpp") do ( + set "NAME=%%~nxh" + REM Skip implementation-fragment headers not meant to be included standalone + REM (VariantType.hpp is included by Variant.hpp, which defines VariantMap/VariantArray first). + if /I not "!NAME!"=="VariantType.hpp" ( + > "%WORK%\tu.cpp" echo #include "!NAME!" + >> "%WORK%\tu.cpp" echo int main^(^){return 0;} + cl %FLAGS% /I "%PUB%" /Zs "%WORK%\tu.cpp" > "%WORK%\err.txt" 2>&1 + if errorlevel 1 ( + echo FAIL: !NAME! + type "%WORK%\err.txt" + set "FAIL=1" + ) else ( + set /a OKC+=1 + ) + ) +) + +if "%FAIL%"=="1" ( + echo Public header gate FAILED. + exit /b 1 +) +echo Public header gate passed. ^(!OKC! headers^) +exit /b 0 diff --git a/tests/headers/check_public_headers.sh b/tests/headers/check_public_headers.sh new file mode 100644 index 000000000..517064893 --- /dev/null +++ b/tests/headers/check_public_headers.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Public header gate (GCC/Clang). +# +# Verifies that every public SDK header is self-contained (compiles on its own, +# in any include order) and warning-clean under strict, consumer-representative +# warning flags. Downstream consumers such as ONNX Runtime / Foundry Local +# compile their own translation units -- which include these headers -- with +# -Wall -Wextra -Werror (plus -Wshorten-64-to-32 on Clang). This gate compiles +# each public header on its own, with no -isystem suppression, so any header +# issue surfaces here instead of at integration time. +# +# Exits non-zero if any header fails to compile or emits a warning. + +set -uo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +PUB="$REPO_ROOT/lib/include/public" + +# Implementation-fragment headers: intentionally included by another public +# header (which supplies their dependencies first) and not meant to be included +# standalone. They are exercised through their public entry point instead. +EXCLUDES=( + "VariantType.hpp" # included by Variant.hpp, which defines VariantMap/VariantArray first +) + +INCLUDES="-I$PUB -I$REPO_ROOT/lib/include" +# Mirrors this repo's own warning flags and what ORT applies to its C++ TUs. +BASE_FLAGS="-std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-but-set-variable" + +is_excluded() { + local n="$1" e + for e in "${EXCLUDES[@]}"; do [ "$e" = "$n" ] && return 0; done + return 1 +} + +fail=0 +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +run_compiler() { + local cc="$1" extra="$2" label="$3" + local n_ok=0 n_fail=0 h name tu out + echo "== $label ==" + for h in "$PUB"/*.hpp; do + name="$(basename "$h")" + is_excluded "$name" && continue + tu="$tmp/tu_${name%.hpp}.cpp" + printf '#include "%s"\nint main() { return 0; }\n' "$name" > "$tu" + if out="$("$cc" $BASE_FLAGS $extra $INCLUDES -fsyntax-only "$tu" 2>&1)"; then + n_ok=$((n_ok + 1)) + else + n_fail=$((n_fail + 1)); fail=1 + echo " FAIL: $name" + echo "$out" | grep -E 'error:|warning:' | head -4 | sed 's/^/ /' + fi + done + echo " $label: $n_ok passed, $n_fail failed" +} + +ran=0 +if command -v g++ >/dev/null 2>&1; then + run_compiler g++ "" "g++ (c++17, -Wall -Wextra -Werror)" + ran=1 +fi +if command -v clang++ >/dev/null 2>&1; then + run_compiler clang++ "-Wshorten-64-to-32" "clang++ (c++17, + -Wshorten-64-to-32)" + ran=1 +fi + +if [ "$ran" -eq 0 ]; then + echo "error: neither g++ nor clang++ was found" >&2 + exit 2 +fi + +if [ "$fail" -ne 0 ]; then + echo "Public header gate FAILED." + exit 1 +fi +echo "Public header gate passed." From 9b095141f1d554446cd0c496f564369542d6dbb0 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 9 Jul 2026 17:26:25 -0500 Subject: [PATCH 2/6] Address Copilot round 1 on #1503: gate .h headers, tighten flags, fix MSVC includes - Gate both *.hpp and *.h so the flat C API headers (mat.h, CommonFields.h) are covered, not just *.hpp. Both compile clean standalone under the gate flags. - Drop -Wno-unused-but-set-variable from the GCC/Clang flags: no public header relies on it, so removing it makes the gate stricter. Kept -Wno-unused-parameter (mirrors this repo's WARN_FLAGS; intentional unused params use UNREFERENCED_PARAMETER). - MSVC gate: add /I lib\include so headers that conditionally pull mat/config.h (e.g. CsProtocol_types.hpp) exercise the same include graph as the GCC/Clang gate. Validated locally: 41/41 public headers pass on g++, clang++, and MSVC cl /W4 /WX. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/headers/check_public_headers.cmd | 4 ++-- tests/headers/check_public_headers.sh | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/headers/check_public_headers.cmd b/tests/headers/check_public_headers.cmd index 2de67e386..f9c17869e 100644 --- a/tests/headers/check_public_headers.cmd +++ b/tests/headers/check_public_headers.cmd @@ -41,14 +41,14 @@ set "FAIL=0" set "OKC=0" echo == cl (c++17, /W4 /WX) == -for %%h in ("%PUB%\*.hpp") do ( +for %%h in ("%PUB%\*.hpp" "%PUB%\*.h") do ( set "NAME=%%~nxh" REM Skip implementation-fragment headers not meant to be included standalone REM (VariantType.hpp is included by Variant.hpp, which defines VariantMap/VariantArray first). if /I not "!NAME!"=="VariantType.hpp" ( > "%WORK%\tu.cpp" echo #include "!NAME!" >> "%WORK%\tu.cpp" echo int main^(^){return 0;} - cl %FLAGS% /I "%PUB%" /Zs "%WORK%\tu.cpp" > "%WORK%\err.txt" 2>&1 + cl %FLAGS% /I "%PUB%" /I "%REPO_ROOT%\lib\include" /Zs "%WORK%\tu.cpp" > "%WORK%\err.txt" 2>&1 if errorlevel 1 ( echo FAIL: !NAME! type "%WORK%\err.txt" diff --git a/tests/headers/check_public_headers.sh b/tests/headers/check_public_headers.sh index 517064893..aa3f2dd1f 100644 --- a/tests/headers/check_public_headers.sh +++ b/tests/headers/check_public_headers.sh @@ -16,6 +16,7 @@ # Exits non-zero if any header fails to compile or emits a warning. set -uo pipefail +shopt -s nullglob REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" PUB="$REPO_ROOT/lib/include/public" @@ -28,8 +29,11 @@ EXCLUDES=( ) INCLUDES="-I$PUB -I$REPO_ROOT/lib/include" -# Mirrors this repo's own warning flags and what ORT applies to its C++ TUs. -BASE_FLAGS="-std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-but-set-variable" +# Mirror this repo's own pipeline warning flags plus what ORT applies to its C++ +# translation units. -Wno-unused-parameter matches this repo's WARN_FLAGS (and +# ORT's Clang build); intentional unused parameters in the public headers use the +# UNREFERENCED_PARAMETER macro. +BASE_FLAGS="-std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter" is_excluded() { local n="$1" e @@ -45,7 +49,7 @@ run_compiler() { local cc="$1" extra="$2" label="$3" local n_ok=0 n_fail=0 h name tu out echo "== $label ==" - for h in "$PUB"/*.hpp; do + for h in "$PUB"/*.hpp "$PUB"/*.h; do name="$(basename "$h")" is_excluded "$name" && continue tu="$tmp/tu_${name%.hpp}.cpp" From a00b1d34689543e3e6823f1f13d1acb04e73539a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 9 Jul 2026 17:32:40 -0500 Subject: [PATCH 3/6] Fold the public-header gate into existing CI workflows Instead of a standalone workflow, add the header gate as an isolated job in the workflows that already run on the same triggers: - build-posix-latest.yml gains a 'public-headers' job (GCC/Clang on ubuntu). - test-win-latest.yml gains a 'public-headers' job (MSVC on windows). Removes .github/workflows/public-header-gate.yml. The gate scripts under tests/headers/ are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-posix-latest.yml | 11 ++++++ .github/workflows/public-header-gate.yml | 49 ------------------------ .github/workflows/test-win-latest.yml | 10 +++++ 3 files changed, 21 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/public-header-gate.yml diff --git a/.github/workflows/build-posix-latest.yml b/.github/workflows/build-posix-latest.yml index 13ac881ab..8f9320e57 100644 --- a/.github/workflows/build-posix-latest.yml +++ b/.github/workflows/build-posix-latest.yml @@ -47,3 +47,14 @@ jobs: continue-on-error: true - name: Test ${{ matrix.os }} ${{ matrix.config }} run: ./build-tests.sh ${{ matrix.config }} + + public-headers: + name: Public header gate (GCC/Clang) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install clang + run: sudo apt-get update && sudo apt-get install -y clang + - name: Compile each public header standalone under strict flags + run: bash tests/headers/check_public_headers.sh diff --git a/.github/workflows/public-header-gate.yml b/.github/workflows/public-header-gate.yml deleted file mode 100644 index cd065c2ef..000000000 --- a/.github/workflows/public-header-gate.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Public header gate - -on: - push: - branches: - - master - - main - - dev - - dev/* - - release/* - - buildme/* - - pull_request: - branches: - - master - - main - - dev - -# Least-privilege GITHUB_TOKEN scope: this workflow only checks out source and -# compiles the public headers. Explicit block satisfies CodeQL rule -# actions/missing-workflow-permissions if Actions analysis is enabled. -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -jobs: - linux: - name: Public headers (GCC/Clang) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install clang - run: sudo apt-get update && sudo apt-get install -y clang - - name: Compile each public header standalone under strict flags - run: bash tests/headers/check_public_headers.sh - - windows: - name: Public headers (MSVC) - runs-on: windows-2022 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Compile each public header standalone under /W4 /WX - shell: cmd - run: tests\headers\check_public_headers.cmd diff --git a/.github/workflows/test-win-latest.yml b/.github/workflows/test-win-latest.yml index 255868a88..4928fc71f 100644 --- a/.github/workflows/test-win-latest.yml +++ b/.github/workflows/test-win-latest.yml @@ -54,3 +54,13 @@ jobs: - name: Test ${{ matrix.arch }} ${{ matrix.build }} shell: cmd run: build-tests.cmd ${{ matrix.arch }} ${{ matrix.build }} + + public-headers: + name: Public header gate (MSVC) + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Compile each public header standalone under /W4 /WX + shell: cmd + run: tests\headers\check_public_headers.cmd From 16d5f3e9006b27b08f36f2f260bdf71b9426c759 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 10 Jul 2026 11:35:39 -0500 Subject: [PATCH 4/6] Header gate: drop -Wno-unused-parameter so it mirrors real consumer flags The gate suppressed -Wunused-parameter, which is not part of the strict consumer flag set it claims to mirror (-Wall -Wextra -Werror). That hid a real break: NullObjects.hpp overrides left parameters unused because the old UNREFERENCED_PARAMETER macro expanded to nothing on GCC/Clang, so a consumer including LogManager.hpp with plain -Wall -Wextra -Werror failed to compile while the gate passed. With the macro now expanding to (void)(...) (merged from main), the headers are clean without the suppression; drop it so the gate actually catches this class of consumer break. Verified: 41/41 public headers pass standalone on g++ and clang++ without -Wno-unused-parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/headers/check_public_headers.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/headers/check_public_headers.sh b/tests/headers/check_public_headers.sh index aa3f2dd1f..a5e7c0249 100644 --- a/tests/headers/check_public_headers.sh +++ b/tests/headers/check_public_headers.sh @@ -29,11 +29,13 @@ EXCLUDES=( ) INCLUDES="-I$PUB -I$REPO_ROOT/lib/include" -# Mirror this repo's own pipeline warning flags plus what ORT applies to its C++ -# translation units. -Wno-unused-parameter matches this repo's WARN_FLAGS (and -# ORT's Clang build); intentional unused parameters in the public headers use the -# UNREFERENCED_PARAMETER macro. -BASE_FLAGS="-std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter" +# Mirror the strict warning flags third-party consumers build with +# (-Wall -Wextra -Werror, matching this repo's pipeline and ORT). Unused +# parameters are deliberately NOT suppressed: intentional ones use the +# UNREFERENCED_PARAMETER macro, which expands to (void)(...) on GCC/Clang, so the +# headers stay warning-clean without a blanket -Wno-unused-parameter that would +# hide a real consumer break (e.g. an override that leaves a parameter unused). +BASE_FLAGS="-std=c++17 -Wall -Wextra -Werror" is_excluded() { local n="$1" e From 713ad42e6007af2016b6cf693531961a59f7a77d Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 10 Jul 2026 15:23:56 -0500 Subject: [PATCH 5/6] Fail the public-header gate when no headers are found or PUB is missing The gate could silently pass without compiling anything: with nullglob the shell header globs expand to nothing (and the batch FOR loop runs zero times) when the computed public-header directory is wrong, so a miscomputed path reported success while testing nothing. Both scripts now validate the public-header directory exists and fail if zero headers were compiled. Also give the MSVC script a unique per-invocation work directory (%TEMP%\pubhdrgate_) so concurrent runs on the same machine cannot clobber each other's temporary translation unit, and clean it up on exit. Files: tests/headers/check_public_headers.sh, tests/headers/check_public_headers.cmd Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/headers/check_public_headers.cmd | 25 ++++++++++++++++++++++++- tests/headers/check_public_headers.sh | 12 ++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/headers/check_public_headers.cmd b/tests/headers/check_public_headers.cmd index f9c17869e..b0157c018 100644 --- a/tests/headers/check_public_headers.cmd +++ b/tests/headers/check_public_headers.cmd @@ -13,6 +13,14 @@ set "SCRIPT_DIR=%~dp0" set "REPO_ROOT=%SCRIPT_DIR%..\.." set "PUB=%REPO_ROOT%\lib\include\public" +REM Fail fast if the public header directory is missing or miscomputed, otherwise +REM the header loop below would run zero times and the gate would silently "pass" +REM without compiling anything -- a false negative. +if not exist "%PUB%" ( + echo error: public header directory not found: %PUB% 1>&2 + exit /b 2 +) + REM Enter the MSVC x64 developer environment via vswhere (portable across runners). set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" if not exist "%VSWHERE%" ( @@ -31,9 +39,15 @@ if errorlevel 1 ( exit /b 2 ) -set "WORK=%TEMP%\pubhdrgate" +REM Unique work directory so concurrent invocations on the same machine (local dev +REM or a reused runner) do not clobber each other's temporary translation unit. +set "WORK=%TEMP%\pubhdrgate_%RANDOM%_%RANDOM%" if exist "%WORK%" rmdir /s /q "%WORK%" mkdir "%WORK%" +if errorlevel 1 ( + echo error: failed to create work directory %WORK% 1>&2 + exit /b 2 +) REM /W4 /WX matches ORT; /external:W0 suppresses STL warnings so only our headers gate. set "FLAGS=/nologo /std:c++17 /permissive- /W4 /WX /EHsc /experimental:external /external:anglebrackets /external:W0" @@ -59,6 +73,15 @@ for %%h in ("%PUB%\*.hpp" "%PUB%\*.h") do ( ) ) +REM No headers compiled means PUB matched nothing -- treat it as a failure rather +REM than a silent pass. +if "!OKC!"=="0" if "%FAIL%"=="0" ( + echo error: no public headers found under %PUB% 1>&2 + set "FAIL=1" +) + +rmdir /s /q "%WORK%" 2>nul + if "%FAIL%"=="1" ( echo Public header gate FAILED. exit /b 1 diff --git a/tests/headers/check_public_headers.sh b/tests/headers/check_public_headers.sh index a5e7c0249..dff3796e1 100644 --- a/tests/headers/check_public_headers.sh +++ b/tests/headers/check_public_headers.sh @@ -21,6 +21,14 @@ shopt -s nullglob REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" PUB="$REPO_ROOT/lib/include/public" +# Fail fast if the public header directory is missing or miscomputed. With +# nullglob on, a bad PUB would make the header globs below expand to nothing and +# the gate would silently "pass" without compiling anything -- a false negative. +if [ ! -d "$PUB" ]; then + echo "error: public header directory not found: $PUB" >&2 + exit 2 +fi + # Implementation-fragment headers: intentionally included by another public # header (which supplies their dependencies first) and not meant to be included # standalone. They are exercised through their public entry point instead. @@ -64,6 +72,10 @@ run_compiler() { echo "$out" | grep -E 'error:|warning:' | head -4 | sed 's/^/ /' fi done + if [ $((n_ok + n_fail)) -eq 0 ]; then + echo " ERROR: no public headers found under $PUB" + fail=1 + fi echo " $label: $n_ok passed, $n_fail failed" } From 4fe54b658effae4d496a7d90caa75c4e2a658a34 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sun, 12 Jul 2026 12:03:42 -0500 Subject: [PATCH 6/6] Strengthen public header gate coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/headers/check_public_headers.cmd | 79 +++++++++++++++++------ tests/headers/check_public_headers.sh | 89 +++++++++++++++++++++----- 2 files changed, 132 insertions(+), 36 deletions(-) diff --git a/tests/headers/check_public_headers.cmd b/tests/headers/check_public_headers.cmd index b0157c018..53860422c 100644 --- a/tests/headers/check_public_headers.cmd +++ b/tests/headers/check_public_headers.cmd @@ -4,14 +4,15 @@ REM SPDX-License-Identifier: Apache-2.0 REM REM Public header gate (MSVC). Compiles each public SDK header on its own under REM /W4 /WX, mirroring how ONNX Runtime / Foundry Local compile their own C++ -REM translation units on Windows. STL headers are treated as external +REM translation units on Windows. STL/Windows SDK headers are treated as external REM (/external:W0) so only the SDK's headers are gated. Exits non-zero if any -REM header fails to compile or emits a warning. +REM header fails to compile or emits a warning. Also compiles mat.h as C11 (/TC). setlocal enabledelayedexpansion set "SCRIPT_DIR=%~dp0" set "REPO_ROOT=%SCRIPT_DIR%..\.." set "PUB=%REPO_ROOT%\lib\include\public" +set "C_API_HEADER=mat.h" REM Fail fast if the public header directory is missing or miscomputed, otherwise REM the header loop below would run zero times and the gate would silently "pass" @@ -39,9 +40,9 @@ if errorlevel 1 ( exit /b 2 ) -REM Unique work directory so concurrent invocations on the same machine (local dev -REM or a reused runner) do not clobber each other's temporary translation unit. -set "WORK=%TEMP%\pubhdrgate_%RANDOM%_%RANDOM%" +REM Unique work directory under the repository so concurrent invocations on the +REM same machine do not clobber each other's temporary translation units. +set "WORK=%REPO_ROOT%\.public-header-gate_%RANDOM%_%RANDOM%" if exist "%WORK%" rmdir /s /q "%WORK%" mkdir "%WORK%" if errorlevel 1 ( @@ -49,42 +50,80 @@ if errorlevel 1 ( exit /b 2 ) -REM /W4 /WX matches ORT; /external:W0 suppresses STL warnings so only our headers gate. -set "FLAGS=/nologo /std:c++17 /permissive- /W4 /WX /EHsc /experimental:external /external:anglebrackets /external:W0" +REM /W4 /WX matches ORT; /external:W0 suppresses platform/STL warnings so only our headers gate. +set "CXX_COMMON=/nologo /permissive- /W4 /WX /EHsc /experimental:external /external:anglebrackets /external:W0" +set "C_COMMON=/nologo /std:c11 /TC /W4 /WX /experimental:external /external:anglebrackets /external:W0" set "FAIL=0" -set "OKC=0" +set "TOTAL=0" + +REM MSVC does not expose a /std:c++11 switch; /std:c++14 is its lowest selectable mode. +call :RunCxxHeaders c++14 /std:c++14 "cl (c++14, /W4 /WX)" +call :RunCxxHeaders c++17 /std:c++17 "cl (c++17, /W4 /WX)" +call :RunCHeader + +rmdir /s /q "%WORK%" 2>nul -echo == cl (c++17, /W4 /WX) == +if "%FAIL%"=="1" ( + echo Public header gate FAILED. + exit /b 1 +) +echo Public header gate passed. ^(!TOTAL! checks^) +exit /b 0 + +:RunCxxHeaders +set "STD_NAME=%~1" +set "STD_FLAG=%~2" +set "LABEL=%~3" +set "OKC=0" +set "FAILC=0" +echo == %LABEL% == for %%h in ("%PUB%\*.hpp" "%PUB%\*.h") do ( set "NAME=%%~nxh" REM Skip implementation-fragment headers not meant to be included standalone REM (VariantType.hpp is included by Variant.hpp, which defines VariantMap/VariantArray first). if /I not "!NAME!"=="VariantType.hpp" ( - > "%WORK%\tu.cpp" echo #include "!NAME!" - >> "%WORK%\tu.cpp" echo int main^(^){return 0;} - cl %FLAGS% /I "%PUB%" /I "%REPO_ROOT%\lib\include" /Zs "%WORK%\tu.cpp" > "%WORK%\err.txt" 2>&1 + > "%WORK%\tu_!STD_NAME!.cpp" echo #include "!NAME!" + >> "%WORK%\tu_!STD_NAME!.cpp" echo int main^(^){return 0;} + cl %CXX_COMMON% %STD_FLAG% /I "%PUB%" /I "%REPO_ROOT%\lib\include" /Zs "%WORK%\tu_!STD_NAME!.cpp" > "%WORK%\err.txt" 2>&1 if errorlevel 1 ( echo FAIL: !NAME! type "%WORK%\err.txt" set "FAIL=1" + set /a FAILC+=1 ) else ( set /a OKC+=1 + set /a TOTAL+=1 ) ) ) - REM No headers compiled means PUB matched nothing -- treat it as a failure rather REM than a silent pass. -if "!OKC!"=="0" if "%FAIL%"=="0" ( +if "!OKC!"=="0" if "!FAILC!"=="0" ( echo error: no public headers found under %PUB% 1>&2 set "FAIL=1" ) +echo %LABEL%: !OKC! passed, !FAILC! failed +exit /b 0 -rmdir /s /q "%WORK%" 2>nul - -if "%FAIL%"=="1" ( - echo Public header gate FAILED. - exit /b 1 +:RunCHeader +set "LABEL=cl (C11 mat.h, /TC, /W4 /WX)" +echo == !LABEL! == +if not exist "%PUB%\%C_API_HEADER%" ( + echo error: C API header not found: %PUB%\%C_API_HEADER% 1>&2 + set "FAIL=1" + echo !LABEL!: 0 passed, 1 failed + exit /b 0 +) +> "%WORK%\tu_mat_c11.c" echo #include "%C_API_HEADER%" +>> "%WORK%\tu_mat_c11.c" echo int main^(void^){return 0;} +cl %C_COMMON% /I "%PUB%" /I "%REPO_ROOT%\lib\include" /Zs "%WORK%\tu_mat_c11.c" > "%WORK%\err.txt" 2>&1 +if errorlevel 1 ( + echo FAIL: %C_API_HEADER% + type "%WORK%\err.txt" + set "FAIL=1" + echo !LABEL!: 0 passed, 1 failed +) else ( + set /a TOTAL+=1 + echo !LABEL!: 1 passed, 0 failed ) -echo Public header gate passed. ^(!OKC! headers^) exit /b 0 diff --git a/tests/headers/check_public_headers.sh b/tests/headers/check_public_headers.sh index dff3796e1..79724e588 100644 --- a/tests/headers/check_public_headers.sh +++ b/tests/headers/check_public_headers.sh @@ -10,8 +10,9 @@ # warning flags. Downstream consumers such as ONNX Runtime / Foundry Local # compile their own translation units -- which include these headers -- with # -Wall -Wextra -Werror (plus -Wshorten-64-to-32 on Clang). This gate compiles -# each public header on its own, with no -isystem suppression, so any header -# issue surfaces here instead of at integration time. +# each public header on its own as both C++11 and C++17, with no -isystem +# suppression, so any header issue surfaces here instead of at integration time. +# It also compiles the ABI-stable C API header (mat.h) as C11. # # Exits non-zero if any header fails to compile or emits a warning. @@ -36,14 +37,13 @@ EXCLUDES=( "VariantType.hpp" # included by Variant.hpp, which defines VariantMap/VariantArray first ) -INCLUDES="-I$PUB -I$REPO_ROOT/lib/include" # Mirror the strict warning flags third-party consumers build with # (-Wall -Wextra -Werror, matching this repo's pipeline and ORT). Unused # parameters are deliberately NOT suppressed: intentional ones use the # UNREFERENCED_PARAMETER macro, which expands to (void)(...) on GCC/Clang, so the # headers stay warning-clean without a blanket -Wno-unused-parameter that would # hide a real consumer break (e.g. an override that leaves a parameter unused). -BASE_FLAGS="-std=c++17 -Wall -Wextra -Werror" +C_API_HEADER="mat.h" is_excluded() { local n="$1" e @@ -52,19 +52,32 @@ is_excluded() { } fail=0 -tmp="$(mktemp -d)" +tmp="" +for _ in 1 2 3 4 5; do + candidate="$REPO_ROOT/.public-header-gate.$$.$RANDOM" + if mkdir "$candidate" 2>/dev/null; then + tmp="$candidate" + break + fi +done +if [ -z "$tmp" ]; then + echo "error: failed to create work directory under $REPO_ROOT" >&2 + exit 2 +fi trap 'rm -rf "$tmp"' EXIT -run_compiler() { - local cc="$1" extra="$2" label="$3" - local n_ok=0 n_fail=0 h name tu out +run_cxx_compiler() { + local cc="$1" std="$2" label="$3" + shift 3 + local n_ok=0 n_fail=0 h name base tu out echo "== $label ==" for h in "$PUB"/*.hpp "$PUB"/*.h; do name="$(basename "$h")" is_excluded "$name" && continue - tu="$tmp/tu_${name%.hpp}.cpp" + base="${name%.*}" + tu="$tmp/tu_${base}_${std}.cpp" printf '#include "%s"\nint main() { return 0; }\n' "$name" > "$tu" - if out="$("$cc" $BASE_FLAGS $extra $INCLUDES -fsyntax-only "$tu" 2>&1)"; then + if out="$("$cc" "-std=$std" -Wall -Wextra -Werror "$@" -I"$PUB" -I"$REPO_ROOT/lib/include" -fsyntax-only "$tu" 2>&1)"; then n_ok=$((n_ok + 1)) else n_fail=$((n_fail + 1)); fail=1 @@ -79,21 +92,65 @@ run_compiler() { echo " $label: $n_ok passed, $n_fail failed" } -ran=0 +run_c_compiler() { + local cc="$1" label="$2" + shift 2 + local tu out + echo "== $label ==" + if [ ! -f "$PUB/$C_API_HEADER" ]; then + echo " ERROR: C API header not found: $PUB/$C_API_HEADER" + fail=1 + echo " $label: 0 passed, 1 failed" + return + fi + tu="$tmp/tu_${C_API_HEADER%.h}_c11.c" + printf '#include "%s"\nint main(void) { return 0; }\n' "$C_API_HEADER" > "$tu" + if out="$("$cc" -std=c11 -Wall -Wextra -Werror "$@" -I"$PUB" -I"$REPO_ROOT/lib/include" -fsyntax-only "$tu" 2>&1)"; then + echo " $label: 1 passed, 0 failed" + else + fail=1 + echo " FAIL: $C_API_HEADER" + echo "$out" | grep -E 'error:|warning:' | head -4 | sed 's/^/ /' + echo " $label: 0 passed, 1 failed" + fi +} + +cxx_ran=0 if command -v g++ >/dev/null 2>&1; then - run_compiler g++ "" "g++ (c++17, -Wall -Wextra -Werror)" - ran=1 + run_cxx_compiler g++ c++11 "g++ (c++11, -Wall -Wextra -Werror)" + run_cxx_compiler g++ c++17 "g++ (c++17, -Wall -Wextra -Werror)" + cxx_ran=1 fi if command -v clang++ >/dev/null 2>&1; then - run_compiler clang++ "-Wshorten-64-to-32" "clang++ (c++17, + -Wshorten-64-to-32)" - ran=1 + run_cxx_compiler clang++ c++11 "clang++ (c++11, + -Wshorten-64-to-32)" -Wshorten-64-to-32 + run_cxx_compiler clang++ c++17 "clang++ (c++17, + -Wshorten-64-to-32)" -Wshorten-64-to-32 + cxx_ran=1 fi -if [ "$ran" -eq 0 ]; then +if [ "$cxx_ran" -eq 0 ]; then echo "error: neither g++ nor clang++ was found" >&2 exit 2 fi +c_ran=0 +if command -v gcc >/dev/null 2>&1; then + run_c_compiler gcc "gcc (c11 mat.h, -Wall -Wextra -Werror)" + c_ran=1 +fi +if command -v clang >/dev/null 2>&1; then + run_c_compiler clang "clang (c11 mat.h, + -Wshorten-64-to-32)" -Wshorten-64-to-32 + c_ran=1 +fi +if [ "$c_ran" -eq 0 ] && command -v cc >/dev/null 2>&1; then + run_c_compiler cc "cc (c11 mat.h, -Wall -Wextra -Werror)" + c_ran=1 +fi + +if [ "$c_ran" -eq 0 ]; then + echo "error: no C compiler (gcc, clang, or cc) was found" >&2 + exit 2 +fi + if [ "$fail" -ne 0 ]; then echo "Public header gate FAILED." exit 1