From 376b08d2616a1f3f8eddd1b9e304d1c2dbfca256 Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 10:42:18 -0700 Subject: [PATCH 1/7] move the dir --- .../gradle/src/main/kotlin/FlutterPluginUtils.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index dfef3a72f457a..21d55ecaac6f5 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -610,6 +610,15 @@ object FlutterPluginUtils { "$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt" ) + // AGP defaults to outputting build artifacts in `android/app/.cxx`. Move these build + // artifacts out of the source directories and to Flutter's build directory. + gradleProjectAndroidExtension.externalNativeBuild.cmake.buildStagingDirectory( + gradleProject.layout.buildDirectory + .dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/.cxx") + .get() + .asFile.path + ) + // CMake will print warnings when you try to build an empty project. // These arguments silence the warnings - our project is intentionally // empty. From 8cea0ad2d1fa27dba1e6597b3d469e88e12cf7b4 Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 11:18:47 -0700 Subject: [PATCH 2/7] write test --- .../bin/tasks/gradle_plugin_fat_apk_test.dart | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart index 50b3be1c3e8ff..f8599885df798 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @@ -152,6 +152,30 @@ Future main() async { throw TaskResult.failure("Shared library doesn't exist"); } } + + section('AGP cxx build artifacts'); + + final String defaultPath = path.join( + project.rootPath, + 'android', + 'app', + '.cxx', + ); + + final String modifiedPath = path.join( + project.rootPath, + 'build', + 'app', + 'intermediates', + 'flutter', + '.cxx', + ); + if (Directory(defaultPath).existsSync()) { + throw TaskResult.failure('Producing unexpected build artifacts in $defaultPath'); + } + if (!Directory(modifiedPath).existsSync()) { + throw TaskResult.failure('Not producing expected build artifacts'); + } }); return TaskResult.success(null); From b0a85f7c1cb4a26eff0967521225ca1b727a5054 Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 11:36:26 -0700 Subject: [PATCH 3/7] format --- dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart index f8599885df798..918d3eea7216c 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @@ -155,12 +155,7 @@ Future main() async { section('AGP cxx build artifacts'); - final String defaultPath = path.join( - project.rootPath, - 'android', - 'app', - '.cxx', - ); + final String defaultPath = path.join(project.rootPath, 'android', 'app', '.cxx'); final String modifiedPath = path.join( project.rootPath, From f9ae11dcb1e4c8e1e774cbb58d16c4a30b9d3e24 Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 12:43:31 -0700 Subject: [PATCH 4/7] change message to provide the path, and to be more specific about what is missing --- dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart index 918d3eea7216c..bccbd22c4e4a5 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @@ -169,7 +169,7 @@ Future main() async { throw TaskResult.failure('Producing unexpected build artifacts in $defaultPath'); } if (!Directory(modifiedPath).existsSync()) { - throw TaskResult.failure('Not producing expected build artifacts'); + throw TaskResult.failure('Not producing external native build output directory in $modifiedPath'); } }); From e58e06200a8e77e40223d65dc4dca01833c7f1db Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 12:43:40 -0700 Subject: [PATCH 5/7] format --- dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart index bccbd22c4e4a5..b0cf6dd7e9c6c 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @@ -169,7 +169,9 @@ Future main() async { throw TaskResult.failure('Producing unexpected build artifacts in $defaultPath'); } if (!Directory(modifiedPath).existsSync()) { - throw TaskResult.failure('Not producing external native build output directory in $modifiedPath'); + throw TaskResult.failure( + 'Not producing external native build output directory in $modifiedPath', + ); } }); From 81082b83fd140197234af84dd81a650f8c82ecf8 Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 12:56:59 -0700 Subject: [PATCH 6/7] fix mocking for forcendkdownload test --- .../src/test/kotlin/FlutterPluginUtilsTest.kt | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index d12b9080d6a0e..38c655a3c89b9 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -22,6 +22,8 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.UnknownTaskException import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.Logger import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider @@ -845,6 +847,8 @@ class FlutterPluginUtilsTest { val project = mockk() val mockCmakeOptions = mockk() val mockDefaultConfig = mockk() + val mockDirectoryProperty = mockk() + val mockDirectory = mockk() every { project.extensions .findByType(BaseExtension::class.java)!! @@ -852,17 +856,24 @@ class FlutterPluginUtilsTest { } returns mockCmakeOptions every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig + val basePath = "/base/path" + val fakeBuildPath = "/randomapp/build/app/" every { mockCmakeOptions.path } returns null every { mockCmakeOptions.path(any()) } returns Unit every { mockDefaultConfig.externalNativeBuild.cmake.arguments(any(), any()) } returns Unit + every { mockCmakeOptions.buildStagingDirectory(any()) } returns Unit + every { project.layout.buildDirectory } returns mockDirectoryProperty + every { mockDirectoryProperty.dir(any()) } returns mockDirectoryProperty + every { mockDirectoryProperty.get() } returns mockDirectory + every { mockDirectory.asFile.path } returns fakeBuildPath - val basePath = "/base/path" FlutterPluginUtils.forceNdkDownload(project, basePath) verify(exactly = 1) { mockCmakeOptions.path } verify(exactly = 1) { mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt") } + verify(exactly = 1) { mockCmakeOptions.buildStagingDirectory(any()) } verify(exactly = 1) { mockDefaultConfig.externalNativeBuild.cmake.arguments( "-Wno-dev", @@ -1276,11 +1287,18 @@ class FlutterPluginUtilsTest { every { tasks } returns mockk { val registerTaskNameSlot = slot() - every { register(capture(registerTaskNameSlot), capture(registerTaskSlot)) } answers registerAnswer@{ + every { + register( + capture(registerTaskNameSlot), + capture(registerTaskSlot) + ) + } answers registerAnswer@{ val mockRegisterTask = mockk { every { name } returns registerTaskNameSlot.captured - every { description = capture(descriptionSlot) } returns Unit + every { + description = capture(descriptionSlot) + } returns Unit every { dependsOn(any()) } returns mockk() val doLastActionSlot = slot>() every { doLast(capture(doLastActionSlot)) } answers doLastAnswer@{ @@ -1302,7 +1320,8 @@ class FlutterPluginUtilsTest { } variants.forEach { variant -> - val testOutputs: DomainObjectCollection = mockk>() + val testOutputs: DomainObjectCollection = + mockk>() val baseVariantSlot = slot>() val baseVariantOutput = mockk() // Create a real file in a temp directory. @@ -1313,7 +1332,9 @@ class FlutterPluginUtilsTest { manifest.writeText(manifestText) val mockProcessResourcesProvider = mockk>() val mockProcessResources = mockk() - every { mockProcessResourcesProvider.hint(ProcessAndroidResources::class).get() } returns mockProcessResources + every { + mockProcessResourcesProvider.hint(ProcessAndroidResources::class).get() + } returns mockProcessResources every { baseVariantOutput.processResourcesProvider } returns mockProcessResourcesProvider // Fallback processing. every { mockProcessResources.manifestFile } returns manifest @@ -1336,7 +1357,10 @@ class FlutterPluginUtilsTest { assert(descriptionSlot.captured.contains("stores app links settings for the given build variant")) assertEquals(variants.size, registerTaskList.size) for (i in 0 until variants.size) { - assertEquals("output${FlutterPluginUtils.capitalize(variants[i].name)}AppLinkSettings", registerTaskList[i].name) + assertEquals( + "output${FlutterPluginUtils.capitalize(variants[i].name)}AppLinkSettings", + registerTaskList[i].name + ) verify(exactly = 1) { registerTaskList[i].dependsOn(any()) } } // Output assertions are minimal which ensures code is running but is not exhaustive testing. From 7d78480ec9fdb55bc757a5bfc7eb9896203f98df Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Mon, 31 Mar 2025 14:43:39 -0700 Subject: [PATCH 7/7] expand comment --- .../gradle/src/main/kotlin/FlutterPluginUtils.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index 21d55ecaac6f5..bf59732f68652 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -610,8 +610,16 @@ object FlutterPluginUtils { "$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt" ) - // AGP defaults to outputting build artifacts in `android/app/.cxx`. Move these build - // artifacts out of the source directories and to Flutter's build directory. + // AGP defaults to outputting build artifacts in `android/app/.cxx`. This directory is a + // build artifact, so we move it from that directory to within Flutter's build directory + // to avoid polluting source directories with build artifacts. + // + // AGP explicitely recommends not setting the buildStagingDirectory to be within a build + // directory in + // https://developer.android.com/reference/tools/gradle-api/8.3/null/com/android/build/api/dsl/Cmake#buildStagingDirectory(kotlin.Any), + // but as we are not actually building anything (and are instead only tricking AGP into + // downloading the NDK), it is acceptable for the buildStagingDirectory to be removed + // and rebuilt when running clean builds. gradleProjectAndroidExtension.externalNativeBuild.cmake.buildStagingDirectory( gradleProject.layout.buildDirectory .dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/.cxx")