android target + APK pipeline; LSP imports honor stdlib paths

Android (toolchain):
  --target android / --target android-arm64 → aarch64-linux-android21.
  target.zig discovers $ANDROID_NDK_HOME (or scans
  ~/Library/Android/sdk/ndk/* for the newest), invokes the NDK clang
  with -shared -fPIC and links libsxhello.so against -llog -landroid
  -lEGL -lGLESv3 -lm -ldl. native_app_glue.c from the NDK is compiled
  and linked alongside the sx .o so apps can use the conventional
  android_main(struct android_app*) shape; -u ANativeActivity_onCreate
  keeps glue's symbol live.

Android (APK):
  --apk <out> wraps the .so into a debug-signed installable APK.
  target.zig discovers the SDK at $ANDROID_HOME (or
  ~/Library/Android/sdk), picks the newest build-tools + platforms,
  generates a NativeActivity AndroidManifest.xml from --bundle-id,
  packages via aapt2 link, appends the lib/ tree, zipalign, then
  apksigner against ~/.android/debug.keystore (auto-generated via
  keytool on first use). One command end-to-end:
      sx build --target android --apk out.apk \\
          --bundle-id co.swipelab.foo main.sx
  Verified on Pixel 7 Pro: install + launch reaches android_main.

Compiler (entry-point linkage):
  Top-level fn defs default to LLVM internal linkage and are lazily
  lowered (only `main` was eagerly lowered before). Added
  isExportedEntryName() — a small allowlist for names the OS loader
  calls: `main`, `android_main`, `ANativeActivity_onCreate`,
  `JNI_OnLoad`. These get eagerly lowered AND keep external linkage,
  so they actually land in .dynsym.

LSP (imports):
  DocumentStore now takes the install-discovered stdlib_paths and
  forwards them into resolveImportPath, mirroring the compiler. Before
  this, every `#import "modules/..."` resolved through the stdlib path
  failed silently inside the LSP and identifiers from those modules
  showed as `undefined variable`. Repro on label.sx: 1 false positive
  before, 0 after.
This commit is contained in:
agra
2026-05-18 23:09:55 +03:00
parent f41a121a29
commit f66cda6d11
5 changed files with 384 additions and 15 deletions

View File

@@ -19,7 +19,7 @@ pub fn main(init: std.process.Init) !void {
// LSP subcommand doesn't need a file argument
if (std.mem.eql(u8, command, "lsp")) {
runLsp(allocator, io);
runLsp(allocator, io, stdlib_paths);
return;
}
@@ -61,6 +61,10 @@ pub fn main(init: std.process.Init) !void {
"arm64-apple-ios14.0-simulator"
else if (std.mem.eql(u8, raw, "ios-sim-x86"))
"x86_64-apple-ios14.0-simulator"
else if (std.mem.eql(u8, raw, "android") or std.mem.eql(u8, raw, "android-arm64"))
"aarch64-linux-android21"
else if (std.mem.eql(u8, raw, "android-x86_64"))
"x86_64-linux-android21"
else
raw;
target_config.triple = (try allocator.dupeZ(u8, expanded)).ptr;
@@ -92,6 +96,18 @@ pub fn main(init: std.process.Init) !void {
i += 1;
if (i >= args.len) { std.debug.print("error: --bundle requires a path (e.g. MyApp.app)\n", .{}); return; }
target_config.bundle_path = args[i];
} else if (std.mem.eql(u8, arg, "--apk")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --apk requires a path (e.g. out.apk)\n", .{}); return; }
target_config.apk_path = args[i];
} else if (std.mem.eql(u8, arg, "--manifest")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --manifest requires a path\n", .{}); return; }
target_config.manifest_path = args[i];
} else if (std.mem.eql(u8, arg, "--keystore")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --keystore requires a path\n", .{}); return; }
target_config.keystore_path = args[i];
} else if (std.mem.eql(u8, arg, "--bundle-id")) {
i += 1;
if (i >= args.len) { std.debug.print("error: --bundle-id requires a value (e.g. co.swipelab.myapp)\n", .{}); return; }
@@ -335,7 +351,7 @@ fn printUsage() void {
, .{});
}
fn runLsp(allocator: std.mem.Allocator, io: std.Io) void {
fn runLsp(allocator: std.mem.Allocator, io: std.Io, stdlib_paths: []const []const u8) void {
const Transport = sx.lsp.transport.Transport;
const Server = sx.lsp.server.Server;
@@ -346,7 +362,7 @@ fn runLsp(allocator: std.mem.Allocator, io: std.Io) void {
var stdin_reader = stdin_file.readerStreaming(io, &read_buf);
var transport = Transport.init(allocator, io, &stdin_reader.interface, stdout_file);
var server = Server.init(allocator, &transport, io);
var server = Server.init(allocator, &transport, io, stdlib_paths);
while (true) {
const msg = transport.readMessage() catch |err| {
@@ -583,6 +599,14 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
std.debug.print("bundled: {s}\n", .{bp});
}
// Wrap into an .apk if requested (Android).
if (merged_config.apk_path) |ap| {
timer.mark();
sx.target.createApk(allocator, io, final_output, merged_config) catch std.process.exit(1);
timer.record("apk");
std.debug.print("apk: {s}\n", .{ap});
}
// Post-process wasm HTML: inject content hash for cache busting
if (merged_config.isEmscripten() and std.mem.endsWith(u8, final_output, ".html")) {
sx.target.postProcessWasmHtml(allocator, io, final_output);