Skip to content

Commit 4caa73e

Browse files
add support for 'android:hasCode="false"' and update "examples/minimal" to use it (#33)
Allow projects to have an AndroidManifest.xml file that has `android:hasCode="false"` as per Android documentation here: https://developer.android.com/ndk/samples/sample_na **Changes** - removes validation rules around requiring Java files - simplifies the minimal example to no longer require Java - fixes `src/android/android.zig` to compile on latest Zig nightly Fixes #32
1 parent 17aa647 commit 4caa73e

File tree

6 files changed

+42
-34
lines changed

6 files changed

+42
-34
lines changed

examples/minimal/android/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
android:icon="@mipmap/ic_launcher"
88
android:label="@string/app_name"
99
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
10+
android:hasCode="false"
1011
tools:targetApi="31">
1112
<activity
1213
android:name="android.app.NativeActivity"

examples/minimal/android/src/NativeInvocationHandler.java

Lines changed: 0 additions & 15 deletions
This file was deleted.

examples/minimal/build.zig

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ pub fn build(b: *std.Build) void {
3232
apk.addResourceDirectory(b.path("android/res"));
3333

3434
// Add Java files
35-
apk.addJavaSourceFile(.{ .file = b.path("android/src/NativeInvocationHandler.java") });
35+
// - If you have 'android:hasCode="false"' in your AndroidManifest.xml then no Java files are required
36+
// see: https://developer.android.com/ndk/samples/sample_na
37+
//
38+
// WARNING: If you do not provide Java files AND android:hasCode="false" isn't explicitly set, then you may get the following error on "adb install"
39+
// Scanning Failed.: Package /data/app/base.apk code is missing]
40+
//
41+
// apk.addJavaSourceFile(.{ .file = b.path("android/src/X.java") });
3642
break :blk apk;
3743
};
3844

examples/minimal/src/minimal.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,16 @@ fn nativeActivityOnCreate(activity: *androidbind.ANativeActivity, savedState: []
7272
log.debug("Successfully started the app.", .{});
7373
}
7474

75+
comptime {
76+
if (builtin.abi.isAndroid()) {
77+
@export(&NativeActivity_onCreate, .{ .name = "ANativeActivity_onCreate" });
78+
} else {
79+
@compileError("This example cannot run on targets other Android");
80+
}
81+
}
82+
7583
/// Android entry point
76-
export fn ANativeActivity_onCreate(activity: *androidbind.ANativeActivity, rawSavedState: ?[*]u8, rawSavedStateSize: usize) callconv(.C) void {
84+
fn NativeActivity_onCreate(activity: *androidbind.ANativeActivity, rawSavedState: ?[*]u8, rawSavedStateSize: usize) callconv(.C) void {
7785
const savedState: []const u8 = if (rawSavedState) |s|
7886
s[0..rawSavedStateSize]
7987
else

src/android/android.zig

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const android_builtin = struct {
1313
pub const package_name: [:0]const u8 = ab.package_name;
1414
};
1515

16+
/// Writes the constant string text to the log, with priority prio and tag tag.
17+
/// Returns: 1 if the message was written to the log, or -EPERM if it was not; see __android_log_is_loggable().
18+
/// Source: https://developer.android.com/ndk/reference/group/logging
1619
extern "log" fn __android_log_write(prio: c_int, tag: [*c]const u8, text: [*c]const u8) c_int;
1720

1821
/// Alternate panic implementation that calls __android_log_write so that you can see the logging via "adb logcat"
@@ -92,7 +95,7 @@ const LogWriter = struct {
9295
/// logs with the package name.
9396
///
9497
/// To workaround this, we bake the package name into the Zig binaries.
95-
var tag: [:0]const u8 = android_builtin.package_name;
98+
const tag: [:0]const u8 = android_builtin.package_name;
9699

97100
level: Level,
98101

@@ -166,6 +169,8 @@ const Panic = struct {
166169
threadlocal var panic_stage: usize = 0;
167170

168171
fn panic(message: []const u8, ret_addr: ?usize) noreturn {
172+
@branchHint(.cold);
173+
if (comptime !builtin.abi.isAndroid()) @compileError("do not use Android panic for non-Android builds");
169174
const first_trace_addr = ret_addr orelse @returnAddress();
170175
panicImpl(first_trace_addr, message);
171176
}
@@ -199,7 +204,12 @@ const Panic = struct {
199204
// }
200205
var act = posix.Sigaction{
201206
.handler = .{ .handler = posix.SIG.DFL },
202-
.mask = posix.empty_sigset,
207+
.mask = if (builtin.zig_version.major == 0 and builtin.zig_version.minor == 14)
208+
// Legacy 0.14.0
209+
posix.empty_sigset
210+
else
211+
// 0.15.0-dev+
212+
posix.sigemptyset(),
203213
.flags = 0,
204214
};
205215
// To avoid a double-panic, do nothing if an error happens here.
@@ -236,14 +246,7 @@ const Panic = struct {
236246
/// - Provide custom "io" namespace so we can easily customize getStdErr() to be our own writer
237247
/// - Provide other functions from std.debug.*
238248
fn panicImpl(first_trace_addr: ?usize, msg: []const u8) noreturn {
239-
// NOTE(jae): 2024-09-22
240-
// Cannot mark this as cold(true) OR setCold() depending on Zig version as we get an invalid builtin function
241-
// comptime {
242-
// if (builtin.zig_version.minor == 13)
243-
// @setCold(true)
244-
// else
245-
// @cold(true);
246-
// }
249+
@branchHint(.cold);
247250

248251
if (enable_segfault_handler) {
249252
// If a segfault happens while panicking, we want it to actually segfault, not trigger

src/androidbuild/apk.zig

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,18 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
203203
}
204204
}
205205
}
206-
if (apk.java_files.items.len == 0) {
207-
// NOTE(jae): 2024-09-19
208-
// We can probably avoid this with a stub or something but for now error so that an "adb install"
209-
// doesn't give users:
210-
// - Scanning Failed.: Package /data/app/base.apk code is missing]
211-
try errors.append(b.fmt("must add at least one Java file to build", .{}));
212-
}
206+
// NOTE(jae): 2025-05-06
207+
// This validation rule has been removed because if you have `android:hasCode="false"` in your AndroidManifest.xml file
208+
// then you can have no Java files.
209+
//
210+
// If you do not provide Java files AND android:hasCode="false" isn't set, then you may get the following error on "adb install"
211+
// - Scanning Failed.: Package /data/app/base.apk code is missing]
212+
//
213+
// Ideally we may want to do something where we can utilize "aapt2 dump X" to determine if "hasCode" is set and if it isn't, throw
214+
// an error at compilation time. Similar to how we use it to extract the package name from AndroidManifest.xml below (ie. "aapt2 dump packagename")
215+
// if (apk.java_files.items.len == 0) {
216+
// try errors.append(b.fmt("must add at least one Java file to build OR you must setup your AndroidManifest.xml to have 'android:hasCode=false'", .{}));
217+
// }
213218
if (errors.items.len > 0) {
214219
printErrorsAndExit("misconfigured Android APK", errors.items);
215220
}

0 commit comments

Comments
 (0)