From 59cf4c19270449763ddd31b0807f8de849bba79a Mon Sep 17 00:00:00 2001 From: Jae B Date: Sat, 24 May 2025 16:42:27 +1000 Subject: [PATCH 01/16] do the work --- build.zig | 15 +- src/androidbuild/BuildTools.zig | 76 +++++++++++ src/androidbuild/Ndk.zig | 172 +++++++++++++++++++++++ src/androidbuild/androidbuild.zig | 5 +- src/androidbuild/apk.zig | 39 ++++-- src/androidbuild/tools.zig | 220 ++++-------------------------- 6 files changed, 317 insertions(+), 210 deletions(-) create mode 100644 src/androidbuild/BuildTools.zig create mode 100644 src/androidbuild/Ndk.zig diff --git a/build.zig b/build.zig index e91d9ba..08206f3 100644 --- a/build.zig +++ b/build.zig @@ -4,17 +4,22 @@ const Apk = @import("src/androidbuild/apk.zig"); // Expose Android build functionality for use in your build.zig -pub const Tools = @import("src/androidbuild/tools.zig"); +// TODO: Make this public and deprecate Tools +const Sdk = @import("src/androidbuild/tools.zig"); + pub const APK = Apk; // TODO(jae): 2025-03-13: Consider deprecating and using 'Apk' to be conventional to Zig pub const APILevel = androidbuild.APILevel; // TODO(jae): 2025-03-13: Consider deprecating and using 'ApiLevel' to be conventional to Zig pub const standardTargets = androidbuild.standardTargets; // Deprecated exposes fields -/// Deprecated: Use Tools.Options instead. -pub const ToolsOptions = Tools.Options; -/// Deprecated: Use Tools.CreateKey instead. -pub const CreateKey = Tools.CreateKey; +/// Deprecated: Use Sdk instead +pub const Tools = @import("src/androidbuild/tools.zig"); + +/// Deprecated: Use Sdk.Options instead. +pub const ToolsOptions = Sdk.Options; +/// Deprecated: Use Sdk.CreateKey instead. +pub const CreateKey = Sdk.CreateKey; /// NOTE: As well as providing the "android" module this declaration is required so this can be imported by other build.zig files pub fn build(b: *std.Build) void { diff --git a/src/androidbuild/BuildTools.zig b/src/androidbuild/BuildTools.zig new file mode 100644 index 0000000..736fd70 --- /dev/null +++ b/src/androidbuild/BuildTools.zig @@ -0,0 +1,76 @@ +//! Setup the path to various command-line tools available in: +//! - $ANDROID_HOME/build-tools/35.0.0 + +const std = @import("std"); +const Allocator = std.mem.Allocator; + +aapt2: []const u8, +zipalign: []const u8, +/// d8 is *.bat or shell script that requires "java"/"java.exe" to exist in your PATH +d8: []const u8, +/// apksigner is *.bat or shell script that requires "java"/"java.exe" to exist in your PATH +apksigner: []const u8, + +pub const empty: BuildTools = .{ + .aapt2 = &[0]u8{}, + .zipalign = &[0]u8{}, + .d8 = &[0]u8{}, + .apksigner = &[0]u8{}, +}; + +const BuildToolError = Allocator.Error || error{BuildToolFailed}; + +pub fn init(b: *std.Build, android_sdk_path: []const u8, build_tools_version: []const u8, errors: *std.ArrayList([]const u8)) BuildToolError!BuildTools { + const prev_errors_len = errors.items.len; + + // Get build tools path + // ie. $ANDROID_HOME/build-tools/35.0.0 + const build_tools_path = b.pathResolve(&[_][]const u8{ android_sdk_path, "build-tools", build_tools_version }); + + // TODO(jae): 2025-05-24 + // We could validate build_tool_version to ensure its 3 numbers with dots seperating + // ie. "35.0.0" + + // Check if build tools path is accessible + // ie. $ANDROID_HOME/build-tools/35.0.0 + std.fs.accessAbsolute(build_tools_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + const message = b.fmt("Android Build Tool version '{s}' not found. Install it via 'sdkmanager' or Android Studio.", .{ + build_tools_version, + }); + errors.append(message) catch @panic("OOM"); + }, + else => { + const message = b.fmt("Android Build Tool version '{s}' had unexpected error: {s}", .{ + build_tools_version, + @errorName(err), + }); + errors.append(message) catch @panic("OOM"); + }, + }; + if (errors.items.len != prev_errors_len) { + return error.BuildToolFailed; + } + + const host_os_tag = b.graph.host.result.os.tag; + const exe_suffix = if (host_os_tag == .windows) ".exe" else ""; + const bat_suffix = if (host_os_tag == .windows) ".bat" else ""; + return .{ + .aapt2 = b.pathResolve(&[_][]const u8{ + build_tools_path, b.fmt("aapt2{s}", .{exe_suffix}), + }), + .zipalign = b.pathResolve(&[_][]const u8{ + build_tools_path, b.fmt("zipalign{s}", .{exe_suffix}), + }), + // d8/apksigner are *.bat or shell scripts that require "java"/"java.exe" to exist in + // your PATH + .d8 = b.pathResolve(&[_][]const u8{ + build_tools_path, b.fmt("d8{s}", .{bat_suffix}), + }), + .apksigner = b.pathResolve(&[_][]const u8{ + build_tools_path, b.fmt("apksigner{s}", .{bat_suffix}), + }), + }; +} + +pub const BuildTools = @This(); diff --git a/src/androidbuild/Ndk.zig b/src/androidbuild/Ndk.zig new file mode 100644 index 0000000..2ea35bf --- /dev/null +++ b/src/androidbuild/Ndk.zig @@ -0,0 +1,172 @@ +//! Setup the path to various command-line tools available in: +//! - $ANDROID_HOME/ndk/29.0.13113456 + +const std = @import("std"); +const androidbuild = @import("androidbuild.zig"); + +const Allocator = std.mem.Allocator; +const ApiLevel = androidbuild.ApiLevel; + +/// ie. $ANDROID_HOME +android_sdk_path: []const u8, +/// ie. "27.0.12077973" +version: []const u8, +/// ie. "$ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot" +sysroot_path: []const u8, +/// ie. "$ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot/usr/include" +include_path: []const u8, + +pub const empty: Ndk = .{ + .android_sdk_path = &[0]u8{}, + .version = &[0]u8{}, + .sysroot_path = &[0]u8{}, + .include_path = &[0]u8{}, +}; + +const NdkError = Allocator.Error || error{NdkFailed}; + +pub fn init(b: *std.Build, android_sdk_path: []const u8, ndk_version: []const u8, errors: *std.ArrayList([]const u8)) NdkError!Ndk { + // Get NDK path + // ie. $ANDROID_HOME/ndk/27.0.12077973 + const android_ndk_path = b.fmt("{s}/ndk/{s}", .{ android_sdk_path, ndk_version }); + + const has_ndk: bool = blk: { + std.fs.accessAbsolute(android_ndk_path, .{}) catch |err| switch (err) { + error.FileNotFound => { + const message = b.fmt("Android NDK version '{s}' not found. Install it via 'sdkmanager' or Android Studio.", .{ + ndk_version, + }); + try errors.append(message); + break :blk false; + }, + else => { + const message = b.fmt("Android NDK version '{s}' had unexpected error: {s} ({s})", .{ + ndk_version, + @errorName(err), + android_ndk_path, + }); + try errors.append(message); + break :blk false; + }, + }; + break :blk true; + }; + if (!has_ndk) { + return error.NdkFailed; + } + + const host_os_tag = b.graph.host.result.os.tag; + const host_os_and_arch: [:0]const u8 = switch (host_os_tag) { + .windows => "windows-x86_64", + .linux => "linux-x86_64", + .macos => "darwin-x86_64", + else => @panic(b.fmt("unhandled operating system: {}", .{host_os_tag})), + }; + + // Get NDK sysroot path + // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot + const android_ndk_sysroot = b.fmt("{s}/ndk/{s}/toolchains/llvm/prebuilt/{s}/sysroot", .{ + android_sdk_path, + ndk_version, + host_os_and_arch, + }); + + // Check if NDK sysroot path is accessible + const has_ndk_sysroot = blk: { + std.fs.accessAbsolute(android_ndk_sysroot, .{}) catch |err| switch (err) { + error.FileNotFound => { + const message = b.fmt("Android NDK sysroot '{s}' had unexpected error. Missing at '{s}'", .{ + ndk_version, + android_ndk_sysroot, + }); + try errors.append(message); + break :blk false; + }, + else => { + const message = b.fmt("Android NDK sysroot '{s}' had unexpected error: {s}, at: '{s}'", .{ + ndk_version, + @errorName(err), + android_ndk_sysroot, + }); + try errors.append(message); + break :blk false; + }, + }; + break :blk true; + }; + if (!has_ndk_sysroot) { + return error.NdkFailed; + } + + return .{ + .android_sdk_path = android_sdk_path, + .version = ndk_version, + .sysroot_path = android_ndk_sysroot, + .include_path = b.fmt("{s}/usr/include", .{android_ndk_sysroot}), + }; +} + +pub fn validateApiLevel(ndk: *const Ndk, b: *std.Build, api_level: ApiLevel, errors: *std.ArrayList([]const u8)) void { + // Get root jar path + const root_jar = b.pathResolve(&[_][]const u8{ + ndk.android_sdk_path, + "platforms", + b.fmt("android-{d}", .{@intFromEnum(api_level)}), + "android.jar", + }); + + // Check if NDK sysroot/usr/lib/{target}/{api_level} path is accessible + _ = blk: { + // "x86" has existed since Android 4.1 (API version 16) + const x86_system_target = "i686-linux-android"; + const ndk_sysroot_target_api_version = b.fmt("{s}/usr/lib/{s}/{d}", .{ ndk.sysroot_path, x86_system_target, @intFromEnum(api_level) }); + std.fs.accessAbsolute(ndk_sysroot_target_api_version, .{}) catch |err| switch (err) { + error.FileNotFound => { + const message = b.fmt("Android NDK version '{s}' does not support API Level {d}. No folder at '{s}'", .{ + ndk.version, + @intFromEnum(api_level), + ndk_sysroot_target_api_version, + }); + errors.append(message) catch @panic("OOM"); + break :blk false; + }, + else => { + const message = b.fmt("Android NDK version '{s}' API Level {d} had unexpected error: {s}, at: '{s}'", .{ + ndk.version, + @intFromEnum(api_level), + @errorName(err), + ndk_sysroot_target_api_version, + }); + errors.append(message) catch @panic("OOM"); + break :blk false; + }, + }; + break :blk true; + }; + + // Check if platforms/android-{api-level}/android.jar exists + _ = blk: { + std.fs.accessAbsolute(root_jar, .{}) catch |err| switch (err) { + error.FileNotFound => { + const message = b.fmt("Android API level {d} not installed. Unable to find '{s}'", .{ + @intFromEnum(api_level), + root_jar, + }); + errors.append(message) catch @panic("OOM"); + break :blk false; + }, + else => { + const message = b.fmt("Android API level {d} had unexpected error: {s}, at: '{s}'", .{ + @intFromEnum(api_level), + @errorName(err), + root_jar, + }); + errors.append(message) catch @panic("OOM"); + break :blk false; + }, + }; + break :blk true; + }; +} + +pub const Ndk = @This(); diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig index fbd9def..7a06909 100644 --- a/src/androidbuild/androidbuild.zig +++ b/src/androidbuild/androidbuild.zig @@ -7,11 +7,14 @@ const LazyPath = std.Build.LazyPath; const log = std.log.scoped(.@"zig-android-sdk"); +/// Deprecated: Use ApiLevel +pub const APILevel = ApiLevel; + /// API Level is an enum the maps the Android OS version to the API level /// /// https://en.wikipedia.org/wiki/Android_version_history /// https://apilevels.com/ -pub const APILevel = enum(u32) { +pub const ApiLevel = enum(u32) { /// KitKat (2013) /// Android 4.4 = 19 android4_4 = 19, diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index 7a55350..a4e6983 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -3,6 +3,7 @@ const androidbuild = @import("androidbuild.zig"); const Tools = @import("tools.zig"); const BuiltinOptionsUpdate = @import("builtin_options_update.zig"); +const ApiLevel = androidbuild.ApiLevel; const KeyStore = androidbuild.KeyStore; const D8Glob = @import("d8glob.zig"); const getAndroidTriple = androidbuild.getAndroidTriple; @@ -37,6 +38,16 @@ artifacts: std.ArrayListUnmanaged(*Step.Compile), java_files: std.ArrayListUnmanaged(LazyPath), resources: std.ArrayListUnmanaged(Resource), +// TODO: Move these Options from androidbuild/tools.zig +// pub const Options = struct { +// /// ie. "35.0.0" +// build_tools_version: []const u8, +// /// ie. "27.0.12077973" +// ndk_version: []const u8, +// /// ie. .android15 = 35 (android 15 uses API version 35) +// api_level: ApiLevel, +// }; + pub fn create(b: *std.Build, tools: *const Tools) *Apk { const apk: *Apk = b.allocator.create(Apk) catch @panic("OOM"); apk.* = .{ @@ -116,7 +127,7 @@ pub fn setKeyStore(apk: *Apk, key_store: KeyStore) void { fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void { const b = apk.b; - const android_ndk_sysroot = apk.tools.ndk_sysroot_path; + const android_ndk_sysroot = apk.tools.ndk.sysroot_path; // get target const target: ResolvedTarget = module.resolved_target orelse { @@ -139,7 +150,7 @@ fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void { // ie. $ANDROID_HOME/ndk/{ndk_version}/sources/android/cpufeatures if (target.result.cpu.arch == .arm) { module.addIncludePath(.{ - .cwd_relative = b.fmt("{s}/ndk/{s}/sources/android/cpufeatures", .{ apk.tools.android_sdk_path, apk.tools.ndk_version }), + .cwd_relative = b.fmt("{s}/ndk/{s}/sources/android/cpufeatures", .{ apk.tools.android_sdk_path, apk.tools.ndk.version }), }); } @@ -241,6 +252,14 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { break :blk false; }; + // ie. "$ANDROID_HOME/Sdk/platforms/android-{api_level}/android.jar" + const root_jar = b.pathResolve(&[_][]const u8{ + apk.tools.android_sdk_path, + "platforms", + b.fmt("android-{d}", .{@intFromEnum(apk.tools.api_level)}), + "android.jar", + }); + // Make resources.apk from: // - resources.flat.zip (created from "aapt2 compile") // - res/values/strings.xml -> values_strings.arsc.flat @@ -255,7 +274,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { apk.tools.build_tools.aapt2, "link", "-I", // add an existing package to base include set - apk.tools.root_jar, + root_jar, }); aapt2link.setName(runNameContext("aapt2 link")); @@ -418,7 +437,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { continue; } const translate_c: *std.Build.Step.TranslateC = @fieldParentPtr("step", step); - translate_c.addIncludePath(.{ .cwd_relative = apk.tools.include_path }); + translate_c.addIncludePath(.{ .cwd_relative = apk.tools.ndk.include_path }); translate_c.addSystemIncludePath(.{ .cwd_relative = apk.tools.getSystemIncludePath(c_translate_target) }); }, else => continue, @@ -465,10 +484,10 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { "-encoding", "utf8", "-cp", - apk.tools.root_jar, - // NOTE(jae): 2024-09-19 - // Debug issues with the SDL.java classes - // "-Xlint:deprecation", + root_jar, + // NOTE(jae): 2024-09-19 + // Debug issues with the SDL.java classes + // "-Xlint:deprecation", }); javac_cmd.setName(runNameContext("javac")); javac_cmd.addArg("-d"); @@ -488,7 +507,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // ie. android_sdk/platforms/android-{api-level}/android.jar d8.addArg("--lib"); - d8.addArg(apk.tools.root_jar); + d8.addArg(root_jar); d8.addArg("--output"); const dex_output_dir = d8.addOutputDirectoryArg("android_dex"); @@ -730,7 +749,7 @@ fn applyLibLinkCppWorkaroundIssue19(apk: *Apk, artifact: *Step.Compile) void { const system_target = getAndroidTriple(artifact.root_module.resolved_target.?) catch |err| @panic(@errorName(err)); const lib_path: LazyPath = .{ - .cwd_relative = b.pathJoin(&.{ apk.tools.ndk_sysroot_path, "usr", "lib", system_target, "libc++abi.a" }), + .cwd_relative = b.pathJoin(&.{ apk.tools.ndk.sysroot_path, "usr", "lib", system_target, "libc++abi.a" }), }; const libcpp_workaround = b.addWriteFiles(); const libcppabi_dir = libcpp_workaround.addCopyFile(lib_path, "libc++abi_zig_workaround.a").dirname(); diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index 712606d..40e4f05 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -6,7 +6,7 @@ const androidbuild = @import("androidbuild.zig"); const RegistryWtf8 = @import("WindowsSdk.zig").RegistryWtf8; const windows = std.os.windows; -const APILevel = androidbuild.APILevel; +const ApiLevel = androidbuild.ApiLevel; const KeyStore = androidbuild.KeyStore; const getAndroidTriple = androidbuild.getAndroidTriple; const runNameContext = androidbuild.runNameContext; @@ -17,30 +17,24 @@ const AccessError = std.fs.Dir.AccessError; const Step = Build.Step; const ResolvedTarget = Build.ResolvedTarget; const LazyPath = std.Build.LazyPath; +const Apk = @import("apk.zig"); +const Ndk = @import("Ndk.zig"); +const BuildTools = @import("BuildTools.zig"); b: *Build, /// On most platforms this will map to the $ANDROID_HOME environment variable android_sdk_path: []const u8, /// ie. .android15 = 35 (android 15 uses API version 35) -api_level: APILevel, -/// ie. "27.0.12077973" -ndk_version: []const u8, -/// ie. "$ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot" -ndk_sysroot_path: []const u8, -/// ie. "$ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot/usr/include" -include_path: []const u8, -/// ie. "$ANDROID_HOME/Sdk/platforms/android-{api_level}/android.jar" -root_jar: []const u8, +api_level: ApiLevel, +/// Path to Native Development Kit, this includes various C-code headers, libraries, and more. +/// ie. $ANDROID_HOME/ndk/29.0.13113456 +ndk: Ndk, // $JDK_HOME, $JAVA_HOME or auto-discovered from java binaries found in $PATH jdk_path: []const u8, +/// Paths to Build Tools such as aapt2, zipalign /// ie. $ANDROID_HOME/build-tools/35.0.0 -build_tools: struct { - aapt2: []const u8, - zipalign: []const u8, - d8: []const u8, - apksigner: []const u8, -}, +build_tools: BuildTools, /// ie. $ANDROID_HOME/cmdline_tools/bin or $ANDROID_HOME/tools/bin /// /// Available to download at: https://developer.android.com/studio#command-line-tools-only @@ -75,17 +69,11 @@ pub const Options = struct { /// ie. "27.0.12077973" ndk_version: []const u8, /// ie. .android15 = 35 (android 15 uses API version 35) - api_level: APILevel, + api_level: ApiLevel, }; pub fn create(b: *std.Build, options: Options) *Tools { const host_os_tag = b.graph.host.result.os.tag; - const host_os_and_arch: [:0]const u8 = switch (host_os_tag) { - .windows => "windows-x86_64", - .linux => "linux-x86_64", - .macos => "darwin-x86_64", - else => @panic(b.fmt("unhandled operating system: {}", .{host_os_tag})), - }; // Discover tool paths var path_search = PathSearch.init(b.allocator, host_os_tag) catch |err| switch (err) { @@ -105,30 +93,6 @@ pub fn create(b: *std.Build, options: Options) *Tools { const android_sdk_path = path_search.findAndroidSDK(b.allocator) catch @panic("OOM"); const jdk_path = path_search.findJDK(b.allocator) catch @panic("OOM"); - // Get build tools path - // ie. $ANDROID_HOME/build-tools/35.0.0 - const build_tools_path = b.pathResolve(&[_][]const u8{ android_sdk_path, "build-tools", options.build_tools_version }); - - // Get NDK path - // ie. $ANDROID_HOME/ndk/27.0.12077973 - const android_ndk_path = b.fmt("{s}/ndk/{s}", .{ android_sdk_path, options.ndk_version }); - - // Get NDK sysroot path - // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot - const android_ndk_sysroot = b.fmt("{s}/ndk/{s}/toolchains/llvm/prebuilt/{s}/sysroot", .{ - android_sdk_path, - options.ndk_version, - host_os_and_arch, - }); - - // Get root jar path - const root_jar = b.pathResolve(&[_][]const u8{ - android_sdk_path, - "platforms", - b.fmt("android-{d}", .{@intFromEnum(options.api_level)}), - "android.jar", - }); - // Validate var errors = std.ArrayList([]const u8).init(b.allocator); defer errors.deinit(); @@ -188,131 +152,17 @@ pub fn create(b: *std.Build, options: Options) *Tools { break :cmdlineblk cmdline_tools; }; - { - // Check if build tools path is accessible - // ie. $ANDROID_HOME/build-tools/35.0.0 - std.fs.accessAbsolute(build_tools_path, .{}) catch |err| switch (err) { - error.FileNotFound => { - const message = b.fmt("Android Build Tool version '{s}' not found. Install it via 'sdkmanager' or Android Studio.", .{ - options.build_tools_version, - }); - errors.append(message) catch @panic("OOM"); - }, - else => { - const message = b.fmt("Android Build Tool version '{s}' had unexpected error: {s}", .{ - options.build_tools_version, - @errorName(err), - }); - errors.append(message) catch @panic("OOM"); - }, - }; - - // Check if NDK path is accessible - // ie. $ANDROID_HOME/ndk/27.0.12077973 - const has_ndk: bool = blk: { - std.fs.accessAbsolute(android_ndk_path, .{}) catch |err| switch (err) { - error.FileNotFound => { - const message = b.fmt("Android NDK version '{s}' not found. Install it via 'sdkmanager' or Android Studio.", .{ - options.ndk_version, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - else => { - const message = b.fmt("Android NDK version '{s}' had unexpected error: {s} ({s})", .{ - options.ndk_version, - @errorName(err), - android_ndk_path, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - }; - break :blk true; - }; - - // Check if NDK API level is accessible - if (has_ndk) { - // Check if NDK sysroot path is accessible - const has_ndk_sysroot = blk: { - std.fs.accessAbsolute(android_ndk_sysroot, .{}) catch |err| switch (err) { - error.FileNotFound => { - const message = b.fmt("Android NDK sysroot '{s}' had unexpected error. Missing at '{s}'", .{ - options.ndk_version, - android_ndk_sysroot, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - else => { - const message = b.fmt("Android NDK sysroot '{s}' had unexpected error: {s}, at: '{s}'", .{ - options.ndk_version, - @errorName(err), - android_ndk_sysroot, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - }; - break :blk true; - }; + const build_tools = BuildTools.init(b, android_sdk_path, options.build_tools_version, &errors) catch |err| switch (err) { + error.BuildToolFailed => BuildTools.empty, // fallthruogh and print all errors below + error.OutOfMemory => @panic("OOM"), + }; - // Check if NDK sysroot/usr/lib/{target}/{api_level} path is accessible - if (has_ndk_sysroot) { - _ = blk: { - // "x86" has existed since Android 4.1 (API version 16) - const x86_system_target = "i686-linux-android"; - const ndk_sysroot_target_api_version = b.fmt("{s}/usr/lib/{s}/{d}", .{ android_ndk_sysroot, x86_system_target, options.api_level }); - std.fs.accessAbsolute(android_ndk_sysroot, .{}) catch |err| switch (err) { - error.FileNotFound => { - const message = b.fmt("Android NDK version '{s}' does not support API Level {d}. No folder at '{s}'", .{ - options.ndk_version, - @intFromEnum(options.api_level), - ndk_sysroot_target_api_version, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - else => { - const message = b.fmt("Android NDK version '{s}' API Level {d} had unexpected error: {s}, at: '{s}'", .{ - options.ndk_version, - @intFromEnum(options.api_level), - @errorName(err), - ndk_sysroot_target_api_version, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - }; - break :blk true; - }; - } + const ndk = Ndk.init(b, android_sdk_path, options.ndk_version, &errors) catch |err| switch (err) { + error.NdkFailed => Ndk.empty, // fallthrough and print all errors below + error.OutOfMemory => @panic("OOM"), + }; + ndk.validateApiLevel(b, options.api_level, &errors); - // Check if platforms/android-{api-level}/android.jar exists - _ = blk: { - std.fs.accessAbsolute(root_jar, .{}) catch |err| switch (err) { - error.FileNotFound => { - const message = b.fmt("Android API level {d} not installed. Unable to find '{s}'", .{ - @intFromEnum(options.api_level), - root_jar, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - else => { - const message = b.fmt("Android API level {d} had unexpected error: {s}, at: '{s}'", .{ - @intFromEnum(options.api_level), - @errorName(err), - root_jar, - }); - errors.append(message) catch @panic("OOM"); - break :blk false; - }, - }; - break :blk true; - }; - } - } if (errors.items.len > 0) { printErrorsAndExit("unable to find required Android installation", errors.items); } @@ -325,27 +175,9 @@ pub fn create(b: *std.Build, options: Options) *Tools { .b = b, .android_sdk_path = android_sdk_path, .api_level = options.api_level, - .ndk_version = options.ndk_version, - .ndk_sysroot_path = android_ndk_sysroot, - .include_path = b.fmt("{s}/usr/include", .{tools.ndk_sysroot_path}), - .root_jar = root_jar, .jdk_path = jdk_path, - .build_tools = .{ - .aapt2 = b.pathResolve(&[_][]const u8{ - build_tools_path, b.fmt("aapt2{s}", .{exe_suffix}), - }), - .zipalign = b.pathResolve(&[_][]const u8{ - build_tools_path, b.fmt("zipalign{s}", .{exe_suffix}), - }), - // d8/apksigner are *.bat or shell scripts that require "java"/"java.exe" to exist in - // your PATH - .d8 = b.pathResolve(&[_][]const u8{ - build_tools_path, b.fmt("d8{s}", .{bat_suffix}), - }), - .apksigner = b.pathResolve(&[_][]const u8{ - build_tools_path, b.fmt("apksigner{s}", .{bat_suffix}), - }), - }, + .ndk = ndk, + .build_tools = build_tools, .cmdline_tools = .{ .lint = b.pathResolve(&[_][]const u8{ cmdline_tools_path, b.fmt("lint{s}", .{bat_suffix}), @@ -454,8 +286,8 @@ pub fn setLibCFile(tools: *const Tools, compile: *Step.Compile) void { b, system_target, tools.api_level, - tools.ndk_sysroot_path, - tools.ndk_version, + tools.ndk.sysroot_path, + tools.ndk.version, ); android_libc_path.addStepDependencies(&compile.step); compile.setLibCFile(android_libc_path); @@ -464,10 +296,10 @@ pub fn setLibCFile(tools: *const Tools, compile: *Step.Compile) void { pub fn getSystemIncludePath(tools: *const Tools, target: ResolvedTarget) []const u8 { const b = tools.b; const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err)); - return b.fmt("{s}/{s}", .{ tools.include_path, system_target }); + return b.fmt("{s}/{s}", .{ tools.ndk.include_path, system_target }); } -fn createLibC(b: *std.Build, system_target: []const u8, android_version: APILevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { +fn createLibC(b: *std.Build, system_target: []const u8, android_version: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { const libc_file_format = \\# Generated by zig-android-sdk. DO NOT EDIT. \\ From be2eaa0875f07962454f74cc2212bb086637b93d Mon Sep 17 00:00:00 2001 From: Jae B Date: Sat, 24 May 2025 23:50:30 +1000 Subject: [PATCH 02/16] refactor --- build.zig | 7 ++- examples/minimal/build.zig | 4 +- examples/raylib/build.zig | 36 +++++------- examples/sdl2/build.zig | 4 +- src/androidbuild/Ndk.zig | 19 +++--- src/androidbuild/androidbuild.zig | 1 + src/androidbuild/apk.zig | 97 ++++++++++++++++++++++--------- src/androidbuild/tools.zig | 52 +++++------------ 8 files changed, 120 insertions(+), 100 deletions(-) diff --git a/build.zig b/build.zig index 08206f3..d7bb23e 100644 --- a/build.zig +++ b/build.zig @@ -1,13 +1,13 @@ const std = @import("std"); const androidbuild = @import("src/androidbuild/androidbuild.zig"); -const Apk = @import("src/androidbuild/apk.zig"); // Expose Android build functionality for use in your build.zig // TODO: Make this public and deprecate Tools const Sdk = @import("src/androidbuild/tools.zig"); -pub const APK = Apk; // TODO(jae): 2025-03-13: Consider deprecating and using 'Apk' to be conventional to Zig +pub const Apk = @import("src/androidbuild/apk.zig"); + pub const APILevel = androidbuild.APILevel; // TODO(jae): 2025-03-13: Consider deprecating and using 'ApiLevel' to be conventional to Zig pub const standardTargets = androidbuild.standardTargets; @@ -15,11 +15,12 @@ pub const standardTargets = androidbuild.standardTargets; /// Deprecated: Use Sdk instead pub const Tools = @import("src/androidbuild/tools.zig"); - /// Deprecated: Use Sdk.Options instead. pub const ToolsOptions = Sdk.Options; /// Deprecated: Use Sdk.CreateKey instead. pub const CreateKey = Sdk.CreateKey; +/// Deprecated: Use Apk not APK +pub const APK = Apk; /// NOTE: As well as providing the "android" module this declaration is required so this can be imported by other build.zig files pub fn build(b: *std.Build) void { diff --git a/examples/minimal/build.zig b/examples/minimal/build.zig index 6df20bd..15b70e0 100644 --- a/examples/minimal/build.zig +++ b/examples/minimal/build.zig @@ -19,12 +19,12 @@ pub fn build(b: *std.Build) void { if (android_targets.len == 0) { break :blk null; } - const android_tools = android.Tools.create(b, .{ + const android_tools = android.Tools.create(b, .{}); + const apk = android.APK.create(b, android_tools, .{ .api_level = .android15, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", }); - const apk = android.APK.create(b, android_tools); const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); apk.setKeyStore(key_store_file); diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 4b054b8..bcb1ea7 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -1,10 +1,6 @@ const android = @import("android"); const std = @import("std"); -//This is targeting android version 10 / API level 29. -//Change the value here and in android/AndroidManifest.xml to target your desired API level -const android_version: android.APILevel = .android10; -const android_api = std.fmt.comptimePrint("{}", .{@intFromEnum(android_version)}); const exe_name = "raylib"; pub fn build(b: *std.Build) void { @@ -22,12 +18,12 @@ pub fn build(b: *std.Build) void { if (android_targets.len == 0) { break :blk null; } - const android_tools = android.Tools.create(b, .{ - .api_level = android_version, + const android_tools = android.Tools.create(b, .{}); + const apk = android.APK.create(b, android_tools, .{ + .api_level = .android10, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", }); - const apk = android.APK.create(b, android_tools); const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); apk.setKeyStore(key_store_file); @@ -51,26 +47,26 @@ pub fn build(b: *std.Build) void { lib.linkLibC(); b.installArtifact(lib); - const android_ndk_path = if(android_apk) |apk| (b.fmt("{s}/ndk/{s}", .{ apk.tools.android_sdk_path, apk.tools.ndk_version })) else ""; - const raylib_dep = if (target.result.abi.isAndroid()) ( - b.dependency("raylib_zig", .{ - .target = target, - .optimize = optimize, - .android_api_version = @as([]const u8, android_api), - .android_ndk = @as([]const u8, android_ndk_path), - })) else ( + const raylib_dep = if (android_apk) |apk| b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, - .shared = true - })); + .android_api_version = @as([]const u8, b.fmt("{}", .{@intFromEnum(apk.api_level)})), + .android_ndk = @as([]const u8, apk.ndk.path), + }) + else + b.dependency("raylib_zig", .{ + .target = target, + .optimize = optimize, + .shared = true, + }); + const raylib_artifact = raylib_dep.artifact("raylib"); lib.linkLibrary(raylib_artifact); const raylib_mod = raylib_dep.module("raylib"); lib.root_module.addImport("raylib", raylib_mod); - if (target.result.abi.isAndroid()) { - const apk: *android.APK = android_apk orelse @panic("Android APK should be initialized"); + if (android_apk) |apk| { const android_dep = b.dependency("android", .{ .optimize = optimize, .target = target, @@ -78,7 +74,7 @@ pub fn build(b: *std.Build) void { lib.root_module.linkSystemLibrary("android", .{ .preferred_link_mode = .dynamic }); lib.root_module.addImport("android", android_dep.module("android")); - const native_app_glue_dir: std.Build.LazyPath = .{ .cwd_relative = b.fmt("{s}/sources/android/native_app_glue", .{android_ndk_path}) }; + const native_app_glue_dir: std.Build.LazyPath = .{ .cwd_relative = b.fmt("{s}/sources/android/native_app_glue", .{apk.ndk.path}) }; lib.root_module.addCSourceFile(.{ .file = native_app_glue_dir.path(b, "android_native_app_glue.c") }); lib.root_module.addIncludePath(native_app_glue_dir); diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index 75342b6..ec0cf1e 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -20,7 +20,8 @@ pub fn build(b: *std.Build) void { if (android_targets.len == 0) { break :blk null; } - const android_tools = android.Tools.create(b, .{ + const android_tools = android.Tools.create(b, .{}); + const apk = android.Apk.create(b, android_tools, .{ .api_level = .android15, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", @@ -34,7 +35,6 @@ pub fn build(b: *std.Build) void { // - ndk/27.0.12077973/toolchains/llvm/prebuilt/{OS}-x86_64/sysroot/usr/include/android/hardware_buffer.h:322:42: // - error: expression is not an integral constant expression }); - const apk = android.APK.create(b, android_tools); const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); apk.setKeyStore(key_store_file); diff --git a/src/androidbuild/Ndk.zig b/src/androidbuild/Ndk.zig index 2ea35bf..9609f45 100644 --- a/src/androidbuild/Ndk.zig +++ b/src/androidbuild/Ndk.zig @@ -11,6 +11,8 @@ const ApiLevel = androidbuild.ApiLevel; android_sdk_path: []const u8, /// ie. "27.0.12077973" version: []const u8, +/// ie. "$ANDROID_HOME/ndk/{ndk_version}" +path: []const u8, /// ie. "$ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot" sysroot_path: []const u8, /// ie. "$ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot/usr/include" @@ -19,6 +21,7 @@ include_path: []const u8, pub const empty: Ndk = .{ .android_sdk_path = &[0]u8{}, .version = &[0]u8{}, + .path = &[0]u8{}, .sysroot_path = &[0]u8{}, .include_path = &[0]u8{}, }; @@ -65,7 +68,7 @@ pub fn init(b: *std.Build, android_sdk_path: []const u8, ndk_version: []const u8 // Get NDK sysroot path // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot - const android_ndk_sysroot = b.fmt("{s}/ndk/{s}/toolchains/llvm/prebuilt/{s}/sysroot", .{ + const ndk_sysroot = b.fmt("{s}/ndk/{s}/toolchains/llvm/prebuilt/{s}/sysroot", .{ android_sdk_path, ndk_version, host_os_and_arch, @@ -73,11 +76,11 @@ pub fn init(b: *std.Build, android_sdk_path: []const u8, ndk_version: []const u8 // Check if NDK sysroot path is accessible const has_ndk_sysroot = blk: { - std.fs.accessAbsolute(android_ndk_sysroot, .{}) catch |err| switch (err) { + std.fs.accessAbsolute(ndk_sysroot, .{}) catch |err| switch (err) { error.FileNotFound => { const message = b.fmt("Android NDK sysroot '{s}' had unexpected error. Missing at '{s}'", .{ ndk_version, - android_ndk_sysroot, + ndk_sysroot, }); try errors.append(message); break :blk false; @@ -86,7 +89,7 @@ pub fn init(b: *std.Build, android_sdk_path: []const u8, ndk_version: []const u8 const message = b.fmt("Android NDK sysroot '{s}' had unexpected error: {s}, at: '{s}'", .{ ndk_version, @errorName(err), - android_ndk_sysroot, + ndk_sysroot, }); try errors.append(message); break :blk false; @@ -98,12 +101,14 @@ pub fn init(b: *std.Build, android_sdk_path: []const u8, ndk_version: []const u8 return error.NdkFailed; } - return .{ + const ndk: Ndk = .{ .android_sdk_path = android_sdk_path, + .path = android_ndk_path, .version = ndk_version, - .sysroot_path = android_ndk_sysroot, - .include_path = b.fmt("{s}/usr/include", .{android_ndk_sysroot}), + .sysroot_path = ndk_sysroot, + .include_path = b.fmt("{s}/usr/include", .{ndk_sysroot}), }; + return ndk; } pub fn validateApiLevel(ndk: *const Ndk, b: *std.Build, api_level: ApiLevel, errors: *std.ArrayList([]const u8)) void { diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig index 7a06909..7e2e770 100644 --- a/src/androidbuild/androidbuild.zig +++ b/src/androidbuild/androidbuild.zig @@ -15,6 +15,7 @@ pub const APILevel = ApiLevel; /// https://en.wikipedia.org/wiki/Android_version_history /// https://apilevels.com/ pub const ApiLevel = enum(u32) { + none = 0, /// KitKat (2013) /// Android 4.4 = 19 android4_4 = 19, diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index a4e6983..705c499 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -3,9 +3,12 @@ const androidbuild = @import("androidbuild.zig"); const Tools = @import("tools.zig"); const BuiltinOptionsUpdate = @import("builtin_options_update.zig"); +const Ndk = @import("Ndk.zig"); +const BuildTools = @import("BuildTools.zig"); +const D8Glob = @import("d8glob.zig"); + const ApiLevel = androidbuild.ApiLevel; const KeyStore = androidbuild.KeyStore; -const D8Glob = @import("d8glob.zig"); const getAndroidTriple = androidbuild.getAndroidTriple; const runNameContext = androidbuild.runNameContext; const printErrorsAndExit = androidbuild.printErrorsAndExit; @@ -30,29 +33,54 @@ pub const Resource = union(enum) { b: *std.Build, tools: *const Tools, - +/// Path to Native Development Kit, this includes various C-code headers, libraries, and more. +/// ie. $ANDROID_HOME/ndk/29.0.13113456 +ndk: Ndk, +/// Paths to Build Tools such as aapt2, zipalign +/// ie. $ANDROID_HOME/build-tools/35.0.0 +build_tools: BuildTools, + +api_level: ApiLevel, key_store: ?KeyStore, - android_manifest: ?LazyPath, artifacts: std.ArrayListUnmanaged(*Step.Compile), java_files: std.ArrayListUnmanaged(LazyPath), resources: std.ArrayListUnmanaged(Resource), // TODO: Move these Options from androidbuild/tools.zig -// pub const Options = struct { -// /// ie. "35.0.0" -// build_tools_version: []const u8, -// /// ie. "27.0.12077973" -// ndk_version: []const u8, -// /// ie. .android15 = 35 (android 15 uses API version 35) -// api_level: ApiLevel, -// }; - -pub fn create(b: *std.Build, tools: *const Tools) *Apk { +pub const Options = struct { + /// ie. "35.0.0" + build_tools_version: []const u8, + /// ie. "27.0.12077973" + ndk_version: []const u8, + /// ie. .android15 = 35 (android 15 uses API version 35) + api_level: ApiLevel, +}; + +pub fn create(b: *std.Build, tools: *const Tools, options: Options) *Apk { + var errors = std.ArrayList([]const u8).init(b.allocator); + defer errors.deinit(); + + const build_tools = BuildTools.init(b, tools.android_sdk_path, options.build_tools_version, &errors) catch |err| switch (err) { + error.BuildToolFailed => BuildTools.empty, // fallthruogh and print all errors below + error.OutOfMemory => @panic("OOM"), + }; + const ndk = Ndk.init(b, tools.android_sdk_path, options.ndk_version, &errors) catch |err| switch (err) { + error.NdkFailed => Ndk.empty, // fallthrough and print all errors below + error.OutOfMemory => @panic("OOM"), + }; + ndk.validateApiLevel(b, options.api_level, &errors); + if (errors.items.len > 0) { + printErrorsAndExit("unable to find required Android installation", errors.items); + } + const apk: *Apk = b.allocator.create(Apk) catch @panic("OOM"); apk.* = .{ .b = b, .tools = tools, + .ndk = ndk, + .build_tools = build_tools, + .api_level = options.api_level, .key_store = null, .android_manifest = null, .artifacts = .empty, @@ -127,7 +155,7 @@ pub fn setKeyStore(apk: *Apk, key_store: KeyStore) void { fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void { const b = apk.b; - const android_ndk_sysroot = apk.tools.ndk.sysroot_path; + const android_ndk_sysroot = apk.ndk.sysroot_path; // get target const target: ResolvedTarget = module.resolved_target orelse { @@ -139,7 +167,7 @@ fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void { // These *must* be in order of API version, then architecture, then non-arch specific otherwise // when starting an *.so from Android or an emulator you can get an error message like this: // - "java.lang.UnsatisfiedLinkError: dlopen failed: TLS symbol "_ZZN8gwp_asan15getThreadLocalsEvE6Locals" in dlopened" - const android_api_version: u32 = @intFromEnum(apk.tools.api_level); + const android_api_version: u32 = @intFromEnum(apk.api_level); // NOTE(jae): 2025-03-09 // Resolve issue where building SDL2 gets the following error for 'arm-linux-androideabi' @@ -150,7 +178,7 @@ fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void { // ie. $ANDROID_HOME/ndk/{ndk_version}/sources/android/cpufeatures if (target.result.cpu.arch == .arm) { module.addIncludePath(.{ - .cwd_relative = b.fmt("{s}/ndk/{s}/sources/android/cpufeatures", .{ apk.tools.android_sdk_path, apk.tools.ndk.version }), + .cwd_relative = b.fmt("{s}/ndk/{s}/sources/android/cpufeatures", .{ apk.tools.android_sdk_path, apk.ndk.version }), }); } @@ -256,7 +284,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { const root_jar = b.pathResolve(&[_][]const u8{ apk.tools.android_sdk_path, "platforms", - b.fmt("android-{d}", .{@intFromEnum(apk.tools.api_level)}), + b.fmt("android-{d}", .{@intFromEnum(apk.api_level)}), "android.jar", }); @@ -271,7 +299,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // Snapshot: http://web.archive.org/web/20241001070128/https://developer.android.com/tools/aapt2#aapt2_element_hierarchy const resources_apk: LazyPath = blk: { const aapt2link = b.addSystemCommand(&[_][]const u8{ - apk.tools.build_tools.aapt2, + apk.build_tools.aapt2, "link", "-I", // add an existing package to base include set root_jar, @@ -295,7 +323,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { aapt2link.addArgs(&[_][]const u8{ "--target-sdk-version", - b.fmt("{d}", .{@intFromEnum(apk.tools.api_level)}), + b.fmt("{d}", .{@intFromEnum(apk.api_level)}), }); // NOTE(jae): 2024-10-02 @@ -329,7 +357,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { switch (resource) { .directory => |resource_directory| { const aapt2compile = b.addSystemCommand(&[_][]const u8{ - apk.tools.build_tools.aapt2, + apk.build_tools.aapt2, "compile", }); aapt2compile.setName(runNameContext("aapt2 compile [dir]")); @@ -355,7 +383,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { const package_name_file = blk: { const aapt2packagename = b.addSystemCommand(&[_][]const u8{ - apk.tools.build_tools.aapt2, + apk.build_tools.aapt2, "dump", "packagename", }); @@ -437,8 +465,8 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { continue; } const translate_c: *std.Build.Step.TranslateC = @fieldParentPtr("step", step); - translate_c.addIncludePath(.{ .cwd_relative = apk.tools.ndk.include_path }); - translate_c.addSystemIncludePath(.{ .cwd_relative = apk.tools.getSystemIncludePath(c_translate_target) }); + translate_c.addIncludePath(.{ .cwd_relative = apk.ndk.include_path }); + translate_c.addSystemIncludePath(.{ .cwd_relative = apk.getSystemIncludePath(c_translate_target) }); }, else => continue, } @@ -459,7 +487,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { updateSharedLibraryOptions(artifact); } } - apk.tools.setLibCFile(artifact); + apk.setLibCFile(artifact); apk.addLibraryPaths(artifact.root_module); artifact.linkLibC(); @@ -501,7 +529,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // From d8.bat // call "%java_exe%" %javaOpts% -cp "%jarpath%" com.android.tools.r8.D8 %params% const d8 = b.addSystemCommand(&[_][]const u8{ - apk.tools.build_tools.d8, + apk.build_tools.d8, }); d8.setName(runNameContext("d8")); @@ -641,7 +669,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // Align contents of .apk (zip) const aligned_apk_file: LazyPath = blk: { var zipalign = b.addSystemCommand(&[_][]const u8{ - apk.tools.build_tools.zipalign, + apk.build_tools.zipalign, }); zipalign.setName(runNameContext("zipalign")); @@ -672,7 +700,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // Sign apk const signed_apk_file: LazyPath = blk: { const apksigner = b.addSystemCommand(&[_][]const u8{ - apk.tools.build_tools.apksigner, + apk.build_tools.apksigner, "sign", }); apksigner.setName(runNameContext("apksigner")); @@ -689,6 +717,17 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { return install_apk; } +fn getSystemIncludePath(apk: *Apk, target: ResolvedTarget) []const u8 { + const b = apk.b; + const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err)); + return b.fmt("{s}/{s}", .{ apk.ndk.include_path, system_target }); +} + +fn setLibCFile(apk: *Apk, compile: *Step.Compile) void { + const tools = apk.tools; + tools.setLibCFile(compile, apk.api_level, apk.ndk.sysroot_path, apk.ndk.version); +} + fn updateLinkObjects(apk: *Apk, root_artifact: *Step.Compile, so_dir: []const u8, raw_top_level_apk_files: *Step.WriteFile) void { const b = apk.b; for (root_artifact.root_module.link_objects.items) |link_object| { @@ -711,7 +750,7 @@ fn updateLinkObjects(apk: *Apk, root_artifact: *Step.Compile, so_dir: []const u8 if (artifact.root_module.link_libc == true or artifact.root_module.link_libcpp == true) { - apk.tools.setLibCFile(artifact); + apk.setLibCFile(artifact); } // Add library paths to find "android", "log", etc @@ -749,7 +788,7 @@ fn applyLibLinkCppWorkaroundIssue19(apk: *Apk, artifact: *Step.Compile) void { const system_target = getAndroidTriple(artifact.root_module.resolved_target.?) catch |err| @panic(@errorName(err)); const lib_path: LazyPath = .{ - .cwd_relative = b.pathJoin(&.{ apk.tools.ndk.sysroot_path, "usr", "lib", system_target, "libc++abi.a" }), + .cwd_relative = b.pathJoin(&.{ apk.ndk.sysroot_path, "usr", "lib", system_target, "libc++abi.a" }), }; const libcpp_workaround = b.addWriteFiles(); const libcppabi_dir = libcpp_workaround.addCopyFile(lib_path, "libc++abi_zig_workaround.a").dirname(); diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index 40e4f05..880b68a 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -25,16 +25,8 @@ b: *Build, /// On most platforms this will map to the $ANDROID_HOME environment variable android_sdk_path: []const u8, -/// ie. .android15 = 35 (android 15 uses API version 35) -api_level: ApiLevel, -/// Path to Native Development Kit, this includes various C-code headers, libraries, and more. -/// ie. $ANDROID_HOME/ndk/29.0.13113456 -ndk: Ndk, // $JDK_HOME, $JAVA_HOME or auto-discovered from java binaries found in $PATH jdk_path: []const u8, -/// Paths to Build Tools such as aapt2, zipalign -/// ie. $ANDROID_HOME/build-tools/35.0.0 -build_tools: BuildTools, /// ie. $ANDROID_HOME/cmdline_tools/bin or $ANDROID_HOME/tools/bin /// /// Available to download at: https://developer.android.com/studio#command-line-tools-only @@ -64,15 +56,15 @@ java_tools: struct { pub const ToolsOptions = Options; pub const Options = struct { - /// ie. "35.0.0" - build_tools_version: []const u8, - /// ie. "27.0.12077973" - ndk_version: []const u8, - /// ie. .android15 = 35 (android 15 uses API version 35) - api_level: ApiLevel, + // /// ie. "35.0.0" + // build_tools_version: []const u8, + // /// ie. "27.0.12077973" + // ndk_version: []const u8, + // /// ie. .android15 = 35 (android 15 uses API version 35) + // api_level: ApiLevel, }; -pub fn create(b: *std.Build, options: Options) *Tools { +pub fn create(b: *std.Build, _: Options) *Tools { const host_os_tag = b.graph.host.result.os.tag; // Discover tool paths @@ -152,17 +144,6 @@ pub fn create(b: *std.Build, options: Options) *Tools { break :cmdlineblk cmdline_tools; }; - const build_tools = BuildTools.init(b, android_sdk_path, options.build_tools_version, &errors) catch |err| switch (err) { - error.BuildToolFailed => BuildTools.empty, // fallthruogh and print all errors below - error.OutOfMemory => @panic("OOM"), - }; - - const ndk = Ndk.init(b, android_sdk_path, options.ndk_version, &errors) catch |err| switch (err) { - error.NdkFailed => Ndk.empty, // fallthrough and print all errors below - error.OutOfMemory => @panic("OOM"), - }; - ndk.validateApiLevel(b, options.api_level, &errors); - if (errors.items.len > 0) { printErrorsAndExit("unable to find required Android installation", errors.items); } @@ -174,10 +155,7 @@ pub fn create(b: *std.Build, options: Options) *Tools { tools.* = .{ .b = b, .android_sdk_path = android_sdk_path, - .api_level = options.api_level, .jdk_path = jdk_path, - .ndk = ndk, - .build_tools = build_tools, .cmdline_tools = .{ .lint = b.pathResolve(&[_][]const u8{ cmdline_tools_path, b.fmt("lint{s}", .{bat_suffix}), @@ -276,27 +254,27 @@ pub fn createKeyStore(tools: *const Tools, options: CreateKey) KeyStore { // TODO: Consider making this be setup on "create" and then we just pass in the "android_libc_writefile" // anytime setLibCFile is called -pub fn setLibCFile(tools: *const Tools, compile: *Step.Compile) void { +pub fn setLibCFile(tools: *const Tools, compile: *Step.Compile, android_api_level: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) void { const b = tools.b; const target: ResolvedTarget = compile.root_module.resolved_target orelse @panic("no 'target' set on Android module"); + // const android_api_level: ApiLevel = @enumFromInt(target.result.os.version_range.linux.android); + // if (android_api_level == .none) @panic("no 'android' api level set on target"); const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err)); const android_libc_path = createLibC( b, system_target, - tools.api_level, - tools.ndk.sysroot_path, - tools.ndk.version, + android_api_level, + ndk_sysroot_path, + ndk_version, ); android_libc_path.addStepDependencies(&compile.step); compile.setLibCFile(android_libc_path); } -pub fn getSystemIncludePath(tools: *const Tools, target: ResolvedTarget) []const u8 { - const b = tools.b; - const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err)); - return b.fmt("{s}/{s}", .{ tools.ndk.include_path, system_target }); +pub fn getSystemIncludePath(_: *const Tools, _: ResolvedTarget) []const u8 { + @compileError("getSystemIncludePath has moved from Tools to Apk"); } fn createLibC(b: *std.Build, system_target: []const u8, android_version: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { From 1c4b868fb2611efdb57733e5debd88c4ec6a0fed Mon Sep 17 00:00:00 2001 From: Jae B Date: Sat, 24 May 2025 23:53:05 +1000 Subject: [PATCH 03/16] more work --- README.md | 8 ++++++-- examples/minimal/build.zig | 11 +++++------ examples/raylib/build.zig | 4 ++-- examples/sdl2/build.zig | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index c1ec23a..d0adda9 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,12 @@ zig build -Dandroid=true const android = @import("android"); pub fn build(b: *std.Build) !void { - const android_tools = android.Tools.create(b, ...); - const apk = android.APK.create(b, android_tools); + const android_tools = android.Tools.create(b, .{}); + const apk = android.Apk.create(b, android_tools, .{ + .api_level = .android15, + .build_tools_version = "35.0.1", + .ndk_version = "29.0.13113456", + }); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); apk.addJavaSourceFile(.{ .file = b.path("android/src/NativeInvocationHandler.java") }); diff --git a/examples/minimal/build.zig b/examples/minimal/build.zig index 15b70e0..6ff7fbe 100644 --- a/examples/minimal/build.zig +++ b/examples/minimal/build.zig @@ -15,12 +15,11 @@ pub fn build(b: *std.Build) void { android_targets; // If building with Android, initialize the tools / build - const android_apk: ?*android.APK = blk: { - if (android_targets.len == 0) { - break :blk null; - } + const android_apk: ?*android.Apk = blk: { + if (android_targets.len == 0) break :blk null; + const android_tools = android.Tools.create(b, .{}); - const apk = android.APK.create(b, android_tools, .{ + const apk = android.Apk.create(b, android_tools, .{ .api_level = .android15, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", @@ -62,7 +61,7 @@ pub fn build(b: *std.Build) void { // NOTE: Android has different CPU targets so you need to build a version of your // code for x86, x86_64, arm, arm64 and more if (target.result.abi.isAndroid()) { - const apk: *android.APK = android_apk orelse @panic("Android APK should be initialized"); + const apk: *android.Apk = android_apk orelse @panic("Android APK should be initialized"); const android_dep = b.dependency("android", .{ .optimize = optimize, .target = target, diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index bcb1ea7..ffdecf7 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -14,12 +14,12 @@ pub fn build(b: *std.Build) void { else android_targets; - const android_apk: ?*android.APK = blk: { + const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) { break :blk null; } const android_tools = android.Tools.create(b, .{}); - const apk = android.APK.create(b, android_tools, .{ + const apk = android.Apk.create(b, android_tools, .{ .api_level = .android10, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index ec0cf1e..bea44e2 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -16,7 +16,7 @@ pub fn build(b: *std.Build) void { android_targets; // If building with Android, initialize the tools / build - const android_apk: ?*android.APK = blk: { + const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) { break :blk null; } @@ -113,7 +113,7 @@ pub fn build(b: *std.Build) void { // NOTE: Android has different CPU targets so you need to build a version of your // code for x86, x86_64, arm, arm64 and more if (target.result.abi.isAndroid()) { - const apk: *android.APK = android_apk orelse @panic("Android APK should be initialized"); + const apk: *android.Apk = android_apk orelse @panic("Android APK should be initialized"); const android_dep = b.dependency("android", .{ .optimize = optimize, .target = target, From 05e31fc621a7f5bdd09bcb966f5895da34fba5d6 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 00:00:47 +1000 Subject: [PATCH 04/16] simplify --- examples/raylib/build.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index ffdecf7..3862d79 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -71,14 +71,12 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .target = target, }); - lib.root_module.linkSystemLibrary("android", .{ .preferred_link_mode = .dynamic }); lib.root_module.addImport("android", android_dep.module("android")); + lib.root_module.linkSystemLibrary("android", .{}); const native_app_glue_dir: std.Build.LazyPath = .{ .cwd_relative = b.fmt("{s}/sources/android/native_app_glue", .{apk.ndk.path}) }; lib.root_module.addCSourceFile(.{ .file = native_app_glue_dir.path(b, "android_native_app_glue.c") }); lib.root_module.addIncludePath(native_app_glue_dir); - - lib.root_module.linkSystemLibrary("log", .{ .preferred_link_mode = .dynamic }); apk.addArtifact(lib); } else { const exe = b.addExecutable(.{ .name = exe_name, .optimize = optimize, .root_module = lib_mod }); From 5193982bc9e3f3a19e18697b268d860b7a314c57 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 00:20:36 +1000 Subject: [PATCH 05/16] touch-up --- build.zig | 6 ++-- examples/minimal/build.zig | 7 ++--- examples/raylib/build.zig | 6 ++-- examples/sdl2/build.zig | 6 ++-- src/androidbuild/apk.zig | 14 +++++---- src/androidbuild/tools.zig | 61 +++++++++++++++----------------------- 6 files changed, 44 insertions(+), 56 deletions(-) diff --git a/build.zig b/build.zig index d7bb23e..60275fa 100644 --- a/build.zig +++ b/build.zig @@ -3,11 +3,9 @@ const androidbuild = @import("src/androidbuild/androidbuild.zig"); // Expose Android build functionality for use in your build.zig -// TODO: Make this public and deprecate Tools -const Sdk = @import("src/androidbuild/tools.zig"); - +// TODO: rename tools.zig to Sdk.zig +pub const Sdk = @import("src/androidbuild/tools.zig"); pub const Apk = @import("src/androidbuild/apk.zig"); - pub const APILevel = androidbuild.APILevel; // TODO(jae): 2025-03-13: Consider deprecating and using 'ApiLevel' to be conventional to Zig pub const standardTargets = androidbuild.standardTargets; diff --git a/examples/minimal/build.zig b/examples/minimal/build.zig index 6ff7fbe..ae51689 100644 --- a/examples/minimal/build.zig +++ b/examples/minimal/build.zig @@ -18,14 +18,13 @@ pub fn build(b: *std.Build) void { const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) break :blk null; - const android_tools = android.Tools.create(b, .{}); - const apk = android.Apk.create(b, android_tools, .{ + const android_sdk = android.Sdk.create(b, .{}); + const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", }); - - const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); + const key_store_file = android_sdk.createKeyStore(android.CreateKey.example()); apk.setKeyStore(key_store_file); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 3862d79..ef6d9ef 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -18,14 +18,14 @@ pub fn build(b: *std.Build) void { if (android_targets.len == 0) { break :blk null; } - const android_tools = android.Tools.create(b, .{}); - const apk = android.Apk.create(b, android_tools, .{ + const android_sdk = android.Sdk.create(b, .{}); + const apk = android_sdk.createApk(.{ .api_level = .android10, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", }); - const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); + const key_store_file = android_sdk.createKeyStore(android.CreateKey.example()); apk.setKeyStore(key_store_file); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index bea44e2..9c420a6 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -20,8 +20,8 @@ pub fn build(b: *std.Build) void { if (android_targets.len == 0) { break :blk null; } - const android_tools = android.Tools.create(b, .{}); - const apk = android.Apk.create(b, android_tools, .{ + const android_sdk = android.Sdk.create(b, .{}); + const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", @@ -36,7 +36,7 @@ pub fn build(b: *std.Build) void { // - error: expression is not an integral constant expression }); - const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); + const key_store_file = android_sdk.createKeyStore(android.CreateKey.example()); apk.setKeyStore(key_store_file); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index 705c499..d8dab3b 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -32,14 +32,15 @@ pub const Resource = union(enum) { }; b: *std.Build, -tools: *const Tools, +tools: *Tools, /// Path to Native Development Kit, this includes various C-code headers, libraries, and more. /// ie. $ANDROID_HOME/ndk/29.0.13113456 ndk: Ndk, /// Paths to Build Tools such as aapt2, zipalign /// ie. $ANDROID_HOME/build-tools/35.0.0 build_tools: BuildTools, - +/// API Level is the target Android API Level +/// ie. .android15 = 35 (android 15 uses API version 35) api_level: ApiLevel, key_store: ?KeyStore, android_manifest: ?LazyPath, @@ -47,7 +48,6 @@ artifacts: std.ArrayListUnmanaged(*Step.Compile), java_files: std.ArrayListUnmanaged(LazyPath), resources: std.ArrayListUnmanaged(Resource), -// TODO: Move these Options from androidbuild/tools.zig pub const Options = struct { /// ie. "35.0.0" build_tools_version: []const u8, @@ -57,7 +57,9 @@ pub const Options = struct { api_level: ApiLevel, }; -pub fn create(b: *std.Build, tools: *const Tools, options: Options) *Apk { +pub fn create(tools: *Tools, options: Options) *Apk { + const b = tools.b; + var errors = std.ArrayList([]const u8).init(b.allocator); defer errors.deinit(); @@ -725,7 +727,9 @@ fn getSystemIncludePath(apk: *Apk, target: ResolvedTarget) []const u8 { fn setLibCFile(apk: *Apk, compile: *Step.Compile) void { const tools = apk.tools; - tools.setLibCFile(compile, apk.api_level, apk.ndk.sysroot_path, apk.ndk.version); + const android_libc_path = tools.createOrGetLibCFile(compile, apk.api_level, apk.ndk.sysroot_path, apk.ndk.version); + android_libc_path.addStepDependencies(&compile.step); + compile.setLibCFile(android_libc_path); } fn updateLinkObjects(apk: *Apk, root_artifact: *Step.Compile, so_dir: []const u8, raw_top_level_apk_files: *Step.WriteFile) void { diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index 880b68a..7cc5a22 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -52,19 +52,12 @@ java_tools: struct { keytool: []const u8, }, -/// Deprecated: Use Options instead. -pub const ToolsOptions = Options; - -pub const Options = struct { - // /// ie. "35.0.0" - // build_tools_version: []const u8, - // /// ie. "27.0.12077973" - // ndk_version: []const u8, - // /// ie. .android15 = 35 (android 15 uses API version 35) - // api_level: ApiLevel, -}; +/// Reserved for future use +const Options = struct {}; + +pub fn create(b: *std.Build, options: Options) *Sdk { + _ = options; -pub fn create(b: *std.Build, _: Options) *Tools { const host_os_tag = b.graph.host.result.os.tag; // Discover tool paths @@ -151,7 +144,7 @@ pub fn create(b: *std.Build, _: Options) *Tools { const exe_suffix = if (host_os_tag == .windows) ".exe" else ""; const bat_suffix = if (host_os_tag == .windows) ".bat" else ""; - const tools: *Tools = b.allocator.create(Tools) catch @panic("OOM"); + const tools: *Sdk = b.allocator.create(Sdk) catch @panic("OOM"); tools.* = .{ .b = b, .android_sdk_path = android_sdk_path, @@ -178,6 +171,10 @@ pub fn create(b: *std.Build, _: Options) *Tools { return tools; } +pub fn createApk(tools: *Sdk, options: Apk.Options) *Apk { + return Apk.create(tools, options); +} + pub const CreateKey = struct { pub const Algorithm = enum { rsa, @@ -212,7 +209,7 @@ pub const CreateKey = struct { } }; -pub fn createKeyStore(tools: *const Tools, options: CreateKey) KeyStore { +pub fn createKeyStore(tools: *const Sdk, options: CreateKey) KeyStore { const b = tools.b; const keytool = b.addSystemCommand(&.{ // https://docs.oracle.com/en/java/javase/17/docs/specs/man/keytool.html @@ -252,32 +249,18 @@ pub fn createKeyStore(tools: *const Tools, options: CreateKey) KeyStore { }; } -// TODO: Consider making this be setup on "create" and then we just pass in the "android_libc_writefile" -// anytime setLibCFile is called -pub fn setLibCFile(tools: *const Tools, compile: *Step.Compile, android_api_level: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) void { +pub fn createOrGetLibCFile(tools: *Sdk, compile: *Step.Compile, android_api_level: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { const b = tools.b; const target: ResolvedTarget = compile.root_module.resolved_target orelse @panic("no 'target' set on Android module"); - // const android_api_level: ApiLevel = @enumFromInt(target.result.os.version_range.linux.android); - // if (android_api_level == .none) @panic("no 'android' api level set on target"); const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err)); - const android_libc_path = createLibC( - b, - system_target, - android_api_level, - ndk_sysroot_path, - ndk_version, - ); - android_libc_path.addStepDependencies(&compile.step); - compile.setLibCFile(android_libc_path); -} - -pub fn getSystemIncludePath(_: *const Tools, _: ResolvedTarget) []const u8 { - @compileError("getSystemIncludePath has moved from Tools to Apk"); -} + // NOTE(jae): 2025-05-25 + // Tried just utilizing the target version here but it was very low (14) and there was no NDK libraries that went + // back that far for NDK version "29.0.13113456" + // const android_api_level: ApiLevel = @enumFromInt(target.result.os.version_range.linux.android); + // if (android_api_level == .none) @panic("no 'android' api level set on target"); -fn createLibC(b: *std.Build, system_target: []const u8, android_version: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { const libc_file_format = \\# Generated by zig-android-sdk. DO NOT EDIT. \\ @@ -308,7 +291,7 @@ fn createLibC(b: *std.Build, system_target: []const u8, android_version: ApiLeve const include_dir = b.fmt("{s}/usr/include", .{ndk_sysroot_path}); const sys_include_dir = b.fmt("{s}/usr/include/{s}", .{ ndk_sysroot_path, system_target }); - const crt_dir = b.fmt("{s}/usr/lib/{s}/{d}", .{ ndk_sysroot_path, system_target, @intFromEnum(android_version) }); + const crt_dir = b.fmt("{s}/usr/lib/{s}/{d}", .{ ndk_sysroot_path, system_target, @intFromEnum(android_api_level) }); const libc_file_contents = b.fmt(libc_file_format, .{ .include_dir = include_dir, @@ -316,13 +299,17 @@ fn createLibC(b: *std.Build, system_target: []const u8, android_version: ApiLeve .crt_dir = crt_dir, }); - const filename = b.fmt("android-libc_target-{s}_version-{}_ndk-{s}.conf", .{ system_target, @intFromEnum(android_version), ndk_version }); + const filename = b.fmt("android-libc_target-{s}_version-{}_ndk-{s}.conf", .{ system_target, @intFromEnum(android_api_level), ndk_version }); const write_file = b.addWriteFiles(); const android_libc_path = write_file.add(filename, libc_file_contents); return android_libc_path; } +pub fn getSystemIncludePath(_: *const Sdk, _: ResolvedTarget) []const u8 { + @compileError("getSystemIncludePath has moved from Tools to Apk"); +} + /// Search JDK_HOME, and then JAVA_HOME fn getJDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { const jdkHome = std.process.getEnvVarOwned(allocator, "JDK_HOME") catch |err| switch (err) { @@ -614,4 +601,4 @@ const PathSearch = struct { } }; -const Tools = @This(); +const Sdk = @This(); From 9e8020eecc09e4110b4ff11459e8af0859ac642a Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 00:25:50 +1000 Subject: [PATCH 06/16] simplify more logic --- build.zig | 10 +++++----- examples/minimal/build.zig | 2 +- examples/raylib/build.zig | 2 +- examples/sdl2/build.zig | 2 +- src/androidbuild/tools.zig | 18 ++++++++---------- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/build.zig b/build.zig index 60275fa..ec16dc9 100644 --- a/build.zig +++ b/build.zig @@ -12,13 +12,13 @@ pub const standardTargets = androidbuild.standardTargets; // Deprecated exposes fields /// Deprecated: Use Sdk instead -pub const Tools = @import("src/androidbuild/tools.zig"); -/// Deprecated: Use Sdk.Options instead. -pub const ToolsOptions = Sdk.Options; +pub const Tools = @compileError("Use android.Sdk instead of android.Tools"); +/// Deprecated: Use Apk.Options instead. +pub const ToolsOptions = @compileError("Use android.Sdk.Options instead of android.Apk.Options with the Sdk.createApk method"); /// Deprecated: Use Sdk.CreateKey instead. -pub const CreateKey = Sdk.CreateKey; +pub const CreateKey = @compileError("Use android.Sdk.CreateKey instead of android.CreateKey"); /// Deprecated: Use Apk not APK -pub const APK = Apk; +pub const APK = @compileError("Use android.Apk instead of android.APK"); /// NOTE: As well as providing the "android" module this declaration is required so this can be imported by other build.zig files pub fn build(b: *std.Build) void { diff --git a/examples/minimal/build.zig b/examples/minimal/build.zig index ae51689..645d6f8 100644 --- a/examples/minimal/build.zig +++ b/examples/minimal/build.zig @@ -24,7 +24,7 @@ pub fn build(b: *std.Build) void { .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", }); - const key_store_file = android_sdk.createKeyStore(android.CreateKey.example()); + const key_store_file = android_sdk.createKeyStore(.example); apk.setKeyStore(key_store_file); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index ef6d9ef..046ecd9 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { .ndk_version = "29.0.13113456", }); - const key_store_file = android_sdk.createKeyStore(android.CreateKey.example()); + const key_store_file = android_sdk.createKeyStore(.example); apk.setKeyStore(key_store_file); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index 9c420a6..4fd5d78 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -36,7 +36,7 @@ pub fn build(b: *std.Build) void { // - error: expression is not an integral constant expression }); - const key_store_file = android_sdk.createKeyStore(android.CreateKey.example()); + const key_store_file = android_sdk.createKeyStore(.example); apk.setKeyStore(key_store_file); apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); apk.addResourceDirectory(b.path("android/res")); diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index 7cc5a22..b2a7774 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -197,16 +197,14 @@ pub const CreateKey = struct { distinguished_name: []const u8, /// Generates an example key that you can use for debugging your application locally - pub fn example() @This() { - return .{ - .alias = "default", - .password = "example_password", - .algorithm = .rsa, - .key_size_in_bits = 4096, - .validity_in_days = 10_000, - .distinguished_name = "CN=example.com, OU=ID, O=Example, L=Doe, S=Jane, C=GB", - }; - } + pub const example: CreateKey = .{ + .alias = "default", + .password = "example_password", + .algorithm = .rsa, + .key_size_in_bits = 4096, + .validity_in_days = 10_000, + .distinguished_name = "CN=example.com, OU=ID, O=Example, L=Doe, S=Jane, C=GB", + }; }; pub fn createKeyStore(tools: *const Sdk, options: CreateKey) KeyStore { From f80aab494260b8a9cbf07913deabfc647aecca25 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 00:39:04 +1000 Subject: [PATCH 07/16] more work --- build.zig | 6 ++++-- src/androidbuild/androidbuild.zig | 8 -------- src/androidbuild/apk.zig | 9 +++------ src/androidbuild/tools.zig | 11 ++++++++++- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/build.zig b/build.zig index ec16dc9..3a1cb84 100644 --- a/build.zig +++ b/build.zig @@ -6,11 +6,13 @@ const androidbuild = @import("src/androidbuild/androidbuild.zig"); // TODO: rename tools.zig to Sdk.zig pub const Sdk = @import("src/androidbuild/tools.zig"); pub const Apk = @import("src/androidbuild/apk.zig"); -pub const APILevel = androidbuild.APILevel; // TODO(jae): 2025-03-13: Consider deprecating and using 'ApiLevel' to be conventional to Zig +pub const ApiLevel = androidbuild.ApiLevel; pub const standardTargets = androidbuild.standardTargets; -// Deprecated exposes fields +// Deprecated exposed fields +/// Deprecated: Use ApiLevel +pub const APILevel = @compileError("use android.ApiLevel instead of android.APILevel"); /// Deprecated: Use Sdk instead pub const Tools = @compileError("Use android.Sdk instead of android.Tools"); /// Deprecated: Use Apk.Options instead. diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig index 7e2e770..0f7901b 100644 --- a/src/androidbuild/androidbuild.zig +++ b/src/androidbuild/androidbuild.zig @@ -7,9 +7,6 @@ const LazyPath = std.Build.LazyPath; const log = std.log.scoped(.@"zig-android-sdk"); -/// Deprecated: Use ApiLevel -pub const APILevel = ApiLevel; - /// API Level is an enum the maps the Android OS version to the API level /// /// https://en.wikipedia.org/wiki/Android_version_history @@ -47,11 +44,6 @@ pub const ApiLevel = enum(u32) { _, }; -pub const KeyStore = struct { - file: LazyPath, - password: []const u8, -}; - pub fn getAndroidTriple(target: ResolvedTarget) error{InvalidAndroidTarget}![]const u8 { if (!target.result.abi.isAndroid()) return error.InvalidAndroidTarget; return switch (target.result.cpu.arch) { diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index d8dab3b..6af9c6e 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -7,8 +7,8 @@ const Ndk = @import("Ndk.zig"); const BuildTools = @import("BuildTools.zig"); const D8Glob = @import("d8glob.zig"); +const KeyStore = Tools.KeyStore; const ApiLevel = androidbuild.ApiLevel; -const KeyStore = androidbuild.KeyStore; const getAndroidTriple = androidbuild.getAndroidTriple; const runNameContext = androidbuild.runNameContext; const printErrorsAndExit = androidbuild.printErrorsAndExit; @@ -150,7 +150,7 @@ pub fn addJavaSourceFiles(apk: *Apk, options: AddJavaSourceFilesOptions) void { /// This is required run on an Android device. /// /// If you want to just use a temporary key for local development, do something like this: -/// - apk.setKeyStore(android_tools.createKeyStore(android.CreateKey.example())); +/// - apk.setKeyStore(android_sdk.createKeyStore(.example); pub fn setKeyStore(apk: *Apk, key_store: KeyStore) void { apk.key_store = key_store; } @@ -205,10 +205,7 @@ pub fn addInstallApk(apk: *Apk) *Step.InstallFile { fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { const b = apk.b; - const key_store: KeyStore = apk.key_store orelse .{ - .file = .{ .cwd_relative = "" }, - .password = "", - }; + const key_store: KeyStore = apk.key_store orelse .empty; // validate { diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index b2a7774..d2af883 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -7,7 +7,6 @@ const RegistryWtf8 = @import("WindowsSdk.zig").RegistryWtf8; const windows = std.os.windows; const ApiLevel = androidbuild.ApiLevel; -const KeyStore = androidbuild.KeyStore; const getAndroidTriple = androidbuild.getAndroidTriple; const runNameContext = androidbuild.runNameContext; const printErrorsAndExit = androidbuild.printErrorsAndExit; @@ -460,6 +459,16 @@ fn getAndroidSDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 return &[0]u8{}; } +pub const KeyStore = struct { + file: LazyPath, + password: []const u8, + + pub const empty: KeyStore = .{ + .file = .{ .cwd_relative = "" }, + .password = "", + }; +}; + /// Searches your PATH environment variable directories for adb, jarsigner, etc const PathSearch = struct { allocator: std.mem.Allocator, From 94c68372e940728414df2c1280018ff942f4b42f Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 01:03:28 +1000 Subject: [PATCH 08/16] more --- README.md | 4 ++-- src/androidbuild/tools.zig | 47 ++++++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d0adda9..0c89aa1 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ zig build -Dandroid=true const android = @import("android"); pub fn build(b: *std.Build) !void { - const android_tools = android.Tools.create(b, .{}); - const apk = android.Apk.create(b, android_tools, .{ + const android_sdk = android.Sdk.create(b, .{}); + const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", .ndk_version = "29.0.13113456", diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index d2af883..5d4bbbd 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -34,6 +34,7 @@ cmdline_tools: struct { /// lint [flags] /// See documentation: https://developer.android.com/studio/write/lint#commandline lint: []const u8, + sdkmanager: []const u8, }, /// Binaries provided by the JDK that usually exist in: /// - Non-Windows: $JAVA_HOME/bin @@ -143,8 +144,8 @@ pub fn create(b: *std.Build, options: Options) *Sdk { const exe_suffix = if (host_os_tag == .windows) ".exe" else ""; const bat_suffix = if (host_os_tag == .windows) ".bat" else ""; - const tools: *Sdk = b.allocator.create(Sdk) catch @panic("OOM"); - tools.* = .{ + const sdk: *Sdk = b.allocator.create(Sdk) catch @panic("OOM"); + sdk.* = .{ .b = b, .android_sdk_path = android_sdk_path, .jdk_path = jdk_path, @@ -152,8 +153,9 @@ pub fn create(b: *std.Build, options: Options) *Sdk { .lint = b.pathResolve(&[_][]const u8{ cmdline_tools_path, b.fmt("lint{s}", .{bat_suffix}), }), - // NOTE(jae): 2024-09-28 - // Consider adding sdkmanager.bat so you can do something like "zig build sdkmanager -- {args}" + .sdkmanager = b.pathResolve(&[_][]const u8{ + cmdline_tools_path, b.fmt("sdkmanager{s}", .{bat_suffix}), + }), }, .java_tools = .{ .jar = b.pathResolve(&[_][]const u8{ @@ -167,11 +169,32 @@ pub fn create(b: *std.Build, options: Options) *Sdk { }), }, }; - return tools; + return sdk; +} + +pub fn createApk(sdk: *Sdk, options: Apk.Options) *Apk { + return Apk.create(sdk, options); } -pub fn createApk(tools: *Sdk, options: Apk.Options) *Apk { - return Apk.create(tools, options); +// TODO: Consider adding step to run: sdkmanager --install "ndk;21.3.6528147" +// pub fn installNdkVersion(ndk_version: []const u8) *Step { +// } + +/// EXPERIMENTAL: Allows invoking the Android SDK manager +/// ie. zig build -Dandroid sdkmanager -- --help +pub fn addSdkManagerStep(sdk: *Sdk) void { + const b = sdk.b; + const sdkmanager_step = b.step("sdkmanager", "Run the Android SDK Manager"); + const args = b.args orelse &.{}; + const sdkmanager = b.addSystemCommand(&.{sdk.cmdline_tools.sdkmanager}); + sdkmanager.setEnvironmentVariable("SKIP_JDK_VERSION_CHECK", "1"); + if (b.verbose) { + sdkmanager.addArg("--verbose"); + } + sdkmanager_step.dependOn(&sdkmanager.step); + for (args) |arg| { + sdkmanager.addArg(arg); + } } pub const CreateKey = struct { @@ -206,11 +229,11 @@ pub const CreateKey = struct { }; }; -pub fn createKeyStore(tools: *const Sdk, options: CreateKey) KeyStore { - const b = tools.b; +pub fn createKeyStore(sdk: *const Sdk, options: CreateKey) KeyStore { + const b = sdk.b; const keytool = b.addSystemCommand(&.{ // https://docs.oracle.com/en/java/javase/17/docs/specs/man/keytool.html - tools.java_tools.keytool, + sdk.java_tools.keytool, "-genkey", "-v", }); @@ -303,10 +326,6 @@ pub fn createOrGetLibCFile(tools: *Sdk, compile: *Step.Compile, android_api_leve return android_libc_path; } -pub fn getSystemIncludePath(_: *const Sdk, _: ResolvedTarget) []const u8 { - @compileError("getSystemIncludePath has moved from Tools to Apk"); -} - /// Search JDK_HOME, and then JAVA_HOME fn getJDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { const jdkHome = std.process.getEnvVarOwned(allocator, "JDK_HOME") catch |err| switch (err) { From 6f06ea4054bcd4044a6b11e68b5a9a135721f780 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 01:08:58 +1000 Subject: [PATCH 09/16] remove old make 0.13.0 support --- src/androidbuild/builtin_options_update.zig | 23 ++++----------------- src/androidbuild/d8glob.zig | 19 ++--------------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/src/androidbuild/builtin_options_update.zig b/src/androidbuild/builtin_options_update.zig index 6b01636..b85f574 100644 --- a/src/androidbuild/builtin_options_update.zig +++ b/src/androidbuild/builtin_options_update.zig @@ -19,16 +19,13 @@ options: *Options, package_name_stdout: LazyPath, pub fn create(owner: *std.Build, options: *Options, package_name_stdout: LazyPath) void { - const builtin_options_update = owner.allocator.create(@This()) catch @panic("OOM"); + const builtin_options_update = owner.allocator.create(BuiltinOptionsUpdate) catch @panic("OOM"); builtin_options_update.* = .{ .step = Step.init(.{ .id = base_id, .name = androidbuild.runNameContext("builtin_options_update"), .owner = owner, - .makeFn = comptime if (std.mem.eql(u8, builtin.zig_version_string, "0.13.0")) - make013 - else - makeLatest, + .makeFn = make, }), .options = options, .package_name_stdout = package_name_stdout, @@ -39,21 +36,9 @@ pub fn create(owner: *std.Build, options: *Options, package_name_stdout: LazyPat package_name_stdout.addStepDependencies(&builtin_options_update.step); } -/// make for zig 0.13.0 -fn make013(step: *Step, prog_node: std.Progress.Node) !void { - _ = prog_node; // autofix - try make(step); -} - -/// make for zig 0.14.0+ -fn makeLatest(step: *Step, options: Build.Step.MakeOptions) !void { - _ = options; // autofix - try make(step); -} - -fn make(step: *Step) !void { +fn make(step: *Step, _: Build.Step.MakeOptions) !void { const b = step.owner; - const builtin_options_update: *@This() = @fieldParentPtr("step", step); + const builtin_options_update: *BuiltinOptionsUpdate = @fieldParentPtr("step", step); const options = builtin_options_update.options; const package_name_path = builtin_options_update.package_name_stdout.getPath2(b, step); diff --git a/src/androidbuild/d8glob.zig b/src/androidbuild/d8glob.zig index 635d8f4..63b898e 100644 --- a/src/androidbuild/d8glob.zig +++ b/src/androidbuild/d8glob.zig @@ -31,10 +31,7 @@ pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { .id = base_id, .name = androidbuild.runNameContext("d8glob"), .owner = owner, - .makeFn = comptime if (std.mem.eql(u8, builtin.zig_version_string, "0.13.0")) - make013 - else - makeLatest, + .makeFn = make, }), .run = run, .dir = dir, @@ -45,19 +42,7 @@ pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void { dir.addStepDependencies(&glob.step); } -/// make for zig 0.13.0 -fn make013(step: *Step, prog_node: std.Progress.Node) !void { - _ = prog_node; // autofix - try make(step); -} - -/// make for zig 0.14.0+ -fn makeLatest(step: *Step, options: Build.Step.MakeOptions) !void { - _ = options; // autofix - try make(step); -} - -fn make(step: *Step) !void { +fn make(step: *Step, _: Build.Step.MakeOptions) !void { const b = step.owner; const arena = b.allocator; const glob: *@This() = @fieldParentPtr("step", step); From 7c20466cf9fe022b421638c3308243ba9cf1b36b Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 12:34:14 +1000 Subject: [PATCH 10/16] add ability to run with adb install --- examples/minimal/build.zig | 12 ++++++--- examples/raylib/build.zig | 11 ++++++-- examples/sdl2/build.zig | 12 ++++++--- src/androidbuild/tools.zig | 52 +++++++++++++++++++++++++++++++++++++- 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/examples/minimal/build.zig b/examples/minimal/build.zig index 645d6f8..cb30b84 100644 --- a/examples/minimal/build.zig +++ b/examples/minimal/build.zig @@ -14,11 +14,10 @@ pub fn build(b: *std.Build) void { else android_targets; - // If building with Android, initialize the tools / build + const android_sdk = android.Sdk.create(b, .{}); const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) break :blk null; - const android_sdk = android.Sdk.create(b, .{}); const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", @@ -80,6 +79,13 @@ pub fn build(b: *std.Build) void { } } if (android_apk) |apk| { - apk.installApk(); + const installed_apk = apk.addInstallApk(); + b.getInstallStep().dependOn(&installed_apk.step); + + const run_step = b.step("run", "Install and run the application on an Android device"); + const adb_install = android_sdk.addAdbInstall(installed_apk.source); + const adb_start = android_sdk.addAdbStart("com.zig.minimal/android.app.NativeActivity"); + adb_start.step.dependOn(&adb_install.step); + run_step.dependOn(&adb_start.step); } } diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 046ecd9..d13e2eb 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -14,11 +14,11 @@ pub fn build(b: *std.Build) void { else android_targets; + const android_sdk = android.Sdk.create(b, .{}); const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) { break :blk null; } - const android_sdk = android.Sdk.create(b, .{}); const apk = android_sdk.createApk(.{ .api_level = .android10, .build_tools_version = "35.0.1", @@ -88,6 +88,13 @@ pub fn build(b: *std.Build) void { } } if (android_apk) |apk| { - apk.installApk(); + const installed_apk = apk.addInstallApk(); + b.getInstallStep().dependOn(&installed_apk.step); + + const run_step = b.step("run", "Install and run the application on an Android device"); + const adb_install = android_sdk.addAdbInstall(installed_apk.source); + const adb_start = android_sdk.addAdbStart("com.zig.raylib/android.app.NativeActivity"); + adb_start.step.dependOn(&adb_install.step); + run_step.dependOn(&adb_start.step); } } diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index 4fd5d78..908ca7a 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -15,12 +15,11 @@ pub fn build(b: *std.Build) void { else android_targets; - // If building with Android, initialize the tools / build + const android_sdk = android.Sdk.create(b, .{}); const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) { break :blk null; } - const android_sdk = android.Sdk.create(b, .{}); const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", @@ -133,6 +132,13 @@ pub fn build(b: *std.Build) void { } } if (android_apk) |apk| { - apk.installApk(); + const installed_apk = apk.addInstallApk(); + b.getInstallStep().dependOn(&installed_apk.step); + + const run_step = b.step("run", "Install and run the application on an Android device"); + const adb_install = android_sdk.addAdbInstall(installed_apk.source); + const adb_start = android_sdk.addAdbStart("com.zig.sdl2/com.zig.sdl2.ZigSDLActivity"); + adb_start.step.dependOn(&adb_install.step); + run_step.dependOn(&adb_start.step); } } diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index 5d4bbbd..eda06e5 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -24,8 +24,12 @@ b: *Build, /// On most platforms this will map to the $ANDROID_HOME environment variable android_sdk_path: []const u8, -// $JDK_HOME, $JAVA_HOME or auto-discovered from java binaries found in $PATH +/// $JDK_HOME, $JAVA_HOME or auto-discovered from java binaries found in $PATH jdk_path: []const u8, +/// ie. $ANDROID_HOME/platform-tools +platform_tools: struct { + adb: []const u8, +}, /// ie. $ANDROID_HOME/cmdline_tools/bin or $ANDROID_HOME/tools/bin /// /// Available to download at: https://developer.android.com/studio#command-line-tools-only @@ -141,6 +145,8 @@ pub fn create(b: *std.Build, options: Options) *Sdk { printErrorsAndExit("unable to find required Android installation", errors.items); } + const platform_tools_path = b.pathResolve(&[_][]const u8{ android_sdk_path, "platform-tools" }); + const exe_suffix = if (host_os_tag == .windows) ".exe" else ""; const bat_suffix = if (host_os_tag == .windows) ".bat" else ""; @@ -149,6 +155,11 @@ pub fn create(b: *std.Build, options: Options) *Sdk { .b = b, .android_sdk_path = android_sdk_path, .jdk_path = jdk_path, + .platform_tools = .{ + .adb = b.pathResolve(&[_][]const u8{ + platform_tools_path, b.fmt("adb{s}", .{exe_suffix}), + }), + }, .cmdline_tools = .{ .lint = b.pathResolve(&[_][]const u8{ cmdline_tools_path, b.fmt("lint{s}", .{bat_suffix}), @@ -180,6 +191,45 @@ pub fn createApk(sdk: *Sdk, options: Apk.Options) *Apk { // pub fn installNdkVersion(ndk_version: []const u8) *Step { // } +/// Start an installed application on your Android device or emulator. +/// To install an APK first see "addAdbInstall" +/// +/// ie. +/// - "adb shell am start -S -W -n com.zig.minimal/android.app.NativeActivity" +/// - "adb shell am start -S -W -n com.zig.sdl2/com.zig.sdl2.ZigSDLActivity" +pub fn addAdbStart(sdk: *Sdk, package_name_and_java_entry: []const u8) *Step.Run { + const b = sdk.b; + if (sdk.platform_tools.adb.len == 0) { + @panic("Cannot call addAdbStart as 'adb' is not installed"); + } + // TODO: Improve this to be its own special Step that can auto-detect the "com.zig.sdl2/com.zig.sdl2.ZigSDLActivity" data + const adb_shell_start = b.addSystemCommand(&.{ sdk.platform_tools.adb, "shell", "am", "start", "-S", "-W", "-n", package_name_and_java_entry }); + return adb_shell_start; +} + +/// Install an APK onto your Android device or emulator +/// ie. "adb install ./zig-out/bin/minimal.apk" +pub fn adbInstall(sdk: *Sdk, apk: LazyPath) void { + const b = sdk.b; + const adb_install = sdk.addAdbInstall(apk); + b.getInstallStep().dependOn(&adb_install.step); +} + +/// Install an APK onto your Android device or emulator +/// ie. "adb install ./zig-out/bin/minimal.apk" +pub fn addAdbInstall(sdk: *Sdk, apk: LazyPath) *Step.Run { + const b = sdk.b; + if (sdk.platform_tools.adb.len == 0) { + @panic("Cannot call addInstallApk as 'adb' is not installed"); + } + const adb_install = b.addSystemCommand(&.{ + sdk.platform_tools.adb, + "install", + }); + adb_install.addFileArg(apk); + return adb_install; +} + /// EXPERIMENTAL: Allows invoking the Android SDK manager /// ie. zig build -Dandroid sdkmanager -- --help pub fn addSdkManagerStep(sdk: *Sdk) void { From da1149c9c995570b527c126ca6aa0af805992aa3 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 12:56:30 +1000 Subject: [PATCH 11/16] document --- src/androidbuild/tools.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index eda06e5..9b6a02e 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -418,12 +418,14 @@ fn getAndroidSDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 .windows => { // First, see if SdkPath in the registry is set // - Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Android Studio - "SdkPath" - // - Computer\KHEY_CURRENT_USER\SOFTWARE\Android Studio - "SdkPath" + // - Computer\KHEY_CURRENT_USER\SOFTWARE\Android Studio - "SdkPath" const android_studio_sdk_path: []const u8 = blk: { for ([_]windows.HKEY{ windows.HKEY_CURRENT_USER, windows.HKEY_LOCAL_MACHINE }) |hkey| { const key = RegistryWtf8.openKey(hkey, "SOFTWARE", .{}) catch |err| switch (err) { error.KeyNotFound => continue, }; + // NOTE(jae): 2025-05-25 - build.txt file says "AI-243.24978.46.2431.13208083" + // For my install, "SdkPath" is an empty string, so this may not be used anymore. const sdk_path = key.getString(allocator, "Android Studio", "SdkPath") catch |err| switch (err) { error.StringNotFound, error.ValueNameNotFound, error.NotAString => continue, error.OutOfMemory => return error.OutOfMemory, From 897f3d466a889c544967309ab1f575d6ef2e3563 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 13:11:18 +1000 Subject: [PATCH 12/16] more touch ups --- examples/minimal/README.md | 9 ++++++++- examples/raylib/README.md | 15 ++++++++------- examples/sdl2/README.md | 13 +++++++------ src/androidbuild/tools.zig | 29 +++++++++++++++-------------- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/examples/minimal/README.md b/examples/minimal/README.md index 3af710b..db9ae39 100644 --- a/examples/minimal/README.md +++ b/examples/minimal/README.md @@ -2,6 +2,13 @@ As of 2024-09-19, this is a thrown together, very quick copy-paste of the minimal example from the original [ZigAndroidTemplate](https://github.com/ikskuh/ZigAndroidTemplate/blob/master/examples/minimal/main.zig) repository. +### Build and run natively on your operating system or install/run on Android device + +```sh +zig build run # Native +zig build run -Dandroid # Android +``` + ### Build, install to test one target against a local emulator and run ```sh @@ -13,7 +20,7 @@ adb shell am start -S -W -n com.zig.minimal/android.app.NativeActivity ### Build and install for all supported Android targets ```sh -zig build -Dandroid=true +zig build -Dandroid adb install ./zig-out/bin/minimal.apk ``` diff --git a/examples/raylib/README.md b/examples/raylib/README.md index f61d924..fc95210 100644 --- a/examples/raylib/README.md +++ b/examples/raylib/README.md @@ -7,6 +7,13 @@ ld.lld: warning: /.zig-cache/o/4227869d730f094811a7cdaaab535797 ``` You can ignore this error for now. +### Build and run natively on your operating system or install/run on Android device + +```sh +zig build run # Native +zig build run -Dandroid # Android +``` + ### Build, install to test one target against a local emulator and run ```sh @@ -18,16 +25,10 @@ adb shell am start -S -W -n com.zig.raylib/android.app.NativeActivity ### Build and install for all supported Android targets ```sh -zig build -Dandroid=true +zig build -Dandroid adb install ./zig-out/bin/raylib.apk ``` -### Build and run natively on your operating system - -```sh -zig build run -``` - ### Uninstall your application If installing your application fails with something like: diff --git a/examples/sdl2/README.md b/examples/sdl2/README.md index 1f66fbf..aadc921 100644 --- a/examples/sdl2/README.md +++ b/examples/sdl2/README.md @@ -2,6 +2,13 @@ This is a copy-paste of [Andrew Kelly's SDL Zig Demo](https://github.com/andrewrk/sdl-zig-demo) but running on Android. The build is setup so you can also target your native operating system as well. +### Build and run natively on your operating system or install/run on Android device + +```sh +zig build run # Native +zig build run -Dandroid # Android +``` + ### Build, install to test one target against a local emulator and run ```sh @@ -17,12 +24,6 @@ zig build -Dandroid=true adb install ./zig-out/bin/sdl-zig-demo.apk ``` -### Build and run natively on your operating system - -```sh -zig build run -``` - ### Uninstall your application If installing your application fails with something like: diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index 9b6a02e..aadc45d 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const androidbuild = @import("androidbuild.zig"); +const Allocator = std.mem.Allocator; /// Used for reading install locations from the registry const RegistryWtf8 = @import("WindowsSdk.zig").RegistryWtf8; @@ -378,24 +379,24 @@ pub fn createOrGetLibCFile(tools: *Sdk, compile: *Step.Compile, android_api_leve /// Search JDK_HOME, and then JAVA_HOME fn getJDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { - const jdkHome = std.process.getEnvVarOwned(allocator, "JDK_HOME") catch |err| switch (err) { + const jdk_home = std.process.getEnvVarOwned(allocator, "JDK_HOME") catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.EnvironmentVariableNotFound => &[0]u8{}, // Windows-only error.InvalidWtf8 => @panic("JDK_HOME environment variable is invalid UTF-8"), }; - if (jdkHome.len > 0) { - return jdkHome; + if (jdk_home.len > 0) { + return jdk_home; } - const javaHome = std.process.getEnvVarOwned(allocator, "JAVA_HOME") catch |err| switch (err) { + const java_home = std.process.getEnvVarOwned(allocator, "JAVA_HOME") catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.EnvironmentVariableNotFound => &[0]u8{}, // Windows-only error.InvalidWtf8 => @panic("JAVA_HOME environment variable is invalid UTF-8"), }; - if (javaHome.len > 0) { - return javaHome; + if (java_home.len > 0) { + return java_home; } return &[0]u8{}; @@ -403,14 +404,14 @@ fn getJDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { /// Caller must free returned memory fn getAndroidSDKPath(allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { - const androidHome = std.process.getEnvVarOwned(allocator, "ANDROID_HOME") catch |err| switch (err) { + const android_home = std.process.getEnvVarOwned(allocator, "ANDROID_HOME") catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.EnvironmentVariableNotFound => &[0]u8{}, // Windows-only error.InvalidWtf8 => @panic("ANDROID_HOME environment variable is invalid UTF-8"), }; - if (androidHome.len > 0) { - return androidHome; + if (android_home.len > 0) { + return android_home; } // Check for Android Studio @@ -567,8 +568,8 @@ const PathSearch = struct { // setup binaries to search for const exe_suffix = if (host_os_tag == .windows) ".exe" else ""; - const adb = std.mem.concat(allocator, u8, &.{ "adb", exe_suffix }) catch |err| return err; - const jarsigner = std.mem.concat(allocator, u8, &.{ "jarsigner", exe_suffix }) catch |err| return err; + const adb = try std.mem.concat(allocator, u8, &.{ "adb", exe_suffix }); + const jarsigner = try std.mem.concat(allocator, u8, &.{ "jarsigner", exe_suffix }); const path_it = std.mem.splitScalar(u8, path_env, ';'); return .{ @@ -586,7 +587,7 @@ const PathSearch = struct { } /// Get the Android SDK Path, the caller owns the memory - pub fn findAndroidSDK(self: *PathSearch, allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { + pub fn findAndroidSDK(self: *PathSearch, allocator: std.mem.Allocator) Allocator.Error![]const u8 { if (self.android_sdk_path == null) { // Iterate over PATH environment folders until we either hit the end or the Android SDK folder try self.getNext(.androidsdk); @@ -598,7 +599,7 @@ const PathSearch = struct { } /// Get the JDK Path, the caller owns the memory - pub fn findJDK(self: *PathSearch, allocator: std.mem.Allocator) error{OutOfMemory}![]const u8 { + pub fn findJDK(self: *PathSearch, allocator: std.mem.Allocator) Allocator.Error![]const u8 { if (self.jdk_path == null) { // Iterate over PATH environment folders until we either hit the end or the Android SDK folder try self.getNext(.jdk); @@ -614,7 +615,7 @@ const PathSearch = struct { jdk, }; - fn getNext(self: *PathSearch, path: PathType) error{OutOfMemory}!void { + fn getNext(self: *PathSearch, path: PathType) Allocator.Error!void { const allocator = self.allocator; while (self.path_it.next()) |path_item| { if (path_item.len == 0) continue; From bb698e24f0ebd831de4422c2a114ce86cb9aa574 Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 13:16:35 +1000 Subject: [PATCH 13/16] more fixes --- examples/minimal/build.zig | 3 ++- examples/raylib/build.zig | 8 ++++---- examples/sdl2/build.zig | 8 ++++---- src/androidbuild/apk.zig | 30 +++++++++++++++--------------- src/androidbuild/tools.zig | 4 ++-- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/examples/minimal/build.zig b/examples/minimal/build.zig index cb30b84..3653b34 100644 --- a/examples/minimal/build.zig +++ b/examples/minimal/build.zig @@ -14,10 +14,10 @@ pub fn build(b: *std.Build) void { else android_targets; - const android_sdk = android.Sdk.create(b, .{}); const android_apk: ?*android.Apk = blk: { if (android_targets.len == 0) break :blk null; + const android_sdk = android.Sdk.create(b, .{}); const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", @@ -82,6 +82,7 @@ pub fn build(b: *std.Build) void { const installed_apk = apk.addInstallApk(); b.getInstallStep().dependOn(&installed_apk.step); + const android_sdk = apk.sdk; const run_step = b.step("run", "Install and run the application on an Android device"); const adb_install = android_sdk.addAdbInstall(installed_apk.source); const adb_start = android_sdk.addAdbStart("com.zig.minimal/android.app.NativeActivity"); diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index d13e2eb..9f0e81c 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -14,11 +14,10 @@ pub fn build(b: *std.Build) void { else android_targets; - const android_sdk = android.Sdk.create(b, .{}); const android_apk: ?*android.Apk = blk: { - if (android_targets.len == 0) { - break :blk null; - } + if (android_targets.len == 0) break :blk null; + + const android_sdk = android.Sdk.create(b, .{}); const apk = android_sdk.createApk(.{ .api_level = .android10, .build_tools_version = "35.0.1", @@ -91,6 +90,7 @@ pub fn build(b: *std.Build) void { const installed_apk = apk.addInstallApk(); b.getInstallStep().dependOn(&installed_apk.step); + const android_sdk = apk.sdk; const run_step = b.step("run", "Install and run the application on an Android device"); const adb_install = android_sdk.addAdbInstall(installed_apk.source); const adb_start = android_sdk.addAdbStart("com.zig.raylib/android.app.NativeActivity"); diff --git a/examples/sdl2/build.zig b/examples/sdl2/build.zig index 908ca7a..06bd422 100644 --- a/examples/sdl2/build.zig +++ b/examples/sdl2/build.zig @@ -15,11 +15,10 @@ pub fn build(b: *std.Build) void { else android_targets; - const android_sdk = android.Sdk.create(b, .{}); const android_apk: ?*android.Apk = blk: { - if (android_targets.len == 0) { - break :blk null; - } + if (android_targets.len == 0) break :blk null; + + const android_sdk = android.Sdk.create(b, .{}); const apk = android_sdk.createApk(.{ .api_level = .android15, .build_tools_version = "35.0.1", @@ -135,6 +134,7 @@ pub fn build(b: *std.Build) void { const installed_apk = apk.addInstallApk(); b.getInstallStep().dependOn(&installed_apk.step); + const android_sdk = apk.sdk; const run_step = b.step("run", "Install and run the application on an Android device"); const adb_install = android_sdk.addAdbInstall(installed_apk.source); const adb_start = android_sdk.addAdbStart("com.zig.sdl2/com.zig.sdl2.ZigSDLActivity"); diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig index 6af9c6e..ef99d7f 100644 --- a/src/androidbuild/apk.zig +++ b/src/androidbuild/apk.zig @@ -1,13 +1,13 @@ const std = @import("std"); const androidbuild = @import("androidbuild.zig"); -const Tools = @import("tools.zig"); +const Sdk = @import("tools.zig"); const BuiltinOptionsUpdate = @import("builtin_options_update.zig"); const Ndk = @import("Ndk.zig"); const BuildTools = @import("BuildTools.zig"); const D8Glob = @import("d8glob.zig"); -const KeyStore = Tools.KeyStore; +const KeyStore = Sdk.KeyStore; const ApiLevel = androidbuild.ApiLevel; const getAndroidTriple = androidbuild.getAndroidTriple; const runNameContext = androidbuild.runNameContext; @@ -32,7 +32,7 @@ pub const Resource = union(enum) { }; b: *std.Build, -tools: *Tools, +sdk: *Sdk, /// Path to Native Development Kit, this includes various C-code headers, libraries, and more. /// ie. $ANDROID_HOME/ndk/29.0.13113456 ndk: Ndk, @@ -57,17 +57,17 @@ pub const Options = struct { api_level: ApiLevel, }; -pub fn create(tools: *Tools, options: Options) *Apk { - const b = tools.b; +pub fn create(sdk: *Sdk, options: Options) *Apk { + const b = sdk.b; var errors = std.ArrayList([]const u8).init(b.allocator); defer errors.deinit(); - const build_tools = BuildTools.init(b, tools.android_sdk_path, options.build_tools_version, &errors) catch |err| switch (err) { + const build_tools = BuildTools.init(b, sdk.android_sdk_path, options.build_tools_version, &errors) catch |err| switch (err) { error.BuildToolFailed => BuildTools.empty, // fallthruogh and print all errors below error.OutOfMemory => @panic("OOM"), }; - const ndk = Ndk.init(b, tools.android_sdk_path, options.ndk_version, &errors) catch |err| switch (err) { + const ndk = Ndk.init(b, sdk.android_sdk_path, options.ndk_version, &errors) catch |err| switch (err) { error.NdkFailed => Ndk.empty, // fallthrough and print all errors below error.OutOfMemory => @panic("OOM"), }; @@ -79,7 +79,7 @@ pub fn create(tools: *Tools, options: Options) *Apk { const apk: *Apk = b.allocator.create(Apk) catch @panic("OOM"); apk.* = .{ .b = b, - .tools = tools, + .sdk = sdk, .ndk = ndk, .build_tools = build_tools, .api_level = options.api_level, @@ -180,7 +180,7 @@ fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void { // ie. $ANDROID_HOME/ndk/{ndk_version}/sources/android/cpufeatures if (target.result.cpu.arch == .arm) { module.addIncludePath(.{ - .cwd_relative = b.fmt("{s}/ndk/{s}/sources/android/cpufeatures", .{ apk.tools.android_sdk_path, apk.ndk.version }), + .cwd_relative = b.fmt("{s}/ndk/{s}/sources/android/cpufeatures", .{ apk.sdk.android_sdk_path, apk.ndk.version }), }); } @@ -281,7 +281,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // ie. "$ANDROID_HOME/Sdk/platforms/android-{api_level}/android.jar" const root_jar = b.pathResolve(&[_][]const u8{ - apk.tools.android_sdk_path, + apk.sdk.android_sdk_path, "platforms", b.fmt("android-{d}", .{@intFromEnum(apk.api_level)}), "android.jar", @@ -503,7 +503,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { if (apk.java_files.items.len > 0) { // https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html const javac_cmd = b.addSystemCommand(&[_][]const u8{ - apk.tools.java_tools.javac, + apk.sdk.java_tools.javac, // NOTE(jae): 2024-09-22 // Force encoding to be "utf8", this fixes the following error occuring in Windows: // error: unmappable character (0x8F) for encoding windows-1252 @@ -557,7 +557,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // See: https://musteresel.github.io/posts/2019/07/build-android-app-bundle-on-command-line.html { const jar = b.addSystemCommand(&[_][]const u8{ - apk.tools.java_tools.jar, + apk.sdk.java_tools.jar, }); jar.setName(runNameContext("jar (unzip resources.apk)")); if (b.verbose) { @@ -601,7 +601,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // - {directory with all resource files like: AndroidManifest.xml, res/values/strings.xml} const zip_file: LazyPath = blk: { const jar = b.addSystemCommand(&[_][]const u8{ - apk.tools.java_tools.jar, + apk.sdk.java_tools.jar, }); jar.setName(runNameContext("jar (zip compress apk)")); @@ -628,7 +628,7 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile { // Update zip with files that are not compressed (ie. resources.arsc) const update_zip: *Step = blk: { const jar = b.addSystemCommand(&[_][]const u8{ - apk.tools.java_tools.jar, + apk.sdk.java_tools.jar, }); jar.setName(runNameContext("jar (update zip with uncompressed files)")); @@ -723,7 +723,7 @@ fn getSystemIncludePath(apk: *Apk, target: ResolvedTarget) []const u8 { } fn setLibCFile(apk: *Apk, compile: *Step.Compile) void { - const tools = apk.tools; + const tools = apk.sdk; const android_libc_path = tools.createOrGetLibCFile(compile, apk.api_level, apk.ndk.sysroot_path, apk.ndk.version); android_libc_path.addStepDependencies(&compile.step); compile.setLibCFile(android_libc_path); diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig index aadc45d..f269ff0 100644 --- a/src/androidbuild/tools.zig +++ b/src/androidbuild/tools.zig @@ -320,8 +320,8 @@ pub fn createKeyStore(sdk: *const Sdk, options: CreateKey) KeyStore { }; } -pub fn createOrGetLibCFile(tools: *Sdk, compile: *Step.Compile, android_api_level: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { - const b = tools.b; +pub fn createOrGetLibCFile(sdk: *Sdk, compile: *Step.Compile, android_api_level: ApiLevel, ndk_sysroot_path: []const u8, ndk_version: []const u8) LazyPath { + const b = sdk.b; const target: ResolvedTarget = compile.root_module.resolved_target orelse @panic("no 'target' set on Android module"); const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err)); From 9adb8a764dbc2d28752c437627912bb66be9db3f Mon Sep 17 00:00:00 2001 From: Jae B Date: Sun, 25 May 2025 14:18:47 +1000 Subject: [PATCH 14/16] improve process --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 3a1cb84..4b092b3 100644 --- a/build.zig +++ b/build.zig @@ -18,7 +18,7 @@ pub const Tools = @compileError("Use android.Sdk instead of android.Tools"); /// Deprecated: Use Apk.Options instead. pub const ToolsOptions = @compileError("Use android.Sdk.Options instead of android.Apk.Options with the Sdk.createApk method"); /// Deprecated: Use Sdk.CreateKey instead. -pub const CreateKey = @compileError("Use android.Sdk.CreateKey instead of android.CreateKey"); +pub const CreateKey = @compileError("Use android.Sdk.CreateKey instead of android.CreateKey. Change 'android_tools.createKeyStore(android.CreateKey.example())' to 'android_sdk.createKeyStore(.example)'"); /// Deprecated: Use Apk not APK pub const APK = @compileError("Use android.Apk instead of android.APK"); From a401510dc60f0a58ced17722e53a53fc14e2c6d6 Mon Sep 17 00:00:00 2001 From: Jae B Date: Wed, 18 Jun 2025 20:17:08 +1000 Subject: [PATCH 15/16] use mlugg/setup-zig@v2 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69d11ed..8bac563 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: # note(jae): 2024-09-15 # Uses download mirror first as preferred by Zig Foundation # see: https://ziglang.org/news/migrate-to-self-hosting/ - uses: mlugg/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: version: "0.14.0" @@ -99,7 +99,7 @@ jobs: # - name: Setup Zig Nightly - uses: mlugg/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: version: "master" From b1f701e2ae181d2b7f32ac46580425951bd2c9ff Mon Sep 17 00:00:00 2001 From: Jae B Date: Wed, 18 Jun 2025 20:19:18 +1000 Subject: [PATCH 16/16] use latest ubuntu --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8bac563..9b39eb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: include: - - os: "ubuntu-22.04" + - os: "ubuntu-latest" - os: "windows-latest" - os: "macos-14" # arm64 as per table: https://github.com/actions/runner-images/blob/8a1eeaf6ac70c66f675a04078d1a7222edd42008/README.md#available-images