From eea6f4f7ed6f3890d2984ab03016255ec8e4ce7a Mon Sep 17 00:00:00 2001 From: Robert Helewka Date: Fri, 24 Jul 2026 09:09:36 -0400 Subject: [PATCH] fix: bake the NDK into the image so debugSymbolLevel works ndk { debugSymbolLevel = "SYMBOL_TABLE" } needs the NDK's llvm-objcopy to extract native symbol tables into the .aab's BUNDLE-METADATA. The image shipped no NDK, so AGP silently skipped the step and Play warned "no debug symbols" on every bundle with native code (confirmed by inspecting Dade's 0.0.2 .aab: onnxruntime .so present, debugsymbols metadata dir absent). Install ndk;27.0.12077973 (AGP 8.13's default, so app repos need no android.ndkVersion), export ANDROID_NDK_ROOT/HOME, and add llvm-objcopy to the image sanity check so an arch/version mismatch fails the image build instead of silently dropping symbols downstream. Co-Authored-By: Claude Opus 4.8 (1M context) --- Dockerfile | 21 +++++++++++++++++++-- README.md | 8 ++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4c81adf..0504573 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,12 @@ FROM eclipse-temurin:21-jdk-noble ARG CMDLINE_TOOLS_VERSION=13114758 ARG BUILD_TOOLS_VERSION=36.0.0 ARG PLATFORM_VERSION=android-36 +# NDK: required for `ndk { debugSymbolLevel }` — AGP shells out to the NDK's +# llvm-objcopy to extract native symbol tables into the .aab's BUNDLE-METADATA. +# Without it AGP silently skips the step and Play warns "no debug symbols". Pin +# to AGP's default NDK (r27 for AGP 8.13) so app repos need no `ndkVersion`. +# Check the default at https://developer.android.com/build/releases/gradle-plugin +ARG NDK_VERSION=27.0.12077973 ENV ANDROID_SDK_ROOT=/opt/android-sdk \ ANDROID_HOME=/opt/android-sdk \ @@ -48,14 +54,25 @@ RUN yes | sdkmanager --licenses >/dev/null && \ sdkmanager --install \ "platform-tools" \ "platforms;${PLATFORM_VERSION}" \ - "build-tools;${BUILD_TOOLS_VERSION}" && \ + "build-tools;${BUILD_TOOLS_VERSION}" \ + "ndk;${NDK_VERSION}" && \ rm -rf ${ANDROID_SDK_ROOT}/.android +# Point AGP at the baked-in NDK. AGP auto-discovers ndk; under the SDK +# root, but exporting these makes it explicit and lets the debugSymbolLevel +# step resolve llvm-objcopy without an app-side android.ndkVersion. +ENV ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION} \ + ANDROID_NDK_HOME=${ANDROID_SDK_ROOT}/ndk/${NDK_VERSION} + # Sanity: fail the image build if the toolchain isn't actually usable. # Execute aapt2 (not just --version a tool) so an arch mismatch in the # build-tools binaries fails the image build here, not in every app build. +# Execute the NDK's llvm-objcopy too — it is the exact binary the bundle's +# debug-symbol extraction shells out to, so an arch/version mismatch fails +# here rather than silently dropping symbols from every app's .aab. RUN java -version && sdkmanager --version && node --version && \ "${ANDROID_SDK_ROOT}/build-tools/${BUILD_TOOLS_VERSION}/aapt2" version && \ - test -d "${ANDROID_SDK_ROOT}/platforms/${PLATFORM_VERSION}" + test -d "${ANDROID_SDK_ROOT}/platforms/${PLATFORM_VERSION}" && \ + "${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objcopy" --version WORKDIR /workspace \ No newline at end of file diff --git a/README.md b/README.md index a740202..90da1ca 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,11 @@ so building on amd64 costs nothing on the output side. ## What's in the image - Eclipse Temurin JDK 21 (native amd64) -- Android cmdline-tools, platform, and build-tools — **baked in**, so prod - builds don't depend on Google's download endpoint at job time +- Android cmdline-tools, platform, build-tools, and **NDK** — **baked in**, so + prod builds don't depend on Google's download endpoint at job time. The NDK is + what makes `ndk { debugSymbolLevel }` work: AGP shells out to its + `llvm-objcopy` to embed native symbol tables in the `.aab`, so Play can + symbolicate native crashes. Without it AGP skips that step silently. - `git`, `curl`, `unzip` - Node.js 24 — so Gitea Actions JS actions (`actions/checkout`, `upload-artifact`, …) run when this image is used as a job `container` @@ -35,6 +38,7 @@ Pinned versions live as `ARG`s at the top of the `Dockerfile`: | `CMDLINE_TOOLS_VERSION` | `13114758` | https://developer.android.com/studio#command-line-tools-only | | `BUILD_TOOLS_VERSION` | `36.0.0` | SDK Manager / release notes | | `PLATFORM_VERSION` | `android-36`| your app's `compileSdk` | +| `NDK_VERSION` | `27.0.12077973` | AGP's default NDK: https://developer.android.com/build/releases/gradle-plugin | The build-tools binaries (`aapt2`, `d8`) are x86_64 ELF — this is why the image is amd64 (see "Why amd64" above).