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.
This commit is contained in:
Luna Schwalbe 2025-08-15 22:12:35 +02:00
parent bfe3317059
commit c2cd8df622
No known key found for this signature in database
GPG key ID: 7E20C1B6E0766C1D
2 changed files with 72 additions and 2 deletions

View file

@ -2120,6 +2120,10 @@ pub inline fn isMuslLibC(target: *const Target) bool {
return target.os.tag == .linux and target.abi.isMusl();
}
pub inline fn isBionicLibC(target: *const Target) bool {
return target.os.tag == .linux and target.abi.isAndroid();
}
pub inline fn isDarwinLibC(target: *const Target) bool {
return switch (target.abi) {
.none, .simulator => target.os.tag.isDarwin(),

View file

@ -210,6 +210,8 @@ pub const DetectError = error{
OSVersionDetectionFail,
Unexpected,
ProcessNotFound,
/// Android-only. Querying API level through `getprop` failed.
ApiLevelQueryFailed,
} || Io.Cancelable;
/// Given a `Target.Query`, which specifies in detail which parts of the
@ -500,6 +502,29 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
}
}
if (builtin.os.tag == .linux and result.isBionicLibC() and query.os_tag == null and query.android_api_level == null) {
result.os.version_range.linux.android = detectAndroidApiLevel(io) catch |err| return switch (err) {
error.InvalidWtf8,
error.CurrentWorkingDirectoryUnlinked,
error.InvalidBatchScriptArg,
error.InvalidHandle,
=> unreachable, // Windows-only
error.ApiLevelQueryFailed => |e| e,
else => blk: {
std.log.err("spawning or reading from getprop failed ({s})", .{@errorName(err)});
switch (err) {
error.SystemResources,
error.FileSystem,
error.ProcessFdQuotaExceeded,
error.SystemFdQuotaExceeded,
error.SymLinkLoop,
=> |e| break :blk e,
else => break :blk error.ApiLevelQueryFailed,
}
},
};
}
return result;
}
@ -1044,8 +1069,11 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ
error.NetworkNotFound,
error.FileTooBig,
error.Unexpected,
=> return error.UnableToOpenElfFile,
=> |e| if (e == error.FileNotFound and os.tag == .linux and mem.eql(u8, file_name, "/usr/bin/env")) {
// Android does not have a /usr directory, so try again
file_name = "/system/bin/env";
continue;
} else return error.UnableToOpenElfFile,
else => |e| return e,
};
var is_elf_file = false;
@ -1130,6 +1158,44 @@ const LdInfo = struct {
abi: Target.Abi,
};
fn detectAndroidApiLevel(io: Io) !u32 {
comptime if (builtin.os.tag != .linux) unreachable;
// `child.spawn()` uses an arena and the exact memory requirement isn't easily determined,
// so we give it 128 * 3 bytes, which was shown in testing to be enough.
var alloc_buf: [128 * 3]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&alloc_buf);
var child = std.process.Child.init(&.{ "/system/bin/getprop", "ro.build.version.sdk" }, fba.allocator());
// pass empty EnvMap, no allocator and deinit() required
child.env_map = &std.process.EnvMap.init(undefined);
child.stdin_behavior = .Ignore;
child.stdout_behavior = .Pipe;
child.stderr_behavior = .Ignore;
try child.spawn();
errdefer _ = child.kill() catch {};
// PROP_VALUE_MAX is 92, output is value + newline.
// Currently API levels are two-digit numbers, but we want to make sure we never read a partial value.
var stdout_buf: [92 + 1]u8 = undefined;
var reader = child.stdout.?.readerStreaming(io, &.{});
const n = try reader.interface.readSliceShort(&stdout_buf);
const api_level = std.fmt.parseInt(u32, stdout_buf[0 .. n - 1], 10) catch |e| {
std.log.err(
"Could not parse API level, unexpected getprop output '{s}' ({s})",
.{ stdout_buf[0 .. n - 1], @errorName(e) },
);
return error.ApiLevelQueryFailed;
};
const term = try child.wait();
if (term != .Exited or term.Exited != 0) {
std.log.err("getprop terminated abnormally: {}", .{term});
return error.ApiLevelQueryFailed;
}
return api_level;
}
test {
_ = NativePaths;