| // 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.artifact.SingleArtifact |
| import com.android.build.api.variant.AndroidComponentsExtension |
| import com.android.build.gradle.AbstractAppExtension |
| import com.android.build.gradle.BaseExtension |
| import com.android.build.gradle.tasks.ProcessAndroidResources |
| import com.android.builder.model.BuildType |
| import com.flutter.gradle.plugins.PluginHandler |
| import com.flutter.gradle.tasks.DeepLinkJsonFromManifestTask |
| 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 java.io.File |
| 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" |
| |
| // ----------------- 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 |
| |
| @JvmStatic |
| @JvmName("shouldProjectDisableAbiFiltering") |
| internal fun shouldProjectDisableAbiFiltering(project: Project): Boolean = project.hasProperty(PROP_DISABLE_ABI_FILTERING) |
| |
| /** |
| * TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560 |
| * |
| * 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 |
| } |
| |
| internal fun getAndroidExtension(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)!! |
| } |
| |
| // Avoid new usages this class is not part of the public AGP DSL. |
| private fun getAndroidAppExtensionOrNull(project: Project): AbstractAppExtension? = |
| project.extensions.findByType(AbstractAppExtension::class.java) |
| |
| /** |
| * 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 = getAndroidExtension(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 |
| } |
| } |
| |
| private fun logPluginCompileSdkWarnings( |
| maxPluginCompileSdkVersion: Int, |
| projectCompileSdkVersion: Int, |
| logger: Logger, |
| pluginsWithHigherSdkVersion: List<PluginVersionPair>, |
| projectDirectory: File |
| ) { |
| logger.error( |
| "Your project is configured to compile against Android SDK $projectCompileSdkVersion, but the following plugin(s) require to be compiled against a higher Android SDK version:" |
| ) |
| for (pluginToCompileSdkVersion in pluginsWithHigherSdkVersion) { |
| logger.error( |
| "- ${pluginToCompileSdkVersion.name} compiles against Android SDK ${pluginToCompileSdkVersion.version}" |
| ) |
| } |
| val buildGradleFile = |
| getBuildGradleFileFromProjectDir( |
| projectDirectory, |
| logger |
| ) |
| logger.error( |
| """ |
| Fix this issue by compiling against the highest Android SDK version (they are backward compatible). |
| Add the following to ${buildGradleFile.path}: |
| |
| android { |
| compileSdk = $maxPluginCompileSdkVersion |
| ... |
| } |
| """.trimIndent() |
| ) |
| } |
| |
| private fun logPluginNdkWarnings( |
| maxPluginNdkVersion: String, |
| projectNdkVersion: String, |
| logger: Logger, |
| pluginsWithDifferentNdkVersion: List<PluginVersionPair>, |
| projectDirectory: File |
| ) { |
| logger.error( |
| "Your project is configured with Android NDK $projectNdkVersion, but the following plugin(s) depend on a different Android NDK version:" |
| ) |
| for (pluginToNdkVersion in pluginsWithDifferentNdkVersion) { |
| logger.error("- ${pluginToNdkVersion.name} requires Android NDK ${pluginToNdkVersion.version}") |
| } |
| val buildGradleFile = |
| getBuildGradleFileFromProjectDir( |
| projectDirectory, |
| logger |
| ) |
| logger.error( |
| """ |
| Fix this issue by using the highest Android NDK version (they are backward compatible). |
| Add the following to ${buildGradleFile.path}: |
| |
| android { |
| ndkVersion = "$maxPluginNdkVersion" |
| ... |
| } |
| """.trimIndent() |
| ) |
| } |
| |
| /** 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?>> |
| ) { |
| project.afterEvaluate { |
| // getCompileSdkFromProject returns a string if the project uses a preview compileSdkVersion |
| // so default to Int.MAX_VALUE in that case. |
| val projectCompileSdkVersion: Int = |
| getCompileSdkFromProject(project).toIntOrNull() ?: Int.MAX_VALUE |
| |
| var maxPluginCompileSdkVersion = projectCompileSdkVersion |
| // TODO(gmackall): This should be updated to reflect newer templates. |
| // The default for AGP 4.1.0 used in old templates. |
| val ndkVersionIfUnspecified = "21.1.6352462" |
| |
| // TODO(gmackall): We can remove this elvis when our minimum AGP is >= 8.2. |
| // This value (ndkVersion) is nullable on AGP versions below that. |
| // See https://developer.android.com/reference/tools/gradle-api/8.1/com/android/build/api/dsl/CommonExtension#ndkVersion(). |
| @Suppress("USELESS_ELVIS") |
| val projectNdkVersion: String = |
| getAndroidExtension(project).ndkVersion ?: ndkVersionIfUnspecified |
| var maxPluginNdkVersion = projectNdkVersion |
| var numProcessedPlugins = pluginList.size |
| val pluginsWithHigherSdkVersion = mutableListOf<PluginVersionPair>() |
| val pluginsWithDifferentNdkVersion = mutableListOf<PluginVersionPair>() |
| pluginList.forEach { pluginObject -> |
| val pluginName: String = |
| requireNotNull( |
| pluginObject["name"] as? String |
| ) { "Missing valid \"name\" property for plugin object: $pluginObject" } |
| val pluginProject: Project = |
| project.rootProject.findProject(":$pluginName") ?: return@forEach |
| pluginProject.afterEvaluate { |
| val pluginCompileSdkVersion: Int = |
| getCompileSdkFromProject(pluginProject).toIntOrNull() ?: Int.MAX_VALUE |
| maxPluginCompileSdkVersion = |
| maxOf(maxPluginCompileSdkVersion, pluginCompileSdkVersion) |
| if (pluginCompileSdkVersion > projectCompileSdkVersion) { |
| pluginsWithHigherSdkVersion.add( |
| PluginVersionPair( |
| pluginName, |
| pluginCompileSdkVersion.toString() |
| ) |
| ) |
| } |
| |
| // TODO(gmackall): We can remove this elvis when our minimum AGP is >= 8.2. |
| // This value (ndkVersion) is nullable on AGP versions below that. |
| // See https://developer.android.com/reference/tools/gradle-api/8.1/com/android/build/api/dsl/CommonExtension#ndkVersion(). |
| @Suppress("USELESS_ELVIS") |
| val pluginNdkVersion: String = |
| getAndroidExtension(pluginProject).ndkVersion ?: ndkVersionIfUnspecified |
| maxPluginNdkVersion = |
| VersionUtils.mostRecentSemanticVersion( |
| pluginNdkVersion, |
| maxPluginNdkVersion |
| ) |
| if (pluginNdkVersion != projectNdkVersion) { |
| pluginsWithDifferentNdkVersion.add( |
| PluginVersionPair( |
| pluginName, |
| pluginNdkVersion |
| ) |
| ) |
| } |
| |
| numProcessedPlugins-- |
| if (numProcessedPlugins == 0) { |
| if (maxPluginCompileSdkVersion > projectCompileSdkVersion) { |
| logPluginCompileSdkWarnings( |
| maxPluginCompileSdkVersion = maxPluginCompileSdkVersion, |
| projectCompileSdkVersion = projectCompileSdkVersion, |
| logger = project.logger, |
| pluginsWithHigherSdkVersion = pluginsWithHigherSdkVersion, |
| projectDirectory = project.projectDir |
| ) |
| } |
| if (maxPluginNdkVersion != projectNdkVersion) { |
| logPluginNdkWarnings( |
| maxPluginNdkVersion = maxPluginNdkVersion, |
| projectNdkVersion = projectNdkVersion, |
| logger = project.logger, |
| pluginsWithDifferentNdkVersion = pluginsWithDifferentNdkVersion, |
| projectDirectory = project.projectDir |
| ) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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 the project is already configuring a native build, we don't need to do anything. |
| val gradleProjectAndroidExtension = getAndroidExtension(gradleProject) |
| val forcingNotRequired: Boolean = |
| gradleProjectAndroidExtension.externalNativeBuild.cmake.path != null |
| if (forcingNotRequired) { |
| return |
| } |
| |
| // Otherwise, point to an empty CMakeLists.txt, and ignore associated warnings. |
| 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("isFlutterAppProject") |
| internal fun isFlutterAppProject(project: Project): Boolean = project.extensions.findByType(AbstractAppExtension::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") { |
| description = "Print the current java version used by gradle. see: " + |
| "https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html" |
| doLast { |
| println(VersionFetcher.getJavaVersion()) |
| } |
| } |
| } |
| |
| // 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") { |
| description = "Print the current kgp version used by the project." |
| doLast { |
| println("KGP Version: " + VersionFetcher.getKGPVersion(project).toString()) |
| } |
| } |
| } |
| |
| // 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) { |
| // Groovy was dynamically getting a different subtype here than our Kotlin getAndroidExtension method. |
| // TODO(gmackall): We should take another pass at the different types we are using in our conversion of |
| // the groovy `flutter.android` lines. |
| val androidExtension = project.extensions.getByType(AbstractAppExtension::class.java) |
| project.tasks.register("printBuildVariants") { |
| description = "Prints out all build variants for this Android project" |
| doLast { |
| androidExtension.applicationVariants.forEach { variant -> |
| println("BuildVariant: ${variant.name}") |
| } |
| } |
| } |
| } |
| |
| // TODO(gmackall): Migrate to AGPs variant api. |
| // https://github.com/flutter/flutter/issues/166550 |
| @Suppress("DEPRECATION") |
| private fun findProcessResources(baseVariantOutput: com.android.build.gradle.api.BaseVariantOutput): ProcessAndroidResources = |
| baseVariantOutput.processResourcesProvider?.get() ?: baseVariantOutput.processResources |
| |
| /** |
| * 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. |
| } |
| } |
| } |
| |
| private data class PluginVersionPair( |
| val name: String, |
| val version: String |
| ) |