🍒 [PM-46265] fix: Install AGP's default NDK in CI so native libraries are stripped (#7568)

This commit is contained in:
mpbw2
2026-11-05 09:26:10 -05:00
committed by GitHub
parent 1470154cf3
commit e59c698ffa
6 changed files with 170 additions and 1 deletions
+80 -1
View File
@@ -1,5 +1,5 @@
name: 'Setup Android Build'
description: 'Setup Android build environment with Gradle, Ruby, and Fastlane'
description: 'Setup Android build environment with Gradle, Ruby, JDK, Android NDK, and Fastlane'
inputs:
java-version:
description: 'Java version to use'
@@ -22,6 +22,85 @@ runs:
distribution: "temurin"
java-version: ${{ inputs.java-version }}
- name: Determine Android NDK versions
id: ndk
shell: bash
run: |
# AGP strips native libraries with the NDK reported by printNdkVersion (AGP's default
# unless a module sets ndkVersion). Install it so stripping doesn't depend on the runner image.
NDK_VERSIONS=$(./gradlew -q printNdkVersion | sed -n 's/^ndkVersion=//p' | sort -u)
if [ -z "$NDK_VERSIONS" ]; then
echo "::error::Unable to determine the NDK version from ./gradlew printNdkVersion"
exit 1
fi
MISSING_NDK_VERSIONS=""
for NDK_VERSION in $NDK_VERSIONS; do
if [ -f "$ANDROID_HOME/ndk/$NDK_VERSION/source.properties" ]; then
echo "NDK $NDK_VERSION is already installed"
else
MISSING_NDK_VERSIONS="$MISSING_NDK_VERSIONS $NDK_VERSION"
fi
done
if [ -z "$MISSING_NDK_VERSIONS" ]; then
echo "No missing NDK versions found"
exit 0
fi
# Flag installs so it's visible when the runner image no longer ships the NDK AGP expects.
echo "::warning title=Android NDK not on runner image::Installing NDK$MISSING_NDK_VERSIONS"
{
echo "### :warning: Android NDK not on runner image"
echo "The following NDK versions were not preinstalled on the runner and were installed by \`setup-android-build\`:"
for NDK_VERSION in $MISSING_NDK_VERSIONS; do
echo "- \`$NDK_VERSION\`"
done
} >> "$GITHUB_STEP_SUMMARY"
{
echo "missing=${MISSING_NDK_VERSIONS# }"
echo "cache-key=android-ndk-${RUNNER_OS}-$(echo $MISSING_NDK_VERSIONS | tr ' ' '-')"
echo "paths<<EOF"
for NDK_VERSION in $MISSING_NDK_VERSIONS; do
echo "$ANDROID_HOME/ndk/$NDK_VERSION"
done
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Restore Android NDK from cache
if: ${{ steps.ndk.outputs.missing != '' }}
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.ndk.outputs.paths }}
key: ${{ steps.ndk.outputs.cache-key }}
- name: Install Android NDK
if: ${{ steps.ndk.outputs.missing != '' }}
shell: bash
env:
NDK_VERSIONS: ${{ steps.ndk.outputs.missing }}
run: |
for NDK_VERSION in $NDK_VERSIONS; do
NDK_DIR="$ANDROID_HOME/ndk/$NDK_VERSION"
if [ -f "$NDK_DIR/source.properties" ]; then
echo "NDK $NDK_VERSION restored from cache"
continue
fi
# Downloads occasionally fail or stall with a corrupt archive, so retry with a timeout.
for ATTEMPT in 1 2 3; do
echo "Installing NDK $NDK_VERSION (attempt $ATTEMPT of 3)"
if timeout 10m "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;$NDK_VERSION" > /dev/null \
&& [ -f "$NDK_DIR/source.properties" ]; then
break
fi
rm -rf "$NDK_DIR"
if [ "$ATTEMPT" -eq 3 ]; then
echo "::error::Failed to install NDK $NDK_VERSION after 3 attempts"
exit 1
fi
sleep 15
done
done
- name: Install Fastlane
shell: bash
run: |
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
set -euo pipefail
# Fails if any native library packaged for a supported ABI still has its debug symbols.
# AGP's strip*DebugSymbols tasks only log a warning when they cannot strip (for example when the
# NDK they need is missing), so this check keeps unstripped libraries from shipping unnoticed.
# Legacy ABIs (armeabi, mips, mips64) are ignored because current NDKs cannot strip them.
# Usage: verify-native-libs-stripped.sh <apk|aab|directory>...
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <apk|aab|directory>..." >&2
exit 1
fi
SUPPORTED_ABI_LIB_REGEX='(^|/)lib/(arm64-v8a|armeabi-v7a|x86|x86_64)/[^/]+\.so$'
archives=()
for path in "$@"; do
if [ -d "$path" ]; then
while IFS= read -r archive; do
archives+=("$archive")
done < <(find "$path" -type f \( -name '*.apk' -o -name '*.aab' \) | sort)
elif [ -f "$path" ]; then
archives+=("$path")
else
echo "::error::$path does not exist"
exit 1
fi
done
if [ "${#archives[@]}" -eq 0 ]; then
echo "::error::No APK or AAB files found in: $*"
exit 1
fi
work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT
failures=0
for archive in "${archives[@]}"; do
libs=$(unzip -Z1 "$archive" | grep -E "$SUPPORTED_ABI_LIB_REGEX" || true)
if [ -z "$libs" ]; then
echo "$archive: no native libraries"
continue
fi
extract_dir="$work_dir/$(basename "$archive")"
mkdir -p "$extract_dir"
count=0
while IFS= read -r lib; do
unzip -q -o "$archive" "$lib" -d "$extract_dir"
count=$((count + 1))
if file -b "$extract_dir/$lib" | grep -q "not stripped"; then
echo "::error::$archive contains an unstripped native library: $lib"
failures=$((failures + 1))
fi
done <<< "$libs"
echo "$archive: checked $count native libraries"
done
if [ "$failures" -gt 0 ]; then
echo "::error::Found $failures unstripped native libraries. Check the strip*DebugSymbols output (run with --info) and make sure the NDK reported by ./gradlew printNdkVersion is installed."
exit 1
fi
echo "All native libraries for supported ABIs are stripped."
@@ -174,6 +174,9 @@ jobs:
keyAlias:"bitwardenauthenticator" \
keyPassword:"$KEY_PASSWORD"
- name: Verify native libraries are stripped
run: ./.github/scripts/verify-native-libs-stripped.sh authenticator/build/outputs
- name: Upload to GitHub Artifacts - prod.aab
if: ${{ matrix.variant == 'aab' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+3
View File
@@ -122,6 +122,9 @@ jobs:
keyAlias:"bitwardentestharness" \
keyPassword:"$KEY_PASSWORD"
- name: Verify native libraries are stripped
run: ./.github/scripts/verify-native-libs-stripped.sh testharness/build/outputs
- name: Upload Test Harness Release APK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
+7
View File
@@ -190,6 +190,10 @@ jobs:
run: |
bundle exec fastlane assembleDebugApks
- name: Verify native libraries are stripped
if: ${{ (matrix.variant == 'prod') || (matrix.artifact == 'apk') }}
run: ./.github/scripts/verify-native-libs-stripped.sh app/build/outputs
- name: Upload to GitHub Artifacts - prod.aab
if: ${{ (matrix.variant == 'prod') && (matrix.artifact == 'aab') }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -444,6 +448,9 @@ jobs:
keyAlias:bitwarden-beta \
keyPassword:$FDROID_BETA_KEY_PASSWORD
- name: Verify native libraries are stripped
run: ./.github/scripts/verify-native-libs-stripped.sh app/build/outputs
- name: Upload to GitHub Artifacts - fdroid.apk
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
+11
View File
@@ -1,3 +1,4 @@
import com.android.build.api.dsl.ApplicationExtension
import io.gitlab.arturbosch.detekt.Detekt
plugins {
@@ -180,6 +181,16 @@ fun Project.getGitStagedFiles(rootDir: File): Provider<List<File>> {
}
subprojects {
// Prints the NDK version AGP uses to strip native libraries (its default unless a module sets
// ndkVersion) so CI can install it without hardcoding a version.
plugins.withId("com.android.application") {
val android = extensions.getByType<ApplicationExtension>()
tasks.register("printNdkVersion") {
description = "Prints the NDK version AGP uses to strip native libraries."
val ndkVersion = android.ndkVersion
doLast { println("ndkVersion=$ndkVersion") }
}
}
tasks.withType<JavaCompile>().configureEach {
options.isFork = true
}