Skip to content

cmake: mark the Homebrew include path SYSTEM so it cannot shadow vendored headers - #5399

Merged
jensenpat merged 2 commits into
aethersdr:mainfrom
on8st:fix/ggml-include-order
Sep 6, 2026
Merged

cmake: mark the Homebrew include path SYSTEM so it cannot shadow vendored headers#5399
jensenpat merged 2 commits into
aethersdr:mainfrom
on8st:fix/ggml-include-order

Conversation

@on8st

@on8st on8st commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

On macOS with Homebrew's ggml installed, a default build fails:

third_party/whisper.cpp/ggml/src/ggml.c:684:6:
  error: use of undeclared identifier 'GGML_TYPE_Q2_0'
third_party/whisper.cpp/ggml/src/ggml.c:1428:14:
  error: use of undeclared identifier 'GGML_FTYPE_MOSTLY_Q2_0'
third_party/whisper.cpp/ggml/src/ggml-quants.c:2116:37:
  error: use of undeclared identifier 'GGML_TYPE_Q2_0'
... 7 errors across the two files.

ggml.c includes "ggml.h" with quotes. That searches the including file's own
directory first — ggml/src/, which does not contain it — and then the -I
list in order. CMakeLists.txt:84 puts /opt/homebrew/include at the head of
that list for every target in the project, so the compiler picks up Homebrew's
ggml 0.15.2 header instead of the vendored one that the rest of the vendored
sources were written against. GGML_TYPE_Q2_0 is declared in the vendored
header (ggml/include/ggml.h:432, = 42) and does not exist in 0.15.2.

The build has deliberately chosen the vendored copy — USE_SYSTEM_LIBWHISPER
defaults OFF — and is then compiling it against the system's headers. That is
the actual defect: the choice is made in CMake and silently reversed by the
include path.

Confirmed by the compiler, not by reading

-H prints the resolved include tree. Same source, same target, one flag
changed:

$ cc -fsyntax-only -H -I/opt/homebrew/include -I$SRC -I$SRC/../include ggml.c
.. /opt/homebrew/include/ggml.h                                    <-- wrong

$ cc -fsyntax-only -H -isystem /opt/homebrew/include -I$SRC -I$SRC/../include ggml.c
.. .../third_party/whisper.cpp/ggml/src/../include/ggml.h          <-- vendored

and the error count goes from 7 to 0 across the two files.

The fix

-        include_directories("${HOMEBREW_PREFIX}/include")
+        # SYSTEM is load-bearing, not cosmetic. A plain include_directories()
+        # here is directory-scoped at the top level, so it is inherited by every
+        # add_subdirectory() and initialises every target's INCLUDE_DIRECTORIES
+        # ahead of whatever that target adds for itself — which means
+        # /opt/homebrew/include outranks every vendored include dir in the tree.
+        # Any Homebrew formula whose headers share a name with a vendored one
+        # then silently wins, reversing the choice USE_SYSTEM_* made in CMake.
+        # SYSTEM emits -isystem, which the compiler searches AFTER all -I paths,
+        # so Homebrew stays findable and stops taking precedence.
+        include_directories(SYSTEM "${HOMEBREW_PREFIX}/include")

One keyword. -isystem directories are searched after every -I directory, so
Homebrew remains available for everything that genuinely needs it (portaudio,
fftw3, hidapi, and the USE_SYSTEM_*=ON paths) while no longer outranking the
tree's own headers. Suppressing warnings from third-party headers is a
side-benefit, not the reason.

This is a class, not an instance — with the count stated honestly

ggml.h is the one that fails loudly. It is not the only header this affects,
but the numbers need separating, because "21 headers fixed" would be a claim
this evidence does not support.

21 vendored public headers share a basename with one in
/opt/homebrew/include on this machine. That is a basename collision count,
obtained by intersecting two directory listings — it is the size of the hazard,
not the size of the demonstrated fix.

7 are demonstrated shadowed and fixed, from -H traces on the real compile
lines of ggml.c, ggml-quants.c, ggml.cpp and whisper.cpp:

