Skip to content

fix(build): make the test tree build on macOS (three portability defects) - #309

Merged
localai-bot merged 3 commits into
mudler:mainfrom
filipsajdak:fix/macos-build
Aug 11, 2026
Merged

fix(build): make the test tree build on macOS (three portability defects)#309
localai-bot merged 3 commits into
mudler:mainfrom
filipsajdak:fix/macos-build

Conversation

@filipsajdak

@filipsajdak filipsajdak commented Aug 10, 2026

Copy link
Copy Markdown

Row

— no roadmap row. A portability bug fix, tracked by issue #308 (kind bug,
Row , the same shape as #250 / #242 / #296 in the roadmap issue table).

Refs #308.

Before starting

  • Issue/PR search and existing claim: before opening The test tree does not build on macOS: three portability defects, six failing targets #308, searched
    open+closed issues for macOS build, getpid, unistd, htonl,
    unused function, A76, and all 18 open PRs. Nothing covered any of the three
    defects and nothing was claimed. The nearest neighbour is [Bug] macOS MLX build fails because warnings in MLX headers are treated as errors (-Werror) #199 (macOS MLX
    -Werror on third-party MLX headers), which is CLOSED and is a different
    failure: it is the opt-in VLLM_CPP_MLX preview build hitting warnings in
    /usr/local/include/mlx, whereas these six targets fail in the default
    cmake -S . -B build configuration in first-party sources.
  • Roadmap or matrix row: none. scripts/ready-for-helper.py not applicable —
    this is not a row claim.
  • Anchors inspected: all nine ::getpid() call sites and their include
    lists; tests/vllm/entrypoints/openai/test_api_server.cpp:404/412/1620/1640
    (the same socket dance, already unqualified, already green in your CI); the
    #if defined(__linux__) blocks already in examples/cpu_kernel_bench/main.cpp
    (SetAffinity, ThrottlingStatus, the PerfGroup stub, the a76 half of
    MakeCounterGroups); scripts/build-macos-release.sh and the macos-15 lanes
    in release.yml; every runs-on in ci.yml.

What changed

Three independent macOS portability defects that together prevent the test tree
from building at all on Apple clang. <unistd.h> is now included in the five
translation units that call ::getpid() without it — three of which fail to
compile today, two of which compile only via a transitive include and would fail
the moment the header carrying it is tidied. The four byte-order calls in the two
lmcache mock servers are now unqualified, because POSIX permits htonl/ntohs
to be macros and the Darwin SDK defines them as parenthesised expressions that
:: cannot precede. And A76Event (with ParsePerfEvent, its only caller) now
sits inside the #if defined(__linux__) guard the file already uses, so it is no
longer dead code tripping -Werror,-Wunused-function off-Linux. Nothing is
deleted, no #ifdef __APPLE__ is introduced, and the Linux build is unchanged in
behaviour.

Evidence

Three commits, one per defect class, each independently buildable.

Red, before the fixcmake -S . -B build && cmake --build build -j -- -k
on macOS 26.5.2 / AppleClang 21.0.0, six targets failing, EXIT=2:

tests/vllm/v1/test_kv_offload_fs.cpp:34:62: error: no member named 'getpid' in the global namespace
tests/vllm/v1/test_kv_offload_tiering.cpp:43:63: error: no member named 'getpid' in the global namespace
tests/vllm/v1/test_kv_offload_connector.cpp:122:63: error: no member named 'getpid' in the global namespace
tests/vllm/v1/kv_offload/lmcache/test_lmcache_client.cpp:55:30: error: expected unqualified-id
tests/vllm/v1/kv_offload/lmcache/test_lmcache_connector.cpp:82:30: error: expected unqualified-id
examples/cpu_kernel_bench/main.cpp:447:26: error: unused function 'A76Event' [-Werror,-Wunused-function]

(-- -k on purpose: without it the build stops at the first of the six and the
other two classes stay hidden.)

Green, after the fix, macOS. The whole default configuration, not just the
six targets — which is also the evidence that there is no seventh failure
behind them:

cmake --build build -j -- -k   ->  384 targets built, BUILD_EXIT=0
                                   0 "error:", 0 "warning:" in the log

The affected test binaries also run (the two latent ones included):

1/7 Test  #10: test_gguf ........................   Passed
2/7 Test  #96: test_kv_offload_fs ...............   Passed
3/7 Test  #97: test_kv_offload_tiering ..........   Passed
4/7 Test  #98: test_kv_offload_connector ........   Passed
5/7 Test #122: test_lmcache_client ..............   Passed
6/7 Test #123: test_lmcache_connector ...........   Passed
7/7 Test #238: test_capi ........................   Passed
100% tests passed, 0 tests failed out of 7      CTEST_EXIT=0

