Repository navigation
[CP-stable][tool] Fix libapp.so dropped from APK/app bundle - #188516
Conversation
## Summary `libapp.so` could be silently dropped from release APKs and app bundles, surfacing as either: - a runtime crash on launch — `VM snapshot invalid and could not be inferred from settings` — for APKs, or - a `Release app bundle failed to strip debug symbols from native libraries` build failure for app bundles. ## Root cause Regression from flutter#181275, which moved `libapp.so` from a jar dependency onto a Flutter Gradle Plugin source-set `jniLibs` directory. That delivery was fragile in two ways, both of which dropped `libapp.so` from the merged native libraries (while `libflutter.so`/`libdartjni.so`, delivered as AAR dependencies, were unaffected — which is why the failure looked asymmetric): 1. The source-set `jniLibs` `srcDir` was resolved eagerly (`.get().asFile`) at `:app` configuration time, while the copy task wrote lazily. When `:app` was evaluated before its build directory had been redirected — a combined `subprojects { … evaluationDependsOn(":app") }` block (the pre-flutter#91030 template shape) together with a plugin whose Gradle subproject name sorts before `:app` — the two disagreed on the build directory and the staged `libapp.so` was never merged. (flutter#186810) 2. The copy task wrote into a directory nested inside the Flutter task's own output directory, creating overlapping task outputs that undermined Gradle's incremental checks — e.g. a flavored project where a prior single-ABI build (a `flutter run` on one device) left the other ABIs missing `libapp.so`. (flutter#187388) ## Fix Stage `libapp.so` (and any bundled native assets) through a dedicated `CopyFlutterJniLibsTask` whose output is registered on each variant via AGP's variant API: [`variant.sources.jniLibs.addGeneratedSourceDirectory(...)`](https://developer.android.com/reference/tools/gradle-api/9.1/com/android/build/api/variant/SourceDirectories#addStaticSourceDirectory(kotlin.String)). AGP then owns the task dependency, resolves the output path lazily (correct regardless of project evaluation order or build-dir redirection), keeps it in its own output directory (no overlapping outputs), and strips/extracts debug symbols from it like any other native library. This restores the robustness of the original jar-based inclusion while keeping `libapp.so` strippable. ## Tests New integration test `gradle_libapp_so_packaging_test.dart` covering both triggers: the combined `subprojects` block + a plugin sorted before `:app`, and a flavored single-ABI build preceding a multi-ABI build. Fixes flutter#186810 Fixes flutter#187388 Fixes flutter#187553
|
@gmackall please fill out the PR description above, afterwards the release team will review this request. |
|
This pull request was opened from and to a release candidate branch. This should only be done as part of the official Flutter release process. If you are attempting to make a regular contribution to the Flutter project, please close this PR and follow the instructions at Tree Hygiene for detailed instructions on contributing to Flutter. Reviewers: Use caution before merging pull requests to release branches. Ensure the proper procedure has been followed. |
There was a problem hiding this comment.
Code Review
This pull request migrates the Flutter Gradle Plugin from using the legacy Android extension to the modern AndroidComponentsExtension for registering JNI libraries. It introduces a new CopyFlutterJniLibsTask to stage native libraries (libapp.so and native assets) into a dedicated destination directory structured by ABI, and adds integration tests to verify packaging. Feedback on the changes suggests skipping target platforms with unresolved ABIs in CopyFlutterJniLibsTask to avoid copying files to or from a directory named "null".
| targetPlatforms.get().forEach { targetPlatform -> | ||
| val abi: String? = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform] | ||
| from(intermediateDir.dir(abi ?: "null")) { | ||
| include("*.so") | ||
| rename { filename: String -> "lib$filename" } | ||
| into(abi ?: "null") | ||
| } | ||
| val nativeAssetsDir = intermediateDir.dir("native_assets/jniLibs/lib/$abi") | ||
| from(nativeAssetsDir) { | ||
| include("*.so") | ||
| into(abi ?: "null") | ||
| } | ||
| } |
There was a problem hiding this comment.
If targetPlatform is not found in PLATFORM_ARCH_MAP, abi will be null. Using abi ?: "null" results in copying files from/to a directory named "null", which is unexpected and could package unwanted files or create a redundant null directory in the build output. It is safer to skip platforms with an unresolved ABI entirely.
| targetPlatforms.get().forEach { targetPlatform -> | |
| val abi: String? = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform] | |
| from(intermediateDir.dir(abi ?: "null")) { | |
| include("*.so") | |
| rename { filename: String -> "lib$filename" } | |
| into(abi ?: "null") | |
| } | |
| val nativeAssetsDir = intermediateDir.dir("native_assets/jniLibs/lib/$abi") | |
| from(nativeAssetsDir) { | |
| include("*.so") | |
| into(abi ?: "null") | |
| } | |
| } | |
| targetPlatforms.get().forEach { targetPlatform -> | |
| val abi = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform] ?: return@forEach | |
| from(intermediateDir.dir(abi)) { | |
| include("*.so") | |
| rename { filename: String -> "lib$filename" } | |
| into(abi) | |
| } | |
| val nativeAssetsDir = intermediateDir.dir("native_assets/jniLibs/lib/$abi") | |
| from(nativeAssetsDir) { | |
| include("*.so") | |
| into(abi) | |
| } | |
| } |
…1be6135990dd6bfee856fb4d1e
c63e437
into
flutter:flutter-3.44-candidate.0
…188516) ### Issue Link: What is the link to the issue this cherry-pick is addressing? flutter#186810 flutter#187388 flutter#187553 ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping of production apps (the app crashes on launch). This information is for domain experts and release engineers to understand the consequences of saying yes or no to the cherry pick. The root cause either presents as the app crashing on startup, or the build failing with the message > "Release app bundle failed to strip debug symbols from native libraries" ### Changelog Description: Explain this cherry pick: * In one line that is accessible to most Flutter developers. * That describes the state prior to the fix. * That includes which platforms are impacted. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples. < Replace with changelog description here > [flutter/186810 flutter/187388 flutter/187553] When building android app bundles using flavors, or with an old app template combined with a plugin coming alphabetically before app, fixes problems with failing to include libapp.so in the produced app bundle. ### Workaround: Is there a workaround for this issue? For the case of the old app template combined with a plugin coming before "app" alphabetically, there is a workaround here flutter#186810 (comment). For the flavors case there is not a workaround. ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? 1. make a fresh project 2. combine the subprojects in `android/build.gradle(.kts)`: find ```kotlin subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } subprojects { project.evaluationDependsOn(":app") } ``` and combine ```kotlin subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) project.evaluationDependsOn(":app") } ``` 3. add a plugin which comes alphabetically before "app" 4. also you will need to disable r8, due to how the templates have drifted over time. ``` // after signingConfig = signingConfigs.getByName("debug") isMinifyEnabled = false isShrinkResources = false ``` Also, you can separately test the repro steps listed in flutter#187388 to validate that this fixes the flavors-symptom of this underlying root cause.
Issue Link:
What is the link to the issue this cherry-pick is addressing?
#186810
#187388
#187553
Impact Description:
What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)?
Does it impact development (ex. flutter doctor crashes when Android Studio is installed),
or the shipping of production apps (the app crashes on launch).
This information is for domain experts and release engineers to understand the consequences of saying yes or no to the cherry pick.
The root cause either presents as the app crashing on startup, or the build failing with the message
Changelog Description:
Explain this cherry pick:
See best practices for examples.
< Replace with changelog description here >
[flutter/186810 flutter/187388 flutter/187553] When building android app bundles using flavors, or with an old app template combined with a plugin coming alphabetically before app, fixes problems with failing to include libapp.so in the produced app bundle.
Workaround:
Is there a workaround for this issue?
For the case of the old app template combined with a plugin coming before "app" alphabetically, there is a workaround here #186810 (comment). For the flavors case there is not a workaround.
Risk:
What is the risk level of this cherry-pick?
Test Coverage:
Are you confident that your fix is well-tested by automated tests?
Validation Steps:
What are the steps to validate that this fix works?
android/build.gradle(.kts):find
subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) } subprojects { project.evaluationDependsOn(":app") }and combine
subprojects { val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) project.layout.buildDirectory.value(newSubprojectBuildDir) project.evaluationDependsOn(":app") }Also, you can separately test the repro steps listed in
#187388
to validate that this fixes the flavors-symptom of this underlying root cause.