header before after
ggml.h /opt/homebrew/include vendored
ggml-alloc.h /opt/homebrew/include vendored
ggml-backend.h /opt/homebrew/include vendored
ggml-cpp.h /opt/homebrew/include vendored
ggml-cpu.h /opt/homebrew/include vendored
gguf.h /opt/homebrew/include vendored
whisper.h /opt/homebrew/include vendored

whisper.h is the one worth pausing on: whisper.cpp compiled cleanly against
Homebrew's header, so nothing announced it. It is the silent case, and it is the
reason this is worth fixing rather than working around.

The remaining 14 are not claimed. ggml-cuda.h, -metal.h, -vulkan.h,
-blas.h, -cann.h, -sycl.h, -rpc.h, -openvino.h, -webgpu.h,
-zendnn.h, -virtgpu.h, -opt.h and parakeet.h belong to backends this
configuration does not build, so they never entered an include tree here and
their shadowing is latent, not demonstrated.

fftw3.h is a different case entirely and is NOT a bug. On macOS FFTW comes
from pkg-config (CMakeLists.txt:239-243); the vendored
third_party/fftw3/include/fftw3.h is inside if(WIN32) and is never added to
the include path on this platform. So it resolving to Homebrew is by design,
and it was never shadowed here. It appeared in the basename intersection and
does not belong in the fixed list.

That distinction matters more than the count: a table with one wrong row invites
a reader to distrust the others, and this one had a row that looked like a bug
and was not.

Testing

Run on macOS 26.5, Apple clang, Qt 6.8.3, Homebrew ggml 0.15.2 installed and
left installed — nothing unlinked, nothing removed — with ENABLE_ASR at its
default. Before and after in the same tree, so the comparison has no second
variable.

  • Before: building ggml-base fails with 7 errors — 4 in ggml.c
    (GGML_TYPE_Q2_0, GGML_FTYPE_MOSTLY_Q2_0) and 3 in ggml-quants.c.
  • After: full build exit 0, 3102 objects, zero errors.
    [16/3102] Building C object …/ggml.c.o and [35/3102] …/ggml-quants.c.o
    the two files that previously read FAILED.
  • Per-header -H evidence above, taken from the build's own
    compile_commands.json rather than reconstructed flags, so the claim is not
    "it builds now" but "it builds now because this header is being chosen".

Scope

macOS only — the block is inside if(APPLE). No behaviour change on Linux or
Windows, and none on macOS beyond include precedence and third-party warning
suppression.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FtHEsQsghFwhUQEZdwVKUz

…ored headers

On macOS with Homebrew's ggml installed, a default build fails:

  third_party/whisper.cpp/ggml/src/ggml.c:684:6:
    error: use of undeclared identifier 'GGML_TYPE_Q2_0'

plus three more in ggml-quants.c. ggml.c includes "ggml.h" with quotes,
which searches the including file's own directory and then the -I list.
CMakeLists.txt:84 put /opt/homebrew/include at the head of that list for
every target in the project, so the compiler picked up Homebrew's ggml
0.15.2 header instead of the vendored one the rest of the vendored
sources were written against. GGML_TYPE_Q2_0 is declared in the vendored
header (ggml/include/ggml.h:432) and does not exist in 0.15.2.

The defect in one sentence: the build deliberately chooses the vendored
copy — USE_SYSTEM_LIBWHISPER defaults OFF — and the include path
silently reverses that choice.

A directory-scoped include_directories() at the top level is inherited
by every add_subdirectory() and INITIALISES each target's
INCLUDE_DIRECTORIES, so it lands ahead of anything a target adds for
itself. SYSTEM emits -isystem, which the compiler searches after all -I
paths: Homebrew stays findable for everything that needs it, and stops
outranking the tree's own headers.

Confirmed with the compiler rather than by reading. cc -H on ggml.c
resolves /opt/homebrew/include/ggml.h before this change and
third_party/whisper.cpp/ggml/src/../include/ggml.h after it, and the
error count goes 4 -> 0 on that file.

This is a class, not an instance. 21 vendored public headers share a
basename with one in /opt/homebrew/include on a stock dev machine —
ggml's family, gguf.h, parakeet.h, and notably whisper.h and fftw3.h.
Only ggml.h fails loudly; the others compile because the versions happen
to agree closely enough, which is the dangerous case: a skew in any of
them mismatches silently and shows up as wrong numbers rather than an
error. The set also changes with whatever the developer brew-installs
next, so a build that works today breaks on a machine differing only by
an unrelated package.

