diff --git a/build.zig b/build.zig
index 5327af9..e91d9ba 100644
--- a/build.zig
+++ b/build.zig
@@ -1,17 +1,21 @@
const std = @import("std");
const androidbuild = @import("src/androidbuild/androidbuild.zig");
-const apk = @import("src/androidbuild/apk.zig");
-const tools = @import("src/androidbuild/tools.zig");
+const Apk = @import("src/androidbuild/apk.zig");
// Expose Android build functionality for use in your build.zig
-pub const ToolsOptions = tools.ToolsOptions;
-pub const Tools = tools.Tools;
-pub const APK = apk.APK;
-pub const APILevel = androidbuild.APILevel;
-pub const CreateKey = tools.CreateKey;
+pub const Tools = @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;
+
/// 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 {
const target = b.standardTargetOptions(.{});
@@ -24,7 +28,7 @@ pub fn build(b: *std.Build) void {
});
// Create stub of builtin options.
- // This is discovered and then replace in src/androidbuild/apk.zig
+ // This is discovered and then replaced by "Apk" in the build process
const android_builtin_options = std.Build.addOptions(b);
android_builtin_options.addOption([:0]const u8, "package_name", "");
module.addImport("android_builtin", android_builtin_options.createModule());
diff --git a/src/androidbuild/WindowsSdk.zig b/src/androidbuild/WindowsSdk.zig
index 70fdba3..253ca67 100644
--- a/src/androidbuild/WindowsSdk.zig
+++ b/src/androidbuild/WindowsSdk.zig
@@ -1,6 +1,5 @@
-// NOTE(jae): 2024-09-15
-// Copy paste of lib/std/zig/WindowsSdk.zig but cutdown to only use Registry functions
-
+//! NOTE(jae): 2024-09-15
+//! Copy paste of lib/std/zig/WindowsSdk.zig but cutdown to only use Registry functions
const WindowsSdk = @This();
const std = @import("std");
const builtin = @import("builtin");
diff --git a/src/androidbuild/androidbuild.zig b/src/androidbuild/androidbuild.zig
index e422bbf..859b7d0 100644
--- a/src/androidbuild/androidbuild.zig
+++ b/src/androidbuild/androidbuild.zig
@@ -5,6 +5,8 @@ const Target = std.Target;
const ResolvedTarget = std.Build.ResolvedTarget;
const LazyPath = std.Build.LazyPath;
+const log = std.log.scoped(.@"zig-android-sdk");
+
/// API Level is an enum the maps the Android OS version to the API level
///
/// https://en.wikipedia.org/wiki/Android_version_history
@@ -35,6 +37,8 @@ pub const APILevel = enum(u32) {
android14 = 34,
/// Vanilla Ice Cream
android15 = 35,
+ /// Baklava
+ android16 = 36,
// allow custom overrides (incase this library is not up to date with the latest android version)
_,
};
@@ -97,8 +101,6 @@ pub fn runNameContext(comptime name: []const u8) []const u8 {
return "zig-android-sdk " ++ name;
}
-const log = std.log.scoped(.@"zig-android-sdk");
-
pub fn printErrorsAndExit(message: []const u8, errors: []const []const u8) noreturn {
nosuspend {
log.err("{s}", .{message});
diff --git a/src/androidbuild/apk.zig b/src/androidbuild/apk.zig
index 5692118..6460e6d 100644
--- a/src/androidbuild/apk.zig
+++ b/src/androidbuild/apk.zig
@@ -1,10 +1,10 @@
const std = @import("std");
const androidbuild = @import("androidbuild.zig");
-const D8Glob = @import("d8glob.zig").D8Glob;
-const Tools = @import("tools.zig").Tools;
-const BuiltinOptionsUpdate = @import("builtin_options_update.zig").BuiltinOptionsUpdate;
+const Tools = @import("tools.zig");
+const BuiltinOptionsUpdate = @import("builtin_options_update.zig");
const KeyStore = androidbuild.KeyStore;
+const D8Glob = @import("d8glob.zig");
const getAndroidTriple = androidbuild.getAndroidTriple;
const runNameContext = androidbuild.runNameContext;
const printErrorsAndExit = androidbuild.printErrorsAndExit;
@@ -14,634 +14,616 @@ const Step = std.Build.Step;
const ResolvedTarget = std.Build.ResolvedTarget;
const LazyPath = std.Build.LazyPath;
-pub const APK = struct {
- pub const AddJavaSourceFileOption = struct {
- file: LazyPath,
- // NOTE(jae): 2024-09-17
- // Consider adding flags to define/declare the target Java version for this file.
- // Not sure what we'll need in the future.
- // flags: []const []const u8 = &.{},
- };
- pub const AddJavaSourceFilesOptions = struct {
- root: LazyPath,
- files: []const []const u8,
- };
- pub const Resource = union(enum) {
- // file: File,
- directory: Directory,
+pub const Resource = union(enum) {
+ // file: File,
+ directory: Directory,
- // pub const File = struct {
- // source: LazyPath,
- // };
+ // pub const File = struct {
+ // source: LazyPath,
+ // };
- pub const Directory = struct {
- source: LazyPath,
- };
+ pub const Directory = struct {
+ source: LazyPath,
};
+};
- b: *std.Build,
- tools: *const Tools,
-
- key_store: ?KeyStore,
-
- android_manifest: ?LazyPath,
- artifacts: std.ArrayListUnmanaged(*Step.Compile),
- java_files: std.ArrayListUnmanaged(LazyPath),
- resources: std.ArrayListUnmanaged(Resource),
-
- pub fn create(b: *std.Build, tools: *const Tools) *@This() {
- const apk: *@This() = b.allocator.create(@This()) catch @panic("OOM");
- apk.* = .{
- .b = b,
- .tools = tools,
- .key_store = null,
- .android_manifest = null,
- .artifacts = .{},
- .java_files = .{},
- .resources = .{},
- };
- return apk;
- }
+b: *std.Build,
+tools: *const Tools,
+
+key_store: ?KeyStore,
+
+android_manifest: ?LazyPath,
+artifacts: std.ArrayListUnmanaged(*Step.Compile),
+java_files: std.ArrayListUnmanaged(LazyPath),
+resources: std.ArrayListUnmanaged(Resource),
+
+pub fn create(b: *std.Build, tools: *const Tools) *Apk {
+ const apk: *Apk = b.allocator.create(Apk) catch @panic("OOM");
+ apk.* = .{
+ .b = b,
+ .tools = tools,
+ .key_store = null,
+ .android_manifest = null,
+ .artifacts = .{},
+ .java_files = .{},
+ .resources = .{},
+ };
+ return apk;
+}
- /// Set the AndroidManifest.xml file to use
- pub fn setAndroidManifest(apk: *@This(), path: LazyPath) void {
- apk.android_manifest = path;
- }
+/// Set the AndroidManifest.xml file to use
+pub fn setAndroidManifest(apk: *Apk, path: LazyPath) void {
+ apk.android_manifest = path;
+}
- /// Set the directory of your Android /res/ folder.
- /// ie.
- /// - values/strings.xml
- /// - mipmap-hdpi/ic_launcher.png
- /// - mipmap-mdpi/ic_launcher.png
- /// - etc
- pub fn addResourceDirectory(apk: *@This(), dir: LazyPath) void {
- const b = apk.b;
- apk.resources.append(b.allocator, Resource{
- .directory = .{
- .source = dir,
- },
- }) catch @panic("OOM");
- }
+/// Set the directory of your Android /res/ folder.
+/// ie.
+/// - values/strings.xml
+/// - mipmap-hdpi/ic_launcher.png
+/// - mipmap-mdpi/ic_launcher.png
+/// - etc
+pub fn addResourceDirectory(apk: *Apk, dir: LazyPath) void {
+ const b = apk.b;
+ apk.resources.append(b.allocator, Resource{
+ .directory = .{
+ .source = dir,
+ },
+ }) catch @panic("OOM");
+}
- /// Add artifact to the Android build, this should be a shared library (*.so)
- /// that targets x86, x86_64, aarch64, etc
- pub fn addArtifact(apk: *@This(), compile: *std.Build.Step.Compile) void {
- const b = apk.b;
- apk.artifacts.append(b.allocator, compile) catch @panic("OOM");
- }
+/// Add artifact to the Android build, this should be a shared library (*.so)
+/// that targets x86, x86_64, aarch64, etc
+pub fn addArtifact(apk: *Apk, compile: *std.Build.Step.Compile) void {
+ const b = apk.b;
+ apk.artifacts.append(b.allocator, compile) catch @panic("OOM");
+}
- /// Add Java file to be transformed into DEX bytecode and packaged into a classes.dex file in the root
- /// of your APK.
- pub fn addJavaSourceFile(apk: *@This(), options: AddJavaSourceFileOption) void {
- const b = apk.b;
- apk.java_files.append(b.allocator, options.file.dupe(b)) catch @panic("OOM");
- }
+pub const AddJavaSourceFileOption = struct {
+ file: LazyPath,
+ // NOTE(jae): 2024-09-17
+ // Consider adding flags to define/declare the target Java version for this file.
+ // Not sure what we'll need in the future.
+ // flags: []const []const u8 = &.{},
+};
- pub fn addJavaSourceFiles(apk: *@This(), options: AddJavaSourceFilesOptions) void {
- const b = apk.b;
- for (options.files) |path| {
- apk.addJavaSourceFile(.{ .file = options.root.path(b, path) });
- }
- }
+/// Add Java file to be transformed into DEX bytecode and packaged into a classes.dex file in the root
+/// of your APK.
+pub fn addJavaSourceFile(apk: *Apk, options: AddJavaSourceFileOption) void {
+ const b = apk.b;
+ apk.java_files.append(b.allocator, options.file.dupe(b)) catch @panic("OOM");
+}
+
+pub const AddJavaSourceFilesOptions = struct {
+ root: LazyPath,
+ files: []const []const u8,
+};
- /// Set the keystore file used to sign the APK file
- /// 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()));
- pub fn setKeyStore(apk: *@This(), key_store: KeyStore) void {
- apk.key_store = key_store;
+pub fn addJavaSourceFiles(apk: *Apk, options: AddJavaSourceFilesOptions) void {
+ const b = apk.b;
+ for (options.files) |path| {
+ apk.addJavaSourceFile(.{ .file = options.root.path(b, path) });
}
+}
- fn addLibraryPaths(apk: *@This(), module: *std.Build.Module) void {
- const b = apk.b;
- const android_ndk_sysroot = apk.tools.ndk_sysroot_path;
+/// Set the keystore file used to sign the APK file
+/// 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()));
+pub fn setKeyStore(apk: *Apk, key_store: KeyStore) void {
+ apk.key_store = key_store;
+}
- // get target
- const target: ResolvedTarget = module.resolved_target orelse {
- @panic(b.fmt("no 'target' set on Android module", .{}));
- };
- const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err));
+fn addLibraryPaths(apk: *Apk, module: *std.Build.Module) void {
+ const b = apk.b;
+ const android_ndk_sysroot = apk.tools.ndk_sysroot_path;
- // NOTE(jae): 2024-09-11
- // 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);
+ // get target
+ const target: ResolvedTarget = module.resolved_target orelse {
+ @panic(b.fmt("no 'target' set on Android module", .{}));
+ };
+ const system_target = getAndroidTriple(target) catch |err| @panic(@errorName(err));
- // NOTE(jae): 2025-03-09
- // Resolve issue where building SDL2 gets the following error for 'arm-linux-androideabi'
- // SDL2-2.32.2/src/cpuinfo/SDL_cpuinfo.c:93:10: error: 'cpu-features.h' file not found
- //
- // This include is specifically needed for: #if defined(__ANDROID__) && defined(__arm__) && !defined(HAVE_GETAUXVAL)
- //
- // ie. $ANDROID_HOME/ndk/{ndk_version}/sources/android/cpufeatures
- if (module.resolved_target) |resolved_target| {
- if (resolved_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 }) });
- }
- }
+ // NOTE(jae): 2024-09-11
+ // 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);
- // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot ++ usr/lib/aarch64-linux-android/35
- module.addLibraryPath(.{ .cwd_relative = b.fmt("{s}/usr/lib/{s}/{d}", .{ android_ndk_sysroot, system_target, android_api_version }) });
- // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot ++ /usr/lib/aarch64-linux-android
- module.addLibraryPath(.{ .cwd_relative = b.fmt("{s}/usr/lib/{s}", .{ android_ndk_sysroot, system_target }) });
+ // NOTE(jae): 2025-03-09
+ // Resolve issue where building SDL2 gets the following error for 'arm-linux-androideabi'
+ // SDL2-2.32.2/src/cpuinfo/SDL_cpuinfo.c:93:10: error: 'cpu-features.h' file not found
+ //
+ // This include is specifically needed for: #if defined(__ANDROID__) && defined(__arm__) && !defined(HAVE_GETAUXVAL)
+ //
+ // ie. $ANDROID_HOME/ndk/{ndk_version}/sources/android/cpufeatures
+ if (module.resolved_target) |resolved_target| {
+ if (resolved_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 }) });
+ }
}
- pub fn installApk(apk: *@This()) void {
- const b = apk.b;
- const install_apk = apk.addInstallApk();
- b.getInstallStep().dependOn(&install_apk.step);
- }
+ // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot ++ usr/lib/aarch64-linux-android/35
+ module.addLibraryPath(.{ .cwd_relative = b.fmt("{s}/usr/lib/{s}/{d}", .{ android_ndk_sysroot, system_target, android_api_version }) });
+ // ie. $ANDROID_HOME/ndk/{ndk_version}/toolchains/llvm/prebuilt/{host_os_and_arch}/sysroot ++ /usr/lib/aarch64-linux-android
+ module.addLibraryPath(.{ .cwd_relative = b.fmt("{s}/usr/lib/{s}", .{ android_ndk_sysroot, system_target }) });
+}
- pub fn addInstallApk(apk: *@This()) *Step.InstallFile {
- return apk.doInstallApk() catch |err| switch (err) {
- error.OutOfMemory => @panic("OOM"),
- };
- }
+pub fn installApk(apk: *Apk) void {
+ const b = apk.b;
+ const install_apk = apk.addInstallApk();
+ b.getInstallStep().dependOn(&install_apk.step);
+}
- fn doInstallApk(apk: *@This()) std.mem.Allocator.Error!*Step.InstallFile {
- const b = apk.b;
+pub fn addInstallApk(apk: *Apk) *Step.InstallFile {
+ return apk.doInstallApk() catch |err| switch (err) {
+ error.OutOfMemory => @panic("OOM"),
+ };
+}
- const key_store: KeyStore = apk.key_store orelse .{
- .file = .{ .cwd_relative = "" },
- .password = "",
- };
+fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
+ const b = apk.b;
- // validate
- {
- var errors = std.ArrayList([]const u8).init(b.allocator);
- if (key_store.password.len == 0) {
- try errors.append("Keystore not configured with password, must be setup with setKeyStore");
- }
- if (apk.android_manifest == null) {
- try errors.append("AndroidManifest.xml not configured, must be set with setAndroidManifest");
- }
- if (apk.artifacts.items.len == 0) {
- try errors.append("Must add at least one artifact targeting a valid Android CPU architecture: aarch64, x86_64, x86, etc");
- } else {
- for (apk.artifacts.items, 0..) |artifact, i| {
- if (artifact.kind == .exe) {
- try errors.append(b.fmt("artifact[{}]: must make Android artifacts be created with addSharedLibrary, not addExecutable", .{i}));
- } else {
- if (artifact.linkage) |linkage| {
- if (linkage != .dynamic) {
- try errors.append(b.fmt("artifact[{}]: invalid linkage, expected it to be created via addSharedLibrary", .{i}));
- }
- } else {
- try errors.append(b.fmt("artifact[{}]: unable to get linkage from artifact, expected it to be created via addSharedLibrary", .{i}));
- }
- }
- if (artifact.root_module.resolved_target) |target| {
- if (!target.result.abi.isAndroid()) {
- try errors.append(b.fmt("artifact[{}]: must be targetting Android abi", .{i}));
- continue;
+ const key_store: KeyStore = apk.key_store orelse .{
+ .file = .{ .cwd_relative = "" },
+ .password = "",
+ };
+
+ // validate
+ {
+ var errors = std.ArrayList([]const u8).init(b.allocator);
+ if (key_store.password.len == 0) {
+ try errors.append("Keystore not configured with password, must be setup with setKeyStore");
+ }
+ if (apk.android_manifest == null) {
+ try errors.append("AndroidManifest.xml not configured, must be set with setAndroidManifest");
+ }
+ if (apk.artifacts.items.len == 0) {
+ try errors.append("Must add at least one artifact targeting a valid Android CPU architecture: aarch64, x86_64, x86, etc");
+ } else {
+ for (apk.artifacts.items, 0..) |artifact, i| {
+ if (artifact.kind == .exe) {
+ try errors.append(b.fmt("artifact[{}]: must make Android artifacts be created with addSharedLibrary, not addExecutable", .{i}));
+ } else {
+ if (artifact.linkage) |linkage| {
+ if (linkage != .dynamic) {
+ try errors.append(b.fmt("artifact[{}]: invalid linkage, expected it to be created via addSharedLibrary", .{i}));
}
} else {
- try errors.append(b.fmt("artifact[{}]: unable to get resolved target from artifact", .{i}));
+ try errors.append(b.fmt("artifact[{}]: unable to get linkage from artifact, expected it to be created via addSharedLibrary", .{i}));
}
}
+ if (artifact.root_module.resolved_target) |target| {
+ if (!target.result.abi.isAndroid()) {
+ try errors.append(b.fmt("artifact[{}]: must be targetting Android abi", .{i}));
+ continue;
+ }
+ } else {
+ try errors.append(b.fmt("artifact[{}]: unable to get resolved target from artifact", .{i}));
+ }
}
- if (apk.java_files.items.len == 0) {
- // NOTE(jae): 2024-09-19
- // We can probably avoid this with a stub or something but for now error so that an "adb install"
- // doesn't give users:
- // - Scanning Failed.: Package /data/app/base.apk code is missing]
- try errors.append(b.fmt("must add at least one Java file to build", .{}));
- }
- if (errors.items.len > 0) {
- printErrorsAndExit("misconfigured Android APK", errors.items);
- }
}
+ if (apk.java_files.items.len == 0) {
+ // NOTE(jae): 2024-09-19
+ // We can probably avoid this with a stub or something but for now error so that an "adb install"
+ // doesn't give users:
+ // - Scanning Failed.: Package /data/app/base.apk code is missing]
+ try errors.append(b.fmt("must add at least one Java file to build", .{}));
+ }
+ if (errors.items.len > 0) {
+ printErrorsAndExit("misconfigured Android APK", errors.items);
+ }
+ }
- // Setup AndroidManifest.xml
- const android_manifest_file: LazyPath = apk.android_manifest orelse {
- @panic("call setAndroidManifestFile and point to your AndroidManifest.xml file");
- };
+ // Setup AndroidManifest.xml
+ const android_manifest_file: LazyPath = apk.android_manifest orelse {
+ @panic("call setAndroidManifestFile and point to your AndroidManifest.xml file");
+ };
- // TODO(jae): 2024-10-01
- // Add option where you can explicitly set an optional release mode with like:
- // - setMode(.debug)
- //
- // If that value ISN'T set then we can just infer based on optimization level.
- const debug_apk: bool = blk: {
- for (apk.artifacts.items) |root_artifact| {
- if (root_artifact.root_module.optimize) |optimize| {
- if (optimize == .Debug) {
- break :blk true;
- }
+ // TODO(jae): 2024-10-01
+ // Add option where you can explicitly set an optional release mode with like:
+ // - setMode(.debug)
+ //
+ // If that value ISN'T set then we can just infer based on optimization level.
+ const debug_apk: bool = blk: {
+ for (apk.artifacts.items) |root_artifact| {
+ if (root_artifact.root_module.optimize) |optimize| {
+ if (optimize == .Debug) {
+ break :blk true;
}
}
- break :blk false;
- };
+ }
+ break :blk false;
+ };
+
+ // Make resources.apk from:
+ // - resources.flat.zip (created from "aapt2 compile")
+ // - res/values/strings.xml -> values_strings.arsc.flat
+ // - AndroidManifest.xml
+ //
+ // This also validates your AndroidManifest.xml and can catch configuration errors
+ // which "aapt" was not capable of.
+ // See: https://developer.android.com/tools/aapt2#aapt2_element_hierarchy
+ // 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,
+ "link",
+ "-I", // add an existing package to base include set
+ apk.tools.root_jar,
+ });
+ aapt2link.setName(runNameContext("aapt2 link"));
+
+ if (b.verbose) {
+ aapt2link.addArg("-v");
+ }
+
+ // Inserts android:debuggable="true" in to the application node of the manifest,
+ // making the application debuggable even on production devices.
+ if (debug_apk) {
+ aapt2link.addArg("--debug-mode");
+ }
+
+ // full path to AndroidManifest.xml to include in APK
+ // ie. --manifest AndroidManifest.xml
+ aapt2link.addArg("--manifest");
+ aapt2link.addFileArg(android_manifest_file);
+
+ aapt2link.addArgs(&[_][]const u8{
+ "--target-sdk-version",
+ b.fmt("{d}", .{@intFromEnum(apk.tools.api_level)}),
+ });
- // Make resources.apk from:
- // - resources.flat.zip (created from "aapt2 compile")
- // - res/values/strings.xml -> values_strings.arsc.flat
- // - AndroidManifest.xml
+ // NOTE(jae): 2024-10-02
+ // Explored just outputting to dir but it gets errors like:
+ // - error: failed to write res/mipmap-mdpi-v4/ic_launcher.png to archive:
+ // The system cannot find the file specified. (2).
//
- // This also validates your AndroidManifest.xml and can catch configuration errors
- // which "aapt" was not capable of.
- // See: https://developer.android.com/tools/aapt2#aapt2_element_hierarchy
- // 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,
- "link",
- "-I", // add an existing package to base include set
- apk.tools.root_jar,
- });
- aapt2link.setName(runNameContext("aapt2 link"));
-
- if (b.verbose) {
- aapt2link.addArg("-v");
- }
+ // So... I'll stick with the creating an APK and extracting it approach.
+ // aapt2link.addArg("--output-to-dir"); // Requires: Android SDK Build Tools 28.0.0 or higher
+ // aapt2link.addArg("-o");
+ // const resources_apk_dir = aapt2link.addOutputDirectoryArg("resources");
+
+ aapt2link.addArg("-o");
+ const resources_apk_file = aapt2link.addOutputFileArg("resources.apk");
+
+ // TODO(jae): 2024-09-17
+ // Add support for asset directories
+ // Additional directory
+ // aapt.step.dependOn(&resource_write_files.step);
+ // for (app_config.asset_directories) |dir| {
+ // make_unsigned_apk.addArg("-A"); // additional directory in which to find raw asset files
+ // make_unsigned_apk.addArg(sdk.b.pathFromRoot(dir));
+ // }
+
+ // Add resource files
+ for (apk.resources.items) |resource| {
+ const resources_flat_zip = resblk: {
+ // Make zip of compiled resource files, ie.
+ // - res/values/strings.xml -> values_strings.arsc.flat
+ // - mipmap/ic_launcher.png -> mipmap-ic_launcher.png.flat
+ switch (resource) {
+ .directory => |resource_directory| {
+ const aapt2compile = b.addSystemCommand(&[_][]const u8{
+ apk.tools.build_tools.aapt2,
+ "compile",
+ });
+ aapt2compile.setName(runNameContext("aapt2 compile [dir]"));
+
+ // add directory
+ aapt2compile.addArg("--dir");
+ aapt2compile.addDirectoryArg(resource_directory.source);
+
+ aapt2compile.addArg("-o");
+ const resources_flat_zip_file = aapt2compile.addOutputFileArg("resource_dir.flat.zip");
+
+ break :resblk resources_flat_zip_file;
+ },
+ }
+ };
- // Inserts android:debuggable="true" in to the application node of the manifest,
- // making the application debuggable even on production devices.
- if (debug_apk) {
- aapt2link.addArg("--debug-mode");
- }
+ // Add resources.flat.zip
+ aapt2link.addFileArg(resources_flat_zip);
+ }
- // full path to AndroidManifest.xml to include in APK
- // ie. --manifest AndroidManifest.xml
- aapt2link.addArg("--manifest");
- aapt2link.addFileArg(android_manifest_file);
-
- aapt2link.addArgs(&[_][]const u8{
- "--target-sdk-version",
- b.fmt("{d}", .{@intFromEnum(apk.tools.api_level)}),
- });
-
- // NOTE(jae): 2024-10-02
- // Explored just outputting to dir but it gets errors like:
- // - error: failed to write res/mipmap-mdpi-v4/ic_launcher.png to archive:
- // The system cannot find the file specified. (2).
- //
- // So... I'll stick with the creating an APK and extracting it approach.
- // aapt2link.addArg("--output-to-dir"); // Requires: Android SDK Build Tools 28.0.0 or higher
- // aapt2link.addArg("-o");
- // const resources_apk_dir = aapt2link.addOutputDirectoryArg("resources");
-
- aapt2link.addArg("-o");
- const resources_apk_file = aapt2link.addOutputFileArg("resources.apk");
-
- // TODO(jae): 2024-09-17
- // Add support for asset directories
- // Additional directory
- // aapt.step.dependOn(&resource_write_files.step);
- // for (app_config.asset_directories) |dir| {
- // make_unsigned_apk.addArg("-A"); // additional directory in which to find raw asset files
- // make_unsigned_apk.addArg(sdk.b.pathFromRoot(dir));
- // }
-
- // Add resource files
- for (apk.resources.items) |resource| {
- const resources_flat_zip = resblk: {
- // Make zip of compiled resource files, ie.
- // - res/values/strings.xml -> values_strings.arsc.flat
- // - mipmap/ic_launcher.png -> mipmap-ic_launcher.png.flat
- switch (resource) {
- .directory => |resource_directory| {
- const aapt2compile = b.addSystemCommand(&[_][]const u8{
- apk.tools.build_tools.aapt2,
- "compile",
- });
- aapt2compile.setName(runNameContext("aapt2 compile [dir]"));
-
- // add directory
- aapt2compile.addArg("--dir");
- aapt2compile.addDirectoryArg(resource_directory.source);
-
- aapt2compile.addArg("-o");
- const resources_flat_zip_file = aapt2compile.addOutputFileArg("resource_dir.flat.zip");
-
- break :resblk resources_flat_zip_file;
- },
- }
- };
+ break :blk resources_apk_file;
+ };
- // Add resources.flat.zip
- aapt2link.addFileArg(resources_flat_zip);
- }
+ const package_name_file = blk: {
+ const aapt2packagename = b.addSystemCommand(&[_][]const u8{
+ apk.tools.build_tools.aapt2,
+ "dump",
+ "packagename",
+ });
+ aapt2packagename.setName(runNameContext("aapt2 dump packagename"));
+ aapt2packagename.addFileArg(resources_apk);
+ break :blk aapt2packagename.captureStdOut();
+ };
- break :blk resources_apk_file;
- };
+ const android_builtin = blk: {
+ const android_builtin_options = std.Build.addOptions(b);
+ BuiltinOptionsUpdate.create(b, android_builtin_options, package_name_file);
+ break :blk android_builtin_options.createModule();
+ };
- const package_name_file = blk: {
- const aapt2packagename = b.addSystemCommand(&[_][]const u8{
- apk.tools.build_tools.aapt2,
- "dump",
- "packagename",
- });
- aapt2packagename.setName(runNameContext("aapt2 dump packagename"));
- aapt2packagename.addFileArg(resources_apk);
- break :blk aapt2packagename.captureStdOut();
+ // We could also use that information to create easy to use Zig step like
+ // - zig build adb-uninstall (adb uninstall "com.zig.sdl2")
+ // - zig build adb-logcat
+ // - Works if process isn't running anymore/crashed: Powershell: adb logcat | Select-String com.zig.sdl2:
+ // - Only works if process is running: adb logcat --pid=`adb shell pidof -s com.zig.sdl2`
+ //
+ // ADB install doesn't require the package name however.
+ // - zig build adb-install (adb install ./zig-out/bin/minimal.apk)
+
+ // These are files that belong in root like:
+ // - lib/x86_64/libmain.so
+ // - lib/x86_64/libSDL2.so
+ // - lib/x86/libmain.so
+ // - classes.dex
+ const apk_files = b.addWriteFiles();
+
+ // Add build artifacts, usually a shared library targetting:
+ // - aarch64-linux-android
+ // - arm-linux-androideabi
+ // - i686-linux-android
+ // - x86_64-linux-android
+ for (apk.artifacts.items, 0..) |artifact, artifact_index| {
+ const target: ResolvedTarget = artifact.root_module.resolved_target orelse {
+ @panic(b.fmt("artifact[{d}] has no 'target' set", .{artifact_index}));
};
- const android_builtin = blk: {
- const android_builtin_options = std.Build.addOptions(b);
- BuiltinOptionsUpdate.create(b, android_builtin_options, package_name_file);
- break :blk android_builtin_options.createModule();
+ // https://developer.android.com/ndk/guides/abis#native-code-in-app-packages
+ const so_dir: []const u8 = switch (target.result.cpu.arch) {
+ .aarch64 => "arm64-v8a",
+ .arm => "armeabi-v7a",
+ .x86_64 => "x86_64",
+ .x86 => "x86",
+ else => @panic(b.fmt("unsupported or unhandled arch: {s}", .{@tagName(target.result.cpu.arch)})),
};
+ _ = apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/libmain.so", .{so_dir}));
- // We could also use that information to create easy to use Zig step like
- // - zig build adb-uninstall (adb uninstall "com.zig.sdl2")
- // - zig build adb-logcat
- // - Works if process isn't running anymore/crashed: Powershell: adb logcat | Select-String com.zig.sdl2:
- // - Only works if process is running: adb logcat --pid=`adb shell pidof -s com.zig.sdl2`
- //
- // ADB install doesn't require the package name however.
- // - zig build adb-install (adb install ./zig-out/bin/minimal.apk)
-
- // These are files that belong in root like:
- // - lib/x86_64/libmain.so
- // - lib/x86_64/libSDL2.so
- // - lib/x86/libmain.so
- // - classes.dex
- const apk_files = b.addWriteFiles();
-
- // Add build artifacts, usually a shared library targetting:
- // - aarch64-linux-android
- // - arm-linux-androideabi
- // - i686-linux-android
- // - x86_64-linux-android
- for (apk.artifacts.items, 0..) |artifact, artifact_index| {
- const target: ResolvedTarget = artifact.root_module.resolved_target orelse {
- @panic(b.fmt("artifact[{d}] has no 'target' set", .{artifact_index}));
- };
-
- // https://developer.android.com/ndk/guides/abis#native-code-in-app-packages
- const so_dir: []const u8 = switch (target.result.cpu.arch) {
- .aarch64 => "arm64-v8a",
- .arm => "armeabi-v7a",
- .x86_64 => "x86_64",
- .x86 => "x86",
- else => @panic(b.fmt("unsupported or unhandled arch: {s}", .{@tagName(target.result.cpu.arch)})),
- };
- _ = apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/libmain.so", .{so_dir}));
-
- // update artifact to:
- // - Be configured to work correctly on Android
- // - To know where C header /lib files are via setLibCFile and linkLibC
- // - Provide path to additional libraries to link to
- {
- if (artifact.linkage) |linkage| {
- if (linkage == .dynamic) {
- updateSharedLibraryOptions(artifact);
- }
+ // update artifact to:
+ // - Be configured to work correctly on Android
+ // - To know where C header /lib files are via setLibCFile and linkLibC
+ // - Provide path to additional libraries to link to
+ {
+ if (artifact.linkage) |linkage| {
+ if (linkage == .dynamic) {
+ updateSharedLibraryOptions(artifact);
}
- apk.tools.setLibCFile(artifact);
- apk.addLibraryPaths(artifact.root_module);
- artifact.linkLibC();
}
+ apk.tools.setLibCFile(artifact);
+ apk.addLibraryPaths(artifact.root_module);
+ artifact.linkLibC();
+ }
- // Add module
- artifact.root_module.addImport("android_builtin", android_builtin);
+ // Add module
+ artifact.root_module.addImport("android_builtin", android_builtin);
- var modules_it = artifact.root_module.import_table.iterator();
- while (modules_it.next()) |entry| {
- const module = entry.value_ptr.*;
- if (module.import_table.get("android_builtin")) |_| {
- module.addImport("android_builtin", android_builtin);
- }
+ var modules_it = artifact.root_module.import_table.iterator();
+ while (modules_it.next()) |entry| {
+ const module = entry.value_ptr.*;
+ if (module.import_table.get("android_builtin")) |_| {
+ module.addImport("android_builtin", android_builtin);
}
-
- // NOTE(jae): 2024-08-09
- // Try to fix compilation issues for ARM 32-bit (ie. arm-linux-androideabi)
- // if (target.result.cpu.arch == .arm) {
- // // artifact.root_module.addCMacro("__ARM_ARCH_7A__", "");
- // // artifact.root_module.addCMacro("_ARM_ARCH_7", "");
- // // artifact.root_module.addCMacro("__ARM_ARCH", "7"); // '__ARM_ARCH' macro redefined
- // // artifact.root_module.addCMacro("_M_ARM", ""); // Fix "openxr/src/common/platform_utils.hpp" No architecture string known!
- // }
-
- // update linked libraries that use C or C++ to:
- // - use Android LibC file
- // - add Android NDK library paths. (libandroid, liblog, etc)
- apk.updateLinkObjects(artifact, so_dir, apk_files);
}
- // Add *.jar files
- // - Even if java_files.items.len == 0, we still always add the root_jar
- 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,
- // 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
- // Source: https://github.com/libsdl-org/SDL/blob/release-2.30.7/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java#L2045
- "-encoding",
- "utf8",
- "-cp",
- apk.tools.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");
- const java_classes_output_dir = javac_cmd.addOutputDirectoryArg("android_classes");
-
- // Add Java files
- for (apk.java_files.items) |java_file| {
- javac_cmd.addFileArg(java_file);
- }
+ // update linked libraries that use C or C++ to:
+ // - use Android LibC file
+ // - add Android NDK library paths. (libandroid, liblog, etc)
+ apk.updateLinkObjects(artifact, so_dir, apk_files);
+ }
- // 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,
- });
- d8.setName(runNameContext("d8"));
+ // Add *.jar files
+ // - Even if java_files.items.len == 0, we still always add the root_jar
+ 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,
+ // 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
+ // Source: https://github.com/libsdl-org/SDL/blob/release-2.30.7/android-project/app/src/main/java/org/libsdl/app/SDLActivity.java#L2045
+ "-encoding",
+ "utf8",
+ "-cp",
+ apk.tools.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");
+ const java_classes_output_dir = javac_cmd.addOutputDirectoryArg("android_classes");
+
+ // Add Java files
+ for (apk.java_files.items) |java_file| {
+ javac_cmd.addFileArg(java_file);
+ }
- // ie. android_sdk/platforms/android-{api-level}/android.jar
- d8.addArg("--lib");
- d8.addArg(apk.tools.root_jar);
+ // 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,
+ });
+ d8.setName(runNameContext("d8"));
- d8.addArg("--output");
- const dex_output_dir = d8.addOutputDirectoryArg("android_dex");
+ // ie. android_sdk/platforms/android-{api-level}/android.jar
+ d8.addArg("--lib");
+ d8.addArg(apk.tools.root_jar);
- // NOTE(jae): 2024-09-22
- // As per documentation for d8, we may want to specific the minimum API level we want
- // to support. Not sure how to test or expose this yet. See: https://developer.android.com/tools/d8
- // d8.addArg("--min-api");
- // d8.addArg(number_as_string);
+ d8.addArg("--output");
+ const dex_output_dir = d8.addOutputDirectoryArg("android_dex");
- // add each output *.class file
- D8Glob.addClassFilesRecursively(b, d8, java_classes_output_dir);
- const dex_file = dex_output_dir.path(b, "classes.dex");
+ // NOTE(jae): 2024-09-22
+ // As per documentation for d8, we may want to specific the minimum API level we want
+ // to support. Not sure how to test or expose this yet. See: https://developer.android.com/tools/d8
+ // d8.addArg("--min-api");
+ // d8.addArg(number_as_string);
- // Append classes.dex to apk
- _ = apk_files.addCopyFile(dex_file, "classes.dex");
- }
+ // add each output *.class file
+ D8Glob.create(b, d8, java_classes_output_dir);
+ const dex_file = dex_output_dir.path(b, "classes.dex");
- // Extract compiled resources.apk and add contents to the folder we'll zip with "jar" below
- // 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,
- });
- jar.setName(runNameContext("jar (unzip resources.apk)"));
- if (b.verbose) {
- jar.addArg("--verbose");
- }
+ // Append classes.dex to apk
+ _ = apk_files.addCopyFile(dex_file, "classes.dex");
+ }
- // Extract *.apk file created with "aapt2 link"
- jar.addArg("--extract");
- jar.addPrefixedFileArg("--file=", resources_apk);
-
- // NOTE(jae): 2024-09-30
- // Extract to directory of resources_apk and force add that to the overall apk files.
- // This currently has an issue where because we can't use "addOutputDirectoryArg" this
- // step will always be executed.
- const extracted_apk_dir = resources_apk.dirname();
- jar.setCwd(extracted_apk_dir);
- _ = apk_files.addCopyDirectory(extracted_apk_dir, "", .{
- // Ignore the *.apk that exists in this directory
- .exclude_extensions = &.{".apk"},
- });
- apk_files.step.dependOn(&jar.step);
+ // Extract compiled resources.apk and add contents to the folder we'll zip with "jar" below
+ // 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,
+ });
+ jar.setName(runNameContext("jar (unzip resources.apk)"));
+ if (b.verbose) {
+ jar.addArg("--verbose");
}
- // Create zip via "jar" as it's cross-platform and aapt2 can't zip *.so or *.dex files.
- // - lib/**/*.so
- // - classes.dex
- // - {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,
- });
- jar.setName(runNameContext("jar (zip compress apk)"));
-
- const directory_to_zip = apk_files.getDirectory();
- jar.setCwd(directory_to_zip);
- // NOTE(jae): 2024-09-30
- // Hack to ensure this side-effect re-triggers zipping this up
- jar.addFileInput(directory_to_zip.path(b, "AndroidManifest.xml"));
-
- // -c = compress
- // -f specify filename
- // -M do not include a MANIFEST file
- const compress_zip_arg = "-cfM";
- if (b.verbose) jar.addArg(compress_zip_arg ++ "v") else jar.addArg(compress_zip_arg);
- const output_zip_file = jar.addOutputFileArg("compiled_code.zip");
- jar.addArg(".");
-
- break :blk output_zip_file;
- };
+ // Extract *.apk file created with "aapt2 link"
+ jar.addArg("--extract");
+ jar.addPrefixedFileArg("--file=", resources_apk);
+
+ // NOTE(jae): 2024-09-30
+ // Extract to directory of resources_apk and force add that to the overall apk files.
+ // This currently has an issue where because we can't use "addOutputDirectoryArg" this
+ // step will always be executed.
+ const extracted_apk_dir = resources_apk.dirname();
+ jar.setCwd(extracted_apk_dir);
+ _ = apk_files.addCopyDirectory(extracted_apk_dir, "", .{
+ // Ignore the *.apk that exists in this directory
+ .exclude_extensions = &.{".apk"},
+ });
+ apk_files.step.dependOn(&jar.step);
+ }
+
+ // Create zip via "jar" as it's cross-platform and aapt2 can't zip *.so or *.dex files.
+ // - lib/**/*.so
+ // - classes.dex
+ // - {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,
+ });
+ jar.setName(runNameContext("jar (zip compress apk)"));
+
+ const directory_to_zip = apk_files.getDirectory();
+ jar.setCwd(directory_to_zip);
+ // NOTE(jae): 2024-09-30
+ // Hack to ensure this side-effect re-triggers zipping this up
+ jar.addFileInput(directory_to_zip.path(b, "AndroidManifest.xml"));
+
+ // -c = compress
+ // -f specify filename
+ // -M do not include a MANIFEST file
+ const compress_zip_arg = "-cfM";
+ if (b.verbose) jar.addArg(compress_zip_arg ++ "v") else jar.addArg(compress_zip_arg);
+ const output_zip_file = jar.addOutputFileArg("compiled_code.zip");
+ jar.addArg(".");
+
+ break :blk output_zip_file;
+ };
- // NOTE(jae): 2024-09-28 - https://github.com/silbinarywolf/zig-android-sdk/issues/8
- // Experimented with using "lint" but it didn't actually catch the issue described
- // in the above Github, ie. having ""
- // outside of an
+ // NOTE(jae): 2024-09-28 - https://github.com/silbinarywolf/zig-android-sdk/issues/8
+ // Experimented with using "lint" but it didn't actually catch the issue described
+ // in the above Github, ie. having ""
+ // outside of an
+ //
+ // const lint = b.addSystemCommand(&[_][]const u8{
+ // apk.tools.commandline_tools.lint,
+ // });
+ // lint.setEnvironmentVariable("PATH", b.pathJoin(&.{ apk.tools.jdk_path, "bin" }));
+ // lint.setEnvironmentVariable("JAVA_HOME", apk.tools.jdk_path);
+ // lint.addFileArg(android_manifest_file);
+
+ const apk_name = apk.artifacts.items[0].name;
+
+ // Align contents of .apk (zip)
+ const aligned_apk_file: LazyPath = blk: {
+ var zipalign = b.addSystemCommand(&[_][]const u8{
+ apk.tools.build_tools.zipalign,
+ });
+ zipalign.setName(runNameContext("zipalign"));
+
+ // If you use apksigner, zipalign must be used before the APK file has been signed.
+ // If you sign your APK using apksigner and make further changes to the APK, its signature is invalidated.
+ // Source: https://developer.android.com/tools/zipalign (10th Sept, 2024)
//
- // const lint = b.addSystemCommand(&[_][]const u8{
- // apk.tools.commandline_tools.lint,
- // });
- // lint.setEnvironmentVariable("PATH", b.pathJoin(&.{ apk.tools.jdk_path, "bin" }));
- // lint.setEnvironmentVariable("JAVA_HOME", apk.tools.jdk_path);
- // lint.addFileArg(android_manifest_file);
-
- const apk_name = apk.artifacts.items[0].name;
-
- // Align contents of .apk (zip)
- const aligned_apk_file: LazyPath = blk: {
- var zipalign = b.addSystemCommand(&[_][]const u8{
- apk.tools.build_tools.zipalign,
- });
- zipalign.setName(runNameContext("zipalign"));
-
- // If you use apksigner, zipalign must be used before the APK file has been signed.
- // If you sign your APK using apksigner and make further changes to the APK, its signature is invalidated.
- // Source: https://developer.android.com/tools/zipalign (10th Sept, 2024)
- //
- // Example: "zipalign -P 16 -f -v 4 infile.apk outfile.apk"
- if (b.verbose) {
- zipalign.addArg("-v");
- }
- zipalign.addArgs(&.{
- "-P", // aligns uncompressed .so files to the specified page size in KiB...
- "16", // ... align to 16kb
- "-f", // overwrite existing files
- // "-z", // recompresses using Zopfli. (very very slow)
- "4",
- });
-
- zipalign.addFileArg(zip_file);
- const apk_file = zipalign.addOutputFileArg(b.fmt("aligned-{s}.apk", .{apk_name}));
- break :blk apk_file;
- };
+ // Example: "zipalign -P 16 -f -v 4 infile.apk outfile.apk"
+ if (b.verbose) {
+ zipalign.addArg("-v");
+ }
+ zipalign.addArgs(&.{
+ "-P", // aligns uncompressed .so files to the specified page size in KiB...
+ "16", // ... align to 16kb
+ "-f", // overwrite existing files
+ // "-z", // recompresses using Zopfli. (very very slow)
+ "4",
+ });
+
+ zipalign.addFileArg(zip_file);
+ const apk_file = zipalign.addOutputFileArg(b.fmt("aligned-{s}.apk", .{apk_name}));
+ break :blk apk_file;
+ };
- // Sign apk
- const signed_apk_file: LazyPath = blk: {
- const apksigner = b.addSystemCommand(&[_][]const u8{
- apk.tools.build_tools.apksigner,
- "sign",
- });
- apksigner.setName(runNameContext("apksigner"));
- apksigner.addArg("--ks"); // ks = keystore
- apksigner.addFileArg(key_store.file);
- apksigner.addArgs(&.{ "--ks-pass", b.fmt("pass:{s}", .{key_store.password}) });
- apksigner.addArg("--out");
- const signed_output_apk_file = apksigner.addOutputFileArg("signed-and-aligned-apk.apk");
- apksigner.addFileArg(aligned_apk_file);
- break :blk signed_output_apk_file;
- };
+ // Sign apk
+ const signed_apk_file: LazyPath = blk: {
+ const apksigner = b.addSystemCommand(&[_][]const u8{
+ apk.tools.build_tools.apksigner,
+ "sign",
+ });
+ apksigner.setName(runNameContext("apksigner"));
+ apksigner.addArg("--ks"); // ks = keystore
+ apksigner.addFileArg(key_store.file);
+ apksigner.addArgs(&.{ "--ks-pass", b.fmt("pass:{s}", .{key_store.password}) });
+ apksigner.addArg("--out");
+ const signed_output_apk_file = apksigner.addOutputFileArg("signed-and-aligned-apk.apk");
+ apksigner.addFileArg(aligned_apk_file);
+ break :blk signed_output_apk_file;
+ };
- const install_apk = b.addInstallBinFile(signed_apk_file, b.fmt("{s}.apk", .{apk_name}));
- return install_apk;
- }
+ const install_apk = b.addInstallBinFile(signed_apk_file, b.fmt("{s}.apk", .{apk_name}));
+ return install_apk;
+}
- fn updateLinkObjects(apk: *@This(), 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| {
- switch (link_object) {
- .other_step => |artifact| {
- switch (artifact.kind) {
- .lib => {
- // If you have a library that is being built as an *.so then install it
- // alongside your library.
- //
- // This was initially added to support building SDL2 with Zig.
- if (artifact.linkage) |linkage| {
- if (linkage == .dynamic) {
- updateSharedLibraryOptions(artifact);
- _ = raw_top_level_apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/lib{s}.so", .{ so_dir, artifact.name }));
- }
+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| {
+ switch (link_object) {
+ .other_step => |artifact| {
+ switch (artifact.kind) {
+ .lib => {
+ // If you have a library that is being built as an *.so then install it
+ // alongside your library.
+ //
+ // This was initially added to support building SDL2 with Zig.
+ if (artifact.linkage) |linkage| {
+ if (linkage == .dynamic) {
+ updateSharedLibraryOptions(artifact);
+ _ = raw_top_level_apk_files.addCopyFile(artifact.getEmittedBin(), b.fmt("lib/{s}/lib{s}.so", .{ so_dir, artifact.name }));
}
+ }
- // If library is built using C or C++ then setLibCFile
- const link_libc = artifact.root_module.link_libc orelse false;
- const link_libcpp = artifact.root_module.link_libcpp orelse false;
- if (link_libc or link_libcpp) {
- // NOTE(jae): 2024-08-09
- // Try to fix compilation issues for arm-linux-androideabi
- // if (target.result.cpu.arch == .arm) {
- // other_step.root_module.addCMacro("_ARM_ARCH_7", "");
- // other_step.root_module.addCMacro("__ARM_ARCH_7A__", ""); // Fixes nothing
- // other_step.root_module.addCMacro("__ARM_ARCH", "7"); // '__ARM_ARCH' macro redefined
- // other_step.root_module.addCMacro("_M_ARM", "");
- // }
- // artifact.root_module.addCMacro("__ANDROID__", "1");
- apk.tools.setLibCFile(artifact);
- }
+ // If library is built using C or C++ then setLibCFile
+ const link_libc = artifact.root_module.link_libc orelse false;
+ const link_libcpp = artifact.root_module.link_libcpp orelse false;
+ if (link_libc or link_libcpp) {
+ apk.tools.setLibCFile(artifact);
+ }
- // Add library paths to find "android", "log", etc
- apk.addLibraryPaths(artifact.root_module);
+ // Add library paths to find "android", "log", etc
+ apk.addLibraryPaths(artifact.root_module);
- // Update libraries linked to this library
- apk.updateLinkObjects(artifact, so_dir, raw_top_level_apk_files);
- },
- else => continue,
- }
- },
- else => {},
- }
+ // Update libraries linked to this library
+ apk.updateLinkObjects(artifact, so_dir, raw_top_level_apk_files);
+ },
+ else => continue,
+ }
+ },
+ else => {},
}
}
-};
+}
fn updateSharedLibraryOptions(artifact: *std.Build.Step.Compile) void {
if (artifact.linkage) |linkage| {
@@ -697,5 +679,6 @@ fn updateSharedLibraryOptions(artifact: *std.Build.Step.Compile) void {
// artifact.link_function_sections = true;
// Seemingly not "needed" anymore, at least for x86_64 Android builds
// artifact.export_table = true;
-
}
+
+const Apk = @This();
diff --git a/src/androidbuild/builtin_options_update.zig b/src/androidbuild/builtin_options_update.zig
index 87de8e4..6b01636 100644
--- a/src/androidbuild/builtin_options_update.zig
+++ b/src/androidbuild/builtin_options_update.zig
@@ -1,3 +1,5 @@
+//! BuiltinOptionsUpdate will update the *Options
+
const std = @import("std");
const androidbuild = @import("androidbuild.zig");
const builtin = @import("builtin");
@@ -9,63 +11,62 @@ const fs = std.fs;
const mem = std.mem;
const assert = std.debug.assert;
-/// BuiltinOptionsUpdate will update the *Options
-pub const BuiltinOptionsUpdate = struct {
- pub const base_id: Step.Id = .custom;
+pub const base_id: Step.Id = .custom;
+
+step: Step,
- step: Step,
+options: *Options,
+package_name_stdout: LazyPath,
- 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");
+ 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,
+ }),
+ .options = options,
+ .package_name_stdout = package_name_stdout,
+ };
+ // Run step relies on this finishing
+ options.step.dependOn(&builtin_options_update.step);
+ // Depend on package name stdout before running this step
+ package_name_stdout.addStepDependencies(&builtin_options_update.step);
+}
- pub fn create(owner: *std.Build, options: *Options, package_name_stdout: LazyPath) void {
- const builtin_options_update = owner.allocator.create(@This()) 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,
- }),
- .options = options,
- .package_name_stdout = package_name_stdout,
- };
- // Run step relies on this finishing
- options.step.dependOn(&builtin_options_update.step);
- // Depend on package name stdout before running this step
- 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.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);
+}
- /// 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 {
+ const b = step.owner;
+ const builtin_options_update: *@This() = @fieldParentPtr("step", step);
+ const options = builtin_options_update.options;
- fn make(step: *Step) !void {
- const b = step.owner;
- const builtin_options_update: *@This() = @fieldParentPtr("step", step);
- const options = builtin_options_update.options;
+ const package_name_path = builtin_options_update.package_name_stdout.getPath2(b, step);
- const package_name_path = builtin_options_update.package_name_stdout.getPath2(b, step);
+ const file = try fs.openFileAbsolute(package_name_path, .{});
- const file = try fs.openFileAbsolute(package_name_path, .{});
+ // Read package name from stdout and strip line feed / carriage return
+ // ie. "com.zig.sdl2\n\r"
+ const package_name_filedata = try file.readToEndAlloc(b.allocator, 8192);
+ const package_name_stripped = std.mem.trimRight(u8, package_name_filedata, " \r\n");
+ const package_name: [:0]const u8 = try b.allocator.dupeZ(u8, package_name_stripped);
- // Read package name from stdout and strip line feed / carriage return
- // ie. "com.zig.sdl2\n\r"
- const package_name_filedata = try file.readToEndAlloc(b.allocator, 8192);
- const package_name_stripped = std.mem.trimRight(u8, package_name_filedata, " \r\n");
- const package_name: [:0]const u8 = try b.allocator.dupeZ(u8, package_name_stripped);
+ options.addOption([:0]const u8, "package_name", package_name);
+}
- options.addOption([:0]const u8, "package_name", package_name);
- }
-};
+const BuiltinOptionsUpdate = @This();
diff --git a/src/androidbuild/d8glob.zig b/src/androidbuild/d8glob.zig
index c52fd7e..635d8f4 100644
--- a/src/androidbuild/d8glob.zig
+++ b/src/androidbuild/d8glob.zig
@@ -1,3 +1,5 @@
+//! D8Glob is specific for D8 and is used to collect all *.class output files after a javac process generates them
+
const std = @import("std");
const androidbuild = @import("androidbuild.zig");
const builtin = @import("builtin");
@@ -9,97 +11,97 @@ const fs = std.fs;
const mem = std.mem;
const assert = std.debug.assert;
-/// D8Glob is specific for D8 and is used to collect all *.class output files after a javac process generates them
-pub const D8Glob = struct {
- pub const base_id: Step.Id = .custom;
+pub const base_id: Step.Id = .custom;
- step: Step,
+step: Step,
- /// Runner to update
- run: *Build.Step.Run,
+/// Runner to update
+run: *Build.Step.Run,
- /// The directory that will contain the files to glob
- dir: LazyPath,
+/// The directory that will contain the files to glob
+dir: LazyPath,
- const file_ext = ".class";
+const file_ext = ".class";
- pub fn addClassFilesRecursively(owner: *std.Build, run: *Run, dir: LazyPath) void {
- const glob = owner.allocator.create(@This()) catch @panic("OOM");
- glob.* = .{
- .step = Step.init(.{
- .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,
- }),
- .run = run,
- .dir = dir,
- };
- // Run step relies on this finishing
- run.step.dependOn(&glob.step);
- // If dir is generated then this will wait for that dir to generate
- dir.addStepDependencies(&glob.step);
- }
+/// Creates a D8Glob step which is used to collect all *.class output files after a javac process generates them
+pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void {
+ const glob = owner.allocator.create(@This()) catch @panic("OOM");
+ glob.* = .{
+ .step = Step.init(.{
+ .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,
+ }),
+ .run = run,
+ .dir = dir,
+ };
+ // Run step relies on this finishing
+ run.step.dependOn(&glob.step);
+ // If dir is generated then this will wait for that dir to generate
+ 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.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);
- }
+/// 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 {
- const b = step.owner;
- const arena = b.allocator;
- const glob: *@This() = @fieldParentPtr("step", step);
- const d8 = glob.run;
+fn make(step: *Step) !void {
+ const b = step.owner;
+ const arena = b.allocator;
+ const glob: *@This() = @fieldParentPtr("step", step);
+ const d8 = glob.run;
- const search_dir = glob.dir.getPath2(b, step);
+ const search_dir = glob.dir.getPath2(b, step);
- // NOTE(jae): 2024-09-22
- // Change current working directory to where the Java classes are
- // This is to avoid the Java error "command line too long" that can occur with d8
- //
- // I was hitting this due to a path this long on Windows
- // J:\ZigProjects\openxr-game\third-party\zig-android-sdk\examples\sdl2\.zig-cache\o\9012552ac182acf9dfb49627cf81376e\android_dex
- //
- // A deeper fix to this problem could be:
- // - Zip up all the *.class files and just provide that as ONE argument or alternatively
- // - If "d8" has the ability to pass a file of command line parameters, that would work too but I haven't seen any in the docs
- d8.setCwd(glob.dir);
+ // NOTE(jae): 2024-09-22
+ // Change current working directory to where the Java classes are
+ // This is to avoid the Java error "command line too long" that can occur with d8
+ //
+ // I was hitting this due to a path this long on Windows
+ // J:\ZigProjects\openxr-game\third-party\zig-android-sdk\examples\sdl2\.zig-cache\o\9012552ac182acf9dfb49627cf81376e\android_dex
+ //
+ // A deeper fix to this problem could be:
+ // - Zip up all the *.class files and just provide that as ONE argument or alternatively
+ // - If "d8" has the ability to pass a file of command line parameters, that would work too but I haven't seen any in the docs
+ d8.setCwd(glob.dir);
- var dir = try fs.openDirAbsolute(search_dir, .{ .iterate = true });
- defer dir.close();
- var walker = try dir.walk(arena);
- defer walker.deinit();
- while (try walker.next()) |entry| {
- if (entry.kind != .file) {
- continue;
- }
- // NOTE(jae): 2024-10-01
- // Initially ignored classes with alternate API postfixes / etc but
- // that did not work with SDL2 so no longer do that.
- // - !std.mem.containsAtLeast(u8, entry.basename, 1, "$") and
- // - !std.mem.containsAtLeast(u8, entry.basename, 1, "_API")
- if (std.mem.endsWith(u8, entry.path, file_ext)) {
- // NOTE(jae): 2024-09-22
- // We set the current working directory to "glob.Dir" and then make arguments be
- // relative to that directory.
- //
- // This is to avoid the Java error "command line too long" that can occur with d8
- d8.addArg(entry.path);
- d8.addFileInput(LazyPath{
- .cwd_relative = try fs.path.resolve(arena, &.{ search_dir, entry.path }),
- });
- }
+ var dir = try fs.openDirAbsolute(search_dir, .{ .iterate = true });
+ defer dir.close();
+ var walker = try dir.walk(arena);
+ defer walker.deinit();
+ while (try walker.next()) |entry| {
+ if (entry.kind != .file) {
+ continue;
+ }
+ // NOTE(jae): 2024-10-01
+ // Initially ignored classes with alternate API postfixes / etc but
+ // that did not work with SDL2 so no longer do that.
+ // - !std.mem.containsAtLeast(u8, entry.basename, 1, "$") and
+ // - !std.mem.containsAtLeast(u8, entry.basename, 1, "_API")
+ if (std.mem.endsWith(u8, entry.path, file_ext)) {
+ // NOTE(jae): 2024-09-22
+ // We set the current working directory to "glob.Dir" and then make arguments be
+ // relative to that directory.
+ //
+ // This is to avoid the Java error "command line too long" that can occur with d8
+ d8.addArg(entry.path);
+ d8.addFileInput(LazyPath{
+ .cwd_relative = try fs.path.resolve(arena, &.{ search_dir, entry.path }),
+ });
}
}
-};
+}
+
+const D8Glob = @This();
diff --git a/src/androidbuild/tools.zig b/src/androidbuild/tools.zig
index 4c3f565..e42bf9f 100644
--- a/src/androidbuild/tools.zig
+++ b/src/androidbuild/tools.zig
@@ -18,41 +18,56 @@ const Step = Build.Step;
const ResolvedTarget = Build.ResolvedTarget;
const LazyPath = std.Build.LazyPath;
-pub const CreateKey = struct {
- pub const Algorithm = enum {
- rsa,
-
- /// arg returns the keytool argument
- fn arg(self: Algorithm) []const u8 {
- return switch (self) {
- .rsa => "RSA",
- };
- }
- };
-
- alias: []const u8,
- password: []const u8,
- algorithm: Algorithm,
- /// in bits, the maximum size of an RSA key supported by the Android keystore is 4096 bits (as of 2024)
- key_size_in_bits: u32,
- validity_in_days: u32,
- /// https://stackoverflow.com/questions/3284055/what-should-i-use-for-distinguished-name-in-our-keystore-for-the-android-marke/3284135#3284135
- distinguished_name: []const u8,
+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/Sdk/platforms/android-{api_level}/android.jar"
+root_jar: []const u8,
+// $JDK_HOME, $JAVA_HOME or auto-discovered from java binaries found in $PATH
+jdk_path: []const u8,
+/// ie. $ANDROID_HOME/build-tools/35.0.0
+build_tools: struct {
+ aapt2: []const u8,
+ zipalign: []const u8,
+ d8: []const u8,
+ apksigner: []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
+/// The commandline tools ZIP expected looks like: commandlinetools-{OS}-11076708_latest.zip
+cmdline_tools: struct {
+ /// lint [flags]
+ /// See documentation: https://developer.android.com/studio/write/lint#commandline
+ lint: []const u8,
+},
+/// Binaries provided by the JDK that usually exist in:
+/// - Non-Windows: $JAVA_HOME/bin
+///
+/// Windows (either of these):
+/// - C:\Program Files\Eclipse Adoptium\jdk-11.0.17.8-hotspot\
+/// - C:\Program Files\Java\jdk-17.0.4.1\
+java_tools: struct {
+ /// jar is used to zip up files in a cross-platform way that does not rely on
+ /// having "zip" in your command-line (Windows does not have this)
+ ///
+ /// ie. https://stackoverflow.com/a/18180154/5013410
+ jar: []const u8,
+ javac: []const u8,
+ keytool: []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",
- };
- }
-};
+/// Deprecated: Use Options instead.
+pub const ToolsOptions = Options;
-pub const ToolsOptions = struct {
+pub const Options = struct {
/// ie. "35.0.0"
build_tools_version: []const u8,
/// ie. "27.0.12077973"
@@ -61,254 +76,172 @@ pub const ToolsOptions = struct {
api_level: APILevel,
};
-pub const Tools = struct {
- b: *Build,
+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})),
+ };
- /// 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/Sdk/platforms/android-{api_level}/android.jar"
- root_jar: []const u8,
- // $JDK_HOME, $JAVA_HOME or auto-discovered from java binaries found in $PATH
- jdk_path: []const u8,
- /// ie. $ANDROID_HOME/build-tools/35.0.0
- build_tools: struct {
- aapt2: []const u8,
- zipalign: []const u8,
- d8: []const u8,
- apksigner: []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
- /// The commandline tools ZIP expected looks like: commandlinetools-{OS}-11076708_latest.zip
- cmdline_tools: struct {
- /// lint [flags]
- /// See documentation: https://developer.android.com/studio/write/lint#commandline
- lint: []const u8,
- },
- /// Binaries provided by the JDK that usually exist in:
- /// - Non-Windows: $JAVA_HOME/bin
- ///
- /// Windows (either of these):
- /// - C:\Program Files\Eclipse Adoptium\jdk-11.0.17.8-hotspot\
- /// - C:\Program Files\Java\jdk-17.0.4.1\
- java_tools: struct {
- /// jar is used to zip up files in a cross-platform way that does not rely on
- /// having "zip" in your command-line (Windows does not have this)
- ///
- /// ie. https://stackoverflow.com/a/18180154/5013410
- jar: []const u8,
- javac: []const u8,
- keytool: []const u8,
- },
-
- pub fn createKeyStore(tools: *const Tools, options: CreateKey) KeyStore {
- const b = tools.b;
- const keytool = b.addSystemCommand(&.{
- // https://docs.oracle.com/en/java/javase/17/docs/specs/man/keytool.html
- tools.java_tools.keytool,
- "-genkey",
- "-v",
- });
- keytool.setName(runNameContext("keytool"));
- keytool.addArg("-keystore");
- const keystore_file = keytool.addOutputFileArg("zig-generated.keystore");
- keytool.addArgs(&.{
- // -alias "ca"
- "-alias",
- options.alias,
- // -keyalg "rsa"
- "-keyalg",
- options.algorithm.arg(),
- "-keysize",
- b.fmt("{d}", .{options.key_size_in_bits}),
- "-validity",
- b.fmt("{d}", .{options.validity_in_days}),
- "-storepass",
- options.password,
- "-keypass",
- options.password,
- // -dname "CN=example.com, OU=ID, O=Example, L=Doe, S=Jane, C=GB"
- "-dname",
- options.distinguished_name,
- });
- // ignore stderr, it just gives you an output like:
- // "Generating 4,096 bit RSA key pair and self-signed certificate (SHA384withRSA) with a validity of 10,000 days
- // for: CN=example.com, OU=ID, O=Example, L=Doe, ST=Jane, C=GB"
- _ = keytool.captureStdErr();
- return .{
- .file = keystore_file,
- .password = options.password,
- };
+ // Discover tool paths
+ var path_search = PathSearch.init(b.allocator, host_os_tag) catch |err| switch (err) {
+ error.OutOfMemory => @panic("OOM"),
+ error.EnvironmentVariableNotFound => @panic("unable to find PATH as an environment variable"),
+ };
+ const configured_jdk_path = getJDKPath(b.allocator) catch @panic("OOM");
+ if (configured_jdk_path.len > 0) {
+ // Set JDK path here so it will not try searching for jarsigner.exe if searching for Android SDK
+ path_search.jdk_path = configured_jdk_path;
}
-
- // 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 {
- const b = tools.b;
-
- const target: ResolvedTarget = compile.root_module.resolved_target orelse {
- @panic(b.fmt("no 'target' set on Android module", .{}));
- };
- 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_libc_path.addStepDependencies(&compile.step);
- compile.setLibCFile(android_libc_path);
+ const configured_android_sdk_path = getAndroidSDKPath(b.allocator) catch @panic("OOM");
+ if (configured_android_sdk_path.len > 0) {
+ // Set android SDK path here so it will not try searching for adb.exe if searching for JDK
+ path_search.android_sdk_path = configured_android_sdk_path;
}
+ 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,
+ });
- pub fn create(b: *std.Build, options: ToolsOptions) *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})),
- };
+ // 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",
+ });
- // Discover tool paths
- var path_search = PathSearch.init(b.allocator, host_os_tag) catch |err| switch (err) {
- error.OutOfMemory => @panic("OOM"),
- error.EnvironmentVariableNotFound => @panic("unable to find PATH as an environment variable"),
+ // Validate
+ var errors = std.ArrayList([]const u8).init(b.allocator);
+ defer errors.deinit();
+
+ // Get commandline tools path
+ // - 1st: $ANDROID_HOME/cmdline-tools/bin
+ // - 2nd: $ANDROID_HOME/tools/bin
+ const cmdline_tools_path = cmdlineblk: {
+ const cmdline_tools = b.pathResolve(&[_][]const u8{ android_sdk_path, "cmdline-tools", "latest", "bin" });
+ std.fs.accessAbsolute(cmdline_tools, .{}) catch |cmderr| switch (cmderr) {
+ error.FileNotFound => {
+ const tools = b.pathResolve(&[_][]const u8{ android_sdk_path, "tools", "bin" });
+ // Check if Commandline tools path is accessible
+ std.fs.accessAbsolute(tools, .{}) catch |toolerr| switch (toolerr) {
+ error.FileNotFound => {
+ const message = b.fmt("Android Command Line Tools not found. Expected at: {s} or {s}", .{
+ cmdline_tools,
+ tools,
+ });
+ errors.append(message) catch @panic("OOM");
+ },
+ else => {
+ const message = b.fmt("Android Command Line Tools path had unexpected error: {s} ({s})", .{
+ @errorName(toolerr),
+ tools,
+ });
+ errors.append(message) catch @panic("OOM");
+ },
+ };
+ },
+ else => {
+ const message = b.fmt("Android Command Line Tools path had unexpected error: {s} ({s})", .{
+ @errorName(cmderr),
+ cmdline_tools,
+ });
+ errors.append(message) catch @panic("OOM");
+ },
};
- const configured_jdk_path = getJDKPath(b.allocator) catch @panic("OOM");
- if (configured_jdk_path.len > 0) {
- // Set JDK path here so it will not try searching for jarsigner.exe if searching for Android SDK
- path_search.jdk_path = configured_jdk_path;
- }
- const configured_android_sdk_path = getAndroidSDKPath(b.allocator) catch @panic("OOM");
- if (configured_android_sdk_path.len > 0) {
- // Set android SDK path here so it will not try searching for adb.exe if searching for JDK
- path_search.android_sdk_path = configured_android_sdk_path;
- }
- const android_sdk_path = path_search.findAndroidSDK(b.allocator) catch @panic("OOM");
- const jdk_path = path_search.findJDK(b.allocator) catch @panic("OOM");
+ break :cmdlineblk cmdline_tools;
+ };
- // Get build tools path
+ if (jdk_path.len == 0) {
+ errors.append(
+ \\JDK not found.
+ \\- Download it from https://www.oracle.com/th/java/technologies/downloads/
+ \\- Then configure your JDK_HOME environment variable to where you've installed it.
+ ) catch @panic("OOM");
+ }
+ if (android_sdk_path.len == 0) {
+ errors.append(
+ \\Android SDK not found.
+ \\- Download it from https://developer.android.com/studio
+ \\- Then configure your ANDROID_HOME environment variable to where you've installed it."
+ ) catch @panic("OOM");
+ } else {
+ // Check if build tools path is accessible
// 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();
-
- // Get commandline tools path
- // - 1st: $ANDROID_HOME/cmdline-tools/bin
- // - 2nd: $ANDROID_HOME/tools/bin
- const cmdline_tools_path = cmdlineblk: {
- const cmdline_tools = b.pathResolve(&[_][]const u8{ android_sdk_path, "cmdline-tools", "latest", "bin" });
- std.fs.accessAbsolute(cmdline_tools, .{}) catch |cmderr| switch (cmderr) {
- error.FileNotFound => {
- const tools = b.pathResolve(&[_][]const u8{ android_sdk_path, "tools", "bin" });
- // Check if Commandline tools path is accessible
- std.fs.accessAbsolute(tools, .{}) catch |toolerr| switch (toolerr) {
- error.FileNotFound => {
- const message = b.fmt("Android Command Line Tools not found. Expected at: {s} or {s}", .{
- cmdline_tools,
- tools,
- });
- errors.append(message) catch @panic("OOM");
- },
- else => {
- const message = b.fmt("Android Command Line Tools path had unexpected error: {s} ({s})", .{
- @errorName(toolerr),
- tools,
- });
- errors.append(message) catch @panic("OOM");
- },
- };
- },
- else => {
- const message = b.fmt("Android Command Line Tools path had unexpected error: {s} ({s})", .{
- @errorName(cmderr),
- cmdline_tools,
- });
- errors.append(message) catch @panic("OOM");
- },
- };
- break :cmdlineblk cmdline_tools;
+ 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");
+ },
};
- if (jdk_path.len == 0) {
- errors.append(
- \\JDK not found.
- \\- Download it from https://www.oracle.com/th/java/technologies/downloads/
- \\- Then configure your JDK_HOME environment variable to where you've installed it.
- ) catch @panic("OOM");
- }
- if (android_sdk_path.len == 0) {
- errors.append(
- \\Android SDK not found.
- \\- Download it from https://developer.android.com/studio
- \\- Then configure your ANDROID_HOME environment variable to where you've installed it."
- ) catch @panic("OOM");
- } else {
- // 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) {
+ // 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 Build Tool version '{s}' not found. Install it via 'sdkmanager' or Android Studio.", .{
- options.build_tools_version,
+ 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 Build Tool version '{s}' had unexpected error: {s}", .{
- options.build_tools_version,
+ 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 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) {
+ // 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 version '{s}' not found. Install it via 'sdkmanager' or Android Studio.", .{
+ 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 version '{s}' had unexpected error: {s} ({s})", .{
+ const message = b.fmt("Android NDK sysroot '{s}' had unexpected error: {s}, at: '{s}'", .{
options.ndk_version,
@errorName(err),
- android_ndk_path,
+ android_ndk_sysroot,
});
errors.append(message) catch @panic("OOM");
break :blk false;
@@ -317,79 +250,28 @@ pub const Tools = struct {
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: {
+ // 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 sysroot '{s}' had unexpected error. Missing at '{s}'", .{
+ const message = b.fmt("Android NDK version '{s}' does not support API Level {d}. No folder 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;
- };
-
- // 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;
- };
- }
-
- // 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,
+ ndk_sysroot_target_api_version,
});
errors.append(message) catch @panic("OOM");
break :blk false;
},
else => {
- const message = b.fmt("Android API level {d} had unexpected error: {s}, at: '{s}'", .{
+ 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),
- root_jar,
+ ndk_sysroot_target_api_version,
});
errors.append(message) catch @panic("OOM");
break :blk false;
@@ -398,62 +280,181 @@ pub const Tools = struct {
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(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);
+ }
+ if (errors.items.len > 0) {
+ printErrorsAndExit("unable to find required Android installation", errors.items);
+ }
+
+ 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");
+ 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,
+ .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}),
+ }),
+ },
+ .cmdline_tools = .{
+ .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}"
+ },
+ .java_tools = .{
+ .jar = b.pathResolve(&[_][]const u8{
+ jdk_path, "bin", b.fmt("jar{s}", .{exe_suffix}),
+ }),
+ .javac = b.pathResolve(&[_][]const u8{
+ jdk_path, "bin", b.fmt("javac{s}", .{exe_suffix}),
+ }),
+ .keytool = b.pathResolve(&[_][]const u8{
+ jdk_path, "bin", b.fmt("keytool{s}", .{exe_suffix}),
+ }),
+ },
+ };
+ return tools;
+}
+
+pub const CreateKey = struct {
+ pub const Algorithm = enum {
+ rsa,
+
+ /// arg returns the keytool argument
+ fn arg(self: Algorithm) []const u8 {
+ return switch (self) {
+ .rsa => "RSA",
+ };
}
+ };
- 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");
- 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,
- .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}),
- }),
- },
- .cmdline_tools = .{
- .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}"
- },
- .java_tools = .{
- .jar = b.pathResolve(&[_][]const u8{
- jdk_path, "bin", b.fmt("jar{s}", .{exe_suffix}),
- }),
- .javac = b.pathResolve(&[_][]const u8{
- jdk_path, "bin", b.fmt("javac{s}", .{exe_suffix}),
- }),
- .keytool = b.pathResolve(&[_][]const u8{
- jdk_path, "bin", b.fmt("keytool{s}", .{exe_suffix}),
- }),
- },
+ alias: []const u8,
+ password: []const u8,
+ algorithm: Algorithm,
+ /// in bits, the maximum size of an RSA key supported by the Android keystore is 4096 bits (as of 2024)
+ key_size_in_bits: u32,
+ validity_in_days: u32,
+ /// https://stackoverflow.com/questions/3284055/what-should-i-use-for-distinguished-name-in-our-keystore-for-the-android-marke/3284135#3284135
+ 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",
};
- return tools;
}
};
+pub fn createKeyStore(tools: *const Tools, options: CreateKey) KeyStore {
+ const b = tools.b;
+ const keytool = b.addSystemCommand(&.{
+ // https://docs.oracle.com/en/java/javase/17/docs/specs/man/keytool.html
+ tools.java_tools.keytool,
+ "-genkey",
+ "-v",
+ });
+ keytool.setName(runNameContext("keytool"));
+ keytool.addArg("-keystore");
+ const keystore_file = keytool.addOutputFileArg("zig-generated.keystore");
+ keytool.addArgs(&.{
+ // -alias "ca"
+ "-alias",
+ options.alias,
+ // -keyalg "rsa"
+ "-keyalg",
+ options.algorithm.arg(),
+ "-keysize",
+ b.fmt("{d}", .{options.key_size_in_bits}),
+ "-validity",
+ b.fmt("{d}", .{options.validity_in_days}),
+ "-storepass",
+ options.password,
+ "-keypass",
+ options.password,
+ // -dname "CN=example.com, OU=ID, O=Example, L=Doe, S=Jane, C=GB"
+ "-dname",
+ options.distinguished_name,
+ });
+ // ignore stderr, it just gives you an output like:
+ // "Generating 4,096 bit RSA key pair and self-signed certificate (SHA384withRSA) with a validity of 10,000 days
+ // for: CN=example.com, OU=ID, O=Example, L=Doe, ST=Jane, C=GB"
+ _ = keytool.captureStdErr();
+ return .{
+ .file = keystore_file,
+ .password = options.password,
+ };
+}
+
+// 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 {
+ const b = tools.b;
+
+ const target: ResolvedTarget = compile.root_module.resolved_target orelse {
+ @panic(b.fmt("no 'target' set on Android module", .{}));
+ };
+ 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_libc_path.addStepDependencies(&compile.step);
+ compile.setLibCFile(android_libc_path);
+}
+
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.
@@ -715,3 +716,5 @@ const PathSearch = struct {
}
}
};
+
+const Tools = @This();