mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 20:59:02 +00:00
Replaces: https://github.com/ghostty-org/ghostty/pull/13427 Zig's Mach-O linker does not emit LC_ENCRYPTION_INFO_64 for physical iOS dylibs. This allows libghostty-vt to build successfully but causes frameworks containing it to fail App Store validation. I think it'd be cleaner to always just build Apple targets on Apple hosts with the native linker. We don't need to rely on Zig being correct and this helps ensure compatibility for details like this.
209 lines
8.2 KiB
Zig
209 lines
8.2 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
/// Helpers for linking Mach-O artifacts with Apple's native toolchain.
|
|
pub const nativeLink = @import("native_link.zig");
|
|
|
|
// The cache. This always uses b.allocator and never frees memory
|
|
// (which is idiomatic for a Zig build exe). We cache the libc txt
|
|
// file we create because it is expensive to generate (subprocesses).
|
|
pub const Cache = struct {
|
|
const Key = struct {
|
|
arch: std.Target.Cpu.Arch,
|
|
os: std.Target.Os.Tag,
|
|
abi: std.Target.Abi,
|
|
};
|
|
|
|
pub const Value = union(enum) {
|
|
native: struct {
|
|
libc: std.Build.LazyPath,
|
|
framework: []const u8,
|
|
system_include: []const u8,
|
|
library: []const u8,
|
|
},
|
|
cross: struct {
|
|
libc: std.Build.LazyPath,
|
|
},
|
|
};
|
|
|
|
var map: std.AutoHashMapUnmanaged(Key, ?Value) = .{};
|
|
};
|
|
|
|
pub fn build(b: *std.Build) !void {
|
|
const target = b.standardTargetOptions(.{});
|
|
const optimize = b.standardOptimizeOption(.{});
|
|
_ = target;
|
|
_ = optimize;
|
|
}
|
|
|
|
/// Fetch or load the paths for the proper Apple SDK for libc and
|
|
/// frameworks. When running on a Darwin host, this uses the native
|
|
/// SDK installed on the system via `xcrun`. When cross-compiling from
|
|
/// a non-Darwin host, it falls back to Zig's bundled Darwin headers.
|
|
pub fn pathsForTarget(b: *std.Build, target: std.Target) !Cache.Value {
|
|
const gop = try Cache.map.getOrPut(b.allocator, .{
|
|
.arch = target.cpu.arch,
|
|
.os = target.os.tag,
|
|
.abi = target.abi,
|
|
});
|
|
|
|
if (!gop.found_existing) init: {
|
|
if (comptime builtin.os.tag.isDarwin()) darwin: {
|
|
// Detect our SDK using the "findNative" Zig stdlib function.
|
|
// This is really important because it forces using `xcrun` to
|
|
// find the SDK path.
|
|
var libc = std.zig.LibCInstallation.findNative(
|
|
b.allocator,
|
|
b.graph.io,
|
|
.{
|
|
.environ_map = &b.graph.environ_map,
|
|
.target = &target,
|
|
.verbose = false,
|
|
},
|
|
) catch break :darwin;
|
|
|
|
// Xcode 27's math.h requests infinity and NaN definitions from
|
|
// Clang's float.h using the __need_infinity_nan protocol. Zig
|
|
// 0.16's bundled Clang resource headers predate that protocol, so
|
|
// compiling Zig's bundled libc++ against the new SDK fails.
|
|
//
|
|
// Put our compatibility include directory between Zig's resource
|
|
// headers and the selected SDK headers. Its math.h forwards to the
|
|
// SDK with #include_next, then supplies the definitions missing
|
|
// from Zig's float.h. This can be removed once Zig's bundled Clang
|
|
// headers implement __need_infinity_nan.
|
|
libc.include_dir = b.dependency("apple_sdk", .{})
|
|
.path("include")
|
|
.getPath(b);
|
|
|
|
// Render the file compatible with the `--libc` Zig flag.
|
|
var stream: std.Io.Writer.Allocating = .init(b.allocator);
|
|
defer stream.deinit();
|
|
try libc.render(&stream.writer);
|
|
|
|
// Create a temporary file to store the libc path because
|
|
// `--libc` expects a file path.
|
|
const wf = b.addWriteFiles();
|
|
const path = wf.add("libc.txt", stream.written());
|
|
|
|
// Determine our framework path. Zig has a bug where it doesn't
|
|
// parse this from the libc txt file for `-framework` flags:
|
|
// https://github.com/ziglang/zig/issues/24024
|
|
const framework_path = framework: {
|
|
const down1 = std.fs.path.dirname(libc.sys_include_dir.?).?;
|
|
const down2 = std.fs.path.dirname(down1).?;
|
|
break :framework try std.fs.path.join(b.allocator, &.{
|
|
down2,
|
|
"System",
|
|
"Library",
|
|
"Frameworks",
|
|
});
|
|
};
|
|
|
|
const library_path = library: {
|
|
const down1 = std.fs.path.dirname(libc.sys_include_dir.?).?;
|
|
break :library try std.fs.path.join(b.allocator, &.{
|
|
down1,
|
|
"lib",
|
|
});
|
|
};
|
|
|
|
gop.value_ptr.* = .{ .native = .{
|
|
.libc = path,
|
|
.framework = framework_path,
|
|
.system_include = libc.sys_include_dir.?,
|
|
.library = library_path,
|
|
} };
|
|
|
|
break :init;
|
|
}
|
|
|
|
// Cross-compiling to Darwin from a non-Darwin host.
|
|
// Zig only bundles macOS headers, so for other Apple platforms
|
|
// we leave the value as null to produce a descriptive error.
|
|
if (target.os.tag != .macos) {
|
|
gop.value_ptr.* = null;
|
|
break :init;
|
|
}
|
|
|
|
// Fall back to Zig's bundled Darwin headers for libc resolution.
|
|
const zig_lib_path = b.graph.zig_lib_directory.path.?;
|
|
const include_dir = b.pathJoin(&.{
|
|
zig_lib_path, "libc", "include", "any-darwin-any",
|
|
});
|
|
|
|
const wf = b.addWriteFiles();
|
|
const path = wf.add("libc.txt", b.fmt(
|
|
\\include_dir={s}
|
|
\\sys_include_dir={s}
|
|
\\crt_dir=
|
|
\\msvc_lib_dir=
|
|
\\kernel32_lib_dir=
|
|
\\gcc_dir=
|
|
\\
|
|
, .{ include_dir, include_dir }));
|
|
|
|
gop.value_ptr.* = .{ .cross = .{ .libc = path } };
|
|
}
|
|
|
|
return gop.value_ptr.* orelse return switch (target.os.tag) {
|
|
// Return a more descriptive error. Before we just returned the
|
|
// generic error but this was confusing a lot of community members.
|
|
// It costs us nothing in the build script to return something better.
|
|
.macos => error.XcodeMacOSSDKNotFound,
|
|
.ios => error.XcodeiOSSDKNotFound,
|
|
.tvos => error.XcodeTVOSSDKNotFound,
|
|
.watchos => error.XcodeWatchOSSDKNotFound,
|
|
else => error.XcodeAppleSDKNotFound,
|
|
};
|
|
}
|
|
|
|
/// Setup the step to point to the proper Apple SDK for libc and
|
|
/// frameworks. When running on a Darwin host, this uses the native
|
|
/// SDK installed on the system via `xcrun`. When cross-compiling from
|
|
/// a non-Darwin host, it falls back to Zig's bundled Darwin headers.
|
|
pub fn addPaths(
|
|
b: *std.Build,
|
|
step: *std.Build.Step.Compile,
|
|
) !void {
|
|
const target = step.rootModuleTarget();
|
|
|
|
// A three paragraph comment to describe a single C macro. Strap in.
|
|
//
|
|
// Zig uses its own libc++ headers when compiling C++. In Zig 0.16,
|
|
// `__config` skips `__config_site`, the file that normally contains
|
|
// platform settings, and expects those settings as `-D` flags:
|
|
// https://codeberg.org/ziglang/zig/src/tag/0.16.0/lib/libcxx/include/__config#L13
|
|
//
|
|
// This macro enables Apple's availability checks. Without them, libc++
|
|
// assumes every symbol declared by these newer headers also exists in the
|
|
// target's runtime library:
|
|
// https://github.com/llvm/llvm-project/blob/llvmorg-21.1.0/libcxx/include/__configuration/availability.h#L63-L125
|
|
//
|
|
// For example, `hash.h` either calls `std::__hash_memory` from the runtime
|
|
// library or compiles an inline fallback:
|
|
// https://github.com/llvm/llvm-project/blob/llvmorg-21.1.0/libcxx/include/__functional/hash.h#L244-L250
|
|
// Without the checks, it chooses the runtime symbol. Older macOS versions
|
|
// don't have that symbol, so dyld aborts at launch:
|
|
// https://github.com/llvm/llvm-project/issues/155531
|
|
step.root_module.addCMacro(
|
|
"_LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS",
|
|
"1",
|
|
);
|
|
|
|
switch (try pathsForTarget(b, target)) {
|
|
.native => |native| {
|
|
step.setLibCFile(native.libc);
|
|
|
|
// This is only necessary until this bug is fixed:
|
|
// https://github.com/ziglang/zig/issues/24024
|
|
step.root_module.addSystemFrameworkPath(.{ .cwd_relative = native.framework });
|
|
step.root_module.addSystemIncludePath(.{ .cwd_relative = native.system_include });
|
|
step.root_module.addLibraryPath(.{ .cwd_relative = native.library });
|
|
},
|
|
.cross => |cross| {
|
|
step.setLibCFile(cross.libc);
|
|
},
|
|
}
|
|
}
|