Scope: macOS only, inside the existing if(APPLE). No behaviour change on
Linux or Windows, and none on macOS beyond include precedence and
third-party warning suppression.

NOT YET COMPLETE: the full ENABLE_ASR-default build that proves this is
paused partway (see hl2-lab pr/ggml-include-order.md). Committed now so
the paused build's premise is recorded rather than living in an
uncommitted working tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SvGM4eqSng7aX62aVCFyh
@on8st
on8st requested a review from a team as a code owner September 3, 2026 20:17
@Ozy311 Ozy311 self-assigned this Sep 4, 2026

@Ozy311 Ozy311 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue fit

There is no linked issue. Against the PR's own stated intent, yes: the top-level plain Homebrew include directory is inherited ahead of target-local vendored directories, while marking it SYSTEM moves it behind ordinary -I directories without making Homebrew-only headers unavailable. This is a bug fix with a clear root cause, not an architectural change or dependency addition, so the absence of an RFC is consistent with GOVERNANCE.md. I could not reproduce the exact ggml 0.15.2 collision because this Mac does not currently have Homebrew ggml/whisper headers installed, but I reproduced the load-bearing CMake/AppleClang ordering behavior directly.

Scope

File What it changes Claimed by title/body? Verdict
CMakeLists.txt Marks the existing Apple/Homebrew include path SYSTEM and documents why Yes In scope; one comment nit below

The semantic change is one keyword inside the existing if(APPLE) block. No Linux/Windows behavior, dependency selection, find_package, CI image, vendored source, test, UI, settings, protocol, or CHANGELOG.md surface changes.

Blockers

None.

Nits

  • Non-blocking: the added source comment says fftw3.h is shadowed on a stock Mac, while the current PR body correctly demonstrates the opposite: the vendored FFTW include path is Windows-only and macOS intentionally resolves FFTW from its installed package. Inline at CMakeLists.txt:91; make the permanent comment agree with the corrected analysis.
  • Non-blocking: the PR body still contains an “orchestrator, not for the PR body” section saying the full build is not yet done, while its Testing section now claims the 3102-object full build passed. Removing or updating that stale internal note would leave one unambiguous evidence record. The commit message's historical state can remain historical.

What I tried to break

  • Built a minimal CMake 3.25-style parent/child project on this arm64 Mac with AppleClang 21. Without SYSTEM, CMake emitted the inherited directory first (-I.../system -I.../vendor/include) and the compile failed on the wrong collision header. With the PR's keyword, it emitted -I.../vendor/include -isystem .../system; the vendored header won and a second header available only in the system directory still compiled. This verifies both load-bearing claims independently of the PR's prose.
  • Read the actual vendored graph: USE_SYSTEM_LIBWHISPER defaults OFF; the vendored tree is added with add_subdirectory; ggml-base adds ggml/include after the inherited root property; ggml.c and whisper.cpp use quoted basename includes. The affected reachability matches the reproduced CMake shape.
  • Checked the platform boundary: the only hunk is under if(APPLE), and include_directories(SYSTEM ...) changes compile classification/order, not CMake's package-discovery paths. Homebrew-only headers remain searchable in the empirical probe.
  • Compared merge base 10a847b7 with current main 0ad8ca80. One intervening sanitizer commit touches CMakeLists.txt in other regions; the Homebrew block is unchanged, GitHub reports MERGEABLE, and git diff --check is clean.
  • Current machine evidence: arm64, Homebrew prefix /opt/homebrew; no installed ggml, whisper-cpp, ggml*.h, whisper.h, or fftw3.h was found under that prefix. Per the review boundary I installed nothing, so the author's exact 7-error before/full-build-after corpus remains unverified here. The commit signature is valid and this head has no CI runs. No GUI, hardware, sockets, or app build was used.

Recommendation

Approve with nits. The one-line fix survives the include-order, inheritance, system-header-findability, platform-scope, and current-main overlap attacks. Correct the fftw3.h sentence when convenient and clean the stale PR-body note; neither changes the correctness of the build fix. Normal human infrastructure/CODEOWNERS review is still required.

