...
This commit is contained in:
@@ -78,13 +78,26 @@ bundle_main :: () -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the linked binary into the bundle as `<exe_name>`. Flat
|
// Apple .app layout: macOS goes through `Contents/{MacOS,Resources}`;
|
||||||
// layout (binary + Info.plist at bundle root) matches the legacy
|
// iOS / iOS-sim lay everything flat at the bundle root.
|
||||||
// Zig path for every Apple target — the canonical macOS
|
is_mac := opts.is_macos();
|
||||||
// `Contents/MacOS/` layout is a follow-up.
|
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);
|
exe_name := basename(binary);
|
||||||
binary_z := str_to_cstr(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 = concat(exe_dest, exe_name);
|
||||||
exe_dest_z := str_to_cstr(exe_dest);
|
exe_dest_z := str_to_cstr(exe_dest);
|
||||||
if !copy_file(binary_z, exe_dest_z) {
|
if !copy_file(binary_z, exe_dest_z) {
|
||||||
@@ -96,7 +109,7 @@ bundle_main :: () -> bool {
|
|||||||
// Write Info.plist. Per-target shape — iOS needs UIDeviceFamily +
|
// Write Info.plist. Per-target shape — iOS needs UIDeviceFamily +
|
||||||
// UIApplicationSceneManifest + DTPlatformName, macOS doesn't.
|
// UIApplicationSceneManifest + DTPlatformName, macOS doesn't.
|
||||||
plist := build_info_plist(opts, exe_name, bid);
|
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);
|
plist_path_z := str_to_cstr(plist_path);
|
||||||
if !write_file(plist_path_z, plist) {
|
if !write_file(plist_path_z, plist) {
|
||||||
out("error: bundle: write Info.plist failed\n");
|
out("error: bundle: write Info.plist failed\n");
|
||||||
@@ -113,15 +126,15 @@ bundle_main :: () -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy any user-registered asset directories into the bundle.
|
// Copy any user-registered asset directories into the bundle.
|
||||||
// Apple .app puts them at `<bundle>/<dest>/`. Android (Week 7) will
|
// macOS: `<bundle>/Contents/Resources/<dest>/`. iOS: `<bundle>/<dest>/`.
|
||||||
// zip them into the APK at the same relative path. Recursive copy
|
// Android (Week 7) will zip them into the APK at the same relative path.
|
||||||
// shells out to `cp -R` until fs.sx grows `list_dir`.
|
// Recursive copy shells out to `cp -R` until fs.sx grows `list_dir`.
|
||||||
asset_count := opts.asset_dir_count();
|
asset_count := opts.asset_dir_count();
|
||||||
j : s64 = 0;
|
j : s64 = 0;
|
||||||
while j < asset_count {
|
while j < asset_count {
|
||||||
src := opts.asset_dir_src_at(j);
|
src := opts.asset_dir_src_at(j);
|
||||||
dest := opts.asset_dir_dest_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("error: bundle: failed to copy asset dir '");
|
||||||
out(src);
|
out(src);
|
||||||
out("'\n");
|
out("'\n");
|
||||||
|
|||||||
@@ -275,6 +275,11 @@ pub const LLVMEmitter = struct {
|
|||||||
// Pass 2.5: Emit Obj-C selector init constructor (Phase 1.5).
|
// Pass 2.5: Emit Obj-C selector init constructor (Phase 1.5).
|
||||||
self.emitObjcSelectorInit();
|
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
|
// Pass 3: Verify typeSizeBytes matches LLVM's ABI sizes
|
||||||
self.verifySizes();
|
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
|
/// Return `{cls_slot, mid_slot}` global pair for the
|
||||||
/// `(name, sig)` literal — created on first lookup, shared across
|
/// `(name, sig)` literal — created on first lookup, shared across
|
||||||
/// later `#jni_call` sites with the same literal pair. Both
|
/// 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"))
|
else if (std.mem.eql(u8, raw, "wasm64"))
|
||||||
"wasm64-unknown-emscripten"
|
"wasm64-unknown-emscripten"
|
||||||
else if (std.mem.eql(u8, raw, "macos") or std.mem.eql(u8, raw, "macos-arm"))
|
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"))
|
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"))
|
else if (std.mem.eql(u8, raw, "linux") or std.mem.eql(u8, raw, "linux-x86"))
|
||||||
"x86_64-unknown-linux-gnu"
|
"x86_64-unknown-linux-gnu"
|
||||||
else if (std.mem.eql(u8, raw, "linux-arm"))
|
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")) {
|
if (std.mem.eql(u8, command, "build")) {
|
||||||
|
target_config.is_aot = true;
|
||||||
const output_name = target_config.output_path orelse blk: {
|
const output_name = target_config.output_path orelse blk: {
|
||||||
const base = deriveOutputName(path);
|
const base = deriveOutputName(path);
|
||||||
if (target_config.isEmscripten()) {
|
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);
|
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 {
|
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, "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;
|
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`
|
/// Path to an entitlements plist. When null and `provisioning_profile`
|
||||||
/// is set, the entitlements are auto-extracted from the profile.
|
/// is set, the entitlements are auto-extracted from the profile.
|
||||||
entitlements_path: ?[]const u8 = null,
|
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 {
|
pub const OptLevel = enum {
|
||||||
none,
|
none,
|
||||||
|
|||||||
Reference in New Issue
Block a user