208 lines
6.4 KiB
Zig
208 lines
6.4 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
pub fn build(_: *std.Build) !void {}
|
|
|
|
// Configure the step to point to the Android NDK for libc and include
|
|
// paths. This requires the Android NDK installed in the system and
|
|
// setting the appropriate environment variables or installing the NDK
|
|
// in the default location.
|
|
//
|
|
// The environment variables can be set as follows:
|
|
// - `ANDROID_NDK_HOME`: Directly points to the NDK path, including the version.
|
|
// - `ANDROID_HOME` or `ANDROID_SDK_ROOT`: Points to the Android SDK path;
|
|
// latest available NDK will be automatically selected.
|
|
//
|
|
// NB: This is a workaround until zig natively supports bionic
|
|
// cross-compilation (ziglang/zig#23906).
|
|
pub fn addPaths(b: *std.Build, step: *std.Build.Step.Compile) !void {
|
|
const Cache = struct {
|
|
const Key = struct {
|
|
arch: std.Target.Cpu.Arch,
|
|
abi: std.Target.Abi,
|
|
api_level: u32,
|
|
};
|
|
|
|
var map: std.AutoHashMapUnmanaged(Key, ?struct {
|
|
libc: std.Build.LazyPath,
|
|
cpp_include: std.Build.LazyPath,
|
|
lib: std.Build.LazyPath,
|
|
}) = .empty;
|
|
};
|
|
|
|
const target = step.rootModuleTarget();
|
|
const gop = try Cache.map.getOrPut(b.allocator, .{
|
|
.arch = target.cpu.arch,
|
|
.abi = target.abi,
|
|
.api_level = target.os.version_range.linux.android,
|
|
});
|
|
|
|
if (!gop.found_existing) {
|
|
const ndk_path = findNDKPath(b) orelse return error.AndroidNDKNotFound;
|
|
|
|
const ndk_triple = ndkTriple(target) orelse {
|
|
gop.value_ptr.* = null;
|
|
return error.AndroidNDKUnsupportedTarget;
|
|
};
|
|
|
|
const host = hostTag() orelse {
|
|
gop.value_ptr.* = null;
|
|
return error.AndroidNDKUnsupportedHost;
|
|
};
|
|
|
|
const sysroot = b.pathJoin(&.{
|
|
ndk_path,
|
|
"toolchains",
|
|
"llvm",
|
|
"prebuilt",
|
|
host,
|
|
"sysroot",
|
|
});
|
|
const include_dir = b.pathJoin(&.{
|
|
sysroot,
|
|
"usr",
|
|
"include",
|
|
});
|
|
const sys_include_dir = b.pathJoin(&.{
|
|
sysroot,
|
|
"usr",
|
|
"include",
|
|
ndk_triple,
|
|
});
|
|
const c_runtime_dir = b.pathJoin(&.{
|
|
sysroot,
|
|
"usr",
|
|
"lib",
|
|
ndk_triple,
|
|
b.fmt("{d}", .{target.os.version_range.linux.android}),
|
|
});
|
|
const lib = b.pathJoin(&.{
|
|
sysroot,
|
|
"usr",
|
|
"lib",
|
|
ndk_triple,
|
|
});
|
|
const cpp_include = b.pathJoin(&.{
|
|
sysroot,
|
|
"usr",
|
|
"include",
|
|
"c++",
|
|
"v1",
|
|
});
|
|
|
|
const libc_txt = b.fmt(
|
|
\\include_dir={s}
|
|
\\sys_include_dir={s}
|
|
\\crt_dir={s}
|
|
\\msvc_lib_dir=
|
|
\\kernel32_lib_dir=
|
|
\\gcc_dir=
|
|
, .{ include_dir, sys_include_dir, c_runtime_dir });
|
|
|
|
const wf = b.addWriteFiles();
|
|
const libc_path = wf.add("libc.txt", libc_txt);
|
|
|
|
gop.value_ptr.* = .{
|
|
.libc = libc_path,
|
|
.cpp_include = .{ .cwd_relative = cpp_include },
|
|
.lib = .{ .cwd_relative = lib },
|
|
};
|
|
}
|
|
|
|
const value = gop.value_ptr.* orelse return error.AndroidNDKNotFound;
|
|
|
|
step.setLibCFile(value.libc);
|
|
step.root_module.addSystemIncludePath(value.cpp_include);
|
|
step.root_module.addLibraryPath(value.lib);
|
|
}
|
|
|
|
fn findNDKPath(b: *std.Build) ?[]const u8 {
|
|
// Check if user has set the environment variable for the NDK path.
|
|
if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null) |value| {
|
|
if (value.len == 0) return null;
|
|
var dir = std.fs.openDirAbsolute(value, .{}) catch return null;
|
|
defer dir.close();
|
|
return value;
|
|
}
|
|
|
|
// Check the common environment variables for the Android SDK path and look for the NDK inside it.
|
|
inline for (.{ "ANDROID_HOME", "ANDROID_SDK_ROOT" }) |env| {
|
|
if (std.process.getEnvVarOwned(b.allocator, env) catch null) |sdk| {
|
|
if (sdk.len > 0) {
|
|
if (findLatestNDK(b, sdk)) |ndk| return ndk;
|
|
}
|
|
}
|
|
}
|
|
|
|
// As a fallback, we assume the most common/default SDK path based on the OS.
|
|
const home = std.process.getEnvVarOwned(
|
|
b.allocator,
|
|
if (builtin.os.tag == .windows) "LOCALAPPDATA" else "HOME",
|
|
) catch return null;
|
|
|
|
const default_sdk_path = b.pathJoin(
|
|
&.{
|
|
home,
|
|
switch (builtin.os.tag) {
|
|
.linux => "Android/sdk",
|
|
.macos => "Library/Android/Sdk",
|
|
.windows => "Android/Sdk",
|
|
else => return null,
|
|
},
|
|
},
|
|
);
|
|
|
|
return findLatestNDK(b, default_sdk_path);
|
|
}
|
|
|
|
fn findLatestNDK(b: *std.Build, sdk_path: []const u8) ?[]const u8 {
|
|
const ndk_dir = b.pathJoin(&.{ sdk_path, "ndk" });
|
|
var dir = std.fs.openDirAbsolute(ndk_dir, .{ .iterate = true }) catch return null;
|
|
defer dir.close();
|
|
|
|
var latest_: ?struct {
|
|
name: []const u8,
|
|
version: std.SemanticVersion,
|
|
} = null;
|
|
var iterator = dir.iterate();
|
|
|
|
while (iterator.next() catch null) |file| {
|
|
if (file.kind != .directory) continue;
|
|
const version = std.SemanticVersion.parse(file.name) catch continue;
|
|
if (latest_) |latest| {
|
|
if (version.order(latest.version) != .gt) continue;
|
|
}
|
|
latest_ = .{
|
|
.name = file.name,
|
|
.version = version,
|
|
};
|
|
}
|
|
|
|
const latest = latest_ orelse return null;
|
|
|
|
return b.pathJoin(&.{ sdk_path, "ndk", latest.name });
|
|
}
|
|
|
|
fn hostTag() ?[]const u8 {
|
|
return switch (builtin.os.tag) {
|
|
.linux => "linux-x86_64",
|
|
// All darwin hosts use the same prebuilt binaries
|
|
// (https://developer.android.com/ndk/guides/other_build_systems).
|
|
.macos => "darwin-x86_64",
|
|
.windows => "windows-x86_64",
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
// We must map the target architecture to the corresponding NDK triple following the NDK
|
|
// documentation: https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md#architectures
|
|
fn ndkTriple(target: std.Target) ?[]const u8 {
|
|
return switch (target.cpu.arch) {
|
|
.arm => "arm-linux-androideabi",
|
|
.aarch64 => "aarch64-linux-android",
|
|
.x86 => "i686-linux-android",
|
|
.x86_64 => "x86_64-linux-android",
|
|
else => null,
|
|
};
|
|
}
|