Comment thread CMakeLists.txt Outdated
@jensenpat

Copy link
Copy Markdown
Collaborator

Taking over completion at Pat’s explicit request, following the completed review above. I will correct the remaining FFTW comment, verify the include-order behavior, and complete the normal CI and merge gates.

@jensenpat jensenpat assigned jensenpat and unassigned Ozy311 Sep 6, 2026

@jensenpat jensenpat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 00bc3d7a560abfde1646ed67ecde673bab84ec96 following Pat’s requested takeover.

The change fits the stated build defect (no linked issue). No source blockers found. Merge recommendation remains pending fresh required CI.

File Scope Verdict
CMakeLists.txt Classify the existing Apple/Homebrew include path as SYSTEM; explain precedence In scope; existing FFTW comment nit corrected in 00bc3d7

The entire diff is confined to the existing Apple block. Vendored whisper/ggml selection still defaults to the vendored implementation; no package discovery, dependency, radio, GUI, persistence, test registration, or changelog changes. The previous FFTW thread is addressed and resolved. The stale PR-body testing note is already absent.

Verification: fresh current-main merge-tree succeeds; diff whitespace check passes; GitHub verifies both commit signatures. A scratch CMake parent/child project on AppleClang 21 proves both claims: the vendored collision header wins and a system-only header remains available. It compiled and passed 1/1 tests; removing SYSTEM fails the intended compile-time header assertion; restoring SYSTEM compiled and passed 1/1 again. The generated command places the vendor -I before system -isystem. This is an include-order probe, not an AetherSDR application build or reproduction with the author’s installed ggml 0.15.2. No app, socket peer, or hardware execution was needed.

Fresh CI runs 34044533165 (Static Checks) and 34044533292 (CI) are action_required pending fork-workflow approval. They have not executed; earlier-head results do not establish current-head CI. Automatic approval review rejected that workflow approval and requires explicit operator authorization. No merge or clean CI verdict is claimed.

Cost: approximately 8 minutes; three small probe configure/build calls (pass, deliberate failure, restored pass), one selected test passed on each passing configuration; zero AetherSDR builds, app launches, or subagents. Prior source review was used as a lead, with fresh diff and compiler verification; tokens unavailable.

@jensenpat jensenpat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final approval following the completed source review and comment correction. All five current-head checks passed: Linux, macOS, Windows, Static checks, and sanitizer configuration. CI tested synthetic merge 6248d86 (head 00bc3d7 into 8b101e5); its macOS selections passed 2/2 and Windows selections passed 5/5. Fresh latest-main merge-tree and whitespace checks pass; all review threads are resolved. The local include-order probe previously passed 1/1, failed with SYSTEM removed, and passed after restoration. No source blockers remain.

@jensenpat
jensenpat merged commit 6eacdae into aethersdr:main Sep 6, 2026
5 checks passed
@on8st

on8st commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

@jensenpat — thank you for finishing this one. The correction in 00bc3d7a is
right, and it was mine to make.

Why it was wrong, rather than just that it was: the fftw3.h clause went into
the source comment on 2026-09-03, when I still believed it. The PR body was
corrected before review and says the opposite outright — the vendored FFTW
include is set only inside the if(WIN32) branch of the FFTW block, macOS
resolves FFTW through pkg_check_modules / find_path, and so Homebrew
winning there is by design and was never shadowing. The correction reached the
description and never reached the file. The PR therefore shipped a body and a
permanent source comment that contradicted each other, and @Ozy311 caught
exactly that on 09-04.

That is the second time this week on my PRs that a correction landed in the
commit message and the description but not in the source. The same thing
happened to a provenance claim in a test on #5402. The comment is the copy that
outlives the pull request, so it is the copy that has to be right, and I will
treat "did the retraction reach the file?" as its own check rather than
assuming the body carries it.

I have read the merged CMakeLists.txt: the block now names ggml.h and
whisper.h, which are two of the seven headers the -H traces in the
description actually demonstrate — so it under-states the set rather than
over-stating it, which is the right direction. Nothing outstanding from me
here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants