Files
sx/library/modules/compiler.sx
agra 632e64512b bundling: Android APK pipeline moved into sx; android.sx state-on-plat
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.
2026-05-23 01:28:32 +03:00

95 lines
4.7 KiB
Plaintext

OperatingSystem :: enum { macos; linux; windows; wasm; ios; android; unknown; }
Architecture :: enum { aarch64; x86_64; wasm32; wasm64; unknown; }
OS : OperatingSystem = .unknown;
ARCH : Architecture = .unknown;
POINTER_SIZE : s64 = 8;
BuildOptions :: struct #compiler {
add_link_flag :: (self: BuildOptions, flag: [:0]u8);
add_framework :: (self: BuildOptions, name: [:0]u8);
set_output_path :: (self: BuildOptions, path: [:0]u8);
set_wasm_shell :: (self: BuildOptions, path: [:0]u8);
// Register a directory of runtime assets to bundle alongside the
// binary. `src` is the path on disk (relative to the CWD at build
// time); `dest` is the relative location inside the bundle / APK.
// Apple .app: copied to `<bundle>/<dest>/`. Android APK (Week 7):
// zipped under `<dest>/` at the APK root. Idiomatic chess form is
// `opts.add_asset_dir("assets", "assets")`.
add_asset_dir :: (self: BuildOptions, src: [:0]u8, dest: [:0]u8);
asset_dir_count :: (self: BuildOptions) -> s64;
asset_dir_src_at :: (self: BuildOptions, i: s64) -> string;
asset_dir_dest_at :: (self: BuildOptions, i: s64) -> string;
// Post-link callback. Registers a sx function the compiler will
// invoke after `target.link()` returns. Used by the sx-side
// bundler (`platform.bundle.bundle_main`) and by user programs
// that want custom post-build steps. Return `false` to fail the build.
set_post_link_callback :: (self: BuildOptions, cb: () -> bool);
// Name-based alternative to `set_post_link_callback`. The
// compiler resolves `<module_name>.bundle_main` after linking.
set_post_link_module :: (self: BuildOptions, module_name: [:0]u8);
// Path of the freshly-linked binary, only meaningful while a
// post-link callback is running. Returns "" before linking.
binary_path :: (self: BuildOptions) -> string;
// Apple `.app` / Android `.apk` bundling parameters. Accessors
// return "" when unset so the bundler can use a single string
// type. macOS bundling needs `bundle_path` and `bundle_id`;
// codesign / provisioning are iOS-device-only.
set_bundle_path :: (self: BuildOptions, path: [:0]u8);
set_bundle_id :: (self: BuildOptions, id: [:0]u8);
set_codesign_identity :: (self: BuildOptions, identity: [:0]u8);
set_provisioning_profile :: (self: BuildOptions, path: [:0]u8);
bundle_path :: (self: BuildOptions) -> string;
bundle_id :: (self: BuildOptions) -> string;
codesign_identity :: (self: BuildOptions) -> string;
provisioning_profile :: (self: BuildOptions) -> string;
// Target accessors. Empty triple before linking; predicates mirror
// TargetConfig.is{MacOS,IOS,IOSDevice,IOSSimulator}() on the Zig
// side. Used by the sx bundler to switch Info.plist shape and
// codesigning ceremony per platform.
target_triple :: (self: BuildOptions) -> string;
is_macos :: (self: BuildOptions) -> bool;
is_ios :: (self: BuildOptions) -> bool;
is_ios_device :: (self: BuildOptions) -> bool;
is_ios_simulator :: (self: BuildOptions) -> bool;
is_android :: (self: BuildOptions) -> bool;
// Framework list accessors. The bundler walks `framework_count() *
// framework_at(i)` to find each `-framework` name and recursively
// copies its `<Name>.framework` directory from one of
// `framework_path_at(0..framework_path_count())` into
// `<bundle>/Frameworks/`. Slice returns aren't natively expressible
// through the compiler-hook bridge yet, hence the indexed form.
framework_count :: (self: BuildOptions) -> s64;
framework_at :: (self: BuildOptions, i: s64) -> string;
framework_path_count :: (self: BuildOptions) -> s64;
framework_path_at :: (self: BuildOptions, i: s64) -> string;
// Android APK bundling parameters. `manifest_path` overrides the
// bundler's auto-generated AndroidManifest.xml; `keystore_path`
// overrides the default `$HOME/.android/debug.keystore`. Accessors
// return "" when unset.
set_manifest_path :: (self: BuildOptions, path: [:0]u8);
set_keystore_path :: (self: BuildOptions, path: [:0]u8);
manifest_path :: (self: BuildOptions) -> string;
keystore_path :: (self: BuildOptions) -> string;
// `#jni_main #jni_class("path") { ... }` decls collected during
// lowering. The Android bundler walks `0..jni_main_count()` and
// for each entry writes a `.java` file at
// `<stage>/java/<foreign_path>.java`, compiles via javac + d8, and
// bundles the resulting classes.dex into the APK.
jni_main_count :: (self: BuildOptions) -> s64;
jni_main_foreign_path_at :: (self: BuildOptions, i: s64) -> string;
jni_main_java_source_at :: (self: BuildOptions, i: s64) -> string;
}
build_options :: () -> BuildOptions #compiler;