Skip to content

build: normalize input archives before Darwin libtool merge#23

Open
take0x wants to merge 1 commit into
manaflow-ai:mainfrom
take0x:fix/normalize-libtool-archives
Open

build: normalize input archives before Darwin libtool merge#23
take0x wants to merge 1 commit into
manaflow-ai:mainfrom
take0x:fix/normalize-libtool-archives

Conversation

@take0x

@take0x take0x commented Mar 31, 2026

Copy link
Copy Markdown

This is the same change as ghostty-org#11999 and is required for the build.

Background

Zig 0.15.2 can produce macOS .a archives where some 64-bit Mach-O members are only 4-byte aligned inside the archive. Recent Apple libtool -static does not handle that layout correctly: it emits "not 8-byte aligned" warnings and, in the failing case, silently drops those members when creating the combined static library.

In Ghostty, this happened in the Darwin libtool merge step that builds libghostty-fat.a. The x86_64 input libghostty.a still contained the expected libghostty_zcu.o and about 97 exported _ghostty_ symbols, but after libtool -static the output archive contained only 4 SIMD symbols because libghostty_zcu.o had been discarded.

Fix

Normalize each input archive by copying it and running ranlib -D on the copy before passing it to libtool. This rewrites the archive into a layout Apple's linker tools accept without flattening members through the filesystem or changing the archive's semantic contents.

Why This Approach

  • ar xar rcs can fix the warnings but is a broader, riskier transformation (duplicate member basenames can collide, non-.o members can be lost, member order can change).
  • ranlib -D rewrites the archive in place, preserves all archive members, and is deterministic (-D strips timestamps).

Validation

After applying the normalization step, a clean zig build succeeded and the final macOS xcframework archive contained all expected _ghostty_ symbols in both the x86_64 and arm64 slices.


Summary by cubic

Normalize input .a archives before the Darwin libtool -static merge to prevent dropped 64‑bit Mach‑O objects and preserve expected _ghostty_ symbols in libghostty-fat.a.
This fixes build failures seen with recent Xcode libtool when using Zig 0.15.2 archives.

  • Bug Fixes
    • Work around libtool misalignment handling by copying each input archive and running /usr/bin/ranlib on the copy before merging.
    • Added normalizeArchive in src/build/LibtoolStep.zig; clean zig build now preserves expected symbols for both x86_64 and arm64.

Written for commit bd75907. Summary will update on new commits.

Summary by CodeRabbit

  • Chores
    • Optimized archive processing in the build system with improved normalization handling to ensure consistency when processing source archives during compilation.

@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 115bb5e5-b0d0-48e5-8b5b-99ab45d0d582

📥 Commits

Reviewing files that changed from the base of the PR and between cf6c326 and bd75907.

📒 Files selected for processing (1)
  • src/build/LibtoolStep.zig

📝 Walkthrough

Walkthrough

Updated LibtoolStep.create to preprocess each source archive via a new normalizeArchive helper before passing it to libtool. Each archive is now normalized through a ranlib step that creates intermediate derived outputs used in the final static library generation.

Changes

Cohort / File(s) Summary
Archive Normalization
src/build/LibtoolStep.zig
Added private normalizeArchive helper function that creates a RunStep executing copy and ranlib commands on source archives. Modified LibtoolStep.create to normalize each source archive before passing to libtool, generating per-source derived outputs with indexed naming.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Each archive gets a ranlib kiss,
Copy, normalize, smooth as this,
No more raw inputs in the pile,
Libtool smiles at organized style! 🎪

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding archive normalization before libtool merging on Darwin, which is the core fix addressing the alignment issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 1 file

@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a pre-normalization step to LibtoolStep.zig that copies each input .a archive and runs ranlib on it before handing the archives to Apple's libtool -static. This works around a known incompatibility where Zig 0.15.2 can produce archives with 64-bit Mach-O members that are only 4-byte aligned inside the archive, causing recent Xcode libtool to silently drop those members (e.g. libghostty_zcu.o) during the Darwin fat-library merge step.

Key points:

  • The normalizeArchive() helper is a clean, self-contained Zig RunStep that uses /bin/sh -c with positional parameters, correctly quoting paths to handle spaces.
  • Output filenames are indexed ({i}-{out_name}) to avoid any cache collisions when multiple sources share the same base name.
  • The step is effectively Darwin-only because LibtoolStep is only invoked from GhosttyLib.initStatic() under the isDarwin() guard, so there is no impact on Linux/FreeBSD builds.
  • One minor discrepancy: the PR description explicitly calls out ranlib -D (deterministic, strips timestamps) as the chosen approach, but the implementation omits the -D flag, producing archives whose symbol-table timestamps vary with wall-clock time.

Confidence Score: 5/5

Safe to merge; the fix is correct and the only remaining finding is a P2 style suggestion about adding the -D flag to match the PR description's stated intent.

The change is small (29 lines), well-scoped to Darwin builds only, and has been validated end-to-end by the author. The shell invocation is idiomatic and handles spaces correctly. The single P2 observation (missing -D on ranlib) does not affect correctness — only build reproducibility.

src/build/LibtoolStep.zig — minor: ranlib -D flag omitted vs. PR description intent.

Important Files Changed

Filename Overview
src/build/LibtoolStep.zig Adds normalizeArchive() helper that copies each input .a archive and runs ranlib on it before passing to libtool, fixing Apple libtool dropping misaligned Mach-O members; uses ranlib without -D despite the PR description stating deterministic mode was intended.

Sequence Diagram

sequenceDiagram
    participant ZigBuild as Zig Build
    participant NormStep as RunStep(ranlib i)
    participant LibtoolRun as RunStep(libtool)
    participant FS as Build Cache

    ZigBuild->>NormStep: for each source .a (index i)
    NormStep->>FS: /bin/cp source to i-libghostty-fat.a
    NormStep->>FS: /usr/bin/ranlib i-libghostty-fat.a
    NormStep-->>LibtoolRun: normalized .a (LazyPath)
    LibtoolRun->>FS: libtool -static -o libghostty-fat.a all normalized inputs
    LibtoolRun-->>ZigBuild: output libghostty-fat.a
Loading

Reviews (1): Last reviewed commit: "build: normalize input archives before D..." | Re-trigger Greptile

Comment thread src/build/LibtoolStep.zig
run_step.addArgs(&.{
"/bin/sh",
"-c",
"/bin/cp \"$1\" \"$2\" && /usr/bin/ranlib \"$2\"",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Missing -D flag for deterministic output

The PR description explicitly states the goal is to use ranlib -D to produce a deterministic archive ("deterministic (-D strips timestamps)"), but the implementation omits the flag. Without -D, ranlib embeds the current wall-clock time into the archive's symbol table. While Zig's input-hashed build cache won't spuriously re-run this step because of non-deterministic output, the resulting archives won't be reproducible across different machines or timestamps — which matters for content-addressed caches, supply-chain tooling, or downstream consumers that verify archive hashes.

Suggested change
"/bin/cp \"$1\" \"$2\" && /usr/bin/ranlib \"$2\"",
"/bin/cp \"$1\" \"$2\" && /usr/bin/ranlib -D \"$2\"",

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.

1 participant