diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c0543beefb39..ae02ede1e531 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -196,6 +196,8 @@ jobs: matrix: include: - target: aarch64-linux-android + # Keep in sync with the Android build pinned in ci/android-bionic-sysroot.sh + artifact-tag: android17 - target: aarch64-unknown-linux-musl - target: aarch64-unknown-linux-musl env: { TEST_MUSL_V1_2_3: 1 } @@ -236,6 +238,9 @@ jobs: - target: x86_64-apple-darwin os: macos-26-intel - target: x86_64-linux-android + env: { TEST_CUTTLEFISH: 1 } + # Keep in sync with the Android build pinned in ci/cuttlefish-setup.sh + artifact-tag: android17 # FIXME: Exec format error (os error 8) # - target: x86_64-unknown-linux-gnux32 - target: x86_64-unknown-linux-musl diff --git a/ci/README.md b/ci/README.md index 4e5b552d5889..db30a58db9a0 100644 --- a/ci/README.md +++ b/ci/README.md @@ -31,8 +31,15 @@ builds are run on stable/beta/nightly, but are the only ones that do so. The remaining architectures look like: -* Android runs in a [docker image][android-docker] with an emulator, the NDK, - and the SDK already set up. The entire build happens within the docker image. +* Android x86_64 tests run on a [Cuttlefish](https://source.android.com/docs/devices/cuttlefish) + virtual device booted on the CI host by `cuttlefish-setup.sh`; the + [docker image][android-docker] cross-compiles the tests with the NDK and + drives the device over adb. Android aarch64 tests run under the QEMU + userspace emulator against the bionic linker and libraries extracted from + a current Android build by `android-bionic-sysroot.sh`, with no Android + system booted. The remaining Android target (32-bit arm) runs in a docker + image with the legacy SDK emulator, the NDK, and the SDK already set up, + with the entire build happening within the docker image. * The MIPS, ARM, and AArch64 builds all use the QEMU userspace emulator to run the generated binary to actually verify the tests pass. * The MUSL build just has to download a MUSL compiler and target libraries and diff --git a/ci/android-bionic-sysroot.sh b/ci/android-bionic-sysroot.sh new file mode 100755 index 000000000000..388d52e8e42b --- /dev/null +++ b/ci/android-bionic-sysroot.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +set -eux + +# Extract the bionic linker and libraries from a current Android build into +# /system, so the test binaries can run under qemu-user with no Android +# system booted (see the aarch64-linux-android Dockerfile). This is a +# stopgap until GitHub's arm64 runners expose KVM, at which point this +# target can move to Cuttlefish like x86_64-linux-android. +# +# The target_files zip is used because it contains the system partition as +# plain files, unlike the flashable image zips. +# +# The pinned build (currently Android 17) matches ci/cuttlefish-setup.sh; +# bump both together with the android* artifact-tags in ci.yaml. + +build="${ANDROID_SYSROOT_BUILD:-15660610}" +target="${ANDROID_SYSROOT_TARGET:-aosp_cf_arm64_only_phone-userdebug}" + +zip_name="${target%-userdebug}-target_files-${build}.zip" +url="https://ci.android.com/builds/submitted/${build}/${target}/latest/${zip_name}" + +td="$(mktemp -d)" + +# The ci.android.com URL serves an HTML page embedding a short-lived signed +# Cloud Storage URL (JSON-escaped '&'); scrape it to get the artifact. +signed_url="$(wget -q -O - "${url}" | + grep -o 'https://storage.googleapis.com/android-build/[^"]*' | + head -1 | + sed 's/\\u0026/\&/g')" +wget -q --tries=5 -O "${td}/target_files.zip" "${signed_url}" + +# Take the full lib64 rather than cherry-picking libc/libm/libdl: the +# linker pulls in supporting libraries that vary between releases. +# Extraction is best-effort; the sanity checks below decide. +unzip -q "${td}/target_files.zip" \ + 'SYSTEM/apex/com.android.runtime.apex' \ + 'SYSTEM/bin/linker64' \ + 'SYSTEM/bin/bootstrap/*' \ + 'SYSTEM/build.prop' \ + 'SYSTEM/etc/ld.config*' \ + 'SYSTEM/lib64/*' \ + -d "${td}" || true + +mkdir -p /system +mv "${td}/SYSTEM/bin" /system/bin +mv "${td}/SYSTEM/lib64" /system/lib64 +mv "${td}/SYSTEM/build.prop" /system/build.prop +if [ -d "${td}/SYSTEM/etc" ]; then + mv "${td}/SYSTEM/etc" /system/etc +fi + +# Since Android 10 bionic lives in the Runtime APEX and the /system paths +# are symlinks into /apex/com.android.runtime; unpack the apex payload +# there so they resolve. +unzip -q "${td}/SYSTEM/apex/com.android.runtime.apex" apex_payload.img \ + -d "${td}" +mkdir -p /apex/com.android.runtime +case "$(file -b "${td}/apex_payload.img")" in + *EROFS*) + fsck.erofs "--extract=/apex/com.android.runtime" \ + "${td}/apex_payload.img" + ;; + *ext[24]*) + debugfs -R "rdump / /apex/com.android.runtime" \ + "${td}/apex_payload.img" + ;; + *) + echo "unrecognized apex payload filesystem" >&2 + exit 1 + ;; +esac + +rm -rf "${td}" + +# Sanity-check the files the tests need (following the /apex symlinks, so +# this also verifies the apex extraction) and log the Android version. +test -s /system/bin/linker64 +test -s /system/lib64/libc.so +test -s /system/lib64/libm.so +test -s /system/lib64/libdl.so +grep ro.build.version.release /system/build.prop diff --git a/ci/android-sysimage.sh b/ci/android-sysimage.sh deleted file mode 100755 index 98484b3521f6..000000000000 --- a/ci/android-sysimage.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -URL=https://dl.google.com/android/repository/sys-img/android - -main() { - local arch="${1}" - local name="${2}" - local dest=/system - local td - td="$(mktemp -d)" - - apt-get install --no-install-recommends e2tools - - pushd "${td}" - wget -q --tries=5 "${URL}/${name}" - unzip -q "${name}" - - local system - system="$(find . -name system.img)" - mkdir -p ${dest}/{bin,lib,lib64} - - # Extract android linker and libraries to /system - # This allows android executables to be run directly (or with qemu) - if [ "${arch}" = "x86_64" ] || [ "${arch}" = "arm64" ]; then - e2cp -p "${system}:/bin/linker64" "${dest}/bin/" - e2cp -p "${system}:/lib64/libdl.so" "${dest}/lib64/" - e2cp -p "${system}:/lib64/libc.so" "${dest}/lib64/" - e2cp -p "${system}:/lib64/libm.so" "${dest}/lib64/" - else - e2cp -p "${system}:/bin/linker" "${dest}/bin/" - e2cp -p "${system}:/lib/libdl.so" "${dest}/lib/" - e2cp -p "${system}:/lib/libc.so" "${dest}/lib/" - e2cp -p "${system}:/lib/libm.so" "${dest}/lib/" - fi - - # clean up - apt-get purge --auto-remove -y e2tools - - popd - - rm -rf "${td}" -} - -main "${@}" diff --git a/ci/create-artifacts.py b/ci/create-artifacts.py index 4145e20491e2..2104b9cac383 100755 --- a/ci/create-artifacts.py +++ b/ci/create-artifacts.py @@ -32,11 +32,20 @@ def main(): archive_name = f"archive-{now}" archive_path = f"{archive_name}.tar.gz" - sp.run( - ["tar", "czvf", archive_path, "-C", build_dir, "-T-"], - input=file_list, - check=True, - ) + # --dereference because kernel.log is a symlink into logs/ + tar_cmd = ["tar", "czvf", archive_path, "--dereference", "-C", build_dir, "-T-"] + + # Grab the runtime logs of the Cuttlefish device launched by ci/cuttlefish-setup.sh. + # The device runs inside the test container, which keeps it under the bind-mounted + # target/ so the logs are visible here. + cf_dir = Path(os.getenv("CUTTLEFISH_DIR", "target/cuttlefish")).resolve() + cf_runtime = cf_dir / "cuttlefish" / "instances" / "cvd-1" + cf_logs = ["logs/logcat", "logs/launcher.log", "kernel.log"] + cf_found = [log for log in cf_logs if cf_runtime.joinpath(log).exists()] + if cf_found: + tar_cmd += ["-C", str(cf_runtime), *cf_found] + + sp.run(tar_cmd, input=file_list, check=True) # If we are in GHA, set these env vars for future use gh_env = os.getenv("GITHUB_ENV") diff --git a/ci/cuttlefish-setup.sh b/ci/cuttlefish-setup.sh new file mode 100755 index 000000000000..14bb782d4088 --- /dev/null +++ b/ci/cuttlefish-setup.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -eux + +# Fetch and boot a Cuttlefish Android virtual device, heavily inspired by +# Mesa's CI: +# https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/.gitlab-ci/cuttlefish-runner.sh +# +# Runs unprivileged (cvd refuses to run as root) inside the test container, +# invoked by cuttlefish-entrypoint.sh after it has prepared the devices, +# networking and groups (see ci/docker/x86_64-linux-android/). +build="${CUTTLEFISH_BUILD:-15660610}" +target="${CUTTLEFISH_TARGET:-aosp_cf_x86_64_only_phone-userdebug}" +cf_dir="${CUTTLEFISH_DIR:-$HOME/cuttlefish}" + +mkdir -p "$cf_dir" +cvd fetch --default_build="${build}/${target}" --target_directory="$cf_dir" + +cd "$cf_dir" + +# -daemon exits only once the guest has fully booted. +# -enable_sandbox=false is needed because crosvm's minijail device sandbox +# (auto-enabled when /var/empty exists) requires unprivileged user +# namespaces, which Ubuntu 24.04+ blocks via AppArmor by default. +HOME="$cf_dir" timeout 10m ./bin/launch_cvd \ + -daemon \ + -enable_sandbox=false \ + -verbosity=INFO \ + -file_verbosity=DEBUG \ + -enable_audio=false \ + -enable_bootanimation=false \ + -enable_minimal_mode=true \ + -enable_modem_simulator=false \ + -enable_wifi=false \ + -report_anonymous_usage_stats=no \ + -cpus=2 \ + -memory_mb=4096 + +# Check the guest's adbd is reachable on the port the test runner uses, +# and log the Android version actually being tested. +HOME="$cf_dir" ./bin/adb connect 127.0.0.1:6520 +HOME="$cf_dir" ./bin/adb -s 127.0.0.1:6520 wait-for-device +HOME="$cf_dir" ./bin/adb devices +HOME="$cf_dir" ./bin/adb -s 127.0.0.1:6520 shell \ + getprop ro.build.version.release diff --git a/ci/docker/aarch64-linux-android/Dockerfile b/ci/docker/aarch64-linux-android/Dockerfile index 91c074150f94..22158381924d 100644 --- a/ci/docker/aarch64-linux-android/Dockerfile +++ b/ci/docker/aarch64-linux-android/Dockerfile @@ -1,46 +1,29 @@ FROM ubuntu:26.04 -RUN dpkg --add-architecture i386 RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ - expect \ + e2fsprogs \ + erofs-utils \ file \ gcc \ - libc6-dev \ - libpulse0 \ - libstdc++6:i386 \ - openjdk-8-jre \ + libc-dev \ python3 \ + qemu-user \ unzip \ wget WORKDIR /android/ -COPY android* /android/ - -ENV ANDROID_ARCH=aarch64 -ENV PATH=$PATH:/android/linux-x86_64/bin:/android/sdk/cmdline-tools/tools:/android/sdk/platform-tools - +COPY android-install-ndk.sh android-bionic-sysroot.sh /android/ RUN /android/android-install-ndk.sh -RUN /android/android-install-sdk.sh $ANDROID_ARCH -RUN mv /root/.android /tmp -RUN chmod 777 -R /tmp/.android -RUN chmod 755 /android/sdk/cmdline-tools/tools/* /android/sdk/emulator/qemu/linux-x86_64/* +RUN /android/android-bionic-sysroot.sh -ENV PATH=$PATH:/rust/bin \ - CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android28-clang \ - CARGO_TARGET_AARCH64_LINUX_ANDROID_RUNNER=/tmp/runtest \ - CC_aarch64_linux_android=aarch64-linux-android28-clang \ +# Test binaries run under qemu-user against the bionic extracted to +# /system by android-bionic-sysroot.sh. This is a stopgap until GitHub's +# arm64 runners expose KVM, at which point we can move to Cuttlefish like +# the x86_64-linux-android test. +ENV PATH=$PATH:/rust/bin:/android/linux-x86_64/bin \ + CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android24-clang \ + CARGO_TARGET_AARCH64_LINUX_ANDROID_RUNNER=qemu-aarch64 \ + CC_aarch64_linux_android=aarch64-linux-android24-clang \ AR_aarch64_linux_android=llvm-ar \ HOME=/tmp - -ADD runtest-android.rs /tmp/runtest.rs -ENTRYPOINT [ \ - "bash", \ - "-c", \ - # set SHELL so android can detect a 64bits system, see - # http://stackoverflow.com/a/41789144 - "SHELL=/bin/dash /android/sdk/emulator/emulator @aarch64 -no-window & \ - rustc /tmp/runtest.rs -o /tmp/runtest && \ - exec \"$@\"", \ - "--" \ -] diff --git a/ci/docker/arm-linux-androideabi/Dockerfile b/ci/docker/arm-linux-androideabi/Dockerfile index 657ff81097c7..90124e093fcf 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 bc9580e6e1c0..1f1d93c6fe79 100644 --- a/ci/docker/x86_64-linux-android/Dockerfile +++ b/ci/docker/x86_64-linux-android/Dockerfile @@ -1,26 +1,38 @@ FROM ubuntu:26.04 RUN apt-get update && apt-get install -y --no-install-recommends \ + adb \ ca-certificates \ + curl \ gcc \ libc-dev \ python3 \ unzip \ wget +# Cuttlefish host packages: the virtual device the tests run on boots +# inside this container (see cuttlefish-entrypoint.sh for the docker run +# flags this needs). +RUN curl -fsSL https://us-apt.pkg.dev/doc/repo-signing-key.gpg \ + -o /etc/apt/trusted.gpg.d/artifact-registry.asc \ + && echo "deb https://us-apt.pkg.dev/projects/android-cuttlefish-artifacts android-cuttlefish main" \ + >/etc/apt/sources.list.d/artifact-registry.list \ + && apt-get update && apt-get install -y cuttlefish-base cuttlefish-user + WORKDIR /android/ ENV ANDROID_ARCH=x86_64 COPY android-install-ndk.sh /android/ RUN /android/android-install-ndk.sh -# We do not run x86_64-linux-android tests on an android emulator. -# See ci/android-sysimage.sh for information about how tests are run. -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++ \ + ANDROID_SERIAL=127.0.0.1:6520 \ + CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER=x86_64-linux-android24-clang \ + CARGO_TARGET_X86_64_LINUX_ANDROID_RUNNER=/tmp/runtest \ + 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 + +ADD runtest-android.rs /tmp/runtest.rs +COPY cuttlefish-setup.sh docker/x86_64-linux-android/cuttlefish-entrypoint.sh /android/ +ENTRYPOINT ["/android/cuttlefish-entrypoint.sh"] diff --git a/ci/docker/x86_64-linux-android/cuttlefish-entrypoint.sh b/ci/docker/x86_64-linux-android/cuttlefish-entrypoint.sh new file mode 100755 index 000000000000..42c99ae8fa58 --- /dev/null +++ b/ci/docker/x86_64-linux-android/cuttlefish-entrypoint.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -eux + +# Boot a Cuttlefish virtual device inside this container, then run the +# tests against it as an unprivileged user (cvd refuses to run as root). +# +# The container needs only /dev/kvm, /dev/net/tun, /dev/vhost-net, +# /dev/vhost-vsock and CAP_NET_ADMIN, passed by ci/run-docker.sh. It's +# the same set upstream uses for its own containerized Cuttlefish CI: +# https://github.com/google/android-cuttlefish/blob/main/.github/workflows/presubmit.yaml + +# The device nodes come in with their host-side ownership: open them up +# for the unprivileged user (container-local, doesn't affect the host). +chmod 666 /dev/kvm /dev/net/tun /dev/vhost-net /dev/vhost-vsock + +# Create the bridges, taps and dnsmasq instances backing the device's +# virtio-net, inside this container's own network namespace. The deb ships +# a sysv script for this, usable without systemd, which is also how +# upstream's container image starts it. +service cuttlefish-host-resources start + +# cvd requires the current process to be in these groups. The groups are +# normally created by the deb postinst, -f covers any it didn't create. +for g in kvm cvdnetwork render; do + groupadd -f "$g" +done +# Match the invoking user's uid so writes to the bind-mounted target/ +# directory stay owned by the host user. +useradd -m -u "${HOST_UID:?}" -G kvm,cvdnetwork,render cf + +run_as_cf() { + runuser -u cf -- env PATH="$PATH" HOME=/tmp "$@" +} + +# Keep the device under target/ (bind-mounted from the host) so its logs +# survive the container for ci/create-artifacts.py to pick up. +export CUTTLEFISH_DIR=/checkout/target/cuttlefish + +run_as_cf /android/cuttlefish-setup.sh + +# The test runner (runtest-android.rs) drives the device with the +# container's adb via $ANDROID_SERIAL. Connect its server to the device +# (this may replace the server started by the fetched adb during boot). +run_as_cf adb connect "$ANDROID_SERIAL" +run_as_cf adb wait-for-device + +run_as_cf rustc /tmp/runtest.rs -o /tmp/runtest + +rc=0 +run_as_cf "$@" || rc=$? + +# Stop the device so its logs are flushed for ci/create-artifacts.py. +run_as_cf env HOME="$CUTTLEFISH_DIR" \ + "$CUTTLEFISH_DIR/bin/stop_cvd" -wait_for_launcher=40 || true + +exit "$rc" diff --git a/ci/run-docker.sh b/ci/run-docker.sh index 2266b2f2d269..699cd4099b3d 100755 --- a/ci/run-docker.sh +++ b/ci/run-docker.sh @@ -57,16 +57,37 @@ run() { docker build "${build_args[@]}" mkdir -p target - if [ -w /dev/kvm ]; then - kvm="--volume /dev/kvm:/dev/kvm" + + extra_args=() + if [ -n "${TEST_CUTTLEFISH:-}" ]; then + # Cuttlefish-based Android targets boot their virtual device inside + # the container (see cuttlefish-entrypoint.sh in the target's docker + # dir): pass the virtualization device nodes and let the entrypoint + # create its tap networking. It starts as root and drops to an + # unprivileged user matching HOST_UID for the device and the tests. + # Same flags as upstream's containerized Cuttlefish CI. + extra_args+=( + --device /dev/kvm + --device /dev/net/tun + --device /dev/vhost-net + --device /dev/vhost-vsock + --cap-add NET_ADMIN + --security-opt seccomp=unconfined + --env CUTTLEFISH_BUILD + --env CUTTLEFISH_TARGET + --env HOST_UID="$(id -u)" + ) else - kvm="" + extra_args+=(--user "$(id -u)":"$(id -g)") + if [ -w /dev/kvm ]; then + extra_args+=(--volume /dev/kvm:/dev/kvm) + fi fi docker run \ --rm \ - --user "$(id -u)":"$(id -g)" \ --env LIBC_BUILD_VERBOSE \ + "${extra_args[@]}" \ --env LIBC_CI \ --env LIBC_CI_ZBUILD_STD \ --env RUSTFLAGS \ @@ -80,7 +101,6 @@ run() { --volume "$(rustc --print sysroot)":/rust:ro,Z \ --volume "$PWD":/checkout:ro,Z \ --volume "$PWD"/target:/checkout/target \ - $kvm \ --init \ --workdir /checkout \ "libc-$target" \ diff --git a/libc-test/build.rs b/libc-test/build.rs old mode 100644 new mode 100755 index 2cf2eba99dda..e1cdd9dde76f --- 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" @@ -2480,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. @@ -6376,6 +6383,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 +6405,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 +6476,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();