diff --git a/.changeset/brave-plants-tap.md b/.changeset/brave-plants-tap.md new file mode 100644 index 00000000..c450b446 --- /dev/null +++ b/.changeset/brave-plants-tap.md @@ -0,0 +1,5 @@ +--- +'@use-voltra/android-client': patch +--- + +Respect Android ABI selections and avoid bundling duplicate Hermes libraries. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 915ec0d8..229fb69a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -243,3 +243,92 @@ jobs: - name: Check Kotlin formatting run: pnpm run format:kotlin:check + + android-tarball-build: + name: '[Android] Tarball + all-ABI native build' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '17' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 11.5.0 + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '24.14.0' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # build/** must exist so the packed tarball is complete. + - name: Build monorepo + run: pnpm run build:all + + # Packaging integrity: pack android-client and install the *tarball* into + # the example app instead of the workspace symlink. `pnpm pack` rewrites + # the `workspace:` deps to real version ranges that match the sibling + # packages, so pnpm links those from the workspace while android-client + # itself is consumed exactly as it would ship to npm. A file missing from + # the tarball (the v2.1.0 CMakeLists.txt incident) then breaks prebuild or + # gradle configure below. + - name: Pack android-client tarball + working-directory: packages/android-client + run: | + mkdir -p "$RUNNER_TEMP/tarballs" + pnpm pack --pack-destination "$RUNNER_TEMP/tarballs" + TARBALL=$(ls "$RUNNER_TEMP/tarballs"/*.tgz | head -n1) + echo "ANDROID_CLIENT_TARBALL=$TARBALL" >> "$GITHUB_ENV" + + # `pnpm pack` rewrites the tarball's `workspace:^` deps to real semver + # ranges, which pnpm would otherwise resolve from the npm registry — + # tripping minimumReleaseAge for freshly published versions and testing + # published siblings instead of this branch. Override every @use-voltra + # sibling back to the workspace so only android-client itself is consumed + # from the tarball (it must NOT be overridden, or the tarball install — + # the whole point of this job — would be replaced by a workspace link). + - name: Resolve sibling packages from the workspace + run: | + { + echo "" + echo "overrides:" + for f in packages/*/package.json; do + name=$(node -p "require('./$f').name") + if [ "$name" != "@use-voltra/android-client" ]; then + echo " \"$name\": workspace:*" + fi + done + } >> pnpm-workspace.yaml + + - name: Install android-client from tarball into example app + run: pnpm --filter voltra-example add "file:$ANDROID_CLIENT_TARBALL" + + - name: Generate Android native project + working-directory: example + run: CI=1 pnpm exec expo prebuild --platform android + + # All-ABI native build (issue #209): a targeted external-native-build task + # keeps CI fast while compiling libvoltra_js_renderer.so and linking + # ReactAndroid::jsi / hermes-engine::hermesvm / fbjni::fbjni for every ABI, + # so a per-ABI (e.g. armeabi-v7a) link regression fails CI. + - name: Build native library for all ABIs + working-directory: example/android + run: >- + ./gradlew :use-voltra_android-client:externalNativeBuildDebug + -PreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 diff --git a/packages/android-client/android/build.gradle b/packages/android-client/android/build.gradle index a2b61838..4e37e9c6 100644 --- a/packages/android-client/android/build.gradle +++ b/packages/android-client/android/build.gradle @@ -23,6 +23,13 @@ def safeExtGet = { prop, fallback -> rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } +// Mirror the app's ABI selection (React Native template convention) so the +// standalone Hermes runtime is only built for the architectures the app ships. +def reactNativeArchitectures() { + def value = rootProject.getProperties().get("reactNativeArchitectures") + return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] +} + // Read version from package.json for BuildConfig def packageJsonFile = new File(project.projectDir.parentFile, "package.json") def packageJson = new groovy.json.JsonSlurper().parseText(packageJsonFile.text) @@ -48,6 +55,10 @@ android { arguments "-DANDROID_STL=c++_shared" } } + + ndk { + abiFilters(*reactNativeArchitectures()) + } } externalNativeBuild { @@ -71,6 +82,7 @@ android { "**/libfbjni.so", "**/libjsi.so", "**/libhermes.so", + "**/libhermesvm.so", ] }