[CP-stable][tool] Don't require a Flutter compile task when staging jniLibs (#189075)
This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request)
Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request.
### Issue Link:
https://github.com/flutter/flutter/issues/189049
https://github.com/flutter/flutter/issues/188785
### Impact Description:
Fixes a compile time crash of [android instrumented tests](https://developer.android.com/training/testing/instrumented-tests) (e.g. `assembleDebugAndroidTest`, which is relied upon by our packages tests and some ecosystem members such as patrol).
### Changelog Description:
Fixes a breakage of [android instrumented tests](https://developer.android.com/training/testing/instrumented-tests) (e.g. `assembleDebugAndroidTest`, which is relied upon by our packages tests and some ecosystem members such as patrol).
< Replace with changelog description here >
[flutter/188805] Fixes a crash when running android instrumented tests.
### Workaround:
No.
### 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?
Explained in detail in https://github.com/flutter/flutter/issues/188785
but in short, go to your flutter/packages checkout, cd to the camera example app and run
```
flutter build apk --debug --config-only
cd android
./gradlew app:assembleAndroidTest -Pverbose=true
```
diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
index df6374b..35edcec 100644
--- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
+++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
@@ -313,22 +313,28 @@
val targetPlatformsList = targetPlatforms
androidComponents.onVariants { variant ->
val capitalizeVariantName = FlutterPluginUtils.capitalize(variant.name)
- // Reference the Flutter compile task by name rather than by provider: this variant API
- // callback runs before the legacy `applicationVariants` callback in addFlutterDeps that
- // registers the task, so its provider does not exist yet here.
val compileTaskName = flutterCompileTaskName(variant.name)
val copyJniLibsTaskProvider: TaskProvider<CopyFlutterJniLibsTask> =
projectToAddTasksTo.tasks.register(
"copyJniLibs${FLUTTER_BUILD_PREFIX}$capitalizeVariantName",
CopyFlutterJniLibsTask::class.java
) {
- dependsOn(compileTaskName)
- val compileTaskProvider = projectToAddTasksTo.tasks.named(compileTaskName, FlutterTask::class.java)
- val outputDirProvider =
- compileTaskProvider.flatMap { task ->
- projectToAddTasksTo.layout.dir(projectToAddTasksTo.provider { task.outputDirectory!! })
- }
- intermediateDir.set(outputDirProvider)
+ // The Flutter compile task is registered later (in the legacy
+ // `applicationVariants` callback in addFlutterDeps) and only for variants that
+ // are actually built as a Flutter app. It is absent for e.g. an
+ // `assembleAndroidTest` build, where `shouldConfigureFlutterTask` returns false.
+ // Look it up tolerantly (findByName, not named) so this task degrades to a no-op
+ // with empty output instead of failing to be created when there is no Flutter
+ // build for the variant. See https://github.com/flutter/flutter/issues/188785.
+ dependsOn(projectToAddTasksTo.tasks.matching { it.name == compileTaskName })
+ intermediateDir.set(
+ projectToAddTasksTo.layout.dir(
+ projectToAddTasksTo.provider {
+ val compileTask = projectToAddTasksTo.tasks.findByName(compileTaskName) as? FlutterTask
+ compileTask?.outputDirectory
+ }
+ )
+ )
this.targetPlatforms.set(targetPlatformsList)
}
variant.sources.jniLibs?.addGeneratedSourceDirectory(
diff --git a/packages/flutter_tools/gradle/src/main/kotlin/tasks/CopyFlutterJniLibsTask.kt b/packages/flutter_tools/gradle/src/main/kotlin/tasks/CopyFlutterJniLibsTask.kt
index 4741fdf..6950e82 100644
--- a/packages/flutter_tools/gradle/src/main/kotlin/tasks/CopyFlutterJniLibsTask.kt
+++ b/packages/flutter_tools/gradle/src/main/kotlin/tasks/CopyFlutterJniLibsTask.kt
@@ -11,6 +11,7 @@
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
@@ -30,7 +31,14 @@
* https://github.com/flutter/flutter/issues/187388.
*/
abstract class CopyFlutterJniLibsTask : DefaultTask() {
- /** The Flutter build output directory (the `flutter assemble` `--output` location). */
+ /**
+ * The Flutter build output directory (the `flutter assemble` `--output` location).
+ *
+ * Optional: it is absent when there is no Flutter compile task for the variant (e.g. an
+ * `assembleAndroidTest` build), in which case this task stages nothing. See
+ * https://github.com/flutter/flutter/issues/188785.
+ */
+ @get:Optional
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val intermediateDir: DirectoryProperty
@@ -48,17 +56,21 @@
fun copy() {
fileSystemOperations.sync {
into(destinationDir)
- 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")
+ // When there is no Flutter build for this variant (e.g. an assembleAndroidTest build),
+ // there is nothing to stage; the empty sync simply clears destinationDir.
+ if (intermediateDir.isPresent) {
+ 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")
+ }
}
}
}
diff --git a/packages/flutter_tools/test/integration.shard/gradle_libapp_so_packaging_test.dart b/packages/flutter_tools/test/integration.shard/gradle_libapp_so_packaging_test.dart
index f76be44..d8119e7 100644
--- a/packages/flutter_tools/test/integration.shard/gradle_libapp_so_packaging_test.dart
+++ b/packages/flutter_tools/test/integration.shard/gradle_libapp_so_packaging_test.dart
@@ -254,6 +254,45 @@
expect(libappContent.contains('probe-BBBB'), isTrue);
expect(libappContent.contains('probe-AAAA'), isFalse);
});
+
+ // Regression test for https://github.com/flutter/flutter/issues/188785.
+ //
+ // Building the androidTest variant directly (as flutter/packages' Firebase Test
+ // Lab tooling does) configures Gradle without a Flutter compile task: the single
+ // `assembleAndroidTest` CLI task makes `shouldConfigureFlutterTask` return false,
+ // so `compileFlutterBuild<Variant>` is never registered. The jniLibs copy task
+ // wired via the variant API must tolerate that (absent) compile task rather than
+ // failing with "Task with name 'compileFlutterBuildDebug' not found".
+ testWithoutContext(
+ 'app:assembleAndroidTest builds when no Flutter compile task is configured',
+ () async {
+ final Directory appDir = _createApp(tempDir);
+
+ // Configure Gradle without running the Flutter build (no `flutter assemble`).
+ final ProcessResult configResult = processManager.runSync(<String>[
+ flutterBin,
+ 'build',
+ 'apk',
+ '--debug',
+ '--config-only',
+ ], workingDirectory: appDir.path);
+ expect(configResult, const ProcessResultMatcher());
+
+ final Directory androidDir = appDir.childDirectory('android');
+ final File gradlew = androidDir.childFile(Platform.isWindows ? 'gradlew.bat' : 'gradlew');
+ final ProcessResult assembleResult = processManager.runSync(<String>[
+ gradlew.path,
+ 'app:assembleAndroidTest',
+ '-Pverbose=true',
+ ], workingDirectory: androidDir.path);
+ expect(
+ assembleResult.exitCode,
+ 0,
+ reason:
+ 'gradlew app:assembleAndroidTest failed:\n${assembleResult.stdout}\n${assembleResult.stderr}',
+ );
+ },
+ );
}
Directory _createApp(Directory workingDir) {