Respect product flavor abiFilters by adding a `disable-abi-filtering` Android project flag. (#177753)
This PR introduces a flag (`disable-abi-filtering`) that disables the
FlutterPlugin's abiFiltering on buildTypes. This should only be used
when the developers know what they are doing. Specifically this allows
developers to set their own abiFilters in ProductFlavors or
defaultConfig.
Because Gradle has complex merging logic, there is a hierarchy of
priority when calculating settings (like abiFilters). For example:
defaultConfig < productFlavors < buildTypes when combining the three
into the final build variant. If abiFilters are set in buildType, it
will override abiFilters in productFlavors.
When the FlutterPlugin executes, it cannot know about the developers
build.gradle settings and therefore we cannot add logic that checks to
see if abiFilters are set in productFlavors. Therefore, this flag gives
developers an escape hatch so they can implement what they want.
Fixes: #175845
## Pre-launch Checklist
- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.
diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
index d6fc07c..a0f3046 100644
--- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
+++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
@@ -166,13 +166,15 @@
//
// If the user has specified abiFilters in their build.gradle file, those
// settings will take precedence over these defaults.
- FlutterPluginUtils.getAndroidExtension(project).buildTypes.forEach { buildType ->
- buildType.ndk.abiFilters.clear()
- FlutterPluginConstants.DEFAULT_PLATFORMS.forEach { platform ->
- val abiValue: String =
- FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]
- ?: throw GradleException("Invalid platform: $platform")
- buildType.ndk.abiFilters.add(abiValue)
+ if (!FlutterPluginUtils.shouldProjectDisableAbiFiltering(project)) {
+ FlutterPluginUtils.getAndroidExtension(project).buildTypes.forEach { buildType ->
+ buildType.ndk.abiFilters.clear()
+ FlutterPluginConstants.DEFAULT_PLATFORMS.forEach { platform ->
+ val abiValue: String =
+ FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]
+ ?: throw GradleException("Invalid platform: $platform")
+ buildType.ndk.abiFilters.add(abiValue)
+ }
}
}
}
diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt
index 9e8dc5d..d7bdc23 100644
--- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt
+++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt
@@ -36,6 +36,7 @@
internal const val PROP_TARGET = "target"
internal const val PROP_LOCAL_ENGINE_BUILD_MODE = "local-engine-build-mode"
internal const val PROP_TARGET_PLATFORM = "target-platform"
+ internal const val PROP_DISABLE_ABI_FILTERING = "disable-abi-filtering"
// ----------------- Methods for string manipulation and comparison. -----------------
@@ -203,6 +204,10 @@
@JvmName("isProjectVerbose")
internal fun isProjectVerbose(project: Project): Boolean = project.findProperty(PROP_IS_VERBOSE)?.toString()?.toBoolean() ?: false
+ @JvmStatic
+ @JvmName("shouldProjectDisableAbiFiltering")
+ internal fun shouldProjectDisableAbiFiltering(project: Project): Boolean = project.hasProperty(PROP_DISABLE_ABI_FILTERING)
+
/**
* TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560
*
diff --git a/packages/flutter_tools/test/integration.shard/gradle_jni_packaging_test.dart b/packages/flutter_tools/test/integration.shard/gradle_jni_packaging_test.dart
index 4a2b1e8..46858a4 100644
--- a/packages/flutter_tools/test/integration.shard/gradle_jni_packaging_test.dart
+++ b/packages/flutter_tools/test/integration.shard/gradle_jni_packaging_test.dart
@@ -77,6 +77,68 @@
expect(_checkLibIsInApk(projectDir, 'lib/armeabi-v7a/libflutter.so'), false);
expect(_checkLibIsInApk(projectDir, 'lib/x86/libflutter.so'), false);
});
+
+ testWithoutContext(
+ 'abiFilters in product flavors provided by the user take precedence over the default',
+ () async {
+ final Directory projectDir = createProjectWithThirdpartyLib(tempDir);
+ final String buildGradleContents = projectDir
+ .childFile('android/app/build.gradle.kts')
+ .readAsStringSync();
+
+ const productFlavorsBlock = '''
+ flavorDimensions += listOf("device")
+ productFlavors {
+ create("arm64") {
+ dimension = "device"
+ ndk {
+ abiFilters.clear()
+ abiFilters.addAll(listOf("arm64-v8a"))
+ }
+ }
+ create("armeabi") {
+ dimension = "device"
+ ndk {
+ abiFilters.clear()
+ abiFilters.addAll(listOf("armeabi-v7a"))
+ }
+ }
+ }''';
+ // Modify the project's build.gradle.kts file to include abiFilters for product flavors.
+ final String updatedBuildGradleContents = buildGradleContents.replaceFirstMapped(
+ RegExp(r'^(android\s*\{)', multiLine: true),
+ (Match match) => '${match.group(1)!}\n$productFlavorsBlock',
+ );
+ projectDir
+ .childFile('android/app/build.gradle.kts')
+ .writeAsStringSync(updatedBuildGradleContents);
+
+ processManager.runSync(<String>[
+ flutterBin,
+ 'build',
+ 'apk',
+ '--release',
+ '--flavor',
+ 'arm64',
+ '-P',
+ 'disable-abi-filtering=true',
+ ], workingDirectory: projectDir.path);
+
+ expect(
+ _checkLibIsInApk(projectDir, 'lib/arm64-v8a/libflutter.so', productFlavor: 'arm64'),
+ true,
+ );
+ expect(
+ _checkLibIsInApk(projectDir, 'lib/x86_64/libflutter.so', productFlavor: 'arm64'),
+ false,
+ );
+ expect(
+ _checkLibIsInApk(projectDir, 'lib/armeabi-v7a/libflutter.so', productFlavor: 'arm64'),
+ false,
+ );
+ expect(_checkLibIsInApk(projectDir, 'lib/x86/libflutter.so', productFlavor: 'arm64'), false);
+ },
+ );
}
Directory createProjectWithThirdpartyLib(Directory workingDir) {
@@ -118,6 +180,7 @@
Directory appDir,
String filename, {
BuildMode buildMode = BuildMode.release,
+ String productFlavor = '',
}) {
final File localPropertiesFile = appDir.childDirectory('android').childFile('local.properties');
if (!localPropertiesFile.existsSync()) {
@@ -139,9 +202,15 @@
.childFile(Platform.isWindows ? 'apkanalyzer.bat' : 'apkanalyzer')
.path;
- final File apkFile = appDir
- .childDirectory('build/app/outputs/apk/${buildMode.cliName}')
- .childFile('app-${buildMode.cliName}.apk');
+ final apkName = (productFlavor.isEmpty)
+ ? 'app-${buildMode.cliName}.apk'
+ : 'app-$productFlavor-${buildMode.cliName}.apk';
+
+ final String apkDir = (productFlavor.isEmpty)
+ ? buildMode.cliName
+ : '$productFlavor/${buildMode.cliName}';
+
+ final File apkFile = appDir.childDirectory('build/app/outputs/apk/$apkDir').childFile(apkName);
if (!apkFile.existsSync()) {
throw StateError('APK file not found at ${apkFile.path}');