Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.zig-cache
zig-out
zig-pkg
3 changes: 2 additions & 1 deletion examples/sdl2/third-party/sdl2/build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.{
.name = "sdl",
.name = .sdl,
.version = "2.32.10",
.dependencies = .{
.sdl2 = .{
Expand All @@ -14,4 +14,5 @@
"build.zig.zon",
"src",
},
.fingerprint = 0xec638ccb4e5e0ae0,
}
89 changes: 49 additions & 40 deletions src/androidbuild/apk.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const getAndroidTriple = androidbuild.getAndroidTriple;
const runNameContext = androidbuild.runNameContext;
const printErrorsAndExit = androidbuild.printErrorsAndExit;

const Allocator = std.mem.Allocator;
const Target = std.Target;
const Step = std.Build.Step;
const ResolvedTarget = std.Build.ResolvedTarget;
Expand Down Expand Up @@ -219,7 +220,7 @@ pub fn addInstallApk(apk: *Apk) *Step.InstallFile {
};
}

fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
fn doInstallApk(apk: *Apk) Allocator.Error!*Step.InstallFile {
const b = apk.b;

const key_store: KeyStore = apk.key_store orelse .empty;
Expand Down Expand Up @@ -299,12 +300,14 @@ 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.sdk.android_sdk_path,
"platforms",
b.fmt("android-{d}", .{@intFromEnum(apk.api_level)}),
"android.jar",
});
const root_jar: LazyPath = .{
.cwd_relative = b.pathResolve(&[_][]const u8{
apk.sdk.android_sdk_path,
"platforms",
b.fmt("android-{d}", .{@intFromEnum(apk.api_level)}),
"android.jar",
}),
};

// Make resources.apk from:
// - resources.flat.zip (created from "aapt2 compile")
Expand All @@ -319,11 +322,13 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
const aapt2link = b.addSystemCommand(&[_][]const u8{
apk.build_tools.aapt2,
"link",
"-I", // add an existing package to base include set
root_jar,
});
aapt2link.setName(runNameContext("aapt2 link"));

// Add '-I android_sdk/platforms/android_version/android.jar'
aapt2link.addArg("-I");
aapt2link.addFileArg(root_jar);

if (b.verbose) {
aapt2link.addArg("-v");
}
Expand Down Expand Up @@ -552,13 +557,20 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
// 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",
root_jar,
// NOTE(jae): 2024-09-19
// Debug issues with the SDL.java classes
// "-Xlint:deprecation",
});
javac_cmd.setName(runNameContext("javac"));

// Add root jar
javac_cmd.addArg("-cp");
javac_cmd.addFileArg(root_jar);

// NOTE(jae): 2026-03-01
// If we have verbose logging on, telling us about deprecated Java files
if (b.verbose) {
javac_cmd.addArg("-Xlint:deprecation");
}

// Output directory
javac_cmd.addArg("-d");
const java_classes_output_dir = javac_cmd.addOutputDirectoryArg("android_classes");

Expand All @@ -574,23 +586,8 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
});
d8.setName(runNameContext("d8"));

// Prepend JDK bin path so d8 can always find "java", etc
if (apk.sdk.jdk_path.len > 0) {
var env_map = d8.getEnvMap();
const path = env_map.get("PATH") orelse &[0]u8{};
const new_path = try std.mem.join(b.allocator, &[1]u8{std.fs.path.delimiter}, &.{
b.fmt("{s}/bin", .{apk.sdk.jdk_path}),
path,
});
try env_map.put("PATH", new_path);
}

// ie. android_sdk/platforms/android-{api-level}/android.jar
d8.addArg("--lib");
d8.addArg(root_jar);

d8.addArg("--output");
const dex_output_dir = d8.addOutputDirectoryArg("android_dex");
// Add JDK bin path so d8 can always find "java", etc
try apk.updatePathWithJdk(d8);

