diff --git a/packages/flutter_tools/gradle/src/main/kotlin/AgpCommonExtensionWrapper.kt b/packages/flutter_tools/gradle/src/main/kotlin/AgpCommonExtensionWrapper.kt index ab0b63c276b67..53b97e1937d75 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/AgpCommonExtensionWrapper.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/AgpCommonExtensionWrapper.kt @@ -7,6 +7,7 @@ package com.flutter.gradle import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.dsl.BuildType import com.android.build.api.dsl.DynamicFeatureExtension +import com.android.build.api.dsl.ExternalNativeBuild import com.android.build.api.dsl.LibraryExtension import com.android.build.api.dsl.Splits import com.android.build.api.dsl.TestExtension @@ -107,6 +108,16 @@ class AgpCommonExtensionWrapper( else -> throw IllegalArgumentException(unsupportedMessage()) } + val externalNativeBuild: ExternalNativeBuild + get() = + when (backingExtension) { + is ApplicationExtension -> backingExtension.externalNativeBuild + is LibraryExtension -> backingExtension.externalNativeBuild + is DynamicFeatureExtension -> backingExtension.externalNativeBuild + is TestExtension -> backingExtension.externalNativeBuild + else -> throw IllegalArgumentException(unsupportedMessage()) + } + val splits: Splits get() = when (backingExtension) { diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt index 6fed48d0e7244..9a7212815bdd6 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt @@ -11,7 +11,6 @@ import com.android.build.api.dsl.ApplicationExtension import com.android.build.api.dsl.DynamicFeatureBuildType import com.android.build.api.dsl.LibraryExtension import com.android.build.api.variant.AndroidComponentsExtension -import com.android.build.gradle.BaseExtension import com.android.builder.model.BuildType import com.flutter.gradle.plugins.PluginHandler import com.flutter.gradle.tasks.DeepLinkJsonFromManifestTask @@ -536,22 +535,6 @@ object FlutterPluginUtils { return project.property(PROP_LOCAL_ENGINE_BUILD_MODE) == flutterBuildMode } - /** - * Returns BaseExtension for the project. Used for compatibility. - * - * From BaseExtension docs: - * "Don't use this extension directly Instead, use one of the following: - * ApplicationExtension, LibraryExtension, TestExtension, DynamicFeatureExtension" - * - * For ApplicationExtension use `getAndroidApplicationExtension`. - * For LibraryExtension use `getAndroidLibraryExtension`. - */ - internal fun getLegacyAndroidExtension(project: Project): BaseExtension { - // Common supertype of the android extension types. - // But maybe this should be https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/dsl/TestedExtension. - return project.extensions.findByType(BaseExtension::class.java)!! - } - internal fun getAndroidExtension(project: Project): AgpCommonExtensionWrapper { // Look up by name to completely avoid importing or resolving CommonExtension val androidExtension = @@ -835,11 +818,11 @@ object FlutterPluginUtils { } // If the project is already configuring a native build, we don't need to do anything. - val gradleProjectAndroidExtension = getLegacyAndroidExtension(gradleProject) + val gradleProjectAndroidExtension = getAndroidExtension(gradleProject) val externalNativeBuild = gradleProjectAndroidExtension.externalNativeBuild val forcingNotRequired: Boolean = - externalNativeBuild?.cmake?.path != null || - externalNativeBuild?.ndkBuild?.path != null + externalNativeBuild.cmake.path != null || + externalNativeBuild.ndkBuild.path != null if (forcingNotRequired) { return } @@ -963,10 +946,9 @@ object FlutterPluginUtils { gradleProject: Project, flutterSdkRootPath: String ) { - val gradleProjectAndroidExtension = getLegacyAndroidExtension(gradleProject) - gradleProjectAndroidExtension.externalNativeBuild.cmake.path( - "$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt" - ) + val gradleProjectAndroidExtension = getAndroidExtension(gradleProject) + gradleProjectAndroidExtension.externalNativeBuild.cmake.path = + File("$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") // 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 @@ -978,22 +960,22 @@ object FlutterPluginUtils { // 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( + gradleProjectAndroidExtension.externalNativeBuild.cmake.buildStagingDirectory = gradleProject.layout.buildDirectory .dir("../.cxx") .get() - .asFile.path - ) + .asFile // CMake will print warnings when you try to build an empty project. // These arguments silence the warnings - our project is intentionally // empty. gradleProjectAndroidExtension.buildTypes.forEach { buildType -> - buildType.externalNativeBuild.cmake.arguments( - "-Wno-dev", - "--no-warn-unused-cli", - "-DCMAKE_BUILD_TYPE=${buildType.name}" - ) + buildType.externalNativeBuild.cmake.arguments += + listOf( + "-Wno-dev", + "--no-warn-unused-cli", + "-DCMAKE_BUILD_TYPE=${buildType.name}" + ) } } diff --git a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt index 77c1a5cb88269..2e0feedfd1876 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/FlutterPluginUtilsTest.kt @@ -7,13 +7,12 @@ package com.flutter.gradle import com.android.build.api.AndroidPluginVersion import com.android.build.api.dsl.ApplicationBuildType import com.android.build.api.dsl.ApplicationExtension +import com.android.build.api.dsl.Cmake import com.android.build.api.dsl.LibraryBuildType +import com.android.build.api.dsl.NdkBuild import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.api.variant.Variant import com.android.build.api.variant.VariantBuilder -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.internal.dsl.CmakeOptions -import com.android.build.gradle.internal.dsl.DefaultConfig import com.android.builder.model.BuildType import com.flutter.gradle.FlutterPluginUtils.BUILT_IN_KOTLIN_DOCS import com.flutter.gradle.FlutterPluginUtils.BUILT_IN_KOTLIN_DOCS_FOR_APPS @@ -22,8 +21,8 @@ import com.flutter.gradle.FlutterPluginUtils.BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMI import com.flutter.gradle.FlutterPluginUtils.detectApplyingKotlinGradlePlugin import com.flutter.gradle.plugins.PluginHandler import com.flutter.gradle.tasks.PrintTask -import io.mockk.called import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkObject import io.mockk.slot @@ -1920,32 +1919,23 @@ class FlutterPluginUtilsTest { val fakeCmakeFile = tempDir.resolve("CMakeLists.txt").toFile() fakeCmakeFile.createNewFile() val project = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() + val mockCmake = mockk() + val mockNdkBuild = mockk() + val mockAndroidExtension = mockk() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .externalNativeBuild.cmake - } returns mockCmakeOptions - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .externalNativeBuild.ndkBuild - } returns mockNdkBuildOptions - every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild - every { mockCmakeOptions.path } returns fakeCmakeFile - every { mockNdkBuildOptions.path } returns null + every { mockCmake.path } returns fakeCmakeFile + every { mockNdkBuild.path } returns null FlutterPluginUtils.forceNdkDownload(project, "ignored") verify(exactly = 1) { - mockCmakeOptions.path + mockCmake.path } - verify(exactly = 0) { mockCmakeOptions.setPath(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test @@ -1955,38 +1945,30 @@ class FlutterPluginUtilsTest { val fakeAndroidMkFile = tempDir.resolve("Android.mk").toFile() fakeAndroidMkFile.createNewFile() val project = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() + val mockCmake = mockk() + val mockNdkBuild = mockk() + val mockAndroidExtension = mockk() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .externalNativeBuild.cmake - } returns mockCmakeOptions - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .externalNativeBuild.ndkBuild - } returns mockNdkBuildOptions - every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild - every { mockCmakeOptions.path } returns null - every { mockNdkBuildOptions.path } returns fakeAndroidMkFile + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns fakeAndroidMkFile FlutterPluginUtils.forceNdkDownload(project, "ignored") verify(exactly = 1) { - mockCmakeOptions.path + mockCmake.path } verify(exactly = 1) { - mockNdkBuildOptions.path + mockNdkBuild.path } - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify(exactly = 0) { mockCmakeOptions.buildStagingDirectory(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } + verify(exactly = 0) { mockCmake.buildStagingDirectory = any() } } + @Test fun `forceNdkDownload installs a missing ndk when tool properties are provided`( @TempDir tempDir: Path ) { @@ -1996,25 +1978,21 @@ class FlutterPluginUtilsTest { val mockExecSpec = mockk() val mockExecResult = mockk() val mockExecOperations = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } returns null + val mockCmake = mockk() + val mockNdkBuild = mockk() + val mockAndroidExtension = mockk() + every { project.extensions.findByType(ApplicationExtension::class.java) } returns null + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild + every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns tempDir.toString() every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "" every { project.gradle.startParameter.taskNames } returns emptyList() every { project.gradle.startParameter.isOffline } returns false - val mockAndroidExtension = mockk() - every { project.extensions.findByName("android") } returns mockAndroidExtension - every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" every { project.serviceOf() } returns mockExecOperations every { mockExecOperations.exec(capture(execActionSlot)) } answers { File(tempDir.toFile(), "ndk/29.0.13846066/source.properties").apply { @@ -2041,38 +2019,32 @@ class FlutterPluginUtilsTest { ) ) } - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test fun `forceNdkDownload skips sdkmanager install when the requested ndk is already installed`() { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } returns null + val mockCmake = mockk() + val mockNdkBuild = mockk() + val mockAndroidExtension = mockk() + every { project.extensions.findByType(ApplicationExtension::class.java) } returns null + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild + every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns "/sdk/root" every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "29.0.13846066" every { project.gradle.startParameter.taskNames } returns emptyList() - val mockAndroidExtension = mockk() - every { project.extensions.findByName("android") } returns mockAndroidExtension - every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" FlutterPluginUtils.forceNdkDownload(project, "/base/path") finalizeDslSlot.captured.invoke(Any()) - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test @@ -2081,48 +2053,28 @@ class FlutterPluginUtilsTest { ) { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockDirectoryProperty = mockk() - val mockDirectory = mockk() - val mockBaseExtension = mockk() + val mockCmake = mockk() + val mockNdkBuild = mockk() + val mockAndroidExtension = mockk() var cmakePath: File? = null every { project.extensions.findByType(ApplicationExtension::class.java) } returns null - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } answers { cmakePath } - every { mockCmakeOptions.path(any()) } returns Unit - every { mockCmakeOptions.buildStagingDirectory(any()) } returns Unit + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild + every { mockNdkBuild.path } returns null + every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" + every { mockCmake.path } answers { cmakePath } every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns null every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns "/sdk/root" every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "" every { project.gradle.startParameter.taskNames } returns emptyList() - every { project.layout.buildDirectory } returns mockDirectoryProperty - every { mockDirectoryProperty.dir(any()) } returns mockDirectoryProperty - every { mockDirectoryProperty.get() } returns mockDirectory - every { mockDirectory.asFile.path } returns "/randomapp/build/app/" - - val mockBuildType = mockk() - every { mockBaseExtension.buildTypes.iterator() } returns mutableListOf(mockBuildType).iterator() - every { mockBuildType.name } returns "Debug" - every { mockBuildType.externalNativeBuild.cmake.arguments(any(), any(), any()) } returns Unit FlutterPluginUtils.forceNdkDownload(project, "/base/path") cmakePath = tempDir.resolve("CMakeLists.txt").toFile() finalizeDslSlot.captured.invoke(Any()) - verify(exactly = 0) { - mockCmakeOptions.path( - "/base/path/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt" - ) - } - verify(exactly = 0) { mockCmakeOptions.buildStagingDirectory(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } + verify(exactly = 0) { mockCmake.buildStagingDirectory = any() } } @Test @@ -2135,91 +2087,17 @@ class FlutterPluginUtilsTest { val mockExecSpec = mockk() val mockExecResult = mockk() val mockExecOperations = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - var configuredNdkVersion = "26.3.11579264" - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } answers { configuredNdkVersion } - every { mockCmakeOptions.path } returns null - every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" - every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns tempDir.toString() - every { - project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) - } returns "26.3.11579264" - every { project.gradle.startParameter.taskNames } returns emptyList() - every { project.gradle.startParameter.isOffline } returns false + val mockCmake = mockk() + val mockNdkBuild = mockk() val mockAndroidExtension = mockk() + var configuredNdkVersion = "26.3.11579264" + every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild every { mockAndroidExtension.ndkVersion } answers { configuredNdkVersion } - every { project.serviceOf() } returns mockExecOperations - every { mockExecOperations.exec(capture(execActionSlot)) } answers { - File(tempDir.toFile(), "ndk/27.3.13750724/source.properties").apply { - parentFile.mkdirs() - createNewFile() - } - mockExecResult - } - every { mockExecResult.assertNormalExitValue() } returns mockExecResult - every { mockExecSpec.commandLine(any>()) } returns mockExecSpec - - FlutterPluginUtils.forceNdkDownload(project, "/base/path") - configuredNdkVersion = "27.3.13750724" - finalizeDslSlot.captured.invoke(Any()) - execActionSlot.captured.execute(mockExecSpec) - - verify(exactly = 1) { mockExecOperations.exec(any>()) } - verify { - mockExecSpec.commandLine( - listOf( - "/sdkmanager", - "--sdk_root=$tempDir", - "--install", - "ndk;27.3.13750724" - ) - ) - } - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } - } - - @Test - fun `forceNdkDownload waits for finalized ApplicationExtension ndkVersion before checking installed versions`( - @TempDir tempDir: Path - ) { - val project = mockk() - val finalizeDslSlot = captureFinalizeDslAction(project) - val execActionSlot = slot>() - val mockExecSpec = mockk() - val mockExecResult = mockk() - val mockExecOperations = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - val mockApplicationExtension = mockk() - var configuredNdkVersion = "26.3.11579264" - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { - project.extensions.findByType(ApplicationExtension::class.java) - } returns mockApplicationExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } answers { - throw AssertionError( - "legacy ndkVersion should not be read when ApplicationExtension is available" - ) - } - every { mockApplicationExtension.ndkVersion } answers { configuredNdkVersion } - every { project.extensions.findByName("android") } returns mockApplicationExtension - every { mockCmakeOptions.path } returns null + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns tempDir.toString() every { @@ -2254,63 +2132,24 @@ class FlutterPluginUtilsTest { ) ) } - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test fun `forceNdkDownload skips fallback when sdkmanager is unavailable but the requested ndk is already installed`() { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - every { project.extensions.findByType(ApplicationExtension::class.java) } returns null - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } returns null - every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns null - every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns "/sdk/root" - every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "29.0.13846066" - every { project.gradle.startParameter.taskNames } returns emptyList() + val mockCmake = mockk() + val mockNdkBuild = mockk() val mockAndroidExtension = mockk() + every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" - - FlutterPluginUtils.forceNdkDownload(project, "/base/path") - finalizeDslSlot.captured.invoke(Any()) - - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } - } - - @Test - fun `forceNdkDownload reads ndkVersion from ApplicationExtension when legacy extension does not expose it`() { - val project = mockk() - val finalizeDslSlot = captureFinalizeDslAction(project) - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - val mockApplicationExtension = mockk() - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { project.extensions.findByType(ApplicationExtension::class.java) } returns mockApplicationExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } answers { - throw AssertionError("legacy ndkVersion should not be read when ApplicationExtension is available") - } - every { mockApplicationExtension.ndkVersion } returns "29.0.13846066" - every { project.extensions.findByName("android") } returns mockApplicationExtension - every { mockCmakeOptions.path } returns null - every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null + every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns null every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns "/sdk/root" every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "29.0.13846066" every { project.gradle.startParameter.taskNames } returns emptyList() @@ -2318,8 +2157,7 @@ class FlutterPluginUtilsTest { FlutterPluginUtils.forceNdkDownload(project, "/base/path") finalizeDslSlot.captured.invoke(Any()) - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test @@ -2330,25 +2168,21 @@ class FlutterPluginUtilsTest { val finalizeDslSlot = captureFinalizeDslAction(project) val mockExecResult = mockk() val mockExecOperations = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } returns null + val mockCmake = mockk() + val mockNdkBuild = mockk() + val mockAndroidExtension = mockk() + every { project.extensions.findByType(ApplicationExtension::class.java) } returns null + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild + every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns tempDir.toString() every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "" every { project.gradle.startParameter.taskNames } returns emptyList() every { project.gradle.startParameter.isOffline } returns false - val mockAndroidExtension = mockk() - every { project.extensions.findByName("android") } returns mockAndroidExtension - every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" every { project.serviceOf() } returns mockExecOperations every { mockExecOperations.exec(any>()) } returns mockExecResult every { mockExecResult.assertNormalExitValue() } returns mockExecResult @@ -2359,20 +2193,14 @@ class FlutterPluginUtilsTest { finalizeDslSlot.captured.invoke(Any()) } - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test fun `forceNdkDownload skips when invoking the ndk metadata task`() { val project = mockk() - val mockCmakeOptions = mockk() - val mockDefaultConfig = mockk() - val mockBaseExtension = mockk() - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockCmakeOptions.path } returns null + val mockCmake = mockk() + val mockNdkBuild = mockk() every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns null every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns null every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns null @@ -2381,30 +2209,28 @@ class FlutterPluginUtilsTest { FlutterPluginUtils.forceNdkDownload(project, "/base/path") - verify(exactly = 0) { mockCmakeOptions.path(any()) } - verify { mockDefaultConfig wasNot called } + verify(exactly = 0) { mockCmake.path = any() } } @Test fun `forceNdkDownload falls back when tool properties are present but sdkmanager is unavailable`() { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() + val mockCmake = mockk() + val mockNdkBuild = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() - val mockBaseExtension = mockk() + val mockAndroidExtension = mockk() + val cmakeArguments = mutableListOf() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } returns null - every { mockCmakeOptions.path(any()) } returns Unit - every { mockCmakeOptions.buildStagingDirectory(any()) } returns Unit + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild + every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null + justRun { mockCmake.path = any() } + justRun { mockCmake.buildStagingDirectory = any() } every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns null every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns "/sdk/root" every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "" @@ -2412,50 +2238,47 @@ class FlutterPluginUtilsTest { every { project.layout.buildDirectory } returns mockDirectoryProperty every { mockDirectoryProperty.dir(any()) } returns mockDirectoryProperty every { mockDirectoryProperty.get() } returns mockDirectory - every { mockDirectory.asFile.path } returns "/randomapp/build/app/" + every { mockDirectory.asFile } returns File("/randomapp/build/app/") val basePath = "/base/path" - val mockBuildType = mockk() - every { mockBaseExtension.buildTypes.iterator() } returns mutableListOf(mockBuildType).iterator() + val mockBuildType = mockk() + every { mockAndroidExtension.buildTypes.iterator() } returns + mutableListOf(mockBuildType).iterator() every { mockBuildType.name } returns "Debug" - every { mockBuildType.externalNativeBuild.cmake.arguments(any(), any(), any()) } returns Unit + every { mockBuildType.externalNativeBuild.cmake.arguments } returns cmakeArguments FlutterPluginUtils.forceNdkDownload(project, basePath) finalizeDslSlot.captured.invoke(Any()) verify(exactly = 1) { - mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") - } - verify(exactly = 1) { mockCmakeOptions.buildStagingDirectory(any()) } - verify(exactly = 1) { - mockBuildType.externalNativeBuild.cmake.arguments( - "-Wno-dev", - "--no-warn-unused-cli", - "-DCMAKE_BUILD_TYPE=Debug" - ) + mockCmake.path = File("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") } + verify(exactly = 1) { mockCmake.buildStagingDirectory = any() } + assertEquals( + listOf("-Wno-dev", "--no-warn-unused-cli", "-DCMAKE_BUILD_TYPE=Debug"), + cmakeArguments + ) } @Test fun `forceNdkDownload falls back when Gradle is offline`() { val project = mockk() val finalizeDslSlot = captureFinalizeDslAction(project) - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() + val mockCmake = mockk() + val mockNdkBuild = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() - val mockBaseExtension = mockk() + val mockAndroidExtension = mockk() + val cmakeArguments = mutableListOf() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null - every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension - every { mockBaseExtension.externalNativeBuild.cmake } returns mockCmakeOptions - every { mockBaseExtension.externalNativeBuild.ndkBuild } returns mockNdkBuildOptions - every { mockNdkBuildOptions.path } returns null - every { mockBaseExtension.defaultConfig } returns mockDefaultConfig - every { mockBaseExtension.ndkVersion } returns "29.0.13846066" - every { mockCmakeOptions.path } returns null - every { mockCmakeOptions.path(any()) } returns Unit - every { mockCmakeOptions.buildStagingDirectory(any()) } returns Unit + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild + every { mockAndroidExtension.ndkVersion } returns "29.0.13846066" + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null + justRun { mockCmake.path = any() } + justRun { mockCmake.buildStagingDirectory = any() } every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns "/sdkmanager" every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns "/sdk/root" every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns "" @@ -2464,90 +2287,73 @@ class FlutterPluginUtilsTest { every { project.layout.buildDirectory } returns mockDirectoryProperty every { mockDirectoryProperty.dir(any()) } returns mockDirectoryProperty every { mockDirectoryProperty.get() } returns mockDirectory - every { mockDirectory.asFile.path } returns "/randomapp/build/app/" + every { mockDirectory.asFile } returns File("/randomapp/build/app/") val basePath = "/base/path" - val mockBuildType = mockk() - every { mockBaseExtension.buildTypes.iterator() } returns mutableListOf(mockBuildType).iterator() + val mockBuildType = mockk() + every { mockAndroidExtension.buildTypes.iterator() } returns + mutableListOf(mockBuildType).iterator() every { mockBuildType.name } returns "Debug" - every { mockBuildType.externalNativeBuild.cmake.arguments(any(), any(), any()) } returns Unit + every { mockBuildType.externalNativeBuild.cmake.arguments } returns cmakeArguments FlutterPluginUtils.forceNdkDownload(project, basePath) finalizeDslSlot.captured.invoke(Any()) verify(exactly = 1) { - mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") - } - verify(exactly = 1) { mockCmakeOptions.buildStagingDirectory(any()) } - verify(exactly = 1) { - mockBuildType.externalNativeBuild.cmake.arguments( - "-Wno-dev", - "--no-warn-unused-cli", - "-DCMAKE_BUILD_TYPE=Debug" - ) + mockCmake.path = File("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") } + verify(exactly = 1) { mockCmake.buildStagingDirectory = any() } + assertEquals( + listOf("-Wno-dev", "--no-warn-unused-cli", "-DCMAKE_BUILD_TYPE=Debug"), + cmakeArguments + ) } @Test fun `forceNdkDownload sets externalNativeBuild properties`() { val project = mockk() - val mockCmakeOptions = mockk() - val mockNdkBuildOptions = mockk() - val mockDefaultConfig = mockk() + val mockCmake = mockk() + val mockNdkBuild = mockk() val mockDirectoryProperty = mockk() val mockDirectory = mockk() + val mockAndroidExtension = mockk() + val cmakeArguments = mutableListOf() every { project.extensions.findByType(ApplicationExtension::class.java) } returns null every { project.findProperty(FlutterPluginUtils.PROP_SDK_MANAGER_PATH) } returns null every { project.findProperty(FlutterPluginUtils.PROP_ANDROID_SDK_ROOT) } returns null every { project.findProperty(FlutterPluginUtils.PROP_INSTALLED_NDK_VERSIONS) } returns null - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .externalNativeBuild.cmake - } returns mockCmakeOptions - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .externalNativeBuild.ndkBuild - } returns mockNdkBuildOptions - every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig + every { project.extensions.findByName("android") } returns mockAndroidExtension + every { mockAndroidExtension.externalNativeBuild.cmake } returns mockCmake + every { mockAndroidExtension.externalNativeBuild.ndkBuild } returns mockNdkBuild val basePath = "/base/path" val fakeBuildPath = "/randomapp/build/app/" - every { mockCmakeOptions.path } returns null - every { mockNdkBuildOptions.path } returns null - every { mockCmakeOptions.path(any()) } returns Unit - every { mockCmakeOptions.buildStagingDirectory(any()) } returns Unit + every { mockCmake.path } returns null + every { mockNdkBuild.path } returns null + justRun { mockCmake.path = any() } + justRun { mockCmake.buildStagingDirectory = any() } every { project.layout.buildDirectory } returns mockDirectoryProperty every { mockDirectoryProperty.dir(any()) } returns mockDirectoryProperty every { mockDirectoryProperty.get() } returns mockDirectory - val realFile = File(fakeBuildPath) - every { mockDirectory.asFile } returns realFile + every { mockDirectory.asFile } returns File(fakeBuildPath) - val mockBuildType = mockk() - every { - project.extensions - .findByType(BaseExtension::class.java)!! - .buildTypes - .iterator() - } returns mutableListOf(mockBuildType).iterator() + val mockBuildType = mockk() + every { mockAndroidExtension.buildTypes.iterator() } returns + mutableListOf(mockBuildType).iterator() every { mockBuildType.name } returns "Debug" - every { mockBuildType.externalNativeBuild.cmake.arguments(any(), any(), any()) } returns Unit + every { mockBuildType.externalNativeBuild.cmake.arguments } returns cmakeArguments FlutterPluginUtils.forceNdkDownload(project, basePath) verify(exactly = 1) { - mockCmakeOptions.path - } - verify(exactly = 1) { mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") } - verify(exactly = 1) { mockCmakeOptions.buildStagingDirectory(any()) } - verify(exactly = 1) { - mockBuildType.externalNativeBuild.cmake.arguments( - "-Wno-dev", - "--no-warn-unused-cli", - "-DCMAKE_BUILD_TYPE=Debug" - ) + mockCmake.path } + verify(exactly = 1) { mockCmake.path = File("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") } + verify(exactly = 1) { mockCmake.buildStagingDirectory = any() } + assertEquals( + listOf("-Wno-dev", "--no-warn-unused-cli", "-DCMAKE_BUILD_TYPE=Debug"), + cmakeArguments + ) } @Test