diff --git a/docs/android.md b/docs/android.md index 69a31b5b5..c8f130996 100644 --- a/docs/android.md +++ b/docs/android.md @@ -120,6 +120,16 @@ PASEO_FDROID_BUILD=1 ./gradlew assembleRelease --no-daemon --max-workers=1 -Dorg The flag must be present for both prebuild and Gradle because Gradle starts Metro for the release bundle. Keep the source build serial and daemon-free as shown above: compiling every Expo module can exhaust memory when Gradle workers run in parallel. The profile enables source-built Expo modules, excludes the proprietary camera, Firebase notification, and Expo development-client native modules, disables EAS updates and Gradle dependency metadata, and substitutes JavaScript stubs for camera and notifications. The resulting app supports direct and pasted-link pairing but not QR scanning or push notifications. +For a single-ABI APK, pass React Native's architecture property to Gradle: + +```bash +PASEO_FDROID_BUILD=1 ./gradlew assembleRelease \ + -PreactNativeArchitectures=arm64-v8a \ + --no-daemon --max-workers=1 -Dorg.gradle.parallel=false +``` + +Supported values are `armeabi-v7a`, `arm64-v8a`, `x86`, and `x86_64`. The F-Droid profile filters native libraries to that ABI and changes the APK version code to `baseVersionCode * 10 + abiSuffix`, where the suffixes are ordered `1` through `4` in that same sequence. F-Droid metadata should use four build blocks with `VercodeOperation` entries `10 * %c + 1` through `10 * %c + 4` and pass the matching `reactNativeArchitectures` value in each build command. Builds without a single architecture keep the base version code. + Keep the excluded npm packages installed. Normal builds use them, while the F-Droid profile removes only their Android native modules and config plugins. Paseo always applies `expo-gradle-jvmargs` with `-Xmx4096m` and `-XX:MaxMetaspaceSize=1024m` so local Expo prebuilds have enough Gradle heap whether they use precompiled AARs or source-built Expo modules. The EAS `production-apk` profile uses the large Android resource class. Release builds compile the native ABIs and run Hermes bundling in the same Gradle invocation; the default worker can exhaust its remaining memory and kill Hermes with exit code 137 even when Gradle's own heap is correctly sized. diff --git a/packages/app/plugins/with-fdroid-autolinking.js b/packages/app/plugins/with-fdroid-autolinking.js index a24d4aa34..0a924a626 100644 --- a/packages/app/plugins/with-fdroid-autolinking.js +++ b/packages/app/plugins/with-fdroid-autolinking.js @@ -11,6 +11,51 @@ const EXCLUDED_ANDROID_MODULES = [ "expo-dev-menu-interface", ]; +const FDROID_ABI_VERSION_CODE_BLOCK = `// Paseo F-Droid single-ABI version codes +def paseoAbiVersionCodes = [ + "armeabi-v7a": 1, + "arm64-v8a": 2, + "x86": 3, + "x86_64": 4, +] +def paseoArchitectures = (findProperty("reactNativeArchitectures") ?: "") + .toString() + .split(",") + .collect { it.trim() } + .findAll { !it.isEmpty() } + +if (paseoArchitectures.size() == 1) { + def paseoAbi = paseoArchitectures[0] + def paseoAbiVersionCode = paseoAbiVersionCodes[paseoAbi] + if (paseoAbiVersionCode == null) { + throw new GradleException("Unsupported Paseo Android ABI: " + paseoAbi) + } + android.defaultConfig.versionCode = android.defaultConfig.versionCode * 10 + paseoAbiVersionCode +} +`; + +function configureFdroidAppBuildGradle(contents) { + let configuredContents = contents; + + if (!configuredContents.includes("dependenciesInfo {")) { + const androidBlock = "android {"; + if (!configuredContents.includes(androidBlock)) { + throw new Error("Could not disable F-Droid dependency metadata in app/build.gradle"); + } + + configuredContents = configuredContents.replace( + androidBlock, + `${androidBlock}\n dependenciesInfo {\n includeInApk = false\n includeInBundle = false\n }`, + ); + } + + if (!configuredContents.includes("// Paseo F-Droid single-ABI version codes")) { + configuredContents = `${configuredContents.trimEnd()}\n\n${FDROID_ABI_VERSION_CODE_BLOCK}`; + } + + return configuredContents; +} + function withFdroidAutolinking(config) { config = withDangerousMod(config, [ "android", @@ -65,19 +110,7 @@ function withFdroidAutolinking(config) { }); return withAppBuildGradle(config, (modConfig) => { - if (modConfig.modResults.contents.includes("dependenciesInfo {")) { - return modConfig; - } - - const androidBlock = "android {"; - if (!modConfig.modResults.contents.includes(androidBlock)) { - throw new Error("Could not disable F-Droid dependency metadata in app/build.gradle"); - } - - modConfig.modResults.contents = modConfig.modResults.contents.replace( - androidBlock, - `${androidBlock}\n dependenciesInfo {\n includeInApk = false\n includeInBundle = false\n }`, - ); + modConfig.modResults.contents = configureFdroidAppBuildGradle(modConfig.modResults.contents); return modConfig; }); }