ios: framework embedding in .app + -F search paths
- `-F <dir>` CLI flag adds Apple framework search paths (parallel to `-L`). - `TargetConfig.framework_paths` flows into the iOS link line (`-F<dir>`). - iOS link adds `-Wl,-rpath,@executable_path/Frameworks` so embedded frameworks resolve at runtime. - `createBundle` now takes the framework list; for each one it locates `<name>.framework` in the `-F` paths and `cp -R`s it into `<bundle>.app/Frameworks/`. - `c_import.compileCToObjects` forwards `-target`/`-isysroot` to clang so `#c_import` works under cross-compile (was using host clang implicitly). - iOS SDK is auto-discovered once at startup and shared by both the C compile and the link paths. - `SX_DEBUG_LINK=1` prints the resolved link argv. - `library/modules/sdl3.sx`: drop `#library "SDL3"` — linking is now per-target (build.sx handles `-lSDL3` on macOS, `-framework SDL3` on iOS).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
sdl3 :: #library "SDL3";
|
||||
// SDL3 bindings. Linking is per-target (handled in build.sx):
|
||||
|
||||
// SDL_InitFlags
|
||||
SDL_INIT_VIDEO :u32: 0x20;
|
||||
@@ -320,27 +320,27 @@ SDL_Event :: enum struct { tag: u32; _: u32; payload: [30]u32; } {
|
||||
}
|
||||
|
||||
// Functions
|
||||
SDL_Init :: (flags: u32) -> bool #foreign sdl3;
|
||||
SDL_Quit :: () -> void #foreign sdl3;
|
||||
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign sdl3;
|
||||
SDL_DestroyWindow :: (window: *void) -> void #foreign sdl3;
|
||||
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign sdl3;
|
||||
SDL_GL_CreateContext :: (window: *void) -> *void #foreign sdl3;
|
||||
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign sdl3;
|
||||
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign sdl3;
|
||||
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign sdl3;
|
||||
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3;
|
||||
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3;
|
||||
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3;
|
||||
SDL_AddEventWatch :: (filter: *void, userdata: *void) -> bool #foreign sdl3;
|
||||
SDL_GetTicks :: () -> u64 #foreign sdl3;
|
||||
SDL_GetPerformanceCounter :: () -> u64 #foreign sdl3;
|
||||
SDL_GetPerformanceFrequency :: () -> u64 #foreign sdl3;
|
||||
SDL_Delay :: (ms: u32) -> void #foreign sdl3;
|
||||
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign sdl3;
|
||||
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
||||
SDL_SetWindowSize :: (window: *void, w: s32, h: s32) -> bool #foreign sdl3;
|
||||
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
||||
SDL_Init :: (flags: u32) -> bool #foreign;
|
||||
SDL_Quit :: () -> void #foreign;
|
||||
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign;
|
||||
SDL_DestroyWindow :: (window: *void) -> void #foreign;
|
||||
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign;
|
||||
SDL_GL_CreateContext :: (window: *void) -> *void #foreign;
|
||||
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign;
|
||||
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign;
|
||||
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign;
|
||||
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign;
|
||||
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign;
|
||||
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign;
|
||||
SDL_AddEventWatch :: (filter: *void, userdata: *void) -> bool #foreign;
|
||||
SDL_GetTicks :: () -> u64 #foreign;
|
||||
SDL_GetPerformanceCounter :: () -> u64 #foreign;
|
||||
SDL_GetPerformanceFrequency :: () -> u64 #foreign;
|
||||
SDL_Delay :: (ms: u32) -> void #foreign;
|
||||
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign;
|
||||
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign;
|
||||
SDL_SetWindowSize :: (window: *void, w: s32, h: s32) -> bool #foreign;
|
||||
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign;
|
||||
|
||||
SDL_Rect :: struct {
|
||||
x: s32;
|
||||
@@ -349,5 +349,5 @@ SDL_Rect :: struct {
|
||||
h: s32;
|
||||
}
|
||||
|
||||
SDL_GetPrimaryDisplay :: () -> u32 #foreign sdl3;
|
||||
SDL_GetDisplayUsableBounds :: (display_id: u32, rect: *SDL_Rect) -> bool #foreign sdl3;
|
||||
SDL_GetPrimaryDisplay :: () -> u32 #foreign;
|
||||
SDL_GetDisplayUsableBounds :: (display_id: u32, rect: *SDL_Rect) -> bool #foreign;
|
||||
|
||||
@@ -175,6 +175,7 @@ pub fn processCImport(
|
||||
pub fn compileCToObjects(
|
||||
allocator: std.mem.Allocator,
|
||||
infos: []const CImportInfo,
|
||||
target_config: @import("target.zig").TargetConfig,
|
||||
) ![]c.LLVMMemoryBufferRef {
|
||||
var obj_bufs = std.ArrayList(c.LLVMMemoryBufferRef).empty;
|
||||
|
||||
@@ -183,6 +184,15 @@ pub fn compileCToObjects(
|
||||
|
||||
// Build clang args: -I dirs, -D defines, raw flags
|
||||
var args_list = std.ArrayList([*c]const u8).empty;
|
||||
// Cross-compile target: forward -target / -isysroot when set.
|
||||
if (target_config.triple) |t| {
|
||||
try args_list.append(allocator, "-target");
|
||||
try args_list.append(allocator, t);
|
||||
}
|
||||
if (target_config.sysroot) |sr| {
|
||||
try args_list.append(allocator, "-isysroot");
|
||||
try args_list.append(allocator, (try allocator.dupeZ(u8, sr)).ptr);
|
||||
}
|
||||
for (info.includes) |inc| {
|
||||
const dir = dirName(inc);
|
||||
try args_list.append(allocator, (try allocPrintZ(allocator, "-I{s}", .{dir})).ptr);
|
||||
|
||||
23
src/main.zig
23
src/main.zig
@@ -27,6 +27,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
var input_path: ?[]const u8 = null;
|
||||
var target_config = sx.target.TargetConfig{};
|
||||
var lib_paths = std.ArrayList([]const u8).empty;
|
||||
var framework_paths = std.ArrayList([]const u8).empty;
|
||||
var link_flags = std.ArrayList([]const u8).empty;
|
||||
var show_timing: bool = false;
|
||||
var explicit_opt: bool = false;
|
||||
@@ -119,6 +120,14 @@ pub fn main(init: std.process.Init) !void {
|
||||
if (i >= args.len) { std.debug.print("error: -L requires a value\n", .{}); return; }
|
||||
try lib_paths.append(allocator, args[i]);
|
||||
}
|
||||
} else if (std.mem.startsWith(u8, arg, "-F")) {
|
||||
if (arg.len > 2) {
|
||||
try framework_paths.append(allocator, arg[2..]);
|
||||
} else {
|
||||
i += 1;
|
||||
if (i >= args.len) { std.debug.print("error: -F requires a value\n", .{}); return; }
|
||||
try framework_paths.append(allocator, args[i]);
|
||||
}
|
||||
} else if (std.mem.eql(u8, arg, "--lflags")) {
|
||||
i += 1;
|
||||
if (i >= args.len) { std.debug.print("error: --lflags requires a value\n", .{}); return; }
|
||||
@@ -132,8 +141,16 @@ pub fn main(init: std.process.Init) !void {
|
||||
}
|
||||
|
||||
target_config.lib_paths = try lib_paths.toOwnedSlice(allocator);
|
||||
target_config.framework_paths = try framework_paths.toOwnedSlice(allocator);
|
||||
target_config.extra_link_flags = try link_flags.toOwnedSlice(allocator);
|
||||
|
||||
// Auto-discover iOS SDK once so both the C compile path and the link
|
||||
// path see the same sysroot. Honors any explicit --sysroot.
|
||||
if (target_config.isIOS() and target_config.sysroot == null) {
|
||||
const sdk_name: []const u8 = if (target_config.isIOSSimulator()) "iphonesimulator" else "iphoneos";
|
||||
target_config.sysroot = sx.target.discoverAppleSdk(allocator, io, sdk_name) catch null;
|
||||
}
|
||||
|
||||
const path = input_path orelse {
|
||||
printUsage();
|
||||
return;
|
||||
@@ -261,7 +278,7 @@ fn compileCForJIT(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Compi
|
||||
const c_infos = try comp.collectCImportSources();
|
||||
if (c_infos.len == 0) return .{ .allocator = allocator };
|
||||
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos, comp.target_config);
|
||||
return try sx.c_import.loadCObjectsForJIT(allocator, io, obj_bufs);
|
||||
}
|
||||
|
||||
@@ -275,7 +292,7 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com
|
||||
return try sx.c_import.compileCWithEmcc(allocator, io, c_infos, comp.target_config, tmp_dir);
|
||||
}
|
||||
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos, comp.target_config);
|
||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs, tmp_dir);
|
||||
}
|
||||
|
||||
@@ -561,7 +578,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
// Wrap into a .app bundle if requested (iOS/macOS).
|
||||
if (merged_config.bundle_path) |bp| {
|
||||
timer.mark();
|
||||
sx.target.createBundle(allocator, io, final_output, merged_config) catch std.process.exit(1);
|
||||
sx.target.createBundle(allocator, io, final_output, merged_config, fws) catch std.process.exit(1);
|
||||
timer.record("bundle");
|
||||
std.debug.print("bundled: {s}\n", .{bp});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ pub const TargetConfig = struct {
|
||||
opt_level: OptLevel = .default,
|
||||
/// Library search paths (-L flags).
|
||||
lib_paths: []const []const u8 = &.{},
|
||||
/// Framework search paths (-F flags). Apple-only.
|
||||
framework_paths: []const []const u8 = &.{},
|
||||
/// Output path override.
|
||||
output_path: ?[]const u8 = null,
|
||||
/// Linker command (null = "cc" on Unix, "link.exe" on Windows).
|
||||
@@ -248,6 +250,8 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
try argv.append(allocator, sdk_path);
|
||||
const min_flag: []const u8 = if (target_config.isIOSSimulator()) "-mios-simulator-version-min=14.0" else "-mios-version-min=14.0";
|
||||
try argv.append(allocator, min_flag);
|
||||
// Embedded framework load path: bundle/Frameworks at runtime.
|
||||
try argv.append(allocator, "-Wl,-rpath,@executable_path/Frameworks");
|
||||
try argv.append(allocator, output_obj);
|
||||
try argv.append(allocator, "-o");
|
||||
try argv.append(allocator, output_bin);
|
||||
@@ -255,6 +259,9 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
for (target_config.lib_paths) |lp| {
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
|
||||
}
|
||||
for (target_config.framework_paths) |fp| {
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-F{s}", .{fp}));
|
||||
}
|
||||
for (libraries) |lib| {
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
|
||||
}
|
||||
@@ -363,6 +370,11 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
}
|
||||
|
||||
const argv_slice = try argv.toOwnedSlice(allocator);
|
||||
if (std.c.getenv("SX_DEBUG_LINK") != null) {
|
||||
std.debug.print("[sx] link argv:", .{});
|
||||
for (argv_slice) |a| std.debug.print(" {s}", .{a});
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
var child = std.process.spawn(io, .{
|
||||
.argv = argv_slice,
|
||||
}) catch return error.LinkError;
|
||||
@@ -375,7 +387,7 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
/// write a minimal Info.plist, and ad-hoc codesign for simulator runs.
|
||||
/// The executable inside the bundle is named after the basename of
|
||||
/// `binary_path` (also used as CFBundleExecutable).
|
||||
pub fn createBundle(allocator: std.mem.Allocator, io: std.Io, binary_path: []const u8, target_config: TargetConfig) !void {
|
||||
pub fn createBundle(allocator: std.mem.Allocator, io: std.Io, binary_path: []const u8, target_config: TargetConfig, frameworks: []const []const u8) !void {
|
||||
const bundle_path = target_config.bundle_path orelse return error.NoBundlePath;
|
||||
const bundle_id = target_config.bundle_id orelse {
|
||||
std.debug.print("error: --bundle requires --bundle-id (e.g. co.swipelab.app)\n", .{});
|
||||
@@ -412,6 +424,18 @@ pub fn createBundle(allocator: std.mem.Allocator, io: std.Io, binary_path: []con
|
||||
try cwd.writeFile(io, .{ .sub_path = embedded_path, .data = profile_data });
|
||||
}
|
||||
|
||||
// Embed any dynamic frameworks the binary links against. iOS apps load
|
||||
// frameworks from `<bundle>.app/Frameworks/<Name>.framework/<Name>` via
|
||||
// the `@executable_path/Frameworks` rpath we set at link time. For each
|
||||
// framework, look it up in `framework_paths` and copy the bundle in.
|
||||
if (target_config.isIOS() and frameworks.len > 0) {
|
||||
const fw_dir = try std.fs.path.join(allocator, &.{ bundle_path, "Frameworks" });
|
||||
try cwd.createDirPath(io, fw_dir);
|
||||
for (frameworks) |fw| {
|
||||
try embedFramework(allocator, io, fw, target_config.framework_paths, fw_dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Codesign: real identity for device, ad-hoc otherwise.
|
||||
const identity: []const u8 = target_config.codesign_identity orelse "-";
|
||||
const ent_path: ?[]const u8 = if (target_config.entitlements_path) |e| e else blk: {
|
||||
@@ -423,6 +447,32 @@ pub fn createBundle(allocator: std.mem.Allocator, io: std.Io, binary_path: []con
|
||||
try codesign(allocator, io, bundle_path, identity, ent_path);
|
||||
}
|
||||
|
||||
/// Find `<name>.framework` in one of `framework_paths` and copy it into
|
||||
/// `<dest_dir>/<name>.framework`. Shells out to `cp -R` because Zig's std
|
||||
/// doesn't expose a recursive-copy primitive on `Io.Dir` yet.
|
||||
fn embedFramework(allocator: std.mem.Allocator, io: std.Io, name: []const u8, framework_paths: []const []const u8, dest_dir: []const u8) !void {
|
||||
const cwd = std.Io.Dir.cwd();
|
||||
const subdir = try std.fmt.allocPrint(allocator, "{s}.framework", .{name});
|
||||
for (framework_paths) |fp| {
|
||||
const candidate = try std.fs.path.join(allocator, &.{ fp, subdir });
|
||||
if (cwd.openDir(io, candidate, .{})) |d| {
|
||||
d.close(io);
|
||||
const dest = try std.fs.path.join(allocator, &.{ dest_dir, subdir });
|
||||
const r = std.process.run(allocator, io, .{
|
||||
.argv = &.{ "cp", "-R", candidate, dest },
|
||||
}) catch return error.FrameworkCopyFailed;
|
||||
defer allocator.free(r.stdout);
|
||||
defer allocator.free(r.stderr);
|
||||
if (r.term != .exited or r.term.exited != 0) {
|
||||
std.debug.print("error: cp -R {s} -> {s} failed: {s}\n", .{ candidate, dest, r.stderr });
|
||||
return error.FrameworkCopyFailed;
|
||||
}
|
||||
return;
|
||||
} else |_| {}
|
||||
}
|
||||
std.debug.print("warning: framework '{s}' not found in any -F path; runtime load will fail\n", .{name});
|
||||
}
|
||||
|
||||
/// Extract entitlements XML from a `.mobileprovision` and resolve the
|
||||
/// `application-identifier` wildcard (`<TEAM>.*`) to the concrete bundle ID
|
||||
/// (`<TEAM>.<bundle_id>`). Without this substitution the device installer
|
||||
|
||||
Reference in New Issue
Block a user