| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package com.flutter.gradle |
| |
| import com.android.build.api.AndroidPluginVersion |
| import com.android.build.api.artifact.SingleArtifact |
| import com.android.build.api.dsl.ApplicationExtension |
| 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 |
| import com.flutter.gradle.tasks.PrintTask |
| import com.flutter.gradle.tasks.ValidateCompileSdkVersionTask |
| import groovy.lang.Closure |
| import org.gradle.api.GradleException |
| import org.gradle.api.Project |
| import org.gradle.api.Task |
| import org.gradle.api.UnknownTaskException |
| import org.gradle.api.logging.Logger |
| import org.gradle.kotlin.dsl.register |
| import org.gradle.kotlin.dsl.support.serviceOf |
| import org.gradle.process.ExecOperations |
| import java.io.File |
| import java.io.IOException |
| import java.nio.charset.StandardCharsets |
| import java.util.Properties |
| |
| /** |
| * A collection of static utility functions used by the Flutter Gradle Plugin. |
| */ |
| object FlutterPluginUtils { |
| // Gradle properties. These must correspond to the values used in |
| // flutter/packages/flutter_tools/lib/src/android/gradle.dart, and therefore it is not |
| // recommended to use these const values in tests. |
| internal const val PROP_SHOULD_SHRINK_RESOURCES = "shrink" |
| internal const val PROP_SPLIT_PER_ABI = "split-per-abi" |
| internal const val PROP_LOCAL_ENGINE_REPO = "local-engine-repo" |
| internal const val PROP_IS_VERBOSE = "verbose" |
| 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" |
| internal const val PROP_SDK_MANAGER_PATH = "flutter.sdkManagerPath" |
| internal const val PROP_ANDROID_SDK_ROOT = "flutter.androidSdkRoot" |
| internal const val PROP_INSTALLED_NDK_VERSIONS = "flutter.installedNdkVersions" |
| internal const val TASK_PRINT_NDK_VERSION = "printNdkVersion" |
| internal const val NDK_VERSION_OUTPUT_PREFIX = "NdkVersion: " |
| |
| private data class ToolNdkProvisioningProperties( |
| val androidSdkRoot: String, |
| val installedNdkVersions: Set<String>, |
| val sdkManagerPath: String? |
| ) |
| |
| internal const val PROP_FORCE_VERSION_CODE_IGNORING_ABI = "force-version-code-ignoring-abi" |
| |
| /** |
| * The URL for documentation for general information on migration to built-in Kotlin. |
| */ |
| internal const val BUILT_IN_KOTLIN_DOCS = |
| "https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin" |
| |
| /** |
| * The URL for documentation instructing app developers how to migrate their app to built-in Kotlin. |
| */ |
| internal const val BUILT_IN_KOTLIN_DOCS_FOR_APPS = |
| "https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-app-developers" |
| |
| /** |
| * The URL for documentation instructing plugin authors how to migrate to their plugin to built-in Kotlin. |
| */ |
| internal const val BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS = |
| "https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors" |
| |
| /** |
| * The URL for documentation instructing app developers on how to report incompatible KGP usage to plugin authors. |
| */ |
| internal const val BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS = |
| "https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-app-developers#report-incompatible-kotlin-gradle-plugin-usage-to-plugin-authors" |
| |
| /** |
| * Matches the AGP application plugin declaration in Kotlin DSL (`build.gradle.kts`). |
| * Targets `id("com.android.application")` or `alias(libs.plugins.android.application)` |
| * within a `plugins { ... }` block. |
| */ |
| internal val appPluginRegexKotlin = |
| """(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)[ \t]*\(\s*(['"]com\.android\.application['"]|libs\.plugins\.android\.application)\s*\)(?=[ \t]*(\n|${'$'}|\}))""" |
| .toRegex() |
| |
| /** |
| * Matches the AGP library plugin declaration in Kotlin DSL (`build.gradle.kts`). |
| * Targets `id("com.android.library")` or `alias(libs.plugins.android.library)` |
| * within a `plugins { ... }` block. |
| */ |
| internal val libPluginRegexKotlin = |
| """(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)[ \t]*\(\s*(['"]com\.android\.library['"]|libs\.plugins\.android\.library)\s*\)(?=[ \t]*(\n|${'$'}|\}))""" |
| .toRegex() |
| |
| /** |
| * Matches the KGP declaration in Kotlin DSL (`build.gradle.kts`). |
| * Targets `kotlin-android`, `org.jetbrains.kotlin.android`, or version catalog aliases |
| * for Kotlin Android within a `plugins { ... }` block. |
| */ |
| internal val kgpRegexKotlin = |
| """(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)[ \t]*\(\s*(['"](?:kotlin-android|org\.jetbrains\.kotlin\.android)['"]|libs\.plugins\.(?:android|kotlin)\.android)\s*\)(?=[ \t]*(\n|${'$'}|\}))""" |
| .toRegex() |
| |
| /** |
| * Matches the AGP application plugin in Groovy DSL (`build.gradle`). |
| * Supports both the legacy `apply plugin: 'com.android.application'` syntax and |
| * the modern `plugins { id 'com.android.application' }` syntax (with or without parentheses). |
| */ |
| internal val appPluginRegexGroovy = |
| """(?m)^[ \t]*apply[ \t]+plugin[ \t]*:[ \t]*(['"])com\.android\.application\1|(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)(?:[ \t]*\(\s*|[ \t]+)(['"]com\.android\.application['"]|libs\.plugins\.android\.application)(?:\s*\))?(?=[ \t]*(\n|${'$'}|\}))""" |
| .toRegex() |
| |
| /** |
| * Matches the AGO library plugin in Groovy DSL (`build.gradle`). |
| * Supports both the legacy `apply plugin: 'com.android.library'` syntax and |
| * the modern `plugins { id 'com.android.library' }` syntax (with or without parentheses). |
| */ |
| internal val libPluginRegexGroovy = |
| """(?m)^[ \t]*apply[ \t]+plugin[ \t]*:[ \t]*(['"])com\.android\.library\1|(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)(?:[ \t]*\(\s*|[ \t]+)(['"]com\.android\.library['"]|libs\.plugins\.android\.library)(?:\s*\))?(?=[ \t]*(\n|${'$'}|\}))""" |
| .toRegex() |
| |
| /** |
| * Matches the KGP in Groovy DSL (`build.gradle`). |
| * Supports both legacy `apply plugin` and modern `plugins {}` blocks for |
| * `kotlin-android` or `org.jetbrains.kotlin.android`. |
| */ |
| internal val kgpRegexGroovy = |
| """(?m)^[ \t]*apply[ \t]+plugin[ \t]*:[ \t]*(['"])(?:kotlin-android|org\.jetbrains\.kotlin\.android)\1|(?m)^[ \t]*plugins[ \t]*\{[^{}]*?(?<=[\n{])[ \t]*(?:id|alias)(?:[ \t]*\(\s*|[ \t]+)(['"](?:kotlin-android|org\.jetbrains\.kotlin\.android)['"]|libs\.plugins\.(?:android|kotlin)\.android)(?:\s*\))?(?=[ \t]*(\n|${'$'}|\}))""" |
| .toRegex() |
| // ----------------- Methods for string manipulation and comparison. ----------------- |
| |
| @JvmStatic |
| fun toCamelCase(parts: List<String>): String { |
| if (parts.isEmpty()) { |
| return "" |
| } |
| return parts[0] + |
| parts.drop(1).joinToString("") { capitalize(it) } |
| } |
| |
| // Kotlin's capitalize function is deprecated, but the suggested replacement uses syntax that |
| // our minimum version doesn't support yet. Centralize the use to one place, so that when our |
| // minimum version does support the replacement we can replace by changing a single line. |
| @JvmStatic |
| @Suppress("DEPRECATION") |
| internal fun capitalize(string: String): String = string.capitalize() |
| |
| @OptIn(ExperimentalStdlibApi::class) |
| internal fun lowercase(string: String): String = string.lowercase() |
| |
| // compareTo implementation of version strings in the format of ints and periods |
| // Will not crash on RC candidate strings but considers all RC candidates the same version. |
| // Returns -1 if firstString < secondString, 0 if firstString == secondString, 1 if firstString > secondString |
| @JvmStatic |
| @JvmName("compareVersionStrings") |
| internal fun compareVersionStrings( |
| firstString: String, |
| secondString: String |
| ): Int { |
| val firstVersion = firstString.split(".") |
| val secondVersion = secondString.split(".") |
| |
| val commonIndices = minOf(firstVersion.size, secondVersion.size) |
| |
| for (i in 0 until commonIndices) { |
| var firstAtIndex = firstVersion[i] |
| var secondAtIndex = secondVersion[i] |
| var firstInt = 0 |
| var secondInt = 0 |
| |
| // Strip any chars after "-". For example "8.6-rc-2" |
| firstAtIndex = firstAtIndex.substringBefore("-") |
| try { |
| firstInt = firstAtIndex.toInt() |
| } catch (nfe: NumberFormatException) { |
| println(nfe) |
| } |
| |
| secondAtIndex = secondAtIndex.substringBefore("-") |
| try { |
| secondInt = secondAtIndex.toInt() |
| } catch (nfe: NumberFormatException) { |
| println(nfe) |
| } |
| |
| val comparisonResult = firstInt.compareTo(secondInt) |
| if (comparisonResult != 0) { |
| return comparisonResult |
| } |
| } |
| |
| // If we got this far then all the common indices are identical, so whichever version is longer must be more recent |
| return firstVersion.size.compareTo(secondVersion.size) |
| } |
| |
| @JvmStatic |
| @JvmName("formatPlatformString") |
| fun formatPlatformString(platform: String): String = FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]!!.replace("-", "_") |
| |
| @JvmStatic |
| @JvmName("readPropertiesIfExist") |
| internal fun readPropertiesIfExist(propertiesFile: File): Properties { |
| val result = Properties() |
| if (propertiesFile.exists()) { |
| propertiesFile |
| .reader(StandardCharsets.UTF_8) |
| .use { reader -> |
| // Use Kotlin's reader with UTF-8 and 'use' for auto-closing |
| result.load(reader) |
| } |
| } |
| return result |
| } |
| |
| // ----------------- Methods that interact primarily with the Gradle project. ----------------- |
| |
| @JvmStatic |
| @JvmName("shouldShrinkResources") |
| fun shouldShrinkResources(project: Project): Boolean { |
| if (project.hasProperty(PROP_SHOULD_SHRINK_RESOURCES)) { |
| val propertyValue = project.property(PROP_SHOULD_SHRINK_RESOURCES) |
| return propertyValue.toString().toBoolean() |
| } |
| return true |
| } |
| |
| // TODO(54566): Can remove this function and its call sites once resolved. |
| |
| /** |
| * Returns the Gradle settings script for the build. When both Groovy and |
| * Kotlin variants exist, then Groovy (settings.gradle) is preferred over |
| * Kotlin (settings.gradle.kts). This is the same behavior as Gradle 8.5. |
| */ |
| @JvmStatic |
| @JvmName("getSettingsGradleFileFromProjectDir") |
| internal fun getSettingsGradleFileFromProjectDir( |
| projectDirectory: File, |
| logger: Logger |
| ): File { |
| val settingsGradle = File(projectDirectory.parentFile, "settings.gradle") |
| val settingsGradleKts = File(projectDirectory.parentFile, "settings.gradle.kts") |
| if (settingsGradle.exists() && settingsGradleKts.exists()) { |
| logger.error( |
| """ |
| Both settings.gradle and settings.gradle.kts exist, so |
| settings.gradle.kts is ignored. This is likely a mistake. |
| """.trimIndent() |
| ) |
| } |
| |
| return if (settingsGradle.exists()) settingsGradle else settingsGradleKts |
| } |
| |
| /** |
| * Returns the Gradle build script for the build. When both Groovy and |
| * Kotlin variants exist, then Groovy (build.gradle) is preferred over |
| * Kotlin (build.gradle.kts). This is the same behavior as Gradle 8.5. |
| */ |
| @JvmStatic |
| @JvmName("getBuildGradleFileFromProjectDir") |
| internal fun getBuildGradleFileFromProjectDir( |
| projectDirectory: File, |
| logger: Logger |
| ): File { |
| val buildGradle = File(File(projectDirectory.parentFile, "app"), "build.gradle") |
| val buildGradleKts = File(File(projectDirectory.parentFile, "app"), "build.gradle.kts") |
| if (buildGradle.exists() && buildGradleKts.exists()) { |
| logger.error( |
| """ |
| Both build.gradle and build.gradle.kts exist, so |
| build.gradle.kts is ignored. This is likely a mistake. |
| """.trimIndent() |
| ) |
| } |
| |
| return if (buildGradle.exists()) buildGradle else buildGradleKts |
| } |
| |
| @JvmStatic |
| @JvmName("shouldProjectSplitPerAbi") |
| internal fun shouldProjectSplitPerAbi(project: Project): Boolean = |
| project |
| .findProperty( |
| PROP_SPLIT_PER_ABI |
| )?.toString() |
| ?.toBoolean() ?: false |
| |
| @JvmStatic |
| @JvmName("shouldProjectUseLocalEngine") |
| internal fun shouldProjectUseLocalEngine(project: Project): Boolean = project.hasProperty(PROP_LOCAL_ENGINE_REPO) |
| |
| @JvmStatic |
| @JvmName("isProjectVerbose") |
| internal fun isProjectVerbose(project: Project): Boolean = project.findProperty(PROP_IS_VERBOSE)?.toString()?.toBoolean() ?: false |
| |
| /** |
| * Developers can set this value by passing `-P disable-abi-filtering=true` |
| * to flutter build. Where "disable-abi-filtering" comes from |
| * PROP_DISABLE_ABI_FILTERING. |
| */ |
| @JvmStatic |
| @JvmName("shouldProjectDisableAbiFiltering") |
| internal fun shouldProjectDisableAbiFiltering(project: Project): Boolean = |
| project.findProperty(PROP_DISABLE_ABI_FILTERING)?.toString()?.toBoolean() ?: false |
| |
| /** |
| * Developers can set this value by passing `-P force-version-code-ignoring-abi=true` |
| * to flutter build. Where "force-version-code-ignoring-abi" comes from |
| * PROP_FORCE_VERSION_CODE_IGNORING_ABI. |
| */ |
| @JvmStatic |
| @JvmName("shouldForceVersionCodeIgnoringAbi") |
| internal fun shouldForceVersionCodeIgnoringAbi(project: Project): Boolean = |
| project.findProperty(PROP_FORCE_VERSION_CODE_IGNORING_ABI)?.toString()?.toBoolean() ?: false |
| |
| /** |
| * TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560 |
| * |
| * In AGP 4.0, the Android linter task depends on the JAR tasks that generate `libapp.so`. |
| * When building APKs, this causes an issue where building release requires the debug JAR, |
| * but Gradle won't build debug. |
| * |
| * To workaround this issue, only configure the JAR task that is required given the task |
| * from the command line. |
| * |
| * The AGP team said that this issue is fixed in Gradle 7.0, which isn't released at the |
| * time of adding this code. Once released, this can be removed. However, after updating to |
| * AGP/Gradle 7.2.0/7.5, removing this hack still causes build failures. Further |
| * investigation necessary to remove this. |
| * |
| * Tested cases: |
| * * `./gradlew assembleRelease` |
| * * `./gradlew app:assembleRelease.` |
| * * `./gradlew assemble{flavorName}Release` |
| * * `./gradlew app:assemble{flavorName}Release` |
| * * `./gradlew assemble.` |
| * * `./gradlew app:assemble.` |
| * * `./gradlew bundle.` |
| * * `./gradlew bundleRelease.` |
| * * `./gradlew app:bundleRelease.` |
| * |
| * Related issues: |
| * https://issuetracker.google.com/issues/158060799 |
| * https://issuetracker.google.com/issues/158753935 |
| */ |
| @JvmStatic |
| @JvmName("shouldConfigureFlutterTask") |
| internal fun shouldConfigureFlutterTask( |
| project: Project, |
| assembleTask: Task |
| ): Boolean { |
| val cliTasksNames = project.gradle.startParameter.taskNames |
| if (cliTasksNames.size != 1 || !cliTasksNames.first().contains("assemble")) { |
| return true |
| } |
| val taskName = cliTasksNames.first().split(":").last() |
| if (taskName == "assemble") { |
| return true |
| } |
| if (taskName == assembleTask.name) { |
| return true |
| } |
| if (taskName.endsWith("Release") && assembleTask.name.endsWith("Release")) { |
| return true |
| } |
| if (taskName.endsWith("Debug") && assembleTask.name.endsWith("Debug")) { |
| return true |
| } |
| if (taskName.endsWith("Profile") && assembleTask.name.endsWith("Profile")) { |
| return true |
| } |
| return false |
| } |
| |
| private fun getFlutterExtensionOrNull(project: Project): FlutterExtension? = project.extensions.findByType(FlutterExtension::class.java) |
| |
| /** |
| * Gets the directory that contains the Flutter source code. |
| * This is the directory containing the `android/` directory. |
| */ |
| @JvmStatic |
| @JvmName("getFlutterSourceDirectory") |
| internal fun getFlutterSourceDirectory(project: Project): File { |
| val flutterExtension = getFlutterExtensionOrNull(project) |
| // TODO(gmackall): clean up this NPE that is still around from the Groovy conversion. |
| if (flutterExtension!!.source == null) { |
| throw GradleException("Flutter source directory not set.") |
| } |
| return project.file(flutterExtension.source!!) |
| } |
| |
| /** |
| * Gets the target file. This is typically `lib/main.dart`. |
| * |
| * Returns |
| * 1. the value of the `target` property, if it exists |
| * 2. the target value set in the FlutterExtension, if it exists |
| * 3. `lib/main.dart` otherwise |
| */ |
| @JvmStatic |
| @JvmName("getFlutterTarget") |
| internal fun getFlutterTarget(project: Project): String { |
| if (project.hasProperty(PROP_TARGET)) { |
| return project.property(PROP_TARGET).toString() |
| } |
| val target: String = getFlutterExtensionOrNull(project)!!.target ?: "lib/main.dart" |
| return target |
| } |
| |
| @JvmStatic |
| @JvmName("isBuiltAsApp") |
| internal fun isBuiltAsApp(project: Project): Boolean { |
| // Projects are built as applications when the they use the `com.android.application` |
| // plugin. |
| return project.plugins.hasPlugin("com.android.application") |
| } |
| |
| // Optional parameters don't work when Groovy makes calls into Kotlin, so provide an additional |
| // signature for the 3 argument version. |
| @JvmStatic |
| @JvmName("addApiDependencies") |
| internal fun addApiDependencies( |
| project: Project, |
| variantName: String, |
| dependency: Any |
| ) { |
| addApiDependencies(project, variantName, dependency, null) |
| } |
| |
| @JvmStatic |
| @JvmName("addApiDependencies") |
| internal fun addApiDependencies( |
| project: Project, |
| variantName: String, |
| dependency: Any, |
| config: Closure<Any>? |
| ) { |
| var configuration: String |
| try { |
| project.configurations.named("api") |
| configuration = "${variantName}Api" |
| } catch (ignored: UnknownTaskException) { |
| // TODO(gmackall): The docs say the above should actually be an UnknownDomainObjectException. |
| configuration = "${variantName}Compile" |
| } |
| |
| if (config == null) { |
| project.dependencies.add( |
| configuration, |
| dependency |
| ) |
| } else { |
| project.dependencies.add(configuration, dependency, config) |
| } |
| } |
| |
| /** |
| * Returns a Flutter build mode suitable for the specified Android buildType. |
| * |
| * @return "debug", "profile", or "release" (fall-back). |
| */ |
| @JvmStatic |
| @JvmName("buildModeFor") |
| internal fun buildModeFor(buildType: BuildType): String { |
| if (buildType.name == "profile") { |
| return "profile" |
| } else if (buildType.isDebuggable) { |
| return "debug" |
| } |
| return "release" |
| } |
| |
| /** |
| * Returns true if the build mode is supported by the current call to Gradle. |
| * This only relevant when using a local engine. Because the engine |
| * is built for a specific mode, the call to Gradle must match that mode. |
| */ |
| @JvmStatic |
| @JvmName("supportsBuildMode") |
| internal fun supportsBuildMode( |
| project: Project, |
| flutterBuildMode: String |
| ): Boolean { |
| if (!shouldProjectUseLocalEngine(project)) { |
| return true |
| } |
| check(project.hasProperty(PROP_LOCAL_ENGINE_BUILD_MODE)) { "Project must have property '$PROP_LOCAL_ENGINE_BUILD_MODE'" } |
| // Don't configure dependencies for a build mode that the local engine |
| // doesn't support. |
| 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 = |
| project.extensions.findByName("android") |
| ?: throw IllegalStateException("The Android plugin must be applied before accessing the Android extension.") |
| |
| return AgpCommonExtensionWrapper(androidExtension) |
| } |
| |
| internal fun getAndroidLibraryExtension(project: Project): LibraryExtension = project.extensions.getByType(LibraryExtension::class.java) |
| |
| internal fun getAndroidApplicationExtension(project: Project): ApplicationExtension = |
| project.extensions.getByType(ApplicationExtension::class.java) |
| |
| internal fun getConfiguredNdkVersion(project: Project): String? = |
| project.extensions.findByType(ApplicationExtension::class.java)?.ndkVersion |
| ?: getLegacyAndroidExtension(project).ndkVersion |
| |
| /** |
| * Expected format of getAndroidExtension(project).compileSdkVersion is a string of the form |
| * `android-` followed by either the numeric version, e.g. `android-35`, or a preview version, |
| * e.g. `android-UpsideDownCake`. |
| */ |
| @JvmStatic |
| @JvmName("getCompileSdkFromProject") |
| internal fun getCompileSdkFromProject(project: Project): String = getLegacyAndroidExtension(project).compileSdkVersion!!.substring(8) |
| |
| /** |
| * Returns: |
| * The default platforms if the `target-platform` property is not set. |
| * The requested platforms after verifying they are supported by the Flutter plugin, otherwise. |
| * Throws a GradleException if any of the requested platforms are not supported. |
| */ |
| @JvmStatic |
| @JvmName("getTargetPlatforms") |
| internal fun getTargetPlatforms(project: Project): List<String> { |
| if (!project.hasProperty(PROP_TARGET_PLATFORM)) { |
| return FlutterPluginConstants.DEFAULT_PLATFORMS |
| } |
| val platformsString = project.property(PROP_TARGET_PLATFORM) as String |
| return platformsString.split(",").map { platform -> |
| if (!FlutterPluginConstants.PLATFORM_ARCH_MAP.containsKey(platform)) { |
| throw GradleException("Invalid platform: $platform") |
| } |
| platform |
| } |
| } |
| |
| /** Prints error message for usage of KGP. */ |
| @JvmStatic |
| @JvmName("detectApplyingKotlinGradlePlugin") |
| internal fun detectApplyingKotlinGradlePlugin(project: Project) { |
| val pluginsWithKGPAppliedList = mutableListOf<String>() |
| val agpVersion = VersionFetcher.getAGPVersion(project) |
| var shouldLogForApp = false |
| project.rootProject.subprojects { |
| val pluginState = getSubprojectPluginState(this) ?: return@subprojects |
| |
| // Ensures applying AGP exists in the build file configuration. |
| if (!pluginState.hasAppPlugin && !pluginState.hasLibPlugin) return@subprojects |
| |
| if (!isBuiltInKotlinEnabled(project, agpVersion) && !pluginState.hasKgpPlugin) { |
| try { |
| pluginManager.apply("kotlin-android") |
| } catch (_: Exception) { |
| logger.quiet( |
| """ |
| Applying the Kotlin Android Plugin (KGP) was unsuccessful. KGP was not found on the classpath. |
| If your project uses Kotlin, ensure KGP is declared in the root plugins block. |
| For more details check: $BUILT_IN_KOTLIN_DOCS |
| """.trimIndent() |
| ) |
| } |
| } |
| |
| // Apply AGP exists and Apply KGP also exists in build.gradle |
| if (pluginState.hasAppPlugin && pluginState.hasKgpPlugin) { |
| shouldLogForApp = true |
| } |
| |
| if (pluginState.hasLibPlugin && pluginState.hasKgpPlugin) { |
| pluginsWithKGPAppliedList.add(name) |
| } |
| } |
| |
| // If no imperative apply KGP declarations were found, there is nothing to log. |
| if (!shouldLogForApp && pluginsWithKGPAppliedList.isEmpty()) { |
| return |
| } |
| |
| project.gradle.projectsEvaluated { |
| if (agpVersion == null || agpVersion.major < 9) { |
| return@projectsEvaluated |
| } |
| if (shouldLogForApp) { |
| project.logger.error( |
| """ |
| WARNING: Your Android app project: ${project.name} located at: ${project.buildFile.absolutePath} |
| applies the Kotlin Gradle Plugin, which will cause build failures in future versions of Flutter. |
| Please migrate your app to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_APPS |
| |
| """.trimIndent() |
| ) |
| } |
| if (pluginsWithKGPAppliedList.isEmpty()) return@projectsEvaluated |
| project.logger.error( |
| """ |
| WARNING: Your app uses the following plugins that apply Kotlin Gradle Plugin (KGP): ${pluginsWithKGPAppliedList.joinToString()} |
| Future versions of Flutter will fail to build if your app uses plugins that apply KGP. |
| |
| Please check the changelogs of these plugins and upgrade to a version that supports Built-in Kotlin. |
| If no such version exists, report the issue to the plugin. If necessary, here is a guide on filing |
| an issue against a plugin: $BUILT_IN_KOTLIN_DOCS_TO_REPORT_UNMIGRATED_PLUGINS |
| |
| If you are a plugin author, please migrate your plugin to Built-in Kotlin using this guide: $BUILT_IN_KOTLIN_DOCS_FOR_PLUGINS |
| """.trimIndent() |
| ) |
| } |
| } |
| |
| /** |
| * Represents whether Kotlin Gradle Plugin, Android Gradle Plugin (for applications), and the |
| * Android Gradle Plugin (for libraries) are declared in a subproject's build script. |
| * |
| * @property hasKgpPlugin `true` if the Kotlin Gradle Plugin (KGP) is declared in the subproject's build script. |
| * @property hasAppPlugin `true` if the Android Gradle Plugin (AGP) for applications is declared in the subproject's build script. |
| * @property hasLibPlugin `true` if the Android Gradle Plugin (AGP) for libraries is declared in the subproject's build script. |
| */ |
| internal data class SubprojectPluginState( |
| val hasKgpPlugin: Boolean, |
| val hasAppPlugin: Boolean, |
| val hasLibPlugin: Boolean |
| ) |
| |
| /** |
| * Scans the build script (`build.gradle` or `build.gradle.kts`) of Flutter Android app modules and Flutter plugin |
| * modules to detect declarations of Kotlin Gradle Plugin, Android Gradle Plugin (for applications), |
| * and Android Gradle Plugin (for libraries). |
| * |
| * This inspects build script files directly via regex rather than querying Gradle plugin |
| * state at runtime. Evaluating Kotlin Gradle Plugin dynamically at runtime to conditionally apply Kotlin Gradle Plugin |
| * during configuration leads to lifecycle and ordering issues (see https://github.com/gradle/gradle/issues/36953). |
| * |
| * Returns null if the build script does not exist, is inside an ephemeral `.android/` directory, |
| * or fails to read due to an [IOException]. |
| */ |
| internal fun getSubprojectPluginState(subproject: Project): SubprojectPluginState? { |
| val buildFile = subproject.buildFile |
| |
| // Accounts for Add-to-app scenarios where the Flutter Module ephemeral .android/ directory |
| // should not be adjusted and by default does not apply KGP |
| if (!buildFile.exists() || buildFile.absolutePath.contains(".android")) { |
| return null |
| } |
| |
| val scriptText: String = |
| try { |
| if (buildFile.absolutePath.contains("app/build.gradle")) { |
| getBuildGradleFileFromProjectDir( |
| subproject.projectDir, |
| subproject.logger |
| ).readText() |
| } else { |
| buildFile.readText() |
| } |
| } catch (e: IOException) { |
| subproject.logger.error("Failed to read build file: ${buildFile.absolutePath}", e) |
| return null |
| } |
| |
| val (hasKgpPlugin, hasAppPlugin, hasLibPlugin) = |
| if (buildFile.extension == "kts") { |
| Triple( |
| kgpRegexKotlin.containsMatchIn(scriptText), |
| appPluginRegexKotlin.containsMatchIn(scriptText), |
| libPluginRegexKotlin.containsMatchIn(scriptText) |
| ) |
| } else { |
| Triple( |
| kgpRegexGroovy.containsMatchIn(scriptText), |
| appPluginRegexGroovy.containsMatchIn(scriptText), |
| libPluginRegexGroovy.containsMatchIn(scriptText) |
| ) |
| } |
| |
| return SubprojectPluginState(hasKgpPlugin, hasAppPlugin, hasLibPlugin) |
| } |
| |
| /** |
| * Determines if the Gradle property `android.builtInKotlin` is enabled globally across the multi-project Gradle build. |
| * |
| * Evaluates the `android.builtInKotlin` Gradle property, supporting any [standard Gradle |
| * configuration source](https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties) (such as the root project's `gradle.properties` file or command-line `-P` flags). |
| * |
| * Defaults to `true` for AGP 9.0+ unless `android.builtInKotlin` is explicitly configured |
| * to `false`. Always returns `false` if the AGP version is below `9.0.0` (or null). |
| * See [Android Migration Guide](https://developer.android.com/build/migrate-to-built-in-kotlin). |
| */ |
| @JvmStatic |
| @JvmName("isBuiltInKotlinEnabled") |
| internal fun isBuiltInKotlinEnabled( |
| project: Project, |
| agpVersion: AndroidPluginVersion? |
| ): Boolean { |
| if (agpVersion == null || agpVersion.major < 9) { |
| return false |
| } |
| return project.providers |
| .gradleProperty("android.builtInKotlin") |
| .orNull |
| ?.toBoolean() ?: true |
| } |
| |
| /** Prints error message and fix for any plugin compileSdkVersion or ndkVersion that are higher than the project. */ |
| @JvmStatic |
| @JvmName("detectLowCompileSdkVersionOrNdkVersion") |
| internal fun detectLowCompileSdkVersionOrNdkVersion( |
| project: Project, |
| pluginList: List<Map<String?, Any?>> |
| ) { |
| val validateTask = |
| project.tasks.register("validateCompileSdkVersion", ValidateCompileSdkVersionTask::class.java) { |
| this.projectDir.set(project.layout.projectDirectory) |
| } |
| |
| val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) |
| androidComponents.finalizeDsl { _ -> |
| val projectAndroidExtension = getAndroidExtension(project) |
| validateTask.configure { |
| projectCompileSdk.set(projectAndroidExtension.compileSdk ?: Int.MAX_VALUE) |
| projectNdkVersion.set(projectAndroidExtension.ndkVersion) |
| |
| val pluginSdksMap = mutableMapOf<String, Int>() |
| val pluginNdksMap = mutableMapOf<String, String>() |
| |
| pluginList.forEach { plugin -> |
| val name = requireNotNull(plugin["name"] as? String) { "Missing valid \"name\" property for plugin object: $plugin" } |
| val pluginProject = project.rootProject.findProject(":$name") |
| if (pluginProject != null) { |
| val pluginAndroidExtension = |
| try { |
| getAndroidExtension(pluginProject) |
| } catch (e: IllegalStateException) { |
| null |
| } |
| |
| pluginSdksMap[name] = pluginAndroidExtension?.compileSdk ?: Int.MAX_VALUE |
| |
| val ndkVersion = pluginAndroidExtension?.ndkVersion ?: projectAndroidExtension.ndkVersion |
| if (ndkVersion != null) { |
| pluginNdksMap[name] = ndkVersion |
| } |
| } |
| } |
| pluginCompileSdks.set(pluginSdksMap) |
| pluginNdkVersions.set(pluginNdksMap) |
| } |
| } |
| |
| // Wire the task to run before compilation. |
| project.tasks.named("preBuild").configure { |
| dependsOn(validateTask) |
| } |
| } |
| |
| /** |
| * Forces the project to download the NDK by configuring properties that makes AGP think the |
| * project actually requires the NDK. |
| */ |
| @JvmStatic |
| @JvmName("forceNdkDownload") |
| internal fun forceNdkDownload( |
| gradleProject: Project, |
| flutterSdkRootPath: String |
| ) { |
| if (isFlutterAppProject(gradleProject) && isInvokingMetadataNdkVersionTask(gradleProject)) { |
| return |
| } |
| |
| // If the project is already configuring a native build, we don't need to do anything. |
| val gradleProjectAndroidExtension = getLegacyAndroidExtension(gradleProject) |
| val externalNativeBuild = gradleProjectAndroidExtension.externalNativeBuild |
| val forcingNotRequired: Boolean = |
| externalNativeBuild?.cmake?.path != null || |
| externalNativeBuild?.ndkBuild?.path != null |
| if (forcingNotRequired) { |
| return |
| } |
| |
| val toolNdkProvisioningProperties = getToolNdkProvisioningProperties(gradleProject) |
| if (toolNdkProvisioningProperties != null) { |
| val androidComponents = |
| gradleProject.extensions.findByType(AndroidComponentsExtension::class.java) |
| if (androidComponents == null) { |
| configureSyntheticExternalNativeBuildFallback( |
| gradleProject = gradleProject, |
| flutterSdkRootPath = flutterSdkRootPath |
| ) |
| return |
| } |
| |
| androidComponents.finalizeDsl { _ -> |
| if (gradleProjectAndroidExtension.externalNativeBuild.cmake.path != null) { |
| return@finalizeDsl |
| } |
| |
| val configuredNdkVersion = getConfiguredNdkVersion(gradleProject) |
| if ( |
| !configuredNdkVersion.isNullOrBlank() && |
| toolNdkProvisioningProperties.installedNdkVersions.contains( |
| configuredNdkVersion |
| ) |
| ) { |
| return@finalizeDsl |
| } |
| if ( |
| toolNdkProvisioningProperties.sdkManagerPath == null || |
| gradleProject.gradle.startParameter.isOffline |
| ) { |
| configureSyntheticExternalNativeBuildFallback( |
| gradleProject = gradleProject, |
| flutterSdkRootPath = flutterSdkRootPath |
| ) |
| return@finalizeDsl |
| } |
| val handledByToolProvisioning = |
| maybeHandleToolNdkProvisioning( |
| gradleProject = gradleProject, |
| toolNdkProvisioningProperties = toolNdkProvisioningProperties |
| ) |
| if (!handledByToolProvisioning) { |
| configureSyntheticExternalNativeBuildFallback( |
| gradleProject = gradleProject, |
| flutterSdkRootPath = flutterSdkRootPath |
| ) |
| } |
| } |
| return |
| } |
| |
| configureSyntheticExternalNativeBuildFallback( |
| gradleProject = gradleProject, |
| flutterSdkRootPath = flutterSdkRootPath |
| ) |
| } |
| |
| private fun getToolNdkProvisioningProperties(project: Project): ToolNdkProvisioningProperties? { |
| val androidSdkRoot = project.findProperty(PROP_ANDROID_SDK_ROOT)?.toString() ?: return null |
| val installedNdkVersions = |
| project |
| .findProperty(PROP_INSTALLED_NDK_VERSIONS) |
| ?.toString() |
| ?.split(",") |
| ?.map(String::trim) |
| ?.filter(String::isNotEmpty) |
| ?.toSet() ?: return null |
| val sdkManagerPath = project.findProperty(PROP_SDK_MANAGER_PATH)?.toString() |
| return ToolNdkProvisioningProperties( |
| androidSdkRoot = androidSdkRoot, |
| installedNdkVersions = installedNdkVersions, |
| sdkManagerPath = sdkManagerPath |
| ) |
| } |
| |
| private fun maybeHandleToolNdkProvisioning( |
| gradleProject: Project, |
| toolNdkProvisioningProperties: ToolNdkProvisioningProperties |
| ): Boolean { |
| val configuredNdkVersion = getConfiguredNdkVersion(gradleProject) |
| if (configuredNdkVersion.isNullOrBlank()) { |
| return false |
| } |
| |
| if (toolNdkProvisioningProperties.installedNdkVersions.contains(configuredNdkVersion)) { |
| return true |
| } |
| |
| val sdkManagerPath = toolNdkProvisioningProperties.sdkManagerPath ?: return false |
| val execOps = gradleProject.serviceOf<ExecOperations>() |
| execOps |
| .exec { |
| commandLine( |
| listOf( |
| sdkManagerPath, |
| "--sdk_root=${toolNdkProvisioningProperties.androidSdkRoot}", |
| "--install", |
| "ndk;$configuredNdkVersion" |
| ) |
| ) |
| }.assertNormalExitValue() |
| |
| val installedNdkMarker = |
| File( |
| toolNdkProvisioningProperties.androidSdkRoot, |
| "ndk/$configuredNdkVersion/source.properties" |
| ) |
| if (!installedNdkMarker.exists()) { |
| throw GradleException( |
| "Android sdkmanager did not install NDK $configuredNdkVersion into ${toolNdkProvisioningProperties.androidSdkRoot}." |
| ) |
| } |
| return true |
| } |
| |
| private fun configureSyntheticExternalNativeBuildFallback( |
| gradleProject: Project, |
| flutterSdkRootPath: String |
| ) { |
| val gradleProjectAndroidExtension = getLegacyAndroidExtension(gradleProject) |
| gradleProjectAndroidExtension.externalNativeBuild.cmake.path( |
| "$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 |
| // to avoid polluting source directories with build artifacts. |
| // |
| // AGP explicitly 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("../.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. |
| gradleProjectAndroidExtension.buildTypes.forEach { buildType -> |
| buildType.externalNativeBuild.cmake.arguments( |
| "-Wno-dev", |
| "--no-warn-unused-cli", |
| "-DCMAKE_BUILD_TYPE=${buildType.name}" |
| ) |
| } |
| } |
| |
| @JvmStatic |
| @JvmName("isInvokingMetadataNdkVersionTask") |
| internal fun isInvokingMetadataNdkVersionTask(project: Project): Boolean = |
| project.gradle.startParameter.taskNames.any { taskName -> |
| taskName == TASK_PRINT_NDK_VERSION || taskName.endsWith(":$TASK_PRINT_NDK_VERSION") |
| } |
| |
| @JvmStatic |
| @JvmName("isFlutterAppProject") |
| internal fun isFlutterAppProject(project: Project): Boolean = |
| project.extensions.findByType( |
| ApplicationExtension::class.java |
| ) != null |
| |
| /** |
| * Ensures that the dependencies required by the Flutter project are available. |
| * This includes: |
| * 1. The embedding |
| * 2. libflutter.so |
| * |
| * Should only be called on the main gradle [Project] for this application |
| * of the [FlutterPlugin]. |
| */ |
| @JvmStatic |
| @JvmName("addFlutterDependencies") |
| internal fun addFlutterDependencies( |
| project: Project, |
| buildType: BuildType, |
| pluginHandler: PluginHandler, |
| engineVersion: String |
| ) { |
| val flutterBuildMode: String = buildModeFor(buildType) |
| if (!supportsBuildMode(project, flutterBuildMode)) { |
| project.logger.quiet( |
| "Project does not support Flutter build mode: $flutterBuildMode, " + |
| "skipping adding Flutter dependencies" |
| ) |
| return |
| } |
| // The embedding is set as an API dependency in a Flutter plugin. |
| // Therefore, don't make the app project depend on the embedding if there are Flutter |
| // plugin dependencies. In release mode, dev dependencies are stripped, so we do not |
| // consider those in the check. |
| // This prevents duplicated classes when using custom build types. That is, a custom build |
| // type like profile is used, and the plugin and app projects have API dependencies on the |
| // embedding. |
| val pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency: List<Map<String?, Any?>> = |
| if (flutterBuildMode == "release") { |
| pluginHandler.getPluginListWithoutDevDependencies() |
| } else { |
| pluginHandler.getPluginList() |
| } |
| |
| if (!isFlutterAppProject(project) || pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency.isEmpty()) { |
| addApiDependencies( |
| project, |
| buildType.name, |
| "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion" |
| ) |
| } |
| val platforms: List<String> = getTargetPlatforms(project) |
| platforms.forEach { platform -> |
| val arch: String = formatPlatformString(platform) |
| // Add the `libflutter.so` dependency. |
| addApiDependencies( |
| project, |
| buildType.name, |
| "io.flutter:${arch}_$flutterBuildMode:$engineVersion" |
| ) |
| } |
| } |
| |
| // ------------------ Task adders (a subset of the above category) |
| |
| // Add a task that can be called on Flutter projects that prints the Java version used in Gradle. |
| // |
| // Format of the output of this task can be used in debugging what version of Java Gradle is using. |
| // Not recommended for use in time sensitive commands like `flutter run` or `flutter build` as |
| // Gradle is slower than we want. Particularly in light of https://github.com/flutter/flutter/issues/119196. |
| @JvmStatic |
| @JvmName("addTaskForJavaVersion") |
| internal fun addTaskForJavaVersion(project: Project) { |
| project.tasks.register("javaVersion", PrintTask::class.java) { |
| description = "Print the current java version used by gradle. see: " + |
| "https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html" |
| message.set(VersionFetcher.getJavaVersion().toString()) |
| } |
| } |
| |
| // Add a task that can be called on Flutter projects that prints the KGP version used in |
| // the project. |
| // |
| // Format of the output of this task can be used in debugging what version of KGP a |
| // project is using. |
| // Not recommended for use in time sensitive commands like `flutter run` or `flutter build` as |
| // Gradle tasks are slower than we want. Particularly in light of https://github.com/flutter/flutter/issues/119196. |
| @JvmStatic |
| @JvmName("addTaskForKGPVersion") |
| internal fun addTaskForKGPVersion(project: Project) { |
| project.tasks.register("kgpVersion", PrintTask::class.java) { |
| description = "Print the current kgp version used by the project." |
| val version = VersionFetcher.getKGPVersion(project)?.toString() ?: "null" |
| message.set("KGP Version: $version") |
| } |
| } |
| |
| // Add a task that can be called on Flutter projects that prints the available build variants |
| // in Gradle. |
| // |
| // This task prints variants in this format: |
| // |
| // BuildVariant: debug |
| // BuildVariant: release |
| // BuildVariant: profile |
| // |
| // Format of the output of this task is used by `AndroidProject.getBuildVariants`. |
| @JvmStatic |
| @JvmName("addTaskForPrintBuildVariants") |
| internal fun addTaskForPrintBuildVariants(project: Project) { |
| val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) |
| val variantsList = project.objects.listProperty(String::class.java) |
| |
| // Collect variant names during configuration phase to avoid lifecycle violations |
| androidComponents.onVariants { variant -> |
| variantsList.add(variant.name) |
| } |
| |
| project.tasks.register("printBuildVariants", PrintTask::class.java) { |
| description = "Prints out all build variants for this Android project" |
| message.set(variantsList.map { list -> list.joinToString("\n") { name -> "BuildVariant: $name" } }) |
| } |
| } |
| |
| // Add a task that can be called on Flutter projects that prints the effective ndkVersion |
| // configured for the Android app. |
| // |
| // This task prints the version in this format: |
| // |
| // NdkVersion: 28.2.13676358 |
| // |
| // Format of the output of this task is kept for diagnostics and targeted testing. |
| @JvmStatic |
| @JvmName("addTaskForPrintNdkVersion") |
| internal fun addTaskForPrintNdkVersion(project: Project) { |
| project.tasks.register(TASK_PRINT_NDK_VERSION, PrintTask::class.java) { |
| description = "Prints out the configured ndkVersion for this Android project" |
| message.set( |
| project.provider { |
| "$NDK_VERSION_OUTPUT_PREFIX${getConfiguredNdkVersion(project)}" |
| } |
| ) |
| } |
| } |
| |
| /** |
| * Adds required tasks for the AppLinkSettings feature. |
| * |
| * Should only be called if the build target is an app, as opposed to an aar/module. |
| * |
| * Add a task that can be called on Flutter projects that outputs app link related project |
| * settings into a json file. |
| * See https://developer.android.com/training/app-links/ for more information about app link. |
| * The json will be saved in path stored in "outputPath" parameter or in the projects build |
| * directory with the file deeplink.json if not specified. |
| * |
| * See DeepLinkJsonFromManifestTask for the structure of the json. |
| * |
| * The output file is parsed and used by devtool. |
| */ |
| @JvmStatic |
| @JvmName("addTasksForOutputsAppLinkSettings") |
| internal fun addTasksForOutputsAppLinkSettings(project: Project) { |
| // Integration test for AppLinkSettings task defined in |
| // flutter/flutter/packages/flutter_tools/test/integration.shard/android_gradle_outputs_app_link_settings_test.dart |
| val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) |
| androidComponents.onVariants { variant -> |
| val manifestUpdater = |
| project.tasks.register("output${capitalize(variant.name)}AppLinkSettings", DeepLinkJsonFromManifestTask::class.java) { |
| namespace.set(variant.namespace) |
| // Flutter should always use project.layout.buildDirectory.file("deeplink.json") |
| // instead of relying on passing in a path. |
| if (project.hasProperty("outputPath")) { |
| deepLinkJson.set( |
| File(project.property("outputPath").toString()) |
| ) |
| } else { |
| deepLinkJson.set(project.layout.buildDirectory.file("deeplink.json")) |
| } |
| } |
| // This task does not modify the manifest despite using an api |
| // designed for modification. The task is responsible for an exact copy of the input |
| // manifest being used for the output manifest. |
| variant.artifacts |
| .use(manifestUpdater) |
| .wiredWithFiles( |
| DeepLinkJsonFromManifestTask::manifestFile, |
| DeepLinkJsonFromManifestTask::updatedManifest |
| ).toTransform(SingleArtifact.MERGED_MANIFEST) // (3) Indicate the artifact and operation type. |
| } |
| } |
| } |