Green on Linux — gcc 14.4.0 / glibc 2.41 / aarch64, configured the way
build-test-cpu does (-DVLLM_CPP_BUILD_TESTS=ON): the same targets build with
0 errors and 0 warnings and the same 7 tests pass. This is the lane that matters
for the A76Event change, because the A76 path is live there and the guard must
not remove it.

Why the include sweep covers five files and not three. The three that fail
are the ones the build can see; tests/capi/test_capi.cpp and
tests/vllm/test_gguf.cpp have the identical defect and are carried by a
transitive include today. Repairing them is this tree's own convention rather
than an imported one — measured across the 369 test .cpp files, only 4% of
(TU, standard header) pairs rely on a transitive include (<cstring> and
<thread> 0%, <vector> 1%, <string> 4%). Happy to drop those two hunks if
you would rather keep the diff to what the compiler currently rejects.

Gates — each run separately, exit status captured on its own (never through a
pipe), arguments as CI passes them (--base = the current main tip, not the
branch point):

python3 scripts/check-commit-trailers.py --range "origin/main..HEAD"   -> OK: commit trailer contract                              (rc 0)
python3 scripts/check-doc-checkpoint.py  --base <main> --head <head>   -> OK: public documents match the claims this change makes  (rc 0)
python3 scripts/check-pr-size.py         --base <main> --head <head>   -> OK: every explicit path class is within its review budget (rc 0)
python3 scripts/check-now-current.py     --base <main> --head <head>   -> OK: .agents/NOW.md is a current, in-budget resume digest  (rc 0)
python3 scripts/check-role-discipline.py --base <main> --head <head> --pending-pr-head <head> -> OK (rc 0)

Both gates that could plausibly rubber-stamp this diff were checked against
known-bad input first, so their OK means something: check-commit-trailers.py
rejects the same message with the FOLLOWING_AGENTS_PROTOCOL paragraph removed,
and rejects it again with a Co-authored-by: naming an AI; check-pr-size.py
fails closed on an unclassified path (unclassified repository path 'unclassified-probe.txt', rc 1) on a scratch commit.

  • tests that cover this change: test_kv_offload_fs,
    test_kv_offload_tiering, test_kv_offload_connector,
    test_lmcache_client, test_lmcache_connector, test_capi, test_gguf
    (build + run) and vllm-cpu-kernel-bench (build). The compile itself is
    the regression surface here — a build failure has no assertion to write,
    and the red output above is the failing-first evidence.
  • same-change doc obligations: none. check-doc-checkpoint.py agrees — no
    lifecycle move, no measurement, and no USER_USAGE path is touched.

Speed claims

  • This PR makes NO speed claim.

Honest gaps

  • scripts/agent-preflight.sh was not run to completion. Its role gate
    requires a declared role, and agent-role.py claim helper requires a
    --row <ROW-ID> that does not exist for this bug. Inventing an ID would have
    written a false entry into records whose value is that they are true, which
    seemed worse than saying this plainly. The record gates it wraps that are
    relevant to this diff were run individually and are quoted above. Happy to redo
    it under a row if you want one opened.
  • src/vllm/entrypoints/openai/server_main.cpp is untouched on purpose. It
    calls getpid() unqualified and includes <unistd.h> itself, so it is already
    correct; it is also under USER_USAGE_PREFIXES in check-doc-checkpoint.py,
    so editing it would owe a docs/USAGE.md update for a change that fixes
    nothing.
  • Not verified: x86-64 Linux (the container was aarch64), and any macOS
    configuration other than the default — in particular the VLLM_CPP_MLX preview
    build of [Bug] macOS MLX build fails because warnings in MLX headers are treated as errors (-Werror) #199 was not exercised. The three fixes are architecture-independent,
    but that is reasoning, not a measurement.
  • Three commits, not one. One per defect class, so a reviewer can disagree
    with the htonl change without re-reading the include sweep. Say the word and
    I will squash them.
  • No macOS CI lane is added here. Nothing stops these three classes from
    recurring: every build lane in ci.yml is ubuntu-*, and the macos-15
    release lane builds only --target server test_metal_backend, so the macOS
    test tree is built by no job at all. A macos-15 build-tests job would close
    that, but it is a CI policy call with runner-cost implications rather than part
    of the repair, so it is written up in the issue instead of smuggled in here.

Filip Sajdak added 3 commits August 11, 2026 00:13
Five test translation units call ::getpid() without including <unistd.h>.
glibc supplies the declaration transitively through a C++ standard
header; libc++ does not, so on macOS the name is absent and three of the
five fail to compile:

  tests/vllm/v1/test_kv_offload_fs.cpp:34:62: error: no member named
  'getpid' in the global namespace

The other two, tests/capi/test_capi.cpp and tests/vllm/test_gguf.cpp,
build today only because a project header happens to pull <unistd.h> in
for them (test_gguf.cpp via gguf_builder.h). They are the same defect
one include-cleanup away from presenting, so they are repaired here
rather than left to fail next.

Include the header in each translation unit that uses it, as its own
POSIX block, matching tests/vllm/models/test_kimi_linear_paged.cpp.
The tree is otherwise include-what-you-use disciplined -- across the 369
test .cpp files, only 4% of (TU, standard header) pairs rely on a
transitive include -- so these five were the exceptions, not the rule.

The remaining ::getpid() call sites already include <unistd.h> directly
and are untouched.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [ClaudeCode]
POSIX allows the byte-order conversions to be macros, and the Darwin SDK
takes that option: <sys/_endian.h> defines htonl(x) as a parenthesised
conditional expression. A ::-qualified call therefore expands to `::`
followed by `(`, which is not a qualified-id, and both lmcache targets
fail to compile on macOS:

  test_lmcache_client.cpp:55:30: error: expected unqualified-id
      addr.sin_addr.s_addr = ::htonl(INADDR_LOOPBACK);
  note: expanded from macro 'htonl'

Preprocessing runs before the qualification is considered, so no include
can rescue the ::-spelling; the qualification itself is the defect. Drop
it, which is what tests/vllm/entrypoints/openai/test_api_server.cpp
already does with the same bind/getsockname sequence. Measured on gcc 14
/ glibc 2.41: both spellings compile at -O0 and -O2, so this is a no-op
for the Linux build rather than a second fix.

Leaves every other ::-qualified libc call (::socket, ::bind, ::listen,
::getsockname) alone: those are functions on both platforms, and the
qualification is the file's deliberate style.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [ClaudeCode]
A76Event reads /sys/bus/event_source/devices/armv8_cortex_a76/ and its
only call site is inside the #if defined(__linux__) half of
MakeCounterGroups. The function itself was never guarded, so off-Linux
it is compiled and unreferenced, which -Werror turns into a hard failure:

  examples/cpu_kernel_bench/main.cpp:447:26: error: unused function
  'A76Event' [-Werror,-Wunused-function]

Put it under the same #if defined(__linux__) the file already uses for
its PerfGroup stub, SetAffinity and ThrottlingStatus. ParsePerfEvent
moves under the guard with it: its only caller is A76Event, so guarding
one alone reproduces the identical error one function up.

Nothing is deleted and the Linux path is byte-for-byte unchanged.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [ClaudeCode]
@localai-org-maint-bot

Copy link
Copy Markdown
Collaborator

@mudler @richiejp Good to merge from my review. The three portability fixes are narrowly scoped, the macOS failure modes are correctly addressed, and the contributor supplied full macOS plus affected Linux/aarch64 build/test evidence.

@richiejp

Copy link
Copy Markdown
Collaborator

Yup, looks good thanks @filipsajdak ! I don't have permission to rubber stamp the workflows though @mudler

@localai-bot

Copy link
Copy Markdown
Collaborator

Landed on main as-is, one merge commit, your three commits intact.

Nothing to argue with here — each of the three is a genuine portability defect with a mechanism, not a warning silenced:

  • ParsePerfEvent/A76Event read /sys/bus/event_source and are only ever reached from the __linux__ half of MakeCounterGroups, so off Linux they are dead code and -Werror -Wunused-function makes that fatal on clang. Guarding them with the same condition as their only caller is the fix, not a pragma.
  • htonl/ntohs being functions in glibc and function-like macros in the Darwin SDK is the kind of thing you only learn by hitting it; unqualified calls are correct on both.
  • ::getpid without <unistd.h> works on glibc purely by transitive inclusion, and libc++ does not oblige.

Linux behavior is unchanged by construction: the added includes are already-satisfied declarations, the :: removals resolve to the same glibc functions, and the new #if wraps functions unreachable off __linux__. Verified in the CPU build — 688 targets, zero warnings under -Werror.

Reviewed and landed with Claude Code.

@localai-bot
localai-bot merged commit 598c3a6 into mudler:main Aug 11, 2026
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.

4 participants