ffi #jni_main R.1: manifest synthesis + default parent → android.app.Activity
When `Compilation.lowering_jni_main_decls` is non-empty, `createApk` synthesises a manifest whose `<activity android:name>` points at the user's `#jni_main` class (dotted form of the foreign path), sets `android:hasCode="true"` so Android loads the bundled classes.dex, and drops the `android.app.lib_name` meta-data (that's the NativeActivity- specific autoload mechanism — Java-driven Activities load the .so via `System.loadLibrary` from a Java static initializer slice R.3 will emit). The legacy NativeActivity path stays as the fallback when no `#jni_main` decl is present. `jni_java_emit.zig`'s default superclass moves from `android.app.NativeActivity` to `android.app.Activity` — the former requires native_app_glue's `ANativeActivity_onCreate` to be in the .so, which the next slice (R.2) will stop linking by default. Verified end-to-end on the slice 2 smoke APK: `aapt2 dump xmltree` shows `android:name="co.swipelab.sxjnimain.SxApp"` + `hasCode="true"`, and `dexdump -l plain` confirms SxApp now extends `Landroid/app/Activity;`. 99-android-egl-clear's APK still uses the NativeActivity manifest as before (legacy path intact for R.2-R.5).
This commit is contained in:
@@ -50,7 +50,7 @@ test "rejects non-main decl" {
|
|||||||
try std.testing.expectError(emit.EmitError.NotAJniMainClass, result);
|
try std.testing.expectError(emit.EmitError.NotAJniMainClass, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "void onCreate(Bundle) with #extends NativeActivity default" {
|
test "void onCreate(Bundle) with default Activity superclass" {
|
||||||
const a = std.testing.allocator;
|
const a = std.testing.allocator;
|
||||||
var arena = std.heap.ArenaAllocator.init(a);
|
var arena = std.heap.ArenaAllocator.init(a);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
@@ -85,7 +85,7 @@ test "void onCreate(Bundle) with #extends NativeActivity default" {
|
|||||||
const expected =
|
const expected =
|
||||||
\\package co.swipelab.sx_runtime;
|
\\package co.swipelab.sx_runtime;
|
||||||
\\
|
\\
|
||||||
\\public class SxNativeActivity extends android.app.NativeActivity {
|
\\public class SxNativeActivity extends android.app.Activity {
|
||||||
\\ @Override
|
\\ @Override
|
||||||
\\ public void onCreate(android.os.Bundle b) {
|
\\ public void onCreate(android.os.Bundle b) {
|
||||||
\\ super.onCreate(b);
|
\\ super.onCreate(b);
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ pub const Options = struct {
|
|||||||
/// Used to resolve `*Foo` cross-class refs in method signatures.
|
/// Used to resolve `*Foo` cross-class refs in method signatures.
|
||||||
classes: ?*const std.StringHashMap([]const u8) = null,
|
classes: ?*const std.StringHashMap([]const u8) = null,
|
||||||
/// Default superclass when the user doesn't write `#extends ...;`.
|
/// Default superclass when the user doesn't write `#extends ...;`.
|
||||||
default_extends: []const u8 = "android.app.NativeActivity",
|
/// `android.app.Activity` is the standard base for Java-driven
|
||||||
|
/// Activities — `NativeActivity` is the legacy NDK path that
|
||||||
|
/// requires native_app_glue's `ANativeActivity_onCreate`.
|
||||||
|
default_extends: []const u8 = "android.app.Activity",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Emit a `.java` source for the given foreign-class decl. Result is
|
/// Emit a `.java` source for the given foreign-class decl. Result is
|
||||||
|
|||||||
@@ -430,13 +430,20 @@ pub fn createApk(allocator: std.mem.Allocator, io: std.Io, so_path: []const u8,
|
|||||||
const so_dest = try std.fs.path.join(allocator, &.{ lib_dir, so_basename });
|
const so_dest = try std.fs.path.join(allocator, &.{ lib_dir, so_basename });
|
||||||
cwd.copyFile(so_path, cwd, so_dest, io, .{}) catch return error.ApkStageFailed;
|
cwd.copyFile(so_path, cwd, so_dest, io, .{}) catch return error.ApkStageFailed;
|
||||||
|
|
||||||
// Manifest: either user-supplied or auto-generated.
|
// Manifest: either user-supplied or auto-generated. When a `#jni_main`
|
||||||
|
// class is declared, the auto-generated manifest points its
|
||||||
|
// `<activity android:name="...">` at the user's class and flips
|
||||||
|
// `android:hasCode="true"` so Android loads the bundled classes.dex.
|
||||||
|
// Otherwise we fall back to the legacy NativeActivity shape.
|
||||||
const manifest_path = if (target_config.manifest_path) |mp|
|
const manifest_path = if (target_config.manifest_path) |mp|
|
||||||
try allocator.dupe(u8, mp)
|
try allocator.dupe(u8, mp)
|
||||||
else blk: {
|
else blk: {
|
||||||
const generated = try std.fmt.allocPrint(allocator, "{s}/AndroidManifest.xml", .{stage});
|
const generated = try std.fmt.allocPrint(allocator, "{s}/AndroidManifest.xml", .{stage});
|
||||||
const lib_name = libNameFromSoBasename(so_basename);
|
const lib_name = libNameFromSoBasename(so_basename);
|
||||||
const manifest_xml = try buildAndroidManifest(allocator, bundle_id, lib_name);
|
const manifest_xml = if (jni_main_decls.len > 0)
|
||||||
|
try buildJniMainManifest(allocator, bundle_id, lib_name, jni_main_decls[0].foreign_path)
|
||||||
|
else
|
||||||
|
try buildAndroidManifest(allocator, bundle_id, lib_name);
|
||||||
try cwd.writeFile(io, .{ .sub_path = generated, .data = manifest_xml });
|
try cwd.writeFile(io, .{ .sub_path = generated, .data = manifest_xml });
|
||||||
break :blk generated;
|
break :blk generated;
|
||||||
};
|
};
|
||||||
@@ -514,6 +521,42 @@ fn libNameFromSoBasename(basename: []const u8) []const u8 {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Manifest for a `#jni_main` Activity: `<activity android:name>` points
|
||||||
|
/// at the user's class, `android:hasCode="true"` so the bundled
|
||||||
|
/// classes.dex is loaded, and the `android.app.lib_name` meta-data is
|
||||||
|
/// dropped (that's a NativeActivity-only mechanism — Java-driven
|
||||||
|
/// Activities load the .so via `System.loadLibrary` from a static
|
||||||
|
/// initializer the Java emitter will synthesize once slice R.3 lands).
|
||||||
|
fn buildJniMainManifest(allocator: std.mem.Allocator, package: []const u8, lib_name: []const u8, foreign_path: []const u8) ![]const u8 {
|
||||||
|
var class_name = std.ArrayList(u8).empty;
|
||||||
|
for (foreign_path) |ch| {
|
||||||
|
try class_name.append(allocator, if (ch == '/') '.' else ch);
|
||||||
|
}
|
||||||
|
const activity_name = try class_name.toOwnedSlice(allocator);
|
||||||
|
return std.fmt.allocPrint(allocator,
|
||||||
|
\\<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
\\<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
\\ package="{s}"
|
||||||
|
\\ android:versionCode="1"
|
||||||
|
\\ android:versionName="1.0">
|
||||||
|
\\ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||||
|
\\ <application android:label="{s}" android:hasCode="true">
|
||||||
|
\\ <activity
|
||||||
|
\\ android:name="{s}"
|
||||||
|
\\ android:exported="true"
|
||||||
|
\\ android:label="{s}"
|
||||||
|
\\ android:configChanges="orientation|keyboardHidden|screenSize">
|
||||||
|
\\ <intent-filter>
|
||||||
|
\\ <action android:name="android.intent.action.MAIN" />
|
||||||
|
\\ <category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
\\ </intent-filter>
|
||||||
|
\\ </activity>
|
||||||
|
\\ </application>
|
||||||
|
\\</manifest>
|
||||||
|
\\
|
||||||
|
, .{ package, lib_name, activity_name, lib_name });
|
||||||
|
}
|
||||||
|
|
||||||
fn buildAndroidManifest(allocator: std.mem.Allocator, package: []const u8, lib_name: []const u8) ![]const u8 {
|
fn buildAndroidManifest(allocator: std.mem.Allocator, package: []const u8, lib_name: []const u8) ![]const u8 {
|
||||||
return std.fmt.allocPrint(allocator,
|
return std.fmt.allocPrint(allocator,
|
||||||
\\<?xml version="1.0" encoding="utf-8"?>
|
\\<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|||||||
Reference in New Issue
Block a user