Skip to content

Commit

Permalink
Add support for Zig 0.14.0 in CI build (0.14.0-dev.1632) and add code…
Browse files Browse the repository at this point in the history
… changes to support both 0.13.0 and 0.14.0-dev (#2)
  • Loading branch information
silbinarywolf authored Sep 22, 2024
1 parent 2ca003d commit 7f74ebb
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 66 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ jobs:
matrix:
include:
- os: "ubuntu-22.04"
suffix: "linux"
zig: "0.13.0" # aka "latest" at time of writing (2024-09-20)

# Use the nightly build
- os: "ubuntu-22.04"
zig: "master"

runs-on: ${{matrix.os}}

Expand All @@ -31,7 +35,7 @@ jobs:
# see: https://ziglang.org/news/migrate-to-self-hosting/
uses: mlugg/setup-zig@v1
with:
version: "0.13.0"
version: ${{matrix.zig}}

- name: Set up JDK 17
uses: actions/setup-java@v3
Expand Down
11 changes: 11 additions & 0 deletions examples/minimal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ zig build -Dandroid=true
adb install ./zig-out/bin/minimal.apk
```

### Uninstall your application

If installing your application fails with something like:
```
adb: failed to install ./zig-out/bin/minimal.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package com.zig.minimal signatures do not match newer version; ignoring!]
```

```sh
adb uninstall "com.zig.minimal"
```

### View logs of application

Powershell
Expand Down
247 changes: 184 additions & 63 deletions src/android/android.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const builtin = @import("builtin");

extern "log" fn __android_log_write(prio: c_int, tag: [*c]const u8, text: [*c]const u8) c_int;

/// Alternate panic implementation that calls __android_log_write so that you can see the logging via "adb logcat"
pub const panic = Panic.panic;

/// Levels for Android
Expand All @@ -23,20 +24,26 @@ pub const Level = enum(u8) {
// verbose = 2, // ANDROID_LOG_VERBOSE
// default = 1, // ANDROID_LOG_DEFAULT

/// Returns a string literal of the given level in full text form.
pub fn asText(comptime self: Level) []const u8 {
return switch (self) {
.err => "error",
.warn => "warning",
.info => "info",
.debug => "debug",
};
}
// Returns a string literal of the given level in full text form.
// pub fn asText(comptime self: Level) []const u8 {
// return switch (self) {
// .err => "error",
// .warn => "warning",
// .info => "info",
// .debug => "debug",
// };
// }
};

/// Alternate log function implementation that calls __android_log_write so that you can see the logging via "adb logcat"
pub fn logFn(
comptime message_level: std.log.Level,
comptime scope: @Type(.EnumLiteral),
comptime scope: if (builtin.zig_version.minor != 13)
// Support Zig 0.14.0-dev
@Type(.enum_literal)
else
// Support Zig 0.13.0
@Type(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
Expand All @@ -61,9 +68,11 @@ pub fn logFn(
}
}

/// LogWriter was was taken basically as is from: https://github.com/ikskuh/ZigAndroidTemplate
const LogWriter = struct {
/// name of the application / log scope
const tag: [*c]const u8 = null; // = "zig-app";
/// if not set, it'll default to the "package" attribute defined in AndroidManifest.xml
const tag: [*c]const u8 = null;

level: Level,

Expand Down Expand Up @@ -109,6 +118,13 @@ const LogWriter = struct {
}
};

/// Panic is a copy-paste of the panic logic from Zig but replaces usages of getStdErr with our own writer
///
/// Example output:
/// 09-22 13:08:49.578 3390 3390 F com.zig.minimal: thread 3390 panic: your panic message here
/// 09-22 13:08:49.637 3390 3390 F com.zig.minimal: zig-android-sdk/examples\minimal/src/minimal.zig:33:15: 0x7ccb77b282dc in nativeActivityOnCreate (minimal)
/// 09-22 13:08:49.637 3390 3390 F com.zig.minimal: zig-android-sdk/examples/minimal/src/minimal.zig:84:27: 0x7ccb77b28650 in ANativeActivity_onCreate (minimal)
/// 09-22 13:08:49.637 3390 3390 F com.zig.minimal: ???:?:?: 0x7ccea4021d9c in ??? (libandroid_runtime.so)
pub const Panic = struct {
/// Non-zero whenever the program triggered a panic.
/// The counter is incremented/decremented atomically.
Expand All @@ -126,17 +142,87 @@ pub const Panic = struct {
panicImpl(stack_trace, first_trace_addr, message);
}

/// Must be called only after adding 1 to `panicking`. There are three callsites.
fn waitForOtherThreadToFinishPanicking() void {
if (panicking.fetchSub(1, .seq_cst) != 1) {
// Another thread is panicking, wait for the last one to finish
// and call abort()
if (builtin.single_threaded) unreachable;

// Sleep forever without hammering the CPU
var futex = std.atomic.Value(u32).init(0);
while (true) std.Thread.Futex.wait(&futex, 0);
unreachable;
}
}

const native_os = builtin.os.tag;
const updateSegfaultHandler = std.debug.updateSegfaultHandler;

fn resetSegfaultHandler() void {
// NOTE(jae): 2024-09-22
// Not applicable for Android as it runs on the OS tag Linux
// if (native_os == .windows) {
// if (windows_segfault_handle) |handle| {
// assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
// windows_segfault_handle = null;
// }
// return;
// }
var act = posix.Sigaction{
.handler = .{ .handler = posix.SIG.DFL },
.mask = posix.empty_sigset,
.flags = 0,
};
// To avoid a double-panic, do nothing if an error happens here.
if (builtin.zig_version.major == 0 and builtin.zig_version.minor == 13) {
// Legacy 0.13.0
updateSegfaultHandler(&act) catch {};
} else {
// 0.14.0-dev+
updateSegfaultHandler(&act);
}
}

const io = struct {
const tty = struct {
inline fn detectConfig(_: *LogWriter) std.io.tty.Config {
return .no_color;
}
};

var writer = LogWriter{
.level = .fatal,
};

inline fn getStdErr() *LogWriter {
return &writer;
}
};

const posix = std.posix;
const enable_segfault_handler = std.options.enable_segfault_handler;

/// Panic is a copy-paste of the panic logic from Zig but replaces usages of getStdErr with our own writer
///
/// - Provide custom "io" namespace so we can easily customize getStdErr() to be our own writer
/// - Provide other functions from std.debug.*
fn panicImpl(trace: ?*const std.builtin.StackTrace, first_trace_addr: ?usize, msg: []const u8) noreturn {
@setCold(true);

// NOTE(jae): 2024-09-15
// resetSegfaultHandler is not a public function
// if (comptime std.options.enable_segfault_handler) {
// // If a segfault happens while panicking, we want it to actually segfault, not trigger
// // the handler.
// std.debug.resetSegfaultHandler();
// NOTE(jae): 2024-09-22
// Cannot mark this as cold(true) OR setCold() depending on Zig version as we get an invalid builtin function
// comptime {
// if (builtin.zig_version.minor == 13)
// @setCold(true)
// else
// @cold(true);
// }

if (enable_segfault_handler) {
// If a segfault happens while panicking, we want it to actually segfault, not trigger
// the handler.
resetSegfaultHandler();
}

// Note there is similar logic in handleSegfaultPosix and handleSegfaultWindowsExtra.
nosuspend switch (panic_stage) {
0 => {
Expand All @@ -149,69 +235,104 @@ pub const Panic = struct {
panic_mutex.lock();
defer panic_mutex.unlock();

var logger = LogWriter{
.level = .fatal,
};
const stderr = logger.writer();
const stderr = io.getStdErr().writer();
if (builtin.single_threaded) {
stderr.print("panic: ", .{}) catch @trap();
stderr.print("panic: ", .{}) catch posix.abort();
} else {
const current_thread_id = std.Thread.getCurrentId();
stderr.print("thread {} panic: ", .{current_thread_id}) catch @trap();
stderr.print("thread {} panic: ", .{current_thread_id}) catch posix.abort();
}
stderr.print("{s}\n", .{msg}) catch @trap();
stderr.print("{s}\n", .{msg}) catch posix.abort();
if (trace) |t| {
// std.debug.dumpStackTrace(t.*);
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
} else {
const debug_info = std.debug.getSelfDebugInfo() catch |err| {
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
@trap();
};
var debug_info_arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = debug_info_arena_allocator.allocator();
std.debug.writeStackTrace(t.*, stderr, allocator, debug_info, .no_color) catch |err| {
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
@trap();
};
}
}
// std.debug.dumpCurrentStackTrace(first_trace_addr);
{
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
} else {
const debug_info = std.debug.getSelfDebugInfo() catch |err| {
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
@trap();
};
std.debug.writeCurrentStackTrace(stderr, debug_info, .no_color, first_trace_addr) catch |err| {
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
@trap();
};
}
dumpStackTrace(t.*);
}
dumpCurrentStackTrace(first_trace_addr);
}
// std.debug.waitForOtherThreadToFinishPanicking();

waitForOtherThreadToFinishPanicking();
},
1 => {
panic_stage = 2;

// A panic happened while trying to print a previous panic message,
// we're still holding the mutex but that's fine as we're going to
// call abort()
var logger = LogWriter{
.level = .fatal,
};
const stderr = logger.writer();
stderr.print("Panicked during a panic. Aborting.\n", .{}) catch @trap();
const stderr = io.getStdErr().writer();
stderr.print("Panicked during a panic. Aborting.\n", .{}) catch posix.abort();
},
else => {
// Panicked while printing "Panicked during a panic."
},
};

@trap();
posix.abort();
}

const getSelfDebugInfo = std.debug.getSelfDebugInfo;
const writeStackTrace = std.debug.writeStackTrace;

// Used for 0.13.0 compatibility, technically this allocator is completely unused by "writeStackTrace"
fn getDebugInfoAllocator() std.mem.Allocator {
return std.heap.page_allocator;
}

fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void {
nosuspend {
if (comptime builtin.target.isWasm()) {
if (native_os == .wasi) {
const stderr = io.getStdErr().writer();
stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
}
return;
}
const stderr = io.getStdErr().writer();
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
return;
}
const debug_info = getSelfDebugInfo() catch |err| {
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
return;
};
if (builtin.zig_version.major == 0 and builtin.zig_version.minor == 13) {
// Legacy 0.13.0
writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| {
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
return;
};
} else {
// 0.14.0-dev+
writeStackTrace(stack_trace, stderr, debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| {
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
return;
};
}
}
}

const writeCurrentStackTrace = std.debug.writeCurrentStackTrace;
fn dumpCurrentStackTrace(start_addr: ?usize) void {
nosuspend {
if (comptime builtin.target.isWasm()) {
if (native_os == .wasi) {
const stderr = io.getStdErr().writer();
stderr.print("Unable to dump stack trace: not implemented for Wasm\n", .{}) catch return;
}
return;
}
const stderr = io.getStdErr().writer();
if (builtin.strip_debug_info) {
stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
return;
}
const debug_info = getSelfDebugInfo() catch |err| {
stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
return;
};
writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| {
stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
return;
};
}
}
};
1 change: 0 additions & 1 deletion src/androidbuild/androidbuild.zig
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ pub fn runNameContext(comptime name: []const u8) []const u8 {
const log = std.log.scoped(.@"zig-android-sdk");

pub fn printErrorsAndExit(message: []const u8, errors: []const []const u8) noreturn {
@setCold(true);
nosuspend {
log.err("{s}", .{message});
const stderr = std.io.getStdErr().writer();
Expand Down

0 comments on commit 7f74ebb

Please sign in to comment.