Week 7 of /Users/agra/.claude/plans/lets-plan-to-move-splendid-pumpkin.md
plus the android.sx refactor + three sx-compiler fixes hit along the way
to get chess on Pixel 7 Pro responding to touch end-to-end.
library/modules/platform/bundle.sx now covers the Android APK shape
alongside macOS / iOS-sim / iOS-device. `android_bundle_main` discovers
the SDK ($ANDROID_HOME / $ANDROID_SDK_ROOT / $HOME/Library/Android/sdk),
picks the highest-versioned build-tools + platforms via
`process.run("ls .. | sort -V | tail -1")`, stages
`<apk>.stage/lib/arm64-v8a/<libfoo.so>`, synthesizes
AndroidManifest.xml (NativeActivity vs `#jni_main` Activity branch),
writes each `#jni_main` decl's Java source under
`<stage>/java/<pkg>/<Cls>.java`, runs javac --release 11 + d8 to
produce classes.dex, aapt2-links the unaligned APK, appends lib/ +
classes.dex + each registered asset tree via zip, zipalign + ensure
debug keystore via keytool + apksigner sign.
Compiler-side accessors (src/ir/compiler_hooks.zig + library/modules/compiler.sx):
- is_android predicate.
- set_manifest_path / manifest_path + set_keystore_path / keystore_path.
- jni_main_count / jni_main_foreign_path_at(i) /
jni_main_java_source_at(i) surface the `#jni_main` emissions that
the Zig createApk previously consumed directly.
- main.zig wires manifest_path, keystore_path, and the per-decl
(foreign_path, java_source) parallel slices into BuildConfig before
invoking the post-link callback.
CLI `--apk <path>` keeps working as a transitional alias: it now feeds
bundle_path so the existing auto-`post_link_module = "platform.bundle"`
shim fires the same way as `--bundle`. main.zig no longer calls
target.createApk directly.
Deletions in src/target.zig: createApk, compileJniMainSources,
buildJniMainManifest, buildAndroidManifest, ensureDebugKeystore,
libNameFromSoBasename, plus helpers splitForeignPath / discoverJavac /
discoverAndroidSdk / findHighestSubdir / runProcess / runProcessIn
(~400 lines). git grep returns only the obituary comment.
library/modules/platform/android.sx refactor (chess Android dependency):
- Module-level globals retired (g_app_window, g_egl_*, g_viewport_*,
g_dpi_scale, g_should_stop, g_render_thread*, g_user_main_fn,
g_touch_*) → AndroidPlatform struct fields.
- All sx_android_* helpers take `plat: *AndroidPlatform` as first arg.
Render thread receives plat via pthread_create's arg.
- New `logical_w: f32 = 0.0` field. Consumers set it before init() to
define the design width in points; `recompute_scale` derives
`dpi_scale = pixel_w / logical_w` (or 1.0 if unset). Called on
init / set_viewport / egl_init. drain_touches divides incoming
physical pixel coords by dpi_scale so chess sees logical-space
positions matching its layout. Touch lands on the right squares.
Three sx-compiler bugs hit + fixed along the way:
1. Top-level `inline if OS == .X { decls }` body decls were silently
dropped because scanDecls/lowerDecls had no .if_expr arm. New
`flattenComptimeConditionals` pre-pass in src/imports.zig
(threaded via ComptimeContext from core.zig) hoists matching arms
recursively. Regression at examples/124-inline-if-hoist-toplevel.sx.
2. Parser rejected `#import` / `#framework` inside inline-if bodies
because parseStmt in src/parser.zig only had arms for `#insert`.
Added the missing arms. Regression at
examples/123-inline-if-import-in-body.sx (landed earlier).
3. JNI `Call<T>Method` switches in src/ir/emit_llvm.zig (instance /
nonvirtual / static) were missing `.f32` rows — jfloat returns
(e.g. MotionEvent.getX/getY) fell into the silent-undef else arm.
Chess's sx_android_push_touch(plat, getAction(), getX(), getY())
delivered garbage f32 coords to the touch ring, so taps landed
nowhere recognisable. Added `.f32 => Jni.Call{Static,Nonvirtual,}FloatMethod`
rows to all three switches; lifted unsupported-type detection
from emit_llvm into lowerForeignMethodCall with proper
source-spanned diagnostics (`isJniReturnTypeSupported`). Regressions
at examples/ffi-jni-call-10-jfloat-return.sx,
examples/ffi-jni-class-09-multi-float-args.sx,
examples/ffi-jni-call-11-unsupported-return-diag.sx.
Stale-snapshot drift in tests/expected/ffi-objc-call-03-selector-sharing.ir
and ffi-objc-call-06-sret-return.ir picks up the new BuildOptions
accessor extern decls (is_android, set_manifest_path,
set_keystore_path, jni_main_count, jni_main_foreign_path_at,
jni_main_java_source_at). Verified diff is dead-decl-only.
Chess on Pixel 7 Pro: tap on e2 white pawn -> yellow selection +
green dots on legal e3/e4 targets; tap on e4 -> board updates with
1. e4, "Black to move" + "1. e4" in info panel.
zig build && zig build test && bash tests/run_examples.sh -> 145/145
green. bash tests/cross_compile.sh -> 7/7 green.
531 lines
22 KiB
Zig
531 lines
22 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const interp_mod = @import("interp.zig");
|
|
const Value = interp_mod.Value;
|
|
const Interpreter = interp_mod.Interpreter;
|
|
const inst = @import("inst.zig");
|
|
const FuncId = inst.FuncId;
|
|
|
|
// ── BuildConfig ─────────────────────────────────────────────────────────
|
|
// Mutable build configuration accumulated by #run blocks via #compiler methods.
|
|
|
|
/// `(src_dir, dest_in_bundle)` pair recorded by
|
|
/// `BuildOptions.add_asset_dir(src, dest)`. The sx bundler walks the
|
|
/// list and recursively copies each `src` directory into the bundle
|
|
/// at the relative `dest` path (e.g. `("assets", "assets")` copies
|
|
/// `./assets/` to `<bundle>/assets/`). Android's Week-7 APK path will
|
|
/// zip the same pairs into the unaligned APK.
|
|
pub const AssetDir = struct {
|
|
src: []const u8,
|
|
dest: []const u8,
|
|
};
|
|
|
|
pub const BuildConfig = struct {
|
|
link_flags: std.ArrayList([]const u8) = .empty,
|
|
frameworks: std.ArrayList([]const u8) = .empty,
|
|
asset_dirs: std.ArrayList(AssetDir) = .empty,
|
|
output_path: ?[]const u8 = null,
|
|
wasm_shell_path: ?[]const u8 = null,
|
|
|
|
/// Post-link callback registered via
|
|
/// `BuildOptions.set_post_link_callback(fn)`. When set, the
|
|
/// compiler re-enters the IR interpreter after `target.link()`
|
|
/// and invokes this function with no args. A `false` return is
|
|
/// treated as a build failure.
|
|
post_link_callback_fn: ?FuncId = null,
|
|
/// Alternative to `post_link_callback_fn`: the qualified name of
|
|
/// a module whose `bundle_main` function should be called
|
|
/// post-link.
|
|
post_link_module: ?[]const u8 = null,
|
|
|
|
/// Path of the freshly-linked binary, populated by `main.zig`
|
|
/// right before the post-link callback runs. The sx-side bundler
|
|
/// reads this via `binary_path()` to know what file to wrap.
|
|
binary_path: ?[]const u8 = null,
|
|
|
|
// Apple `.app` / Android `.apk` bundling parameters. Set either
|
|
// by the sx-side `BuildOptions.set_bundle_*` methods (preferred)
|
|
// or by main.zig from CLI flags (transitional fallback). The sx
|
|
// bundler reads them via the matching accessor methods.
|
|
bundle_path: ?[]const u8 = null,
|
|
bundle_id: ?[]const u8 = null,
|
|
codesign_identity: ?[]const u8 = null,
|
|
provisioning_profile: ?[]const u8 = null,
|
|
|
|
/// Target triple as supplied to `--target` (canonicalized).
|
|
/// Populated by main.zig before the post-link callback runs so the
|
|
/// sx bundler can switch on iOS vs. macOS vs. simulator.
|
|
target_triple: ?[]const u8 = null,
|
|
|
|
/// Frameworks the binary links against (`-framework` names) and
|
|
/// the search paths to look them up in (`-F` directories), forwarded
|
|
/// from the link step so the sx bundler can embed them into
|
|
/// `<bundle>/Frameworks/`.
|
|
target_frameworks: []const []const u8 = &.{},
|
|
target_framework_paths: []const []const u8 = &.{},
|
|
|
|
/// User-supplied `AndroidManifest.xml` override (`--manifest <path>`
|
|
/// or `BuildOptions.set_manifest_path("...")`). When null, the
|
|
/// Android bundler synthesizes a default manifest.
|
|
manifest_path: ?[]const u8 = null,
|
|
/// User-supplied debug keystore path (`--keystore <path>` or
|
|
/// `BuildOptions.set_keystore_path("...")`). When null, the Android
|
|
/// bundler uses `$HOME/.android/debug.keystore` (auto-generated on
|
|
/// first use via `keytool`).
|
|
keystore_path: ?[]const u8 = null,
|
|
|
|
/// `#jni_main #jni_class("path") { ... }` decls discovered during
|
|
/// lowering, paired with their pre-rendered Java source. The
|
|
/// Android bundler writes each entry to
|
|
/// `<stage>/java/<pkg>/<Class>.java`, compiles via `javac` + `d8`,
|
|
/// and bundles the resulting `classes.dex` into the APK. Slices
|
|
/// reference compiler-owned memory that outlives the post-link
|
|
/// callback.
|
|
jni_main_foreign_paths: []const []const u8 = &.{},
|
|
jni_main_java_sources: []const []const u8 = &.{},
|
|
|
|
pub fn deinit(self: *BuildConfig, alloc: Allocator) void {
|
|
self.link_flags.deinit(alloc);
|
|
self.frameworks.deinit(alloc);
|
|
self.asset_dirs.deinit(alloc);
|
|
}
|
|
};
|
|
|
|
// ── Hook system ─────────────────────────────────────────────────────────
|
|
|
|
pub const HookError = error{
|
|
CannotEvalComptime,
|
|
TypeError,
|
|
};
|
|
|
|
/// Hook function signature. Receives the interpreter (for heap/string access),
|
|
/// resolved argument values, and the mutable build config.
|
|
pub const HookFn = *const fn (
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value;
|
|
|
|
pub const Registry = struct {
|
|
hooks: std.StringHashMap(HookFn),
|
|
|
|
pub fn init(alloc: Allocator) Registry {
|
|
return .{ .hooks = std.StringHashMap(HookFn).init(alloc) };
|
|
}
|
|
|
|
pub fn deinit(self: *Registry) void {
|
|
self.hooks.deinit();
|
|
}
|
|
|
|
pub fn get(self: *const Registry, name: []const u8) ?HookFn {
|
|
return self.hooks.get(name);
|
|
}
|
|
|
|
/// Register all built-in compiler hooks.
|
|
pub fn registerDefaults(self: *Registry) void {
|
|
self.hooks.put("build_options", &hookBuildOptions) catch {};
|
|
self.hooks.put("BuildOptions.add_link_flag", &hookAddLinkFlag) catch {};
|
|
self.hooks.put("BuildOptions.add_framework", &hookAddFramework) catch {};
|
|
self.hooks.put("BuildOptions.add_asset_dir", &hookAddAssetDir) catch {};
|
|
self.hooks.put("BuildOptions.asset_dir_count", &hookAssetDirCount) catch {};
|
|
self.hooks.put("BuildOptions.asset_dir_src_at", &hookAssetDirSrcAt) catch {};
|
|
self.hooks.put("BuildOptions.asset_dir_dest_at", &hookAssetDirDestAt) catch {};
|
|
self.hooks.put("BuildOptions.set_output_path", &hookSetOutputPath) catch {};
|
|
self.hooks.put("BuildOptions.set_wasm_shell", &hookSetWasmShell) catch {};
|
|
self.hooks.put("BuildOptions.set_post_link_callback", &hookSetPostLinkCallback) catch {};
|
|
self.hooks.put("BuildOptions.set_post_link_module", &hookSetPostLinkModule) catch {};
|
|
self.hooks.put("BuildOptions.binary_path", &hookGetBinaryPath) catch {};
|
|
// Bundling setters
|
|
self.hooks.put("BuildOptions.set_bundle_path", &hookSetBundlePath) catch {};
|
|
self.hooks.put("BuildOptions.set_bundle_id", &hookSetBundleId) catch {};
|
|
self.hooks.put("BuildOptions.set_codesign_identity", &hookSetCodesignIdentity) catch {};
|
|
self.hooks.put("BuildOptions.set_provisioning_profile", &hookSetProvisioningProfile) catch {};
|
|
// Bundling accessors
|
|
self.hooks.put("BuildOptions.bundle_path", &hookGetBundlePath) catch {};
|
|
self.hooks.put("BuildOptions.bundle_id", &hookGetBundleId) catch {};
|
|
self.hooks.put("BuildOptions.codesign_identity", &hookGetCodesignIdentity) catch {};
|
|
self.hooks.put("BuildOptions.provisioning_profile", &hookGetProvisioningProfile) catch {};
|
|
// Target accessors — mirror TargetConfig.is{MacOS,IOS,IOSDevice,IOSSimulator,Android}()
|
|
self.hooks.put("BuildOptions.target_triple", &hookGetTargetTriple) catch {};
|
|
self.hooks.put("BuildOptions.is_macos", &hookIsMacOS) catch {};
|
|
self.hooks.put("BuildOptions.is_ios", &hookIsIOS) catch {};
|
|
self.hooks.put("BuildOptions.is_ios_device", &hookIsIOSDevice) catch {};
|
|
self.hooks.put("BuildOptions.is_ios_simulator", &hookIsIOSSimulator) catch {};
|
|
self.hooks.put("BuildOptions.is_android", &hookIsAndroid) catch {};
|
|
// Android-specific setters + accessors
|
|
self.hooks.put("BuildOptions.set_manifest_path", &hookSetManifestPath) catch {};
|
|
self.hooks.put("BuildOptions.manifest_path", &hookGetManifestPath) catch {};
|
|
self.hooks.put("BuildOptions.set_keystore_path", &hookSetKeystorePath) catch {};
|
|
self.hooks.put("BuildOptions.keystore_path", &hookGetKeystorePath) catch {};
|
|
// #jni_main class emissions, exposed by index so bundle.sx can iterate.
|
|
self.hooks.put("BuildOptions.jni_main_count", &hookJniMainCount) catch {};
|
|
self.hooks.put("BuildOptions.jni_main_foreign_path_at", &hookJniMainForeignPathAt) catch {};
|
|
self.hooks.put("BuildOptions.jni_main_java_source_at", &hookJniMainJavaSourceAt) catch {};
|
|
// Framework list accessors (for `.app/Frameworks/` embedding)
|
|
self.hooks.put("BuildOptions.framework_count", &hookFrameworkCount) catch {};
|
|
self.hooks.put("BuildOptions.framework_at", &hookFrameworkAt) catch {};
|
|
self.hooks.put("BuildOptions.framework_path_count", &hookFrameworkPathCount) catch {};
|
|
self.hooks.put("BuildOptions.framework_path_at", &hookFrameworkPathAt) catch {};
|
|
}
|
|
};
|
|
|
|
// ── build_options() hook ────────────────────────────────────────────────
|
|
|
|
fn hookBuildOptions(
|
|
_: *const Interpreter,
|
|
_: []const Value,
|
|
_: *BuildConfig,
|
|
_: Allocator,
|
|
) HookError!Value {
|
|
// build_options() returns a sentinel value; the real work happens
|
|
// when methods like add_link_flag/set_output_path are called on it.
|
|
return .void_val;
|
|
}
|
|
|
|
// ── BuildOptions hooks ──────────────────────────────────────────────────
|
|
|
|
fn hookAddLinkFlag(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
// args: [self (BuildOptions value), flag_string]
|
|
if (args.len < 2) return .void_val;
|
|
const str_val = args[1];
|
|
if (str_val.asString(interp)) |s| {
|
|
bc.link_flags.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookAddFramework(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
// args: [self (BuildOptions value), framework_name]
|
|
if (args.len < 2) return .void_val;
|
|
const str_val = args[1];
|
|
if (str_val.asString(interp)) |s| {
|
|
bc.frameworks.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookAddAssetDir(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
// args: [self (BuildOptions value), src_path, dest_path_in_bundle]
|
|
if (args.len < 3) return .void_val;
|
|
const src = args[1].asString(interp) orelse return error.TypeError;
|
|
const dest = args[2].asString(interp) orelse return error.TypeError;
|
|
const src_dup = alloc.dupe(u8, src) catch return error.CannotEvalComptime;
|
|
const dest_dup = alloc.dupe(u8, dest) catch return error.CannotEvalComptime;
|
|
bc.asset_dirs.append(alloc, .{ .src = src_dup, .dest = dest_dup }) catch return error.CannotEvalComptime;
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookAssetDirCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .int = @intCast(bc.asset_dirs.items.len) };
|
|
}
|
|
|
|
fn hookAssetDirSrcAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (args.len < 2) return Value{ .string = "" };
|
|
const idx = args[1].asInt() orelse return error.TypeError;
|
|
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.asset_dirs.items.len) return Value{ .string = "" };
|
|
return Value{ .string = bc.asset_dirs.items[@intCast(idx)].src };
|
|
}
|
|
|
|
fn hookAssetDirDestAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (args.len < 2) return Value{ .string = "" };
|
|
const idx = args[1].asInt() orelse return error.TypeError;
|
|
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.asset_dirs.items.len) return Value{ .string = "" };
|
|
return Value{ .string = bc.asset_dirs.items[@intCast(idx)].dest };
|
|
}
|
|
|
|
fn hookSetOutputPath(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
// args: [self (BuildOptions value), path_string]
|
|
if (args.len < 2) return .void_val;
|
|
const str_val = args[1];
|
|
if (str_val.asString(interp)) |s| {
|
|
bc.output_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookSetWasmShell(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
// args: [self (BuildOptions value), path_string]
|
|
if (args.len < 2) return .void_val;
|
|
const str_val = args[1];
|
|
if (str_val.asString(interp)) |s| {
|
|
bc.wasm_shell_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookSetPostLinkCallback(
|
|
_: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
_: Allocator,
|
|
) HookError!Value {
|
|
// args: [self (BuildOptions value), fn_value]. We accept a function
|
|
// value (.func_ref) and stash the FuncId so `main.zig` can re-enter
|
|
// the interpreter after linking.
|
|
if (args.len < 2) return .void_val;
|
|
switch (args[1]) {
|
|
.func_ref => |id| bc.post_link_callback_fn = id,
|
|
else => return error.TypeError,
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookSetPostLinkModule(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.post_link_module = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
/// Read the linked-binary path that main.zig populated right before
|
|
/// invoking the post-link callback. Returns the fat-string aggregate
|
|
/// the interpreter normally hands out for sx `string` values.
|
|
fn hookGetBinaryPath(
|
|
interp: *const Interpreter,
|
|
_: []const Value,
|
|
bc: *BuildConfig,
|
|
_: Allocator,
|
|
) HookError!Value {
|
|
_ = interp;
|
|
const path = bc.binary_path orelse "";
|
|
return Value{ .string = path };
|
|
}
|
|
|
|
// ── Bundling setters & accessors ─────────────────────────────────────
|
|
// Same pattern as set_output_path: take a string arg, dupe into the
|
|
// long-lived allocator, store on BuildConfig. The companion accessor
|
|
// reads back the same field; empty string when unset.
|
|
|
|
fn hookSetBundlePath(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.bundle_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookSetBundleId(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.bundle_id = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookSetCodesignIdentity(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.codesign_identity = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookSetProvisioningProfile(
|
|
interp: *const Interpreter,
|
|
args: []const Value,
|
|
bc: *BuildConfig,
|
|
alloc: Allocator,
|
|
) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.provisioning_profile = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookGetBundlePath(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.bundle_path orelse "" };
|
|
}
|
|
|
|
fn hookGetBundleId(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.bundle_id orelse "" };
|
|
}
|
|
|
|
fn hookGetCodesignIdentity(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.codesign_identity orelse "" };
|
|
}
|
|
|
|
fn hookGetProvisioningProfile(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.provisioning_profile orelse "" };
|
|
}
|
|
|
|
// ── Target accessors ──────────────────────────────────────────────────
|
|
// These look at the target_triple that main.zig populates and answer
|
|
// the same questions TargetConfig's helpers do for Zig callers.
|
|
|
|
fn tripleContains(triple: ?[]const u8, needle: []const u8) bool {
|
|
const t = triple orelse return false;
|
|
return std.mem.indexOf(u8, t, needle) != null;
|
|
}
|
|
|
|
fn isIOSTriple(triple: ?[]const u8) bool {
|
|
return tripleContains(triple, "apple-ios");
|
|
}
|
|
|
|
fn hookGetTargetTriple(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.target_triple orelse "" };
|
|
}
|
|
|
|
fn hookIsMacOS(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (isIOSTriple(bc.target_triple)) return Value{ .boolean = false };
|
|
const t = bc.target_triple orelse "";
|
|
const is_mac = std.mem.indexOf(u8, t, "apple-macosx") != null or
|
|
std.mem.indexOf(u8, t, "apple-macos") != null or
|
|
std.mem.indexOf(u8, t, "apple-darwin") != null;
|
|
return Value{ .boolean = is_mac };
|
|
}
|
|
|
|
fn hookIsIOS(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .boolean = isIOSTriple(bc.target_triple) };
|
|
}
|
|
|
|
fn hookIsIOSDevice(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
const ios = isIOSTriple(bc.target_triple);
|
|
const sim = tripleContains(bc.target_triple, "simulator");
|
|
return Value{ .boolean = ios and !sim };
|
|
}
|
|
|
|
fn hookIsIOSSimulator(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
const ios = isIOSTriple(bc.target_triple);
|
|
const sim = tripleContains(bc.target_triple, "simulator");
|
|
return Value{ .boolean = ios and sim };
|
|
}
|
|
|
|
fn hookIsAndroid(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .boolean = tripleContains(bc.target_triple, "android") };
|
|
}
|
|
|
|
// ── Android-specific bundling setters + accessors ─────────────────────
|
|
|
|
fn hookSetManifestPath(interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.manifest_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookGetManifestPath(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.manifest_path orelse "" };
|
|
}
|
|
|
|
fn hookSetKeystorePath(interp: *const Interpreter, args: []const Value, bc: *BuildConfig, alloc: Allocator) HookError!Value {
|
|
if (args.len < 2) return .void_val;
|
|
if (args[1].asString(interp)) |s| {
|
|
bc.keystore_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
|
}
|
|
return .void_val;
|
|
}
|
|
|
|
fn hookGetKeystorePath(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .string = bc.keystore_path orelse "" };
|
|
}
|
|
|
|
// ── #jni_main emission accessors ──────────────────────────────────────
|
|
// The Android bundler walks these as `0..jni_main_count()` and reads
|
|
// each entry's `(foreign_path, java_source)` pair so it can write a
|
|
// `.java` file per decl, compile via javac, and produce classes.dex
|
|
// via d8 before zipping into the APK.
|
|
|
|
fn hookJniMainCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return Value{ .int = @intCast(bc.jni_main_foreign_paths.len) };
|
|
}
|
|
|
|
fn hookJniMainForeignPathAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (args.len < 2) return Value{ .string = "" };
|
|
const idx = args[1].asInt() orelse return error.TypeError;
|
|
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.jni_main_foreign_paths.len) return Value{ .string = "" };
|
|
return Value{ .string = bc.jni_main_foreign_paths[@intCast(idx)] };
|
|
}
|
|
|
|
fn hookJniMainJavaSourceAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (args.len < 2) return Value{ .string = "" };
|
|
const idx = args[1].asInt() orelse return error.TypeError;
|
|
if (idx < 0 or @as(usize, @intCast(idx)) >= bc.jni_main_java_sources.len) return Value{ .string = "" };
|
|
return Value{ .string = bc.jni_main_java_sources[@intCast(idx)] };
|
|
}
|
|
|
|
// ── Framework list accessors ──────────────────────────────────────────
|
|
// The Apple .app bundler in `library/modules/platform/bundle.sx` walks
|
|
// the framework list to recursively copy each `<Name>.framework`
|
|
// directory from the user's -F search paths into `<bundle>/Frameworks/`.
|
|
// Slice-of-string returns aren't natively expressible as a Value, so we
|
|
// expose count + indexed lookups instead.
|
|
|
|
fn intValue(n: i64) Value {
|
|
return Value{ .int = n };
|
|
}
|
|
|
|
fn hookFrameworkCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return intValue(@intCast(bc.target_frameworks.len));
|
|
}
|
|
|
|
fn hookFrameworkAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (args.len < 2) return Value{ .string = "" };
|
|
const idx_i64 = args[1].asInt() orelse return error.TypeError;
|
|
if (idx_i64 < 0 or @as(usize, @intCast(idx_i64)) >= bc.target_frameworks.len) {
|
|
return Value{ .string = "" };
|
|
}
|
|
return Value{ .string = bc.target_frameworks[@intCast(idx_i64)] };
|
|
}
|
|
|
|
fn hookFrameworkPathCount(_: *const Interpreter, _: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
return intValue(@intCast(bc.target_framework_paths.len));
|
|
}
|
|
|
|
fn hookFrameworkPathAt(_: *const Interpreter, args: []const Value, bc: *BuildConfig, _: Allocator) HookError!Value {
|
|
if (args.len < 2) return Value{ .string = "" };
|
|
const idx_i64 = args[1].asInt() orelse return error.TypeError;
|
|
if (idx_i64 < 0 or @as(usize, @intCast(idx_i64)) >= bc.target_framework_paths.len) {
|
|
return Value{ .string = "" };
|
|
}
|
|
return Value{ .string = bc.target_framework_paths[@intCast(idx_i64)] };
|
|
}
|