commit bf9137427b2a9497d5c5436fdc159b9cc7a5e56a
parent b4523172e9848697219b5bf500d4e025a4235c51
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 09:39:48 -0700
Add Android arm64 NDK shared library support
Diffstat:
30 files changed, 527 insertions(+), 36 deletions(-)
diff --git a/doc/PORT.md b/doc/PORT.md
@@ -33,19 +33,22 @@ Related: [BUILD.md](BUILD.md) (the 3-stage self-build mechanism),
The support set, one token grammar everywhere: **`<os>[-<libc>]-<arch>`**.
```
-cross matrix (16):
+cross matrix:
linux-{glibc,musl}-{aa64,x64,rv64} (6)
freebsd-{aarch64,amd64,riscv64} (3)
windows-{aarch64,x64} (2)
- macos-aarch64 (1)
- freestanding-{aa64,x64,rv64,rv32} (4) ← rv32 lives here only
+ android-aa64 (1) ← NativeActivity .so build/link
+ macos-{aa64,x64} (2)
+ freestanding-{aa64,x64,rv64,rv32,arm32} (5) ← rv32/arm32 live here only
-selfhost matrix (12): the same, minus freestanding
+selfhost matrix: the same, minus freestanding and android, and filtered to
+macOS host-compatible arch
```
-Freestanding has **no OS**, so there is nothing to run a compiler *on*: it
-participates in `cross` only. `selfhost` of a `freestanding-*` target is a hard
-error.
+Freestanding has **no OS**, so there is nothing to run a compiler *on*.
+Android currently has no device/emulator runner in the exec seam. Both
+participate in `cross` only; `android-*` is build/link-only unless `RUN=0` is
+used or an Android runner is added.
Arch tokens are the short forms **`aa64` / `x64` / `rv64`** (plus `rv32`,
freestanding-only). The long forms (`aarch64`/`amd64`/`x86_64`/`riscv64`) are
@@ -75,7 +78,7 @@ Resolved centrally by `scripts/hosted.sh expand <selector> [--mode=cross|selfhos
| Selector | Expands to |
|---|---|
| `all` | every support-set token (mode- and `KIT_VM`-filtered) |
-| `linux` / `freebsd` / `windows` / `macos` / `freestanding` | all configs for that OS |
+| `linux` / `freebsd` / `windows` / `android` / `macos` / `freestanding` | all configs for that OS |
| `linux-musl` / `linux-glibc` | that libc, all 3 arches |
| `linux-glibc-x64`, `freebsd-aarch64`, `freestanding-rv32`, … | one exact config |
| `a,b,c` | comma list of any of the above |
diff --git a/doc/TESTING.md b/doc/TESTING.md
@@ -411,8 +411,9 @@ support set** and run on it, checked against an exit-code + stdout oracle. It's
the principled counterpart to the per-libc lanes above — one corpus, one runner
seam, many OSes. The seed case is `hello.c`. Two verdicts per (case, config):
`:build` (compile + link succeeded) and `:run` (correct exit code + stdout). The
-full matrix is 15 configs: Linux {aa64,x64,rv64} × {musl-static, musl-dynamic,
-glibc} + macOS-arm64 + Windows {x64,aarch64} + FreeBSD {amd64,aarch64,riscv64}.
+full matrix is 16 configs: Linux {aa64,x64,rv64} × {musl-static, musl-dynamic,
+glibc} + macOS-arm64 + Android-aarch64 shared-library build/link +
+Windows {x64,aarch64} + FreeBSD {amd64,aarch64,riscv64}.
The libc rides in the exec tag (`<arch>-linux` = musl/alpine,
`<arch>-linux-glibc` = glibc/debian), so a single flush routes every config to
its runner (alpine/debian container, native, or VM).
@@ -421,10 +422,12 @@ The two pieces it composes are reusable on their own:
- **`scripts/hosted.sh`** — one front-end over every sysroot provisioner. Targets
are `<os>[-<libc>]-<arch>` (`linux-{glibc,musl}-{aa64,x64,rv64}`,
- `freebsd-{amd64,aarch64,riscv64}`, `windows-{x64,aarch64}`, `macos-aarch64`).
+ `freebsd-{amd64,aarch64,riscv64}`, `windows-{x64,aarch64}`,
+ `android-aarch64`, `macos-aarch64`).
`prepare` provisions the sysroot (wrapping `freebsd_sysroot.sh` /
- `llvm_mingw_sysroot.sh` / the libc container `extract.sh`); `cc` compiles+links
- for a target (adding the right `-target`/`--sysroot`/`-mconsole`/`-isysroot`);
+ `llvm_mingw_sysroot.sh` / `android_ndk_sysroot.sh` / the libc container
+ `extract.sh`); `cc` compiles+links for a target (adding the right
+ `-target`/`--sysroot`/`-mconsole`/`-isysroot`);
`path`/`triple`/`tag`/`doctor` round it out.
- **`test/lib/exec_target.sh`** runs the result (native / qemu-user / podman /
VM), picking the runner from the tag.
diff --git a/driver/cmd/build.c b/driver/cmd/build.c
@@ -1093,7 +1093,8 @@ static int build_is_link_output(const BuildOptions* o) {
* -ffreestanding / -nostdinc / -nostdlib opt back out; -dynamic (shared) keeps
* its standalone meaning. */
static void build_enable_hosted_for_sysroot(BuildOptions* o) {
- if (o->wants_hosted_libc || o->shared) return;
+ if (o->wants_hosted_libc) return;
+ if (o->shared && !driver_target_shared_uses_hosted(o->target)) return;
if (!o->sysroot || !o->sysroot[0]) return;
if (o->freestanding || o->nostdinc) return;
if (o->no_stdlib || o->no_defaultlibs) return;
@@ -1108,7 +1109,8 @@ static void build_apply_default_hosted_profile(BuildOptions* o) {
}
static int build_apply_hosted_profile(BuildOptions* o) {
- if (!o->wants_hosted_libc || o->shared) return 0;
+ int shared_hosted = o->shared && driver_target_shared_uses_hosted(o->target);
+ if (!o->wants_hosted_libc || (o->shared && !shared_hosted)) return 0;
if (o->no_stdlib || o->no_defaultlibs) {
driver_errf(o->tool,
"-lc hosted expansion is disabled by -nostdlib/-nodefaultlibs");
@@ -1118,7 +1120,7 @@ static int build_apply_hosted_profile(BuildOptions* o) {
* apply: pass link_action = 1. */
return driver_link_inputs_apply_hosted(
&o->inputs, &o->hosted, &o->groups[0].cf, &o->link, o->target, o->sysroot,
- o->static_link, o->no_startfiles, 1);
+ o->static_link, o->shared, o->no_startfiles, 1);
}
/* ===================================================================== */
@@ -2462,7 +2464,8 @@ static int build_main(int argc, char** argv, int kind, const char* tool,
rc = 1;
goto done;
}
- if (!o.shared && !o.no_stdlib && !o.no_defaultlibs) {
+ if ((!o.shared || driver_target_shared_uses_hosted(o.target)) &&
+ !o.no_stdlib && !o.no_defaultlibs) {
DriverRuntimeArchive rt = {0};
if (driver_runtime_prepare_archive(&env, tool, &runtime, o.target,
o.epoch, &rt) != 0) {
diff --git a/driver/cmd/cc.c b/driver/cmd/cc.c
@@ -551,7 +551,8 @@ static int cc_apply_hosted_profile(CcOptions* o) {
* + define + lib-search-dir parts apply regardless. */
int link_action = !o->compile_only && !o->preprocess_only &&
o->dep_mode != CC_DEP_M && o->dep_mode != CC_DEP_MM;
- if (!o->wants_hosted_libc || o->shared) return 0;
+ int shared_hosted = o->shared && driver_target_shared_uses_hosted(o->target);
+ if (!o->wants_hosted_libc || (o->shared && !shared_hosted)) return 0;
if (o->no_stdlib || o->no_defaultlibs) {
driver_errf(CC_TOOL,
"-lc hosted expansion is disabled by -nostdlib/-nodefaultlibs");
@@ -559,7 +560,7 @@ static int cc_apply_hosted_profile(CcOptions* o) {
}
return driver_link_inputs_apply_hosted(
&o->inputs, &o->hosted, &o->cf, &o->link, o->target, o->sysroot,
- o->static_link, o->no_startfiles, link_action);
+ o->static_link, o->shared, o->no_startfiles, link_action);
}
static int cc_apply_env(CcOptions* o) {
@@ -586,7 +587,8 @@ static int cc_has_link_action(const CcOptions* o) {
* keep their existing meaning. The Windows-COFF default profile and an explicit
* -lc already set the flag, so this is a no-op in those cases. */
static void cc_enable_hosted_for_sysroot(CcOptions* o) {
- if (o->wants_hosted_libc || o->shared) return;
+ if (o->wants_hosted_libc) return;
+ if (o->shared && !driver_target_shared_uses_hosted(o->target)) return;
if (!o->sysroot || !o->sysroot[0]) return;
if (o->freestanding || o->nostdinc) return;
if (o->no_stdlib || o->no_defaultlibs) return;
@@ -2605,7 +2607,9 @@ static int driver_cc_main(int argc, char** argv, int force_check) {
return 1;
}
- if (link_action && !co.shared && !co.no_stdlib && !co.no_defaultlibs) {
+ if (link_action &&
+ (!co.shared || driver_target_shared_uses_hosted(co.target)) &&
+ !co.no_stdlib && !co.no_defaultlibs) {
DriverRuntimeArchive rt_archive = {0};
if (!runtime_resolved) {
driver_errf(CC_TOOL, "support dir not found");
diff --git a/driver/cmd/ld.c b/driver/cmd/ld.c
@@ -2835,10 +2835,12 @@ static KitStatus ld_print_memory_usage(LdOptions* o, KitLinkSession* link) {
* driver_link_inputs_insert_runtime_archives. The wants-hosted / -shared guard
* stays here. */
static int ld_apply_hosted(LdOptions* o) {
- if (!o->wants_hosted_libc || o->shared) return 0;
+ int shared_hosted = o->shared && driver_target_shared_uses_hosted(o->target);
+ if (!o->wants_hosted_libc || (o->shared && !shared_hosted)) return 0;
return driver_link_inputs_apply_hosted(&o->inputs, &o->hosted, &o->cf, &o->lf,
o->target, o->sysroot, o->static_link,
- o->nostartfiles, /*link_action=*/1);
+ o->shared, o->nostartfiles,
+ /*link_action=*/1);
}
/* ---------- link execution ---------- */
diff --git a/driver/driver.h b/driver/driver.h
@@ -145,7 +145,7 @@ int driver_argv_wants_help(int argc, char** argv, int accept_short_h);
* KitTargetSpec. Recognized arches: x86_64/amd64, i386/i486/i586/i686, aarch64/
* arm64, arm/armv7, riscv64, riscv32, wasm32, wasm64. Recognized OSes (scanned
* across the remaining components, so vendor tokens like `pc`/`apple`/
- * `unknown` are skipped): linux, darwin/macos, ios/iphoneos,
+ * `unknown` are skipped): linux, android, darwin/macos, ios/iphoneos,
* iphonesimulator/ios-simulator, windows/win32, wasi, none/freestanding. Sets
* arch/os/obj/ptr_size/ptr_align/big_endian; pic and code_model are left at
* their defaults. Returns 0 on success, nonzero on unrecognized arch or NULL
@@ -268,6 +268,11 @@ int driver_target_needs_sysroot_libdir(KitTargetSpec target);
* (Windows-COFF, given a sysroot and no -nostdlib). */
int driver_target_default_hosted_profile(KitTargetSpec target);
+/* Whether a shared-library link should still use hosted CRT/libc scaffolding
+ * and the kit runtime. Most targets leave shared output standalone; Android
+ * NDK DSOs require crtbegin_so.o/crtend_so.o and Bionic DSOs. */
+int driver_target_shared_uses_hosted(KitTargetSpec target);
+
/* Set `target->code_model` from a -mcmodel= value. Accepts the x86 spellings
* (small/medium/large), the RISC-V aliases (medlow->small, medany->medium),
* and kernel as a conservative alias for large. Returns 0 on success, 1 on an
diff --git a/driver/lib/hosted.c b/driver/lib/hosted.c
@@ -109,6 +109,7 @@ int driver_hosted_resolve(const DriverHostedRequest* req,
os_req.tool = req->tool;
os_req.target = req->target;
os_req.static_link = req->static_link;
+ os_req.shared_link = req->shared_link;
os_req.link_inputs = req->link_inputs;
rc = ops->resolve(&os_req, &dirs, out);
diff --git a/driver/lib/hosted.h b/driver/lib/hosted.h
@@ -27,6 +27,7 @@ typedef struct DriverHostedRequest {
KitTargetSpec target;
const char* sysroot;
int static_link;
+ int shared_link;
int link_inputs;
} DriverHostedRequest;
diff --git a/driver/lib/link_inputs.c b/driver/lib/link_inputs.c
@@ -450,7 +450,8 @@ int driver_link_inputs_apply_hosted(DriverLinkInputSet* set,
DriverHostedPlan* plan, DriverCflags* cf,
DriverLinkFlags* link, KitTargetSpec target,
const char* sysroot, int static_link,
- int no_startfiles, int link_action) {
+ int shared_link, int no_startfiles,
+ int link_action) {
DriverHostedRequest req = {0};
uint32_t i;
uint32_t insert_pos = 0;
@@ -459,6 +460,7 @@ int driver_link_inputs_apply_hosted(DriverLinkInputSet* set,
req.target = target;
req.sysroot = sysroot;
req.static_link = static_link;
+ req.shared_link = shared_link;
req.link_inputs = link_action;
if (driver_hosted_resolve(&req, plan) != 0) return 1;
for (i = 0; i < plan->nsystem_includes; ++i)
diff --git a/driver/lib/link_inputs.h b/driver/lib/link_inputs.h
@@ -198,6 +198,7 @@ int driver_link_inputs_apply_hosted(DriverLinkInputSet* set,
DriverHostedPlan* plan, DriverCflags* cf,
DriverLinkFlags* link, KitTargetSpec target,
const char* sysroot, int static_link,
- int no_startfiles, int link_action);
+ int shared_link, int no_startfiles,
+ int link_action);
#endif
diff --git a/driver/lib/runtime.c b/driver/lib/runtime.c
@@ -234,6 +234,10 @@ static const RuntimeVariant kRtVariants[] = {
"lib/include/lp64_le", 1, 1, kRtSrcAarch64Elf,
(uint32_t)(sizeof(kRtSrcAarch64Elf) / sizeof(kRtSrcAarch64Elf[0])),
KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
+ {"aarch64-linux-android", KIT_ARCH_ARM_64, KIT_OS_ANDROID, KIT_OBJ_ELF, 8,
+ 8, "lib/include/lp64_le", 1, 1, kRtSrcAarch64Elf,
+ (uint32_t)(sizeof(kRtSrcAarch64Elf) / sizeof(kRtSrcAarch64Elf[0])),
+ KIT_FLOAT_ABI_DEFAULT, NULL, NULL},
{"aarch64-freebsd", KIT_ARCH_ARM_64, KIT_OS_FREEBSD, KIT_OBJ_ELF, 8, 8,
"lib/include/lp64_le", 1, 1, kRtSrcAarch64Elf,
(uint32_t)(sizeof(kRtSrcAarch64Elf) / sizeof(kRtSrcAarch64Elf[0])),
diff --git a/driver/lib/target.c b/driver/lib/target.c
@@ -46,6 +46,11 @@ int driver_target_default_hosted_profile(KitTargetSpec target) {
return kit_target_default_hosted_profile(target);
}
+int driver_target_shared_uses_hosted(KitTargetSpec target) {
+ return target.os == KIT_OS_ANDROID && target.obj == KIT_OBJ_ELF &&
+ target.arch == KIT_ARCH_ARM_64;
+}
+
static int target_features_grow(DriverTargetFeatures* tf) {
DriverEnv* env = tf->env;
uint32_t old_cap = tf->cap_features;
diff --git a/include/kit/core.h b/include/kit/core.h
@@ -146,6 +146,7 @@ typedef enum KitOSKind {
KIT_OS_IOS_SIMULATOR,
KIT_OS_WINDOWS,
KIT_OS_FREEBSD,
+ KIT_OS_ANDROID,
KIT_OS_WASI,
} KitOSKind;
diff --git a/include/kit/os.h b/include/kit/os.h
@@ -85,6 +85,7 @@ typedef struct KitOsHostedRequest {
const char* tool;
KitTargetSpec target;
int static_link;
+ int shared_link;
int link_inputs;
} KitOsHostedRequest;
diff --git a/include/kit/target.h b/include/kit/target.h
@@ -42,7 +42,8 @@ KIT_API bool kit_arch_from_name(const char* name, KitArchKind* arch_out,
* KitTargetSpec. Recognized arches: see kit_arch_from_name. Recognized OSes are
* scanned across the remaining components (so vendor tokens like
* pc/apple/unknown are skipped): linux, darwin/macos, ios/iphoneos,
- * iphonesimulator/ios-simulator, windows/win32, freebsd[<ver>], wasi,
+ * iphonesimulator/ios-simulator, windows/win32, freebsd[<ver>],
+ * android[<api>] (also a `linux-android[<api>]` env component), wasi,
* none/freestanding. An unrecognized or absent OS token resolves to a
* freestanding ELF/WASM target by design (covers bare-metal / vendor-only
* triples like "riscv64-unknown-elf").
@@ -55,8 +56,9 @@ KIT_API bool kit_arch_from_name(const char* name, KitArchKind* arch_out,
KIT_API bool kit_target_from_triple(const char* triple, KitTargetSpec* out);
/* Render a canonical target triple for `spec` into `buf` (e.g.
- * "x86_64-linux", "aarch64-apple-darwin"). Returns true on success, false when
- * `buf` is NULL/zero-length or too small to hold the triple plus its NUL. */
+ * "x86_64-linux", "aarch64-apple-darwin", "aarch64-linux-android21").
+ * Returns true on success, false when `buf` is NULL/zero-length or too small
+ * to hold the triple plus its NUL. */
KIT_API bool kit_target_to_triple(KitTargetSpec spec, char* buf, size_t cap);
/* Map a -mcmodel= value to a KitCodeModel. Accepts the x86 spellings
diff --git a/lang/cpp/pp/pp.c b/lang/cpp/pp/pp.c
@@ -828,6 +828,7 @@ static void pp_register_os_predefined(Pp* pp, KitTargetSpec target) {
* to avoid a redefinition-with-different-replacement error. */
break;
case KIT_OS_LINUX:
+ case KIT_OS_ANDROID:
case KIT_OS_MACOS:
case KIT_OS_IOS:
case KIT_OS_IOS_SIMULATOR:
diff --git a/lang/cpp/pp/pp_directive.c b/lang/cpp/pp/pp_directive.c
@@ -1171,6 +1171,12 @@ static void do_include(Pp* pp, const Tok* line, u32 n, LocRef loc,
* already serves the bytes syscall-free, but re-lexing them through
* skip_until_active still costs the full lex+intern of every token. */
pkey = inc_cache_key(pp, resolved);
+ if (pkey) {
+ for (i = pp->nsources; i > 0; --i) {
+ TokSrc* tp = &pp->sources[i - 1];
+ if (tp->kind == SRC_LEX && tp->once && tp->path_key == pkey) return;
+ }
+ }
if (pkey && pp->inc_cache_ready) {
IncEntry* e = IncCache_get(&pp->inc_cache, pkey);
if (e && (e->once || (e->guard && mt_get(pp, e->guard) != NULL))) return;
diff --git a/mk/lib_srcs.mk b/mk/lib_srcs.mk
@@ -254,7 +254,8 @@ ifneq ($(filter 1,$(KIT_OBJ_ELF_ENABLED) $(KIT_EMU_ENABLED)),)
LIB_SRCS += src/os/linux/os.c
endif
ifeq ($(KIT_OBJ_ELF_ENABLED),1)
-LIB_SRCS += src/os/linux/hosted.c src/os/freebsd/os.c src/os/freebsd/hosted.c
+LIB_SRCS += src/os/linux/hosted.c src/os/freebsd/os.c src/os/freebsd/hosted.c \
+ src/os/android/os.c src/os/android/hosted.c
endif
ifeq ($(KIT_OBJ_MACHO_ENABLED),1)
LIB_SRCS += src/os/macos/os.c src/os/macos/hosted.c
diff --git a/mk/rt.mk b/mk/rt.mk
@@ -19,6 +19,7 @@ RT_VARIANTS = \
x86_64-apple-ios-simulator \
x86_64-elf \
aarch64-linux \
+ aarch64-linux-android \
aarch64-freebsd \
aarch64-apple-darwin \
aarch64-apple-ios \
@@ -117,6 +118,13 @@ RT_aarch64-linux_CORO = aarch64
RT_aarch64-linux_LDBL128 = 1
RT_EXTRA_SRCS_aarch64-linux = rt/lib/coro/aarch64_elf.s
+RT_aarch64-linux-android_TARGET = aarch64-linux-android21
+RT_aarch64-linux-android_ABI = lp64
+RT_aarch64-linux-android_INT128 = 1
+RT_aarch64-linux-android_CORO = aarch64
+RT_aarch64-linux-android_LDBL128 = 1
+RT_EXTRA_SRCS_aarch64-linux-android = rt/lib/coro/aarch64_elf.s
+
RT_aarch64-freebsd_TARGET = aarch64-unknown-freebsd
RT_aarch64-freebsd_ABI = lp64
RT_aarch64-freebsd_INT128 = 1
diff --git a/scripts/android_ndk_sysroot.sh b/scripts/android_ndk_sysroot.sh
@@ -0,0 +1,207 @@
+#!/usr/bin/env bash
+# Download and cache the Android NDK, exposing its sysroot for kit's aarch64
+# Android hosted profile.
+#
+# usage:
+# scripts/android_ndk_sysroot.sh prepare [aa64]
+# scripts/android_ndk_sysroot.sh path [aa64]
+# scripts/android_ndk_sysroot.sh root
+# scripts/android_ndk_sysroot.sh env [aa64]
+
+set -eu
+
+RELEASE="${KIT_ANDROID_NDK_RELEASE:-r27d}"
+REPO_XML="${KIT_ANDROID_NDK_REPOSITORY_XML:-https://dl.google.com/android/repository/repository2-1.xml}"
+BASE_URL="${KIT_ANDROID_NDK_BASE_URL:-https://dl.google.com/android/repository}"
+XDG_CACHE_HOME="${XDG_CACHE_HOME:-${HOME:-}/.cache}"
+CACHE="${KIT_ANDROID_NDK_CACHE:-$XDG_CACHE_HOME/kit/android-ndk/$RELEASE}"
+
+die() { printf 'android-ndk-sysroot: %s\n' "$*" >&2; exit 1; }
+
+canon_arch() {
+ case "${1:-aa64}" in
+ aa64|aarch64|arm64|"") echo aarch64 ;;
+ all) echo all ;;
+ *) die "unknown arch '${1:-}' (Android support is aarch64 only)" ;;
+ esac
+}
+
+host_asset() {
+ case "$(uname -s 2>/dev/null || echo unknown)" in
+ Darwin) echo "android-ndk-$RELEASE-darwin.zip" ;;
+ Linux) echo "android-ndk-$RELEASE-linux.zip" ;;
+ MINGW*|MSYS*|CYGWIN*) echo "android-ndk-$RELEASE-windows.zip" ;;
+ *) die "unsupported host OS: $(uname -s 2>/dev/null || echo unknown)" ;;
+ esac
+}
+
+sha1_file() {
+ if command -v shasum >/dev/null 2>&1; then
+ shasum -a 1 "$1" | awk '{ print $1 }'
+ return 0
+ fi
+ if command -v sha1sum >/dev/null 2>&1; then
+ sha1sum "$1" | awk '{ print $1 }'
+ return 0
+ fi
+ die "missing shasum or sha1sum"
+}
+
+metadata_field() {
+ local asset="$1" field="$2" xml="$CACHE/repository2-1.xml"
+ mkdir -p "$CACHE"
+ if [ "${FORCE:-0}" = 1 ] || [ ! -f "$xml" ]; then
+ command -v curl >/dev/null 2>&1 || die "curl not found"
+ curl -fsSL "$REPO_XML" -o "$xml.tmp"
+ mv "$xml.tmp" "$xml"
+ fi
+ case "$field" in
+ checksum)
+ awk -v want="$asset" '
+ /<checksum>/ { c=$0; sub(/^.*<checksum>/, "", c); sub(/<\/checksum>.*$/, "", c) }
+ /<url>/ { u=$0; sub(/^.*<url>/, "", u); sub(/<\/url>.*$/, "", u); if (u == want) { print c; exit } }
+ ' "$xml"
+ ;;
+ url)
+ awk -v want="$asset" '
+ /<url>/ { u=$0; sub(/^.*<url>/, "", u); sub(/<\/url>.*$/, "", u); if (u == want) { print u; exit } }
+ ' "$xml"
+ ;;
+ *) die "internal bad metadata field: $field" ;;
+ esac
+}
+
+archive_path() { printf '%s/%s\n' "$CACHE" "$(host_asset)"; }
+install_root() { printf '%s/android-ndk-%s\n' "$CACHE" "$RELEASE"; }
+
+verify_archive() {
+ local path="$1" want="$2" got
+ [ -n "$want" ] || die "no repository checksum found for $(basename "$path")"
+ got="$(sha1_file "$path")"
+ [ "$got" = "$want" ] ||
+ die "checksum mismatch for $(basename "$path"): got $got want $want"
+}
+
+fetch_archive() {
+ local asset full_url path want url
+ asset="$(host_asset)"
+ path="$(archive_path)"
+ want="$(metadata_field "$asset" checksum)"
+ [ -n "$want" ] || die "no repository checksum found for $asset"
+ url="$(metadata_field "$asset" url)"
+ [ -n "$url" ] || die "no repository URL found for $asset"
+ mkdir -p "$CACHE"
+ if [ -f "$path" ]; then
+ verify_archive "$path" "$want"
+ printf 'archive already verified: %s\n' "$path"
+ return 0
+ fi
+ command -v curl >/dev/null 2>&1 || die "curl not found"
+ full_url="$BASE_URL/$url"
+ printf 'download: %s\n' "$full_url"
+ curl -fL --retry 3 --retry-delay 2 --continue-at - -o "$path.part" "$full_url"
+ mv "$path.part" "$path"
+ verify_archive "$path" "$want"
+ printf 'verified: %s\n' "$path"
+}
+
+prebuilt_dir() {
+ local root="$1" d
+ for d in "$root"/toolchains/llvm/prebuilt/*; do
+ [ -d "$d" ] || continue
+ printf '%s\n' "$d"
+ return 0
+ done
+ return 1
+}
+
+sysroot_path() {
+ local root d
+ canon_arch "${1:-aa64}" >/dev/null
+ root="$(install_root)"
+ if d="$(prebuilt_dir "$root" 2>/dev/null)"; then
+ printf '%s/sysroot\n' "$d"
+ return 0
+ fi
+ case "$(uname -s 2>/dev/null || echo unknown)" in
+ Darwin) printf '%s/toolchains/llvm/prebuilt/darwin-x86_64/sysroot\n' "$root" ;;
+ Linux) printf '%s/toolchains/llvm/prebuilt/linux-x86_64/sysroot\n' "$root" ;;
+ MINGW*|MSYS*|CYGWIN*) printf '%s/toolchains/llvm/prebuilt/windows-x86_64/sysroot\n' "$root" ;;
+ *) die "unsupported host OS: $(uname -s 2>/dev/null || echo unknown)" ;;
+ esac
+}
+
+prepare() {
+ local arch root path sr
+ arch="$(canon_arch "${1:-aa64}")"
+ [ "$arch" = aarch64 ] || die "prepare requires aa64/aarch64"
+ root="$(install_root)"
+ if [ "${FORCE:-0}" != 1 ] && [ -r "$(sysroot_path "$arch")/usr/include/android/native_activity.h" ]; then
+ printf 'sysroot cached: %s\n' "$(sysroot_path "$arch")"
+ return 0
+ fi
+ fetch_archive
+ path="$(archive_path)"
+ rm -rf "$root.tmp"
+ mkdir -p "$CACHE"
+ command -v unzip >/dev/null 2>&1 || die "unzip not found"
+ unzip -q "$path" -d "$root.tmp"
+ if [ -d "$root.tmp/android-ndk-$RELEASE" ]; then
+ rm -rf "$root"
+ mv "$root.tmp/android-ndk-$RELEASE" "$root"
+ rm -rf "$root.tmp"
+ else
+ rm -rf "$root.tmp"
+ die "archive did not contain android-ndk-$RELEASE"
+ fi
+ sr="$(sysroot_path "$arch")"
+ [ -r "$sr/usr/include/android/native_activity.h" ] ||
+ die "NDK sysroot missing android/native_activity.h"
+ [ -d "$sr/usr/lib/aarch64-linux-android" ] ||
+ die "NDK sysroot missing aarch64 Android libraries"
+ {
+ printf 'release=%s\n' "$RELEASE"
+ printf 'asset=%s\n' "$(basename "$path")"
+ printf 'url=%s/%s\n' "$BASE_URL" "$(metadata_field "$(basename "$path")" url)"
+ printf 'sha1=%s\n' "$(metadata_field "$(basename "$path")" checksum)"
+ printf 'target=aarch64-linux-android\n'
+ } > "$root/PROVENANCE"
+ printf 'sysroot ready: %s\n' "$sr"
+}
+
+doctor() {
+ local asset root sr
+ asset="$(host_asset)"
+ root="$(install_root)"
+ sr="$(sysroot_path aa64)"
+ printf 'host: %s/%s\n' "$(uname -s 2>/dev/null)" "$(uname -m 2>/dev/null)"
+ printf 'release: %s\n' "$RELEASE"
+ printf 'asset: %s\n' "$asset"
+ printf 'download cache: %s\n' "$CACHE"
+ printf 'ndk root: %s%s\n' "$root" "$([ -d "$root" ] && echo '' || echo ' (missing)')"
+ printf 'sysroot: %s%s\n' "$sr" "$([ -d "$sr" ] && echo '' || echo ' (missing)')"
+ for tool in curl unzip shasum sha1sum; do
+ if command -v "$tool" >/dev/null 2>&1; then
+ printf ' OK %s (%s)\n' "$tool" "$(command -v "$tool")"
+ else
+ printf ' MISSING %s\n' "$tool"
+ fi
+ done
+}
+
+cmd="${1:-}"
+case "$cmd" in
+ doctor) doctor ;;
+ fetch) fetch_archive ;;
+ prepare) shift; prepare "${1:-aa64}" ;;
+ path) shift; sysroot_path "${1:-aa64}" ;;
+ root) install_root ;;
+ env)
+ shift
+ printf 'export KIT_SYSROOT=%s\n' "$(sysroot_path "${1:-aa64}")"
+ ;;
+ -h|--help|help|"")
+ sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
+ ;;
+ *) die "unknown command '$cmd' (try: doctor fetch prepare path root env)" ;;
+esac
diff --git a/scripts/cross_test.sh b/scripts/cross_test.sh
@@ -90,7 +90,7 @@ preflight_token() {
local t="$1" os tag sr
os="$(tok_os "$t")"; tag="$("$HOSTED" tag "$t")"; sr="$("$HOSTED" path "$t" 2>/dev/null)"
case "$os" in
- linux|freebsd|windows)
+ linux|freebsd|windows|android)
if [ -z "$sr" ] || [ ! -d "$sr" ]; then
echo "sysroot missing — run 'make provision TARGET=$t'"; return
fi ;;
@@ -101,6 +101,7 @@ preflight_token() {
freebsd|windows) echo "VM not provisioned/reachable — run 'make provision TARGET=$t KIT_VM=1'" ;;
macos) echo "macOS exec needs a Darwin host with matching arch" ;;
linux) echo "no linux runner — run 'make provision TARGET=$t' (sysroot) + 'make test-images'/'make hosted-glibc-images' (run image)" ;;
+ android) echo "no Android runner — use RUN=0 for build/link-only validation" ;;
*) echo "no runner for $tag" ;;
esac
fi
@@ -113,6 +114,10 @@ preflight_token() {
smoke_build_token() {
local t="$1" os name m
os="$(tok_os "$t")"
+ if [ "$os" = android ]; then
+ smoke_build_android "$t"
+ return
+ fi
if [ "$os" = freestanding ]; then
smoke_build_case "$t" exit "" # bare-metal: no link mode
return
@@ -124,9 +129,38 @@ smoke_build_token() {
done
}
+smoke_build_android() {
+ local t="$1" tag cdir src obj so label
+ tag="$("$HOSTED" tag "$t")"
+ cdir="$BUILD_DIR/$t"; mkdir -p "$cdir"
+ src="$CASES/native_activity.c"
+ obj="$cdir/native_activity.o"
+ so="$cdir/libkit_native_activity.so"
+ label="native_activity/$t"
+ if ! "$HOSTED" cc "$t" -O1 -fPIC -c "$src" -o "$obj" \
+ >"$cdir/native_activity.cc.out" 2>"$cdir/native_activity.cc.err"; then
+ kit_fail "$label:cc" "cc failed"; sed 's/^/ | /' "$cdir/native_activity.cc.err" | head; return
+ fi
+ kit_pass "$label:cc"
+ if ! "$HOSTED" cc "$t" -shared "$obj" -landroid -o "$so" \
+ >"$cdir/native_activity.ld.out" 2>"$cdir/native_activity.ld.err"; then
+ kit_fail "$label:ld" "ld failed"; sed 's/^/ | /' "$cdir/native_activity.ld.err" | head; return
+ fi
+ if [ ! -s "$so" ]; then
+ kit_fail "$label:ld" "missing shared object"
+ return
+ fi
+ : "$tag"
+ kit_pass "$label:ld"
+}
+
coarse_build_token() {
local t="$1" os mode opt sfx cdir tag
os="$(tok_os "$t")"
+ if [ "$os" = android ]; then
+ printf 'cross: NOTE %s — Android coarse lane uses the native_activity shared-library smoke only\n' "$t"
+ return
+ fi
tag="$($HOSTED tag "$t")"
cdir="$BUILD_DIR/$t"; mkdir -p "$cdir"
for opt in O0 O1; do
diff --git a/scripts/hosted.sh b/scripts/hosted.sh
@@ -4,12 +4,13 @@
# the per-OS provisioners (it does not replace them):
# FreeBSD -> scripts/freebsd_sysroot.sh (base.txz extract)
# Windows -> scripts/llvm_mingw_sysroot.sh (llvm-mingw UCRT)
+# Android -> scripts/android_ndk_sysroot.sh (Android NDK sysroot)
# Linux -> test/libc/{glibc,musl}/extract.sh (podman container extract)
# macOS -> host SDK (native; no cross sysroot)
#
# Targets: <os>[-<libc>]-<arch> (canonical arch tokens: aa64/x64/rv64[/rv32])
# linux-glibc-{aa64,x64,rv64} linux-musl-{aa64,x64,rv64}
-# freebsd-{aa64,x64,rv64} windows-{aa64,x64} macos-aa64
+# freebsd-{aa64,x64,rv64} windows-{aa64,x64} android-aa64 macos-aa64
# freestanding-{aa64,x64,rv64,rv32,arm32} (no OS — cross only, bare-metal exec)
# Long arch spellings (aarch64/amd64/x86_64/riscv64) are accepted as input
# aliases; canon_token normalizes them to the short forms above.
@@ -43,6 +44,7 @@ linux-glibc-aa64 linux-glibc-x64 linux-glibc-rv64
linux-musl-aa64 linux-musl-x64 linux-musl-rv64
freebsd-aa64 freebsd-x64 freebsd-rv64
windows-aa64 windows-x64
+android-aa64
macos-aa64 macos-x64
freestanding-aa64 freestanding-x64 freestanding-rv64 freestanding-rv32 freestanding-arm32
"
@@ -78,6 +80,11 @@ parse_target() {
aarch64|arm64|aa64) T_ARCH=aarch64 ;;
*) die "bad windows arch '$rest' in '$t'" ;;
esac ;;
+ android)
+ case "$rest" in
+ aarch64|arm64|aa64) T_ARCH=aarch64 ;;
+ *) die "bad android arch '$rest' in '$t' (want aa64)" ;;
+ esac ;;
macos)
case "$rest" in
aarch64|arm64|aa64) T_ARCH=aarch64 ;;
@@ -118,6 +125,10 @@ triple_of() {
x64) echo x86_64-windows ;;
aarch64) echo aarch64-windows ;;
esac ;;
+ android)
+ case "$T_ARCH" in
+ aarch64) echo aarch64-linux-android21 ;;
+ esac ;;
macos)
case "$T_ARCH" in
aarch64) echo aarch64-apple-darwin ;;
@@ -145,6 +156,7 @@ tag_of() {
macos) echo "$ea-macos" ;;
freebsd) echo "$T_ARCH-freebsd" ;;
windows) echo "$T_ARCH-windows" ;;
+ android) echo "$ea-android" ;;
freestanding) echo "$ea-freestanding" ;;
esac
}
@@ -169,6 +181,7 @@ modes_of() {
parse_target "$1"
case "$T_OS" in
freestanding) echo cross ;;
+ android) echo cross ;;
macos)
# Self-host runs the freshly built kit on the host. A non-host macOS arch
# (e.g. an x86_64 kit on Apple silicon) would run under Rosetta, but that
@@ -194,6 +207,7 @@ path_of() {
linux) _linux_sysroot_dir "$T_LIBC" "$T_ARCH" ;;
freebsd) "$ROOT/scripts/freebsd_sysroot.sh" path "$T_ARCH" 2>/dev/null ;;
windows) "$ROOT/scripts/llvm_mingw_sysroot.sh" path "$T_ARCH" 2>/dev/null ;;
+ android) "$ROOT/scripts/android_ndk_sysroot.sh" path "$T_ARCH" 2>/dev/null ;;
macos) echo "" ;;
freestanding) echo "" ;;
esac
@@ -208,6 +222,7 @@ prepare_one() {
bash "$ROOT/test/libc/$T_LIBC/extract.sh" -a "$T_ARCH" ;;
freebsd) "$ROOT/scripts/freebsd_sysroot.sh" "$T_ARCH" ;;
windows) "$ROOT/scripts/llvm_mingw_sysroot.sh" prepare "$T_ARCH" ;;
+ android) "$ROOT/scripts/android_ndk_sysroot.sh" prepare "$T_ARCH" ;;
macos) printf 'macos: native host SDK, nothing to prepare\n' ;;
freestanding)
printf 'freestanding: no sysroot to provision; run-time prereq is qemu-system-%s\n' \
@@ -266,7 +281,7 @@ doctor() {
_match_one() {
local it="$1" t
case "$it" in
- linux|freebsd|windows|macos|freestanding)
+ linux|freebsd|windows|android|macos|freestanding)
for t in $SUPPORT_SET; do case "$t" in "$it"-*) printf '%s ' "$t" ;; esac; done
return ;;
linux-musl|linux-glibc)
diff --git a/scripts/provision.sh b/scripts/provision.sh
@@ -22,7 +22,7 @@ TOKENS="$("$HOSTED" expand "$SELECTOR")" || exit 1
printf 'provision: targets=%s\n' "$(echo $TOKENS | tr '\n' ' ')"
# 1. Sysroots (hosted.sh routes per OS: linux extract / freebsd base.txz /
-# windows UCRT; macos + freestanding are no-ops).
+# windows UCRT / Android NDK; macos + freestanding are no-ops).
"$HOSTED" prepare "$SELECTOR" || exit 1
# 2. Run images + VMs, driven by which OSes are in scope.
diff --git a/src/api/target.c b/src/api/target.c
@@ -42,6 +42,19 @@ static bool triple_tok_is_ios_sim(const char* s, size_t n) {
triple_tok_eq(s, n, "iphonesimulator");
}
+static bool triple_tok_is_android(const char* s, size_t n,
+ uint8_t* api_out) {
+ size_t l = strlen("android");
+ unsigned v = 0;
+ size_t i;
+ if (n < l || memcmp(s, "android", l) != 0) return false;
+ for (i = l; i < n && s[i] >= '0' && s[i] <= '9'; ++i)
+ v = v * 10u + (unsigned)(s[i] - '0');
+ if (i != n) return false;
+ if (api_out) *api_out = (uint8_t)(v > 255u ? 0u : v);
+ return true;
+}
+
static bool riscv_arch_tok(const char* s, size_t n, const char* base) {
size_t l = strlen(base);
size_t i;
@@ -181,9 +194,29 @@ bool kit_target_from_triple(const char* triple, KitTargetSpec* out) {
os_set = 0;
for (i = 1; i < np; ++i) {
+ {
+ uint8_t api = 0;
+ if (triple_tok_is_android(parts[i], plen[i], &api)) {
+ t.os = KIT_OS_ANDROID;
+ t.obj = KIT_OBJ_ELF;
+ t.os_version_major = api;
+ os_set = 1;
+ break;
+ }
+ }
if (triple_tok_eq(parts[i], plen[i], "linux")) {
- t.os = KIT_OS_LINUX;
+ int android = 0;
+ uint8_t api = 0;
+ int j;
+ for (j = i + 1; j < np; ++j) {
+ if (triple_tok_is_android(parts[j], plen[j], &api)) {
+ android = 1;
+ break;
+ }
+ }
+ t.os = android ? KIT_OS_ANDROID : KIT_OS_LINUX;
t.obj = KIT_OBJ_ELF;
+ t.os_version_major = android ? api : 0;
os_set = 1;
break;
}
@@ -321,6 +354,13 @@ bool kit_target_to_triple(KitTargetSpec target, char* buf, size_t cap) {
case KIT_OS_FREEBSD:
os = "freebsd";
break;
+ case KIT_OS_ANDROID:
+ if (target.os_version_major)
+ n = snprintf(buf, cap, "%s-linux-android%u", arch,
+ (unsigned)target.os_version_major);
+ else
+ n = snprintf(buf, cap, "%s-linux-android", arch);
+ return !(n < 0 || (size_t)n >= cap);
case KIT_OS_WASI:
os = "wasi";
break;
diff --git a/src/os/android/hosted.c b/src/os/android/hosted.c
@@ -0,0 +1,101 @@
+#include "os/hosted_internal.h"
+
+#include <stdio.h>
+
+static unsigned android_api_level(KitTargetSpec target) {
+ unsigned api = target.os_version_major ? target.os_version_major : 21u;
+ return api < 21u ? 21u : api;
+}
+
+static int hosted_add_android_defines(KitOsHostedPlan* plan,
+ KitTargetSpec target) {
+ char api_buf[4];
+ int n = snprintf(api_buf, sizeof api_buf, "%u", android_api_level(target));
+ if (n < 0 || (unsigned)n >= sizeof api_buf) return 1;
+ if (kit_os_hosted_add_clang_compat_defines(plan) != 0 ||
+ kit_os_hosted_add_define(plan, "__ANDROID__", "1") != 0 ||
+ kit_os_hosted_add_define(plan, "__BIONIC__", "1") != 0 ||
+ kit_os_hosted_add_define(plan, "__linux__", "1") != 0 ||
+ kit_os_hosted_add_define(plan, "__linux", "1") != 0 ||
+ kit_os_hosted_add_define(plan, "linux", "1") != 0 ||
+ kit_os_hosted_add_define(plan, "__ELF__", "1") != 0 ||
+ kit_os_hosted_add_define(plan, "__ANDROID_API__", api_buf) != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_resolve_android(const KitOsHostedRequest* req,
+ const KitOsHostedDirs* dirs,
+ KitOsHostedPlan* plan) {
+ if (req->target.arch != KIT_ARCH_ARM_64) {
+ kit_os_hosted_errf(req->host, req->host_user, req->tool,
+ "Android hosted profile supports aarch64 only");
+ return 1;
+ }
+ if (req->static_link) {
+ kit_os_hosted_errf(req->host, req->host_user, req->tool,
+ "Android hosted profile supports dynamic links only");
+ return 1;
+ }
+ if (dirs->nlibdirs == 0) {
+ kit_os_hosted_errf(req->host, req->host_user, req->tool,
+ "Android hosted profile requires --sysroot or "
+ "KIT_SYSROOT");
+ return 1;
+ }
+ plan->profile_name =
+ req->shared_link ? "android-bionic-shared" : "android-bionic-dynamic";
+ if (!req->shared_link) plan->interp_path = "/system/bin/linker64";
+ if (hosted_add_android_defines(plan, req->target) != 0 ||
+ kit_os_hosted_add_incdirs(plan, req->host, req->host_user, dirs) != 0) {
+ kit_os_hosted_errf(req->host, req->host_user, req->tool, "out of memory");
+ return 1;
+ }
+ if (!req->link_inputs) return 0;
+ if (kit_os_hosted_add_required_search(
+ plan->before, &plan->nbefore, KIT_OS_HOSTED_MAX_BEFORE, req, dirs,
+ req->shared_link ? "crtbegin_so.o" : "crtbegin_dynamic.o",
+ KIT_OS_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ if (kit_os_hosted_add_required_search(
+ plan->after, &plan->nafter, KIT_OS_HOSTED_MAX_AFTER, req, dirs,
+ "libc.so", KIT_OS_HOSTED_INPUT_DSO) != 0 ||
+ kit_os_hosted_add_required_search(
+ plan->after, &plan->nafter, KIT_OS_HOSTED_MAX_AFTER, req, dirs,
+ "libdl.so", KIT_OS_HOSTED_INPUT_DSO) != 0)
+ return 1;
+ if (kit_os_hosted_add_required_search(
+ plan->final, &plan->nfinal, KIT_OS_HOSTED_MAX_FINAL, req, dirs,
+ req->shared_link ? "crtend_so.o" : "crtend_android.o",
+ KIT_OS_HOSTED_INPUT_OBJECT) != 0)
+ return 1;
+ return 0;
+}
+
+static int hosted_sysroot_layout_android(KitOsHostedDirs* dirs,
+ KitTargetSpec target,
+ const char* sysroot) {
+ char api_dir[64];
+ int n;
+ if (target.arch != KIT_ARCH_ARM_64) return 0;
+ n = snprintf(api_dir, sizeof api_dir, "usr/lib/aarch64-linux-android/%u",
+ android_api_level(target));
+ if (n < 0 || (unsigned)n >= sizeof api_dir) return 1;
+ if (kit_os_hosted_dirs_add_inc_join(dirs, sysroot, "usr/include") != 0 ||
+ kit_os_hosted_dirs_add_inc_join(dirs, sysroot,
+ "usr/include/aarch64-linux-android") !=
+ 0 ||
+ kit_os_hosted_dirs_add_lib_join(dirs, sysroot, api_dir) != 0 ||
+ kit_os_hosted_dirs_add_lib_join(dirs, sysroot,
+ "usr/lib/aarch64-linux-android") != 0)
+ return 1;
+ return 0;
+}
+
+const KitOsHostedOps kit_os_android_hosted_ops = {
+ .obj = KIT_OBJ_ELF,
+ .default_profile = 1,
+ .needs_sysroot_libdir = 0,
+ .resolve = hosted_resolve_android,
+ .sysroot_layout = hosted_sysroot_layout_android,
+};
diff --git a/src/os/android/os.c b/src/os/android/os.c
@@ -0,0 +1,10 @@
+#include <kit/os.h>
+
+extern const KitOsHostedOps kit_os_android_hosted_ops;
+
+const KitOsImpl kit_os_android_impl = {
+ .kind = KIT_OS_ANDROID,
+ .name = "android",
+ .hosted = &kit_os_android_hosted_ops,
+ .emu = NULL,
+};
diff --git a/src/os/hosted.c b/src/os/hosted.c
@@ -189,7 +189,10 @@ int kit_os_hosted_add_clang_compat_defines(KitOsHostedPlan* plan) {
kit_os_hosted_add_define(plan, "__has_include_next(x)", "0") != 0 ||
kit_os_hosted_add_define(plan, "__has_feature(x)", "0") != 0 ||
kit_os_hosted_add_define(plan, "__has_extension(x)", "0") != 0 ||
- kit_os_hosted_add_define(plan, "__has_attribute(x)", "0") != 0)
+ kit_os_hosted_add_define(plan, "__has_attribute(x)", "0") != 0 ||
+ kit_os_hosted_add_define(plan, "_Nonnull", "/*empty*/") != 0 ||
+ kit_os_hosted_add_define(plan, "_Nullable", "/*empty*/") != 0 ||
+ kit_os_hosted_add_define(plan, "_Null_unspecified", "/*empty*/") != 0)
return 1;
return 0;
}
diff --git a/src/os/registry.c b/src/os/registry.c
@@ -11,6 +11,7 @@ extern const KitOsImpl kit_os_linux_impl;
#endif
#if KIT_OBJ_ELF_ENABLED
extern const KitOsImpl kit_os_freebsd_impl;
+extern const KitOsImpl kit_os_android_impl;
#endif
#if KIT_OBJ_COFF_ENABLED
extern const KitOsImpl kit_os_windows_impl;
@@ -33,6 +34,8 @@ const KitOsImpl* kit_os_lookup(KitOSKind kind) {
#if KIT_OBJ_ELF_ENABLED
case KIT_OS_FREEBSD:
return &kit_os_freebsd_impl;
+ case KIT_OS_ANDROID:
+ return &kit_os_android_impl;
#endif
#if KIT_OBJ_COFF_ENABLED
case KIT_OS_WINDOWS:
diff --git a/test/api/target_test.c b/test/api/target_test.c
@@ -85,6 +85,21 @@ static void check_target_triple_parse(void) {
EXPECT(strcmp(buf, "aarch64-apple-ios-simulator") == 0,
"iOS simulator target renders canonical triple");
}
+
+ memset(&t, 0, sizeof t);
+ EXPECT(kit_target_from_triple("aarch64-linux-android24", &t),
+ "aarch64 Android API triple parses");
+ EXPECT(t.arch == KIT_ARCH_ARM_64 && t.os == KIT_OS_ANDROID &&
+ t.obj == KIT_OBJ_ELF && t.os_version_major == 24,
+ "aarch64 Android API triple maps to ELF Android");
+
+ {
+ char buf[64];
+ EXPECT(kit_target_to_triple(t, buf, sizeof buf),
+ "Android target renders");
+ EXPECT(strcmp(buf, "aarch64-linux-android24") == 0,
+ "Android target renders canonical API triple");
+ }
}
static void check_x64_defaults_and_isa(void) {
diff --git a/test/cross/cases/native_activity.c b/test/cross/cases/native_activity.c
@@ -0,0 +1,9 @@
+#include <android/native_activity.h>
+#include <stddef.h>
+
+void ANativeActivity_onCreate(ANativeActivity* activity, void* saved_state,
+ size_t saved_state_size) {
+ (void)saved_state;
+ (void)saved_state_size;
+ ANativeActivity_setWindowFlags(activity, 0, 0);
+}