From 2cf3cc0f2fdb55e0479e47226e255b52065026cf Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Sat, 11 Jul 2026 12:13:49 +0300 Subject: [PATCH 1/4] libc-test: Detect the targeted Android API level from the toolchain Add "android" to the Versions platform-version detection. Knowing it allows version-gating the Android test skips instead of hardcoding them. The level can appear in two macros: 1. Old clang defines __ANDROID_API__ directly as an integer. 2. Modern clang defines it as an alias of __ANDROID_MIN_SDK_VERSION__. A toolchain whose target triple carries no API level defines neither number, so nothing is parsed and the level stays undetected. An undetected level (android == None) causes its consumers to treat it conservatively: every API-gated skip stays active, as if the toolchain targeted the oldest level possible. Nothing consumes the value yet, so it has no effect on what is tested. Next commits will add users for it. Suggested-by: Trevor Gross Assisted-By: Claude Fable 5 Signed-off-by: Adrian Ratiu --- libc-test/build.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) mode change 100644 => 100755 libc-test/build.rs diff --git a/libc-test/build.rs b/libc-test/build.rs old mode 100644 new mode 100755 index 2cf2eba99dda8..f22037e872573 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -6376,6 +6376,10 @@ struct Versions { netbsd: Option<(u32, u32)>, macos: Option<(u32, u32)>, emscripten: Option<(u32, u32)>, + /// Android API level. Unlike the (major, minor) platform versions + /// above, Android versions its libc ABI with a single increasing + /// integer (e.g. 28). There is no minor component. + android: Option, } impl Versions { @@ -6394,6 +6398,13 @@ impl Versions { #include "gnu/libc-version.h" #endif + #ifdef __ANDROID__ + /* The clang driver predefines __ANDROID_API__ and __ANDROID_MIN_SDK_VERSION__ + * from the API level in the target triple. When that is missing, this header + * supplies the __ANDROID_API_FUTURE__ fallback definition. */ + #include "android/api-level.h" + #endif + #if defined(__FreeBSD__) \ || defined(__NetBSD__) \ || defined(__OpenBSD__) \ @@ -6458,6 +6469,15 @@ impl Versions { } "__GLIBC__" => ret.glibc.get_or_insert_default().0 = value.parse().unwrap(), "__GLIBC_MINOR__" => ret.glibc.get_or_insert_default().1 = value.parse().unwrap(), + // The API level is in __ANDROID_API__ (old clang) or __ANDROID_MIN_SDK_VERSION__ + // (modern clang), so take a number from either. When the API is undetected (e.g. + // the triple carries no API level), its consumers fall back to skipping every + // API-gated test as if the toolchain targeted the oldest level possible. + "__ANDROID_API__" | "__ANDROID_MIN_SDK_VERSION__" => { + if let Ok(level) = value.parse() { + ret.android = Some(level); + } + } "__MAC_OS_X_VERSION_MAX_ALLOWED" => { let caps = mac_re.captures(value).unwrap(); let major: u32 = caps[1].parse().unwrap(); From 2ae062e0995193474cc496e69f827134578c4d84 Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Sat, 11 Jul 2026 11:07:27 +0300 Subject: [PATCH 2/4] libc-test: Version-gate the Android API level skips Convert the existing hardcoded "added in API level N" test skips into gates on the detected API level added in the previous commit. Items stay skipped when the toolchain builds below their introduction level (or when no level was detected) and get tested once the toolchain provably targets a high enough level. There are no test coverage changes. Assisted-By: Claude Fable 5 Signed-off-by: Adrian Ratiu --- libc-test/build.rs | 50 +++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/libc-test/build.rs b/libc-test/build.rs index f22037e872573..e0cd6e799b5df 100755 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -2045,6 +2045,10 @@ fn test_android(target: &str) { }; let x86 = target.contains("i686") || target.contains("x86_64"); let aarch64 = target.contains("aarch64"); + // The API level the NDK toolchain targets, which caps the available libc + // API surface (see `Versions`). Items introduced later must be skipped when + // building below their level and also when no level could be detected. + let skip_below_api = |level: u32| VERSIONS.android.map_or(true, |api| api < level); let mut cfg = ctest_cfg(); cfg.define("_GNU_SOURCE", None); @@ -2237,7 +2241,7 @@ fn test_android(target: &str) { "posix_spawnattr_t" => true, // Added in API level 24 - "if_nameindex" => true, + "if_nameindex" => skip_below_api(24), _ => false, } @@ -2429,40 +2433,32 @@ fn test_android(target: &str) { "reallocarray" => true, "__system_property_wait" => true, - // Added in API level 30, but tests use level 28. - "memfd_create" | "mlock2" | "renameat2" | "statx" | "statx_timestamp" => true, + // Added in API level 30. + "memfd_create" | "mlock2" | "renameat2" | "statx" | "statx_timestamp" => { + skip_below_api(30) + } // Added in glibc 2.25. "getentropy" => true, - // Added in API level 28, but some tests use level 24. - "getrandom" => true, - - // Added in API level 28, but some tests use level 24. - "syncfs" => true, - - // Added in API level 28, but some tests use level 24. - "pthread_attr_getinheritsched" | "pthread_attr_setinheritsched" => true, - // Added in API level 28, but some tests use level 24. - "fread_unlocked" | "fwrite_unlocked" | "fgets_unlocked" | "fflush_unlocked" => true, - - // Added in API level 28, but some tests use level 24. - "aligned_alloc" => true, + // Added in API level 28. + "getrandom" | "syncfs" | "aligned_alloc" => skip_below_api(28), - // Added in API level 26, but some tests use level 24. - "getgrent" => true, + // Added in API level 28. + "pthread_attr_getinheritsched" | "pthread_attr_setinheritsched" => skip_below_api(28), - // Added in API level 26, but some tests use level 24. - "setgrent" => true, - - // Added in API level 26, but some tests use level 24. - "endgrent" => true, + // Added in API level 28. + "fread_unlocked" | "fwrite_unlocked" | "fgets_unlocked" | "fflush_unlocked" => { + skip_below_api(28) + } - // Added in API level 26, but some tests use level 24. - "getpwent" | "setpwent" | "endpwent" => true, + // Added in API level 26. + "getgrent" | "setgrent" | "endgrent" | "getpwent" | "setpwent" | "endpwent" => { + skip_below_api(26) + } - // Added in API level 26, but some tests use level 24. - "getdomainname" | "setdomainname" => true, + // Added in API level 26. + "getdomainname" | "setdomainname" => skip_below_api(26), // FIXME(android): bad function pointers: "isalnum" | "isalpha" | "iscntrl" | "isdigit" | "isgraph" | "islower" | "isprint" From 9d3d6fc1276272572a614ee1b392a6ed689c3aeb Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Sat, 11 Jul 2026 11:07:56 +0300 Subject: [PATCH 3/4] libc-test: Skip termios function pointer checks below Android API 28 Bionic implements the termios API as static inline functions in until API 28 turns them into real libc.so symbols, so below that level the C-side function address is the header's inline, never matching the symbol Rust links against. Skip the function pointer identity check (the only check ctest generates for a foreign function) for the termios family below API 28. This surfaced when pinning the C test compilation to a real API level (see next commit). At level 24, e.g. on arm, all thirteen termios pointer comparisons fail. It is ordered before the pin commit to keep commits bisectable. Assisted-By: Claude Fable 5 Signed-off-by: Adrian Ratiu --- libc-test/build.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/libc-test/build.rs b/libc-test/build.rs index e0cd6e799b5df..e1cdd9dde76f8 100755 --- a/libc-test/build.rs +++ b/libc-test/build.rs @@ -2476,6 +2476,17 @@ fn test_android(target: &str) { } }); + cfg.skip_fn_ptrcheck(move |func| { + match func { + // termios functions are inlines below API 28, so + // their C-side address never matches the symbol Rust links. + "tcdrain" | "tcflow" | "tcflush" | "tcgetattr" | "tcgetsid" | "tcsendbreak" + | "tcsetattr" | "cfgetispeed" | "cfgetospeed" | "cfmakeraw" | "cfsetispeed" + | "cfsetospeed" | "cfsetspeed" => skip_below_api(28), + _ => false, + } + }); + cfg.skip_struct_field_type(move |struct_, field| { match (struct_.ident(), field.ident()) { // This is a weird union, don't check the type. From cc8308135961ef9d7394abf666654f5458047cd2 Mon Sep 17 00:00:00 2001 From: Adrian Ratiu Date: Mon, 13 Jul 2026 16:45:47 +0300 Subject: [PATCH 4/4] ci: Point each Android toolchain at the API level bionic actually runs Each CC wrapper's name (e.g. x86_64-linux-android28-clang) tells clang which API to target: clang infers "-target" from the invoked binary name whenever none is given explicitly. Ours said 28 everywhere, which only reflects the NDK's link-time stub level, not the level of the bionic that actually runs the tests: arm emulators and the x86_64 system image android-sysimage.sh installs (from x86_64-24_r07.zip) are all API 24. This mismatch was invisible before because the tests were hardcoded to skip higher API levels regardless of the toolchain; it surfaced after extracting the real API level from the toolchain and wiring in the dynamic skip mechanism (previous commits) instead of hardcoding. With the toolchain matching the runtime, detection resolves and the new gated skips activate. Raising an image's runtime later only needs its own wrapper name bumped. Assisted-By: Claude Fable 5 Signed-off-by: Adrian Ratiu --- ci/docker/aarch64-linux-android/Dockerfile | 4 ++-- ci/docker/arm-linux-androideabi/Dockerfile | 4 ++-- ci/docker/x86_64-linux-android/Dockerfile | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/docker/aarch64-linux-android/Dockerfile b/ci/docker/aarch64-linux-android/Dockerfile index 91c074150f94d..420e594108c58 100644 --- a/ci/docker/aarch64-linux-android/Dockerfile +++ b/ci/docker/aarch64-linux-android/Dockerfile @@ -27,9 +27,9 @@ RUN chmod 777 -R /tmp/.android RUN chmod 755 /android/sdk/cmdline-tools/tools/* /android/sdk/emulator/qemu/linux-x86_64/* ENV PATH=$PATH:/rust/bin \ - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android28-clang \ + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android24-clang \ CARGO_TARGET_AARCH64_LINUX_ANDROID_RUNNER=/tmp/runtest \ - CC_aarch64_linux_android=aarch64-linux-android28-clang \ + CC_aarch64_linux_android=aarch64-linux-android24-clang \ AR_aarch64_linux_android=llvm-ar \ HOME=/tmp diff --git a/ci/docker/arm-linux-androideabi/Dockerfile b/ci/docker/arm-linux-androideabi/Dockerfile index 657ff81097c7e..90124e093fcff 100644 --- a/ci/docker/arm-linux-androideabi/Dockerfile +++ b/ci/docker/arm-linux-androideabi/Dockerfile @@ -27,9 +27,9 @@ RUN chmod 777 -R /tmp/.android RUN chmod 755 /android/sdk/cmdline-tools/tools/* /android/sdk/emulator/qemu/linux-x86_64/* ENV PATH=$PATH:/rust/bin \ - CARGO_TARGET_ARM_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi28-clang \ + CARGO_TARGET_ARM_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi24-clang \ CARGO_TARGET_ARM_LINUX_ANDROIDEABI_RUNNER=/tmp/runtest \ - CC_arm_linux_androideabi=armv7a-linux-androideabi28-clang \ + CC_arm_linux_androideabi=armv7a-linux-androideabi24-clang \ AR_arm_linux_androideabi=llvm-ar \ HOME=/tmp diff --git a/ci/docker/x86_64-linux-android/Dockerfile b/ci/docker/x86_64-linux-android/Dockerfile index bc9580e6e1c0d..80827d0bdb000 100644 --- a/ci/docker/x86_64-linux-android/Dockerfile +++ b/ci/docker/x86_64-linux-android/Dockerfile @@ -19,8 +19,8 @@ COPY android-sysimage.sh /android/ RUN /android/android-sysimage.sh x86_64 x86_64-24_r07.zip ENV PATH=$PATH:/rust/bin:/android/linux-x86_64/bin \ - CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=x86_64-linux-android28-clang \ - CC_x86_64_linux_android=x86_64-linux-android28-clang \ - CXX_x86_64_linux_android=x86_64-linux-android28-clang++ \ + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=x86_64-linux-android24-clang \ + CC_x86_64_linux_android=x86_64-linux-android24-clang \ + CXX_x86_64_linux_android=x86_64-linux-android24-clang++ \ AR_x86_64_linux_android=llvm-ar \ HOME=/tmp