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_InitFlags
|
||||||
SDL_INIT_VIDEO :u32: 0x20;
|
SDL_INIT_VIDEO :u32: 0x20;
|
||||||
@@ -320,27 +320,27 @@ SDL_Event :: enum struct { tag: u32; _: u32; payload: [30]u32; } {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
SDL_Init :: (flags: u32) -> bool #foreign sdl3;
|
SDL_Init :: (flags: u32) -> bool #foreign;
|
||||||
SDL_Quit :: () -> void #foreign sdl3;
|
SDL_Quit :: () -> void #foreign;
|
||||||
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign sdl3;
|
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign;
|
||||||
SDL_DestroyWindow :: (window: *void) -> void #foreign sdl3;
|
SDL_DestroyWindow :: (window: *void) -> void #foreign;
|
||||||
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign sdl3;
|
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign;
|
||||||
SDL_GL_CreateContext :: (window: *void) -> *void #foreign sdl3;
|
SDL_GL_CreateContext :: (window: *void) -> *void #foreign;
|
||||||
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign sdl3;
|
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign;
|
||||||
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign sdl3;
|
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign;
|
||||||
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign sdl3;
|
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign;
|
||||||
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3;
|
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign;
|
||||||
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3;
|
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign;
|
||||||
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3;
|
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign;
|
||||||
SDL_AddEventWatch :: (filter: *void, userdata: *void) -> bool #foreign sdl3;
|
SDL_AddEventWatch :: (filter: *void, userdata: *void) -> bool #foreign;
|
||||||
SDL_GetTicks :: () -> u64 #foreign sdl3;
|
SDL_GetTicks :: () -> u64 #foreign;
|
||||||
SDL_GetPerformanceCounter :: () -> u64 #foreign sdl3;
|
SDL_GetPerformanceCounter :: () -> u64 #foreign;
|
||||||
SDL_GetPerformanceFrequency :: () -> u64 #foreign sdl3;
|
SDL_GetPerformanceFrequency :: () -> u64 #foreign;
|
||||||
SDL_Delay :: (ms: u32) -> void #foreign sdl3;
|
SDL_Delay :: (ms: u32) -> void #foreign;
|
||||||
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign sdl3;
|
SDL_GetWindowDisplayScale :: (window: *void) -> f32 #foreign;
|
||||||
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
SDL_GetWindowSize :: (window: *void, w: *s32, h: *s32) -> bool #foreign;
|
||||||
SDL_SetWindowSize :: (window: *void, w: s32, h: s32) -> bool #foreign sdl3;
|
SDL_SetWindowSize :: (window: *void, w: s32, h: s32) -> bool #foreign;
|
||||||
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign sdl3;
|
SDL_GetWindowSizeInPixels :: (window: *void, w: *s32, h: *s32) -> bool #foreign;
|
||||||
|
|
||||||
SDL_Rect :: struct {
|
SDL_Rect :: struct {
|
||||||
x: s32;
|
x: s32;
|
||||||
@@ -349,5 +349,5 @@ SDL_Rect :: struct {
|
|||||||
h: s32;
|
h: s32;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_GetPrimaryDisplay :: () -> u32 #foreign sdl3;
|
SDL_GetPrimaryDisplay :: () -> u32 #foreign;
|
||||||
SDL_GetDisplayUsableBounds :: (display_id: u32, rect: *SDL_Rect) -> bool #foreign sdl3;
|
SDL_GetDisplayUsableBounds :: (display_id: u32, rect: *SDL_Rect) -> bool #foreign;
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ pub fn processCImport(
|
|||||||
pub fn compileCToObjects(
|
pub fn compileCToObjects(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
infos: []const CImportInfo,
|
infos: []const CImportInfo,
|
||||||
|
target_config: @import("target.zig").TargetConfig,
|
||||||
) ![]c.LLVMMemoryBufferRef {
|
) ![]c.LLVMMemoryBufferRef {
|
||||||
var obj_bufs = std.ArrayList(c.LLVMMemoryBufferRef).empty;
|
var obj_bufs = std.ArrayList(c.LLVMMemoryBufferRef).empty;
|
||||||
|
|
||||||
@@ -183,6 +184,15 @@ pub fn compileCToObjects(
|
|||||||
|
|
||||||
// Build clang args: -I dirs, -D defines, raw flags
|
// Build clang args: -I dirs, -D defines, raw flags
|
||||||
var args_list = std.ArrayList([*c]const u8).empty;
|
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| {
|
for (info.includes) |inc| {
|
||||||
const dir = dirName(inc);
|
const dir = dirName(inc);
|
||||||
try args_list.append(allocator, (try allocPrintZ(allocator, "-I{s}", .{dir})).ptr);
|
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 input_path: ?[]const u8 = null;
|
||||||
var target_config = sx.target.TargetConfig{};
|
var target_config = sx.target.TargetConfig{};
|
||||||
var lib_paths = std.ArrayList([]const u8).empty;
|
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 link_flags = std.ArrayList([]const u8).empty;
|
||||||
var show_timing: bool = false;
|
var show_timing: bool = false;
|
||||||
var explicit_opt: 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; }
|
if (i >= args.len) { std.debug.print("error: -L requires a value\n", .{}); return; }
|
||||||
try lib_paths.append(allocator, args[i]);
|
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")) {
|
} else if (std.mem.eql(u8, arg, "--lflags")) {
|
||||||
i += 1;
|
i += 1;
|
||||||
if (i >= args.len) { std.debug.print("error: --lflags requires a value\n", .{}); return; }
|
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.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);
|
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 {
|
const path = input_path orelse {
|
||||||
printUsage();
|
printUsage();
|
||||||
return;
|
return;
|
||||||
@@ -261,7 +278,7 @@ fn compileCForJIT(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Compi
|
|||||||
const c_infos = try comp.collectCImportSources();
|
const c_infos = try comp.collectCImportSources();
|
||||||
if (c_infos.len == 0) return .{ .allocator = allocator };
|
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);
|
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);
|
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);
|
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).
|
// Wrap into a .app bundle if requested (iOS/macOS).
|
||||||
if (merged_config.bundle_path) |bp| {
|
if (merged_config.bundle_path) |bp| {
|
||||||
timer.mark();
|
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");
|
timer.record("bundle");
|
||||||
std.debug.print("bundled: {s}\n", .{bp});
|
std.debug.print("bundled: {s}\n", .{bp});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ pub const TargetConfig = struct {
|
|||||||
opt_level: OptLevel = .default,
|
opt_level: OptLevel = .default,
|
||||||
/// Library search paths (-L flags).
|
/// Library search paths (-L flags).
|
||||||
lib_paths: []const []const u8 = &.{},
|
lib_paths: []const []const u8 = &.{},
|
||||||
|
/// Framework search paths (-F flags). Apple-only.
|
||||||
|
framework_paths: []const []const u8 = &.{},
|
||||||
/// Output path override.
|
/// Output path override.
|
||||||
output_path: ?[]const u8 = null,
|
output_path: ?[]const u8 = null,
|
||||||
/// Linker command (null = "cc" on Unix, "link.exe" on Windows).
|
/// 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);
|
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";
|
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);
|
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, output_obj);
|
||||||
try argv.append(allocator, "-o");
|
try argv.append(allocator, "-o");
|
||||||
try argv.append(allocator, output_bin);
|
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| {
|
for (target_config.lib_paths) |lp| {
|
||||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{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| {
|
for (libraries) |lib| {
|
||||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{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);
|
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, .{
|
var child = std.process.spawn(io, .{
|
||||||
.argv = argv_slice,
|
.argv = argv_slice,
|
||||||
}) catch return error.LinkError;
|
}) 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.
|
/// write a minimal Info.plist, and ad-hoc codesign for simulator runs.
|
||||||
/// The executable inside the bundle is named after the basename of
|
/// The executable inside the bundle is named after the basename of
|
||||||
/// `binary_path` (also used as CFBundleExecutable).
|
/// `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_path = target_config.bundle_path orelse return error.NoBundlePath;
|
||||||
const bundle_id = target_config.bundle_id orelse {
|
const bundle_id = target_config.bundle_id orelse {
|
||||||
std.debug.print("error: --bundle requires --bundle-id (e.g. co.swipelab.app)\n", .{});
|
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 });
|
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.
|
// Codesign: real identity for device, ad-hoc otherwise.
|
||||||
const identity: []const u8 = target_config.codesign_identity orelse "-";
|
const identity: []const u8 = target_config.codesign_identity orelse "-";
|
||||||
const ent_path: ?[]const u8 = if (target_config.entitlements_path) |e| e else blk: {
|
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);
|
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
|
/// Extract entitlements XML from a `.mobileprovision` and resolve the
|
||||||
/// `application-identifier` wildcard (`<TEAM>.*`) to the concrete bundle ID
|
/// `application-identifier` wildcard (`<TEAM>.*`) to the concrete bundle ID
|
||||||
/// (`<TEAM>.<bundle_id>`). Without this substitution the device installer
|
/// (`<TEAM>.<bundle_id>`). Without this substitution the device installer
|
||||||
|
|||||||
Reference in New Issue
Block a user