build: Xcode 26, macOS Tahoe support (build tooling only) (#7559)

This updates our build script and CI to support Xcode 26 and macOS
Tahoe. **This doesn't update the Ghostty app to resolve any Tahoe
issues.**

For CI, we've added a new build job that runs on macOS Tahoe with Xcode
26. I've stopped short of updating our tip release job, since I think I
want to wait until I verify a bit more about Tahoe before we flip that
bit. Also, ideally, we'd run Xcode 26 on Sequoia (macOS 15) for
stability reasons and Namespace doesn't have Xcode 26 on 15 yet.

For builds, this updates our build script to find Metal binaries using
`xcodebuild -find-executable` instead of `xcrun`. The latter doesn't
work with Xcode 26, but the former does and also still works with older
Xcodes. I'm not sure if this is a bug but I did report it: FB17874042.
pull/7561/head
Mitchell Hashimoto 2025-06-10 07:22:28 -07:00 committed by GitHub
commit ad4facf8f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 12 deletions

View File

@ -18,6 +18,7 @@ jobs:
- build-nix
- build-snap
- build-macos
- build-macos-tahoe
- build-macos-matrix
- build-windows
- build-windows-cross
@ -284,7 +285,7 @@ jobs:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
@ -296,7 +297,58 @@ jobs:
- name: Build GhosttyKit
run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }}
# The native app is built with native XCode tooling. This also does
# The native app is built with native Xcode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
# Nix breaks xcodebuild so this has to be run outside.
- name: Build Ghostty.app
run: cd macos && xcodebuild -target Ghostty
# Build the iOS target without code signing just to verify it works.
- name: Build Ghostty iOS
run: |
cd macos
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO"
build-macos-tahoe:
runs-on: namespace-profile-ghostty-macos-tahoe
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
# TODO(tahoe): https://github.com/NixOS/nix/issues/13342
- uses: DeterminateSystems/nix-installer-action@main
with:
determinate: true
- uses: cachix/cachix-action@v16
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.0.app
# TODO(tahoe):
# https://developer.apple.com/documentation/xcode-release-notes/xcode-26-release-notes#Interface-Builder
# We allow this step to fail because if our image already has
# the workaround in place this will fail.
- name: Xcode 26 Beta 17A5241e Metal Workaround
continue-on-error: true
run: |
xcodebuild -downloadComponent metalToolchain -exportPath /tmp/MyMetalExport/
sed -i '' -e 's/17A5241c/17A5241e/g' /tmp/MyMetalExport/MetalToolchain-17A5241c.exportedBundle/ExportMetadata.plist
xcodebuild -importComponent metalToolchain -importPath /tmp/MyMetalExport/MetalToolchain-17A5241c.exportedBundle
- name: get the Zig deps
id: deps
run: nix build -L .#deps && echo "deps=$(readlink ./result)" >> $GITHUB_OUTPUT
# GhosttyKit is the framework that is built from Zig for our native
# Mac app to access.
- name: Build GhosttyKit
run: nix develop -c zig build --system ${{ steps.deps.outputs.deps }}
# The native app is built with native Xcode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
# Nix breaks xcodebuild so this has to be run outside.
- name: Build Ghostty.app
@ -324,7 +376,7 @@ jobs:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps
@ -642,7 +694,7 @@ jobs:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
- name: get the Zig deps

View File

@ -22,13 +22,12 @@ step: *Step,
output: LazyPath,
pub fn create(b: *std.Build, opts: Options) ?*MetallibStep {
const self = b.allocator.create(MetallibStep) catch @panic("OOM");
switch (opts.target.result.os.tag) {
.macos, .ios => {},
else => return null, // Only macOS and iOS are supported.
}
const sdk = switch (opts.target.result.os.tag) {
.macos => "macosx",
.ios => "iphoneos",
else => return null,
};
const self = b.allocator.create(MetallibStep) catch @panic("OOM");
const min_version = if (opts.target.query.os_version_min) |v|
b.fmt("{}", .{v.semver})
@ -38,11 +37,31 @@ pub fn create(b: *std.Build, opts: Options) ?*MetallibStep {
else => unreachable,
};
// Find the metal and metallib executables. The Apple docs
// at the time of writing (June 2025) say to use
// `xcrun --sdk <sdk> metal` but this doesn't work with Xcode 26.
//
// I don't know if this is a bug but the xcodebuild approach also
// works with Xcode 15 so it seems safe to use this instead.
//
// Reported bug: FB17874042.
var code: u8 = undefined;
const metal_exe = std.mem.trim(u8, b.runAllowFail(
&.{ "xcodebuild", "-find-executable", "metal" },
&code,
.Ignore,
) catch return null, "\r\n ");
const metallib_exe = std.mem.trim(u8, b.runAllowFail(
&.{ "xcodebuild", "-find-executable", "metallib" },
&code,
.Ignore,
) catch return null, "\r\n ");
const run_ir = RunStep.create(
b,
b.fmt("metal {s}", .{opts.name}),
);
run_ir.addArgs(&.{ "/usr/bin/xcrun", "-sdk", sdk, "metal", "-o" });
run_ir.addArgs(&.{ metal_exe, "-o" });
const output_ir = run_ir.addOutputFileArg(b.fmt("{s}.ir", .{opts.name}));
run_ir.addArgs(&.{"-c"});
for (opts.sources) |source| run_ir.addFileArg(source);
@ -62,7 +81,7 @@ pub fn create(b: *std.Build, opts: Options) ?*MetallibStep {
b,
b.fmt("metallib {s}", .{opts.name}),
);
run_lib.addArgs(&.{ "/usr/bin/xcrun", "-sdk", sdk, "metallib", "-o" });
run_lib.addArgs(&.{ metallib_exe, "-o" });
const output_lib = run_lib.addOutputFileArg(b.fmt("{s}.metallib", .{opts.name}));
run_lib.addFileArg(output_ir);
run_lib.step.dependOn(&run_ir.step);