// NOTE(jae): 2024-09-22
// As per documentation for d8, we may want to specific the minimum API level we want
Expand All @@ -599,7 +596,14 @@ fn doInstallApk(apk: *Apk) std.mem.Allocator.Error!*Step.InstallFile {
// d8.addArg(number_as_string);

// add each output *.class file
D8Glob.create(b, d8, java_classes_output_dir);
D8Glob.create(b, d8, java_classes_output_dir, root_jar);

// ie. android_sdk/platforms/android-{api-level}/android.jar
d8.addArg("--lib");
d8.addFileArg(root_jar);

d8.addArg("--output");
const dex_output_dir = d8.addOutputDirectoryArg("android_dex");
const dex_file = dex_output_dir.path(b, "classes.dex");

// Append classes.dex to apk
Expand Down Expand Up @@ -949,18 +953,23 @@ fn updateSharedLibraryOptions(artifact: *std.Build.Step.Compile) void {
}

/// Prepend JDK bin path so "d8", "apksigner", etc can always find "java"
fn updatePathWithJdk(apk: *Apk, run: *std.Build.Step.Run) !void {
fn updatePathWithJdk(apk: *Apk, run: *std.Build.Step.Run) Allocator.Error!void {
if (apk.sdk.jdk_path.len == 0) return;

const b = apk.b;

var env_map = run.getEnvMap();
const path = env_map.get("PATH") orelse &[0]u8{};
const new_path = try std.mem.join(b.allocator, &[1]u8{std.fs.path.delimiter}, &.{
b.fmt("{s}/bin", .{apk.sdk.jdk_path}),
path,
});
try env_map.put("PATH", new_path);
if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15) {
// Deprecated path: Add "java" path to env
var env_map = run.getEnvMap();
const path = env_map.get("PATH") orelse &[0]u8{};
const new_path = try std.mem.join(b.allocator, &[1]u8{std.fs.path.delimiter}, &.{
b.fmt("{s}/bin", .{apk.sdk.jdk_path}),
path,
});
try env_map.put("PATH", new_path);
} else {
run.addPathDir(b.pathJoin(&.{ apk.sdk.jdk_path, "bin" }));
}
}

const Apk = @This();
42 changes: 38 additions & 4 deletions src/androidbuild/d8glob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,18 @@ run: *Build.Step.Run,
/// The directory that will contain the files to glob
dir: LazyPath,

/// Track the files added to the Run step for --watch
argv_range: ?ArgRange,

const file_ext = ".class";

const ArgRange = struct {
start: usize,
end: usize,
};

/// 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 {
pub fn create(owner: *std.Build, run: *Run, dir: LazyPath, root_jar: LazyPath) void {
const glob = owner.allocator.create(@This()) catch @panic("OOM");
glob.* = .{
.step = Step.init(.{
Expand All @@ -35,19 +43,31 @@ pub fn create(owner: *std.Build, run: *Run, dir: LazyPath) void {
}),
.run = run,
.dir = dir,
.argv_range = null,
};
// 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);
// If root_jar changes, then this should trigger
root_jar.addStepDependencies(&glob.step);
}

fn make(step: *Step, _: Build.Step.MakeOptions) !void {
fn make(step: *Step, options: Build.Step.MakeOptions) !void {
_ = options;
const b = step.owner;
const arena = b.allocator;
const gpa = b.allocator;
const glob: *@This() = @fieldParentPtr("step", step);

const d8 = glob.run;

// Triggers on --watch if a Java file is modified.
// For example: ZigSDLActivity.java
if (glob.argv_range) |argv_range| {
try d8.argv.replaceRange(d8.step.owner.allocator, argv_range.start, argv_range.end - argv_range.start, &.{});
}

const search_dir = if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15)
glob.dir.getPath3(b, step)
else
Expand Down Expand Up @@ -76,6 +96,7 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void {
else
dir.close(b.graph.io);

var optional_argv_start: ?usize = null;
var walker = try dir.walk(arena);
defer walker.deinit();
while (if (builtin.zig_version.major == 0 and builtin.zig_version.minor <= 15)
Expand All @@ -92,17 +113,30 @@ fn make(step: *Step, _: Build.Step.MakeOptions) !void {
// - !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)) {
const absolute_file_path = try search_dir.root_dir.join(gpa, &.{ search_dir.sub_path, entry.path });
const relative_to_dir_path = absolute_file_path[search_dir.sub_path.len + 1 ..];
// 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);
if (optional_argv_start == null) {
optional_argv_start = d8.argv.items.len;
}
d8.addFileInput(LazyPath{
.cwd_relative = try search_dir.root_dir.join(b.allocator, &.{ search_dir.sub_path, entry.path }),
.cwd_relative = absolute_file_path,
});
d8.addArg(relative_to_dir_path);
}
}

// Track arguments added to "d8" so that we can remove them if "make" is re-run in --watch mode
if (optional_argv_start) |argv_start| {
glob.argv_range = .{
.start = argv_start,
.end = d8.argv.items.len,
};
}
}

const D8Glob = @This();