...
This commit is contained in:
@@ -78,13 +78,26 @@ bundle_main :: () -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy the linked binary into the bundle as `<exe_name>`. Flat
|
||||
// layout (binary + Info.plist at bundle root) matches the legacy
|
||||
// Zig path for every Apple target — the canonical macOS
|
||||
// `Contents/MacOS/` layout is a follow-up.
|
||||
// Apple .app layout: macOS goes through `Contents/{MacOS,Resources}`;
|
||||
// iOS / iOS-sim lay everything flat at the bundle root.
|
||||
is_mac := opts.is_macos();
|
||||
macos_dir := if is_mac then concat(bundle, "/Contents/MacOS") else bundle;
|
||||
plist_dir := if is_mac then concat(bundle, "/Contents") else bundle;
|
||||
asset_root := if is_mac then concat(bundle, "/Contents/Resources") else bundle;
|
||||
if is_mac {
|
||||
if !create_dir_all(str_to_cstr(macos_dir)) {
|
||||
out("error: bundle: cannot create Contents/MacOS\n");
|
||||
return false;
|
||||
}
|
||||
if !create_dir_all(str_to_cstr(asset_root)) {
|
||||
out("error: bundle: cannot create Contents/Resources\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exe_name := basename(binary);
|
||||
binary_z := str_to_cstr(binary);
|
||||
exe_dest := concat(bundle, "/");
|
||||
exe_dest := concat(macos_dir, "/");
|
||||
exe_dest = concat(exe_dest, exe_name);
|
||||
exe_dest_z := str_to_cstr(exe_dest);
|
||||
if !copy_file(binary_z, exe_dest_z) {
|
||||
@@ -96,7 +109,7 @@ bundle_main :: () -> bool {
|
||||
// Write Info.plist. Per-target shape — iOS needs UIDeviceFamily +
|
||||
// UIApplicationSceneManifest + DTPlatformName, macOS doesn't.
|
||||
plist := build_info_plist(opts, exe_name, bid);
|
||||
plist_path := concat(bundle, "/Info.plist");
|
||||
plist_path := concat(plist_dir, "/Info.plist");
|
||||
plist_path_z := str_to_cstr(plist_path);
|
||||
if !write_file(plist_path_z, plist) {
|
||||
out("error: bundle: write Info.plist failed\n");
|
||||
@@ -113,15 +126,15 @@ bundle_main :: () -> bool {
|
||||
}
|
||||
|
||||
// Copy any user-registered asset directories into the bundle.
|
||||
// Apple .app puts them at `<bundle>/<dest>/`. Android (Week 7) will
|
||||
// zip them into the APK at the same relative path. Recursive copy
|
||||
// shells out to `cp -R` until fs.sx grows `list_dir`.
|
||||
// macOS: `<bundle>/Contents/Resources/<dest>/`. iOS: `<bundle>/<dest>/`.
|
||||
// Android (Week 7) will zip them into the APK at the same relative path.
|
||||
// Recursive copy shells out to `cp -R` until fs.sx grows `list_dir`.
|
||||
asset_count := opts.asset_dir_count();
|
||||
j : s64 = 0;
|
||||
while j < asset_count {
|
||||
src := opts.asset_dir_src_at(j);
|
||||
dest := opts.asset_dir_dest_at(j);
|
||||
if !copy_asset_dir(src, dest, bundle) {
|
||||
if !copy_asset_dir(src, dest, asset_root) {
|
||||
out("error: bundle: failed to copy asset dir '");
|
||||
out(src);
|
||||
out("'\n");
|
||||
|
||||
@@ -275,6 +275,11 @@ pub const LLVMEmitter = struct {
|
||||
// Pass 2.5: Emit Obj-C selector init constructor (Phase 1.5).
|
||||
self.emitObjcSelectorInit();
|
||||
|
||||
// Pass 2.6: On macOS, chdir to the .app bundle's Resources dir at
|
||||
// startup so relative asset paths work when Finder/`open`
|
||||
// launches the binary with CWD=/. Non-bundled binaries no-op.
|
||||
self.emitMacosBundleChdir();
|
||||
|
||||
// Pass 3: Verify typeSizeBytes matches LLVM's ABI sizes
|
||||
self.verifySizes();
|
||||
}
|
||||
@@ -387,6 +392,104 @@ pub const LLVMEmitter = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, emit a startup helper that chdir's to the .app bundle's
|
||||
/// `Contents/Resources` directory when the executable lives inside a
|
||||
/// `.app/Contents/MacOS/` path. Lets relative asset paths like
|
||||
/// `assets/foo.png` resolve correctly when Finder/`open` launches the
|
||||
/// binary with CWD=/.
|
||||
///
|
||||
/// Bundled binary: strstr finds the marker, chdir succeeds.
|
||||
/// CLI binary / `sx run`: strstr returns null, the function no-ops.
|
||||
///
|
||||
/// The call is injected at the very start of `main()` (matching the
|
||||
/// pattern used for the Obj-C selector init) rather than registered
|
||||
/// via `@llvm.global_ctors`, so the ORC JIT path runs it too without
|
||||
/// special handling.
|
||||
fn emitMacosBundleChdir(self: *LLVMEmitter) void {
|
||||
if (!self.target_config.is_aot) return;
|
||||
if (!self.target_config.isMacOS()) return;
|
||||
|
||||
const ptr_ty = self.cached_ptr;
|
||||
const i32_ty = self.cached_i32;
|
||||
const i8_ty = self.cached_i8;
|
||||
const void_ty = self.cached_void;
|
||||
|
||||
// Declare libc externs (re-use if already declared).
|
||||
var ns_params: [2]c.LLVMTypeRef = .{ ptr_ty, ptr_ty };
|
||||
const ns_ty = c.LLVMFunctionType(i32_ty, &ns_params, 2, 0);
|
||||
var ns_fn = c.LLVMGetNamedFunction(self.llvm_module, "_NSGetExecutablePath");
|
||||
if (ns_fn == null) ns_fn = c.LLVMAddFunction(self.llvm_module, "_NSGetExecutablePath", ns_ty);
|
||||
|
||||
var chdir_params: [1]c.LLVMTypeRef = .{ptr_ty};
|
||||
const chdir_ty = c.LLVMFunctionType(i32_ty, &chdir_params, 1, 0);
|
||||
var chdir_fn = c.LLVMGetNamedFunction(self.llvm_module, "chdir");
|
||||
if (chdir_fn == null) chdir_fn = c.LLVMAddFunction(self.llvm_module, "chdir", chdir_ty);
|
||||
|
||||
var ss_params: [2]c.LLVMTypeRef = .{ ptr_ty, ptr_ty };
|
||||
const ss_ty = c.LLVMFunctionType(ptr_ty, &ss_params, 2, 0);
|
||||
var ss_fn = c.LLVMGetNamedFunction(self.llvm_module, "strstr");
|
||||
if (ss_fn == null) ss_fn = c.LLVMAddFunction(self.llvm_module, "strstr", ss_ty);
|
||||
|
||||
var sc_params: [2]c.LLVMTypeRef = .{ ptr_ty, ptr_ty };
|
||||
const sc_ty = c.LLVMFunctionType(ptr_ty, &sc_params, 2, 0);
|
||||
var sc_fn = c.LLVMGetNamedFunction(self.llvm_module, "strcpy");
|
||||
if (sc_fn == null) sc_fn = c.LLVMAddFunction(self.llvm_module, "strcpy", sc_ty);
|
||||
|
||||
var no_params: [0]c.LLVMTypeRef = .{};
|
||||
const ctor_ty = c.LLVMFunctionType(void_ty, &no_params, 0, 0);
|
||||
const ctor = c.LLVMAddFunction(self.llvm_module, "__sx_macos_bundle_chdir", ctor_ty);
|
||||
c.LLVMSetLinkage(ctor, c.LLVMInternalLinkage);
|
||||
|
||||
const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, ctor, "entry");
|
||||
const found_bb = c.LLVMAppendBasicBlockInContext(self.context, ctor, "found");
|
||||
const done_bb = c.LLVMAppendBasicBlockInContext(self.context, ctor, "done");
|
||||
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, entry_bb);
|
||||
|
||||
const buf_ty = c.LLVMArrayType2(i8_ty, 1024);
|
||||
const buf = c.LLVMBuildAlloca(self.builder, buf_ty, "buf");
|
||||
const bufsize = c.LLVMBuildAlloca(self.builder, i32_ty, "bufsize");
|
||||
_ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_ty, 1024, 0), bufsize);
|
||||
|
||||
var ns_args: [2]c.LLVMValueRef = .{ buf, bufsize };
|
||||
_ = c.LLVMBuildCall2(self.builder, ns_ty, ns_fn, &ns_args, 2, "");
|
||||
|
||||
const needle = self.emitCStringGlobal("/Contents/MacOS/", "__sx_macos_chdir_needle");
|
||||
const replacement = self.emitCStringGlobal("/Contents/Resources", "__sx_macos_chdir_replacement");
|
||||
|
||||
var ss_args: [2]c.LLVMValueRef = .{ buf, needle };
|
||||
const p = c.LLVMBuildCall2(self.builder, ss_ty, ss_fn, &ss_args, 2, "p");
|
||||
|
||||
const is_null = c.LLVMBuildIsNull(self.builder, p, "is_null");
|
||||
_ = c.LLVMBuildCondBr(self.builder, is_null, done_bb, found_bb);
|
||||
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, found_bb);
|
||||
var sc_args: [2]c.LLVMValueRef = .{ p, replacement };
|
||||
_ = c.LLVMBuildCall2(self.builder, sc_ty, sc_fn, &sc_args, 2, "");
|
||||
var chdir_args: [1]c.LLVMValueRef = .{buf};
|
||||
_ = c.LLVMBuildCall2(self.builder, chdir_ty, chdir_fn, &chdir_args, 1, "");
|
||||
_ = c.LLVMBuildBr(self.builder, done_bb);
|
||||
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, done_bb);
|
||||
_ = c.LLVMBuildRetVoid(self.builder);
|
||||
|
||||
// Inject a call at the very start of main(). Matches the
|
||||
// emitObjcSelectorInit pattern so the ORC JIT path picks it up
|
||||
// without needing `@llvm.global_ctors` plumbing.
|
||||
const main_fn = c.LLVMGetNamedFunction(self.llvm_module, "main");
|
||||
if (main_fn != null) {
|
||||
const main_entry = c.LLVMGetEntryBasicBlock(main_fn);
|
||||
const first_inst = c.LLVMGetFirstInstruction(main_entry);
|
||||
if (first_inst != null) {
|
||||
c.LLVMPositionBuilderBefore(self.builder, first_inst);
|
||||
} else {
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, main_entry);
|
||||
}
|
||||
var no_args: [0]c.LLVMValueRef = .{};
|
||||
_ = c.LLVMBuildCall2(self.builder, ctor_ty, ctor, &no_args, 0, "");
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `{cls_slot, mid_slot}` global pair for the
|
||||
/// `(name, sig)` literal — created on first lookup, shared across
|
||||
/// later `#jni_call` sites with the same literal pair. Both
|
||||
|
||||
23
src/main.zig
23
src/main.zig
@@ -46,9 +46,9 @@ pub fn main(init: std.process.Init) !void {
|
||||
else if (std.mem.eql(u8, raw, "wasm64"))
|
||||
"wasm64-unknown-emscripten"
|
||||
else if (std.mem.eql(u8, raw, "macos") or std.mem.eql(u8, raw, "macos-arm"))
|
||||
"aarch64-apple-macos"
|
||||
try macosTripleForArch(allocator, "aarch64")
|
||||
else if (std.mem.eql(u8, raw, "macos-x86"))
|
||||
"x86_64-apple-macos"
|
||||
try macosTripleForArch(allocator, "x86_64")
|
||||
else if (std.mem.eql(u8, raw, "linux") or std.mem.eql(u8, raw, "linux-x86"))
|
||||
"x86_64-unknown-linux-gnu"
|
||||
else if (std.mem.eql(u8, raw, "linux-arm"))
|
||||
@@ -182,6 +182,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
};
|
||||
|
||||
if (std.mem.eql(u8, command, "build")) {
|
||||
target_config.is_aot = true;
|
||||
const output_name = target_config.output_path orelse blk: {
|
||||
const base = deriveOutputName(path);
|
||||
if (target_config.isEmscripten()) {
|
||||
@@ -321,6 +322,24 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com
|
||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs, tmp_dir);
|
||||
}
|
||||
|
||||
/// Build an Apple Darwin triple for `arch` (e.g. "aarch64" / "x86_64") using
|
||||
/// the host's OS version suffix from `LLVMGetDefaultTargetTriple()`. Without
|
||||
/// the version suffix, the emitted object file carries no platform load
|
||||
/// command and `ld` warns "no platform load command found ... assuming: macOS".
|
||||
/// Falls back to "darwin" if the host triple doesn't start with darwin (e.g.
|
||||
/// when sx is cross-built on Linux for macOS).
|
||||
fn macosTripleForArch(allocator: std.mem.Allocator, arch: []const u8) ![]const u8 {
|
||||
const host = sx.llvm_api.c.LLVMGetDefaultTargetTriple();
|
||||
defer sx.llvm_api.c.LLVMDisposeMessage(host);
|
||||
const span = std.mem.span(host);
|
||||
var it = std.mem.splitScalar(u8, span, '-');
|
||||
_ = it.next();
|
||||
_ = it.next();
|
||||
const os_part = it.next() orelse "darwin";
|
||||
const os_suffix = if (std.mem.startsWith(u8, os_part, "darwin")) os_part else "darwin";
|
||||
return std.fmt.allocPrint(allocator, "{s}-apple-{s}", .{ arch, os_suffix });
|
||||
}
|
||||
|
||||
fn parseOptLevel(s: []const u8) ?sx.target.TargetConfig.OptLevel {
|
||||
if (std.mem.eql(u8, s, "none") or std.mem.eql(u8, s, "0")) return .none;
|
||||
if (std.mem.eql(u8, s, "less") or std.mem.eql(u8, s, "1")) return .less;
|
||||
|
||||
@@ -65,6 +65,12 @@ pub const TargetConfig = struct {
|
||||
/// Path to an entitlements plist. When null and `provisioning_profile`
|
||||
/// is set, the entitlements are auto-extracted from the profile.
|
||||
entitlements_path: ?[]const u8 = null,
|
||||
/// True when emitting an ahead-of-time binary (`sx build`), false for
|
||||
/// in-process JIT (`sx run`). Used by emit_llvm to gate code that only
|
||||
/// makes sense for a standalone executable — e.g. the macOS bundle
|
||||
/// `chdir` shouldn't run in JIT mode because it would mutate the host
|
||||
/// sx process's CWD.
|
||||
is_aot: bool = false,
|
||||
|
||||
pub const OptLevel = enum {
|
||||
none,
|
||||
|
||||
Reference in New Issue
Block a user