Skip to content

Upgrade Zig toolchain to 0.15.2 - #259

Merged
linzhp merged 5 commits into
uber:mainfrom
DhashS:0.15.2
Jul 27, 2026
Merged

Upgrade Zig toolchain to 0.15.2#259
linzhp merged 5 commits into
uber:mainfrom
DhashS:0.15.2

Conversation

@DhashS

@DhashS DhashS commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Bumps the pinned Zig SDK from 0.14.0 → 0.15.2 and adapts to the layout changes in the 0.15 release. Three .bzl files change (~32 lines); zig-wrapper.zig needs no source changes for 0.15.2.

Changes

  • toolchain/private/zig_sdk.bzlVERSION + all six host SHA256s. Zig ≥0.14.1 renamed its release tarballs to zig-{arch}-{os}-{version} (arch first), so the download URL and archive strip-prefix now use a {zig_platform} (arch-os) format var. The SHA256/ext dicts stay keyed by {os}-{arch} to preserve the public host_platform_sha256 / host_platform_ext override API.
  • toolchain/defs.bzl — build the arch-os slug for the URL/stripPrefix; stage the renamed lib/tsanlib/libtsan tree, plus the new lib/c/** tree that lib/c.zig (the libzigc shim built while linking) imports.
  • toolchain/private/defs.bzl — Zig 0.15 folded the x86_64-linux-gnu libc-include dir into the shared x86-linux-gnu dir; the glibc target's include list is updated to match (verified against zig cc -v). aarch64 and all musl targets were unchanged.

Verification (darwin-arm64)

  • tools/bazel test //... — passes; same 4-pass/7-platform-skipped result as the 0.14.0 baseline (the wasm and windows cross-links that surfaced the lib/libtsan and lib/c changes now build).
  • tools/bazel test --config=macos_toolchains --@rules_go//go/config:static --@rules_go//go/config:pure //test/c/... — passes (native Darwin zig toolchain).
  • zig fmt --check and zig test on zig-wrapper.zig with the real 0.15.2 compiler — pass.
  • buildifier on all edited files — clean.
  • examples/bzlmod built against this module via --override_modulecgo_test passes.

🤖 Generated with Claude Code

Bumps the pinned Zig SDK from 0.14.0 to 0.15.2 and adapts to the layout
changes in the 0.15 release:

- zig_sdk.bzl: VERSION + all six host SHA256s. Zig >=0.14.1 renamed its
  release tarballs to zig-{arch}-{os}-{version} (arch first), so the
  download URL and archive strip-prefix now use a {zig_platform} (arch-os)
  format var. The SHA256/ext dicts stay keyed by {os}-{arch} to preserve
  the public host_platform_sha256 / host_platform_ext override API.
- defs.bzl: build the arch-os slug for the URL/stripPrefix; stage the
  renamed lib/tsan -> lib/libtsan tree, and the new lib/c/** tree that
  lib/c.zig (the libzigc shim built while linking) imports.
- private/defs.bzl: Zig 0.15 folded the x86_64-linux-gnu libc-include dir
  into the shared x86-linux-gnu dir; update the glibc target's include
  list to match (verified against `zig cc -v`).

zig-wrapper.zig needs no source changes for 0.15.2.

Verified on darwin-arm64: `bazel test //...`, the macOS-native zig
toolchain config, `zig fmt --check`/`zig test` on the wrapper, buildifier,
and the bzlmod example built against this module all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jul 21, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@DhashS

DhashS commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Request for maintainers: it would be great to cut a tagged release (and BCR entry) after merging this PR, before the Zig 0.16.0 upgrade (#260) lands — so there's a released hermetic_cc_toolchain version pinned to Zig 0.15.x that consumers can adopt without jumping straight from 0.14.0 to 0.16.0.

Addresses the two workaround-able issues reported when testing the Zig
0.15.2 upgrade at Uber (see uber#247 discussion):

1. Zig 0.15 compiles C/C++ with UBSan instrumentation by default, which
   surfaces as `ld.lld: error: undefined symbol: __ubsan_handle_*` at
   link time. The wrapper now passes -fno-sanitize=undefined in cc mode,
   placed before the caller's args so an explicit -fsanitize=undefined
   still takes precedence. The dbg/fastbuild features repeat the flag so
   the intent is visible in the toolchain config.

2. Zig 0.15's linker cannot read thin archives (ziglang/zig#25694):
       error: unexpected token in LD script: literal: '!<thin>'
   Build systems like Meson create thin archives for internal static
   libraries via ar's `T` modifier. Since archives produced by this
   toolchain are also consumed by it, the wrapper's ar mode now strips
   the `T` modifier from the operation string and drops `--thin`, so
   regular archives are created instead.

Both are verified against the real 0.15.2 toolchain: default compiles
reference __ubsan_* symbols and -fno-sanitize=undefined removes them;
`zig ar rcsT` produces a `!<thin>` archive that fails to link with
exactly the error above, while the stripped `rcs` archive links fine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DhashS

DhashS commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 610cbf9, addressing points (1) and (2) from @linzhp's report on the earlier 0.15.2 attempt (#247 (comment)):

1. UBSan-by-default — Zig 0.15 compiles C/C++ with UBSan instrumentation by default (zig cc -v shows a long -fsanitize=... list in the cc1 line), which surfaces as ld.lld: error: undefined symbol: __ubsan_handle_* at link time. The wrapper now passes -fno-sanitize=undefined in cc mode at the three places suggested. Two notes on placement:

  • In the wrapper, the flag is inserted before the caller's args (not appended at the end like -target), so an explicit -fsanitize=undefined from a user still takes precedence.
  • Since every compile/link goes through the wrapper, the dbg/fastbuild feature flags are technically redundant — they're repeated there so the intent is visible in the toolchain config.

Verified against the real 0.15.2 toolchain: a default compile of a file using __builtin_unreachable() references __ubsan_* symbols; with the flag it's clean.

2. Thin archives (ziglang/zig#25694) — reproduced exactly: zig ar rcsT produces a !<thin> archive, and linking it fails with error: unexpected token in LD script: literal: '!<thin>', while a regular archive links fine. Rather than requiring consumers to disable thin archives in Meson etc., the wrapper's ar mode now strips the T modifier from the operation string and drops --thin, so regular archives are created instead. Archives produced by this toolchain are also consumed by it, so this is behavior-preserving (regular archives are strictly more compatible, just larger). Covered by a new parseArgs unit test.

Point (3) (#203 / #249) is not addressed here.

🤖 Generated with Claude Code

Two sh_tests around the ar wrapper's thin-archive handling:

- ar_thin_request_test: asking the toolchain's ar wrapper for a thin
  archive (`rcsT`) must produce a regular `!<arch>` archive that links.

- thin_archive_canary_test: a genuinely thin archive created by raw
  `zig ar` (bypassing the wrapper) must still FAIL to link with the
  ziglang/zig#25694 error. This is a canary: when a Zig upgrade fixes
  the upstream bug, this test starts failing, signalling that the
  expectation should be flipped and stripThinArchiveFlags removed from
  the wrapper.

The @zig_config repo (which holds the SDK filegroups the tests need as
runfiles) is now use_repo'd in MODULE.bazel; the toolchains extension is
a dev_dependency there, so consumers are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@DhashS

DhashS commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Added test/ar/ (commit d8b5d0a) with two sh_tests covering the thin-archive workaround from the previous comment:

  • ar_thin_request_test — the positive path: it asks the toolchain's ar wrapper for a thin archive (ar rcsT), asserts the result is a regular !<arch> archive (the wrapper stripped the request), and links a binary against it. This is what real build systems that pass T/--thin (Meson, etc.) exercise.

  • thin_archive_canary_test — an upstream canary: it bypasses the wrapper and calls raw zig ar rcsT to produce a genuinely !<thin> archive, then asserts that linking it fails with the unexpected token in LD script: literal: '!<thin>' error. In other words, it pins the fact that ziglang/zig#25694 is still present in Zig 0.15.2 and the wrapper strip is therefore still required.

The point of the canary: when a future Zig upgrade fixes #25694, this test starts failing — deliberately. That failure is the signal to flip the expectation (thin archives should now link) and delete stripThinArchiveFlags from the wrapper. The failure message spells this out. Concretely, that's exactly what happens on the 0.16.0 branch (#260), where the test is flipped to assert success — see that PR for the inverse.

Plumbing note: the tests need the SDK filegroups as runfiles, so @zig_config is now use_repo'd in MODULE.bazel. The toolchains extension is a dev_dependency there, so downstream consumers are unaffected. Both tests are sh_tests and are marked target_compatible_with non-Windows.

🤖 Generated with Claude Code

@nmattia

nmattia commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

See also: #247

@linzhp

linzhp commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Rather than requiring consumers to disable thin archives in Meson etc., the wrapper's ar mode now strips the T modifier from the operation string and drops --thin, so regular archives are created instead.

the problem is Meson doesn't call the wrapper. It calls zig ar directly. So changes in the zig wrapper doesn't help. I confirm in Uber's Go monorepo that disabling tests and tools in those libraries is still needed.

Given that the issue is fixed in 0.16 and stripThinArchiveFlags doesn't actually work around the issue, I would rather carry the issue to 0.16 upgrade and avoid extra hacks in hermetic_cc_toolchain.

Can you remove stripThinArchiveFlags and test/ar? The rest looks good

@DhashS

DhashS commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the update wrt the uber-meson situation.
I'll drop the test out of the 0.16.0 PR as well.
Thanks for the timely turnaround on this!

DhashS and others added 2 commits July 26, 2026 22:53
The wrapper's ar mode stripped thin-archive flags so that regular
archives were produced, but the build systems that create thin archives
(e.g. Meson) invoke `zig ar` directly and never go through the wrapper,
so the workaround never fired for the real use case. The underlying
issue (ziglang/zig#25694) is fixed in Zig 0.16, so carry it to the 0.16
upgrade rather than keeping a hack that does not help.

Removes stripThinArchiveFlags (plus its parseArgs test case) and the
test/ar sh_tests, and reverts the MODULE.bazel zig_config use_repo that
those tests required. The -fno-sanitize=undefined UBSan default is kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	toolchain/zig-wrapper.zig

@linzhp linzhp 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.

Thanks for the contribution

@linzhp
linzhp merged commit f96b47d into uber:main Jul 27, 2026
6 checks passed
@DhashS
DhashS deleted the 0.15.2 branch July 27, 2026 12:33
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