authorgravatar for dev@luna.glLuna Schwalbe <dev@luna.gl> 2025-08-15 22:12:35+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-09 02:47:13+01:00
log04e73d03bd4cd7c584c059c7cee02faf1a17e36c
treebab8e588b741ba683fc3f4f80b72e8009bf82922
parente8af0f2cc017f44a01f70b25a1a81892d2374794

android: detect native ABI and API level correctly

ABI detection previously did not take into account the non-standard directory structure of Android. This has been fixed. The API level is detected by running `getprop ro.build.version.sdk`, since we don't want to depend on bionic, and reading system properties ourselves is not trivially possible.

2 files changed, 68 insertions(+), 2 deletions(-)

lib/std/Target.zig+4
...@@ -2131,6 +2131,10 @@ pub inline fn isMuslLibC(target: *const Target) bool {...@@ -2131,6 +2131,10 @@ pub inline fn isMuslLibC(target: *const Target) bool {
2131 return target.os.tag == .linux and target.abi.isMusl();2131 return target.os.tag == .linux and target.abi.isMusl();
2132}2132}
21332133
2134pub inline fn isBionicLibC(target: *const Target) bool {
2135 return target.os.tag == .linux and target.abi.isAndroid();
2136}
2137
2134pub inline fn isDarwinLibC(target: *const Target) bool {2138pub inline fn isDarwinLibC(target: *const Target) bool {
2135 return switch (target.abi) {2139 return switch (target.abi) {
2136 .none, .simulator => target.os.tag.isDarwin(),2140 .none, .simulator => target.os.tag.isDarwin(),
lib/std/zig/system.zig+64-2
...@@ -211,6 +211,8 @@ pub const DetectError = error{...@@ -211,6 +211,8 @@ pub const DetectError = error{
211 DeviceBusy,211 DeviceBusy,
212 OSVersionDetectionFail,212 OSVersionDetectionFail,
213 Unexpected,213 Unexpected,
214 /// Android-only. Querying API level through `getprop` failed.
215 ApiLevelQueryFailed,
214} || Io.Cancelable;216} || Io.Cancelable;
215217
216/// Given a `Target.Query`, which specifies in detail which parts of the218/// Given a `Target.Query`, which specifies in detail which parts of the
...@@ -500,6 +502,28 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -500,6 +502,28 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
500 }502 }
501 }503 }
502504
505 if (builtin.os.tag == .linux and result.isBionicLibC() and query.os_tag == null and query.android_api_level == null) {
506 result.os.version_range.linux.android = detectAndroidApiLevel(io) catch |err| return switch (err) {
507 error.InvalidWtf8,
508 error.CurrentWorkingDirectoryUnlinked,
509 error.InvalidBatchScriptArg,
510 => unreachable, // Windows-only
511 error.ApiLevelQueryFailed => |e| e,
512 else => blk: {
513 std.log.err("spawning or reading from getprop failed ({s})", .{@errorName(err)});
514 switch (err) {
515 error.SystemResources,
516 error.FileSystem,
517 error.ProcessFdQuotaExceeded,
518 error.SystemFdQuotaExceeded,
519 error.SymLinkLoop,
520 => |e| break :blk e,
521 else => break :blk error.ApiLevelQueryFailed,
522 }
523 },
524 };
525 }
526
503 return result;527 return result;
504}528}
505529
...@@ -1046,8 +1070,11 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ...@@ -1046,8 +1070,11 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
1046 error.NetworkNotFound,1070 error.NetworkNotFound,
1047 error.FileTooBig,1071 error.FileTooBig,
1048 error.Unexpected,1072 error.Unexpected,
1049 => return error.UnableToOpenElfFile,1073 => |e| if (e == error.FileNotFound and os.tag == .linux and mem.eql(u8, file_name, "/usr/bin/env")) {
10501074 // Android does not have a /usr directory, so try again
1075 file_name = "/system/bin/env";
1076 continue;
1077 } else return error.UnableToOpenElfFile,
1051 else => |e| return e,1078 else => |e| return e,
1052 };1079 };
1053 var is_elf_file = false;1080 var is_elf_file = false;
...@@ -1131,6 +1158,41 @@ const LdInfo = struct {...@@ -1131,6 +1158,41 @@ const LdInfo = struct {
1131 abi: Target.Abi,1158 abi: Target.Abi,
1132};1159};
11331160
1161fn detectAndroidApiLevel(io: Io) !u32 {
1162 comptime if (builtin.os.tag != .linux) unreachable;
1163
1164 var child = try std.process.spawn(io, .{
1165 .argv = &.{
1166 "/system/bin/getprop",
1167 "ro.build.version.sdk",
1168 },
1169 .stdin = .ignore,
1170 .stdout = .pipe,
1171 .stderr = .ignore,
1172 });
1173 errdefer child.kill(io);
1174
1175 // PROP_VALUE_MAX is 92, output is value + newline.
1176 // Currently API levels are two-digit numbers, but we want to make sure we never read a partial value.
1177 var stdout_buf: [92 + 1]u8 = undefined;
1178 var reader = child.stdout.?.readerStreaming(io, &.{});
1179 const n = try reader.interface.readSliceShort(&stdout_buf);
1180 const api_level = std.fmt.parseInt(u32, stdout_buf[0 .. n - 1], 10) catch |e| {
1181 std.log.err(
1182 "Could not parse API level, unexpected getprop output '{s}' ({s})",
1183 .{ stdout_buf[0 .. n - 1], @errorName(e) },
1184 );
1185 return error.ApiLevelQueryFailed;
1186 };
1187
1188 const term = try child.wait(io);
1189 if (term != .exited or term.exited != 0) {
1190 std.log.err("getprop terminated abnormally: {}", .{term});
1191 return error.ApiLevelQueryFailed;
1192 }
1193 return api_level;
1194}
1195
1134test {1196test {
1135 _ = NativePaths;1197 _ = NativePaths;
11361198