mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
build: normalize input archives before Darwin libtool merge (#11999)
## Root cause 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. The same warning pattern also appeared in third-party input archives such as `libfreetype.a` and `libz.a`, so this was not only a `libghostty.a` problem. ## What needed to be done The inputs to Apple `libtool` needed to be normalized before they were merged. The safest fix is to copy each input archive and run `ranlib -D` on the copy before passing it to `libtool`. `ranlib` rewrites the archive into a form that Apple’s linker tools accept, fixing the alignment/layout issue without changing the archive’s semantic contents. ## Why this approach An `ar x` -> `ar rcs` workaround can also make the warnings go away, but it is a broader and riskier transformation. Extracting archive members into a flat directory is not semantics-preserving: - duplicate member basenames can collide - non-`.o` members can be lost - member order can change That means an `ar`-based rearchive can silently change valid archives while fixing alignment. `ranlib -D` avoids those hazards because it rewrites the archive in place instead of flattening it through the filesystem. `-D` is also important because plain `ranlib` is not deterministic. In local testing, `ranlib -D` still fixed the alignment issue, preserved all 97 `_ghostty_` symbols, produced no `libtool` warnings, and was byte-stable across repeated runs. ## Validation This was reproduced directly: - before normalization, running `libtool -static` on the affected x86_64 `libghostty.a` produced a `libghostty_zcu.o not 8-byte aligned` warning and the output archive dropped from 97 `_ghostty_` symbols to 4 - after `ranlib -D`, the same `libtool -static` command preserved all 97 `_ghostty_` symbols and emitted no alignment warnings After applying the normalization step, a clean `zig build` succeeded, and the final macOS xcframework archive contained 97 `_ghostty_` symbols in both the `x86_64` and `arm64` slices. ## Summary This was not a Metal issue, not an Xcode project issue, and not a stale-cache issue. The actual root cause was an Apple `libtool` interoperability problem with Zig-produced macOS archives. The required fix was to normalize each archive before the Darwin `libtool` merge step, and `ranlib -D` is the least invasive way to do that while preserving archive semantics.
This commit is contained in:
@@ -33,7 +33,15 @@ pub fn create(b: *std.Build, opts: Options) *LibtoolStep {
|
||||
const run_step = RunStep.create(b, b.fmt("libtool {s}", .{opts.name}));
|
||||
run_step.addArgs(&.{ "libtool", "-static", "-o" });
|
||||
const output = run_step.addOutputFileArg(opts.out_name);
|
||||
for (opts.sources) |source| run_step.addFileArg(source);
|
||||
for (opts.sources, 0..) |source, i| {
|
||||
run_step.addFileArg(normalizeArchive(
|
||||
b,
|
||||
opts.name,
|
||||
opts.out_name,
|
||||
i,
|
||||
source,
|
||||
));
|
||||
}
|
||||
|
||||
self.* = .{
|
||||
.step = &run_step.step,
|
||||
@@ -42,3 +50,29 @@ pub fn create(b: *std.Build, opts: Options) *LibtoolStep {
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
fn normalizeArchive(
|
||||
b: *std.Build,
|
||||
step_name: []const u8,
|
||||
out_name: []const u8,
|
||||
index: usize,
|
||||
source: LazyPath,
|
||||
) LazyPath {
|
||||
// Newer Xcode libtool can drop 64-bit archive members if the input
|
||||
// archive layout doesn't match what it expects. ranlib rewrites the
|
||||
// archive without flattening members through the filesystem, so we
|
||||
// normalize each source archive first. This is a Zig/toolchain
|
||||
// interoperability workaround, not a Ghostty archive format change.
|
||||
const run_step = RunStep.create(
|
||||
b,
|
||||
b.fmt("ranlib {s} #{d}", .{ step_name, index }),
|
||||
);
|
||||
run_step.addArgs(&.{
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"/bin/cp \"$1\" \"$2\" && /usr/bin/ranlib \"$2\"",
|
||||
"_",
|
||||
});
|
||||
run_step.addFileArg(source);
|
||||
return run_step.addOutputFileArg(b.fmt("{d}-{s}", .{ index, out_name }));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user