mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
More FlutterPlugin
static method conversion (#165506)
Moves a lot more functionality from `flutter.groovy` to `FlutterPluginUtils.kt` that could be made static. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Gray Mackall <mackall@google.com>
This commit is contained in:
parent
8c5bbf2931
commit
c9667e07ac
@ -5,6 +5,7 @@
|
||||
|
||||
import com.android.build.OutputFile
|
||||
import com.android.build.gradle.AbstractAppExtension
|
||||
import com.android.tools.r8.P
|
||||
import com.flutter.gradle.AppLinkSettings
|
||||
import com.android.build.gradle.api.BaseVariantOutput
|
||||
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||
@ -72,7 +73,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
this.project = project
|
||||
|
||||
Project rootProject = project.rootProject
|
||||
if (isFlutterAppProject()) {
|
||||
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||
rootProject.tasks.register("generateLockfiles") {
|
||||
doLast {
|
||||
rootProject.subprojects.each { subproject ->
|
||||
@ -135,7 +136,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
extension.flutterVersionName = localProperties.getProperty("flutter.versionName", "1.0")
|
||||
|
||||
this.addFlutterTasks(project)
|
||||
forceNdkDownload(project, flutterRootPath)
|
||||
FlutterPluginUtils.forceNdkDownload(project, flutterRootPath)
|
||||
|
||||
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
|
||||
// Configuring split per ABI allows to generate separate APKs for each abi.
|
||||
@ -162,7 +163,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
getTargetPlatforms().each { targetArch ->
|
||||
FlutterPluginUtils.getTargetPlatforms(project).each { targetArch ->
|
||||
String abiValue = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
|
||||
project.android {
|
||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||
@ -256,52 +257,6 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
project.android.buildTypes.all(this.&addFlutterDependencies)
|
||||
}
|
||||
|
||||
private static Properties readPropertiesIfExist(File propertiesFile) {
|
||||
Properties result = new Properties()
|
||||
if (propertiesFile.exists()) {
|
||||
propertiesFile.withReader("UTF-8") { reader -> result.load(reader) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 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.
|
||||
private static void addTaskForJavaVersion(Project project) {
|
||||
// Warning: the name of this task is used by other code. Change with caution.
|
||||
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(JavaVersion.current())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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`.
|
||||
private static void addTaskForPrintBuildVariants(Project project) {
|
||||
// Warning: The name of this task is used by `AndroidProject.getBuildVariants`.
|
||||
project.tasks.register("printBuildVariants") {
|
||||
description "Prints out all build variants for this Android project"
|
||||
doLast {
|
||||
project.android.applicationVariants.all { variant ->
|
||||
println "BuildVariant: ${variant.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a task that can be called on Flutter projects that outputs app link related project
|
||||
// settings into a json file.
|
||||
//
|
||||
@ -428,29 +383,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
* 2. libflutter.so
|
||||
*/
|
||||
void addFlutterDependencies(BuildType buildType) {
|
||||
String flutterBuildMode = FlutterPluginUtils.buildModeFor(buildType)
|
||||
if (!FlutterPluginUtils.supportsBuildMode(project, flutterBuildMode)) {
|
||||
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.
|
||||
List<Map<String, Object>> pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency = flutterBuildMode == "release" ? getPluginListWithoutDevDependencies(project) : getPluginList(project);
|
||||
if (!isFlutterAppProject() || pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency.size() == 0) {
|
||||
FlutterPluginUtils.addApiDependencies(project, buildType.name,
|
||||
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
|
||||
}
|
||||
List<String> platforms = getTargetPlatforms().collect()
|
||||
platforms.each { platform ->
|
||||
String arch = FlutterPluginUtils.formatPlatformString(platform)
|
||||
// Add the `libflutter.so` dependency.
|
||||
FlutterPluginUtils.addApiDependencies(project, buildType.name,
|
||||
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")
|
||||
}
|
||||
FlutterPluginUtils.addFlutterDependencies(project, buildType, getPluginList(project), engineVersion)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -462,8 +395,12 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
*/
|
||||
private void configurePlugins(Project project) {
|
||||
configureLegacyPluginEachProjects(project)
|
||||
getPluginList(project).each(this.&configurePluginProject)
|
||||
getPluginList(project).each(this.&configurePluginDependencies)
|
||||
getPluginList(project).each { Map<String, Object> plugin ->
|
||||
FlutterPluginUtils.configurePluginProject(project, plugin, engineVersion)
|
||||
}
|
||||
getPluginList(project).each {Map<String, Object> plugin ->
|
||||
FlutterPluginUtils.configurePluginDependencies(project, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(54566, 48918): Can remove once the issues are resolved.
|
||||
@ -516,7 +453,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
} else if (FlutterPluginUtils.pluginSupportsAndroidPlatform(pluginProject)) {
|
||||
// Plugin has a functioning `android` folder and is included successfully, although it's not supported.
|
||||
// It must be configured nonetheless, to not throw an "Unresolved reference" exception.
|
||||
configurePluginProject(it)
|
||||
FlutterPluginUtils.configurePluginProject(project, it, engineVersion)
|
||||
/* groovylint-disable-next-line EmptyElseBlock */
|
||||
} else {
|
||||
// Plugin has no or an empty `android` folder. No action required.
|
||||
@ -524,226 +461,6 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the plugin project dependency to the app project. */
|
||||
private void configurePluginProject(Map<String, Object> pluginObject) {
|
||||
assert(pluginObject.name instanceof String)
|
||||
Project pluginProject = project.rootProject.findProject(":${pluginObject.name}")
|
||||
if (pluginProject == null) {
|
||||
return
|
||||
}
|
||||
// Apply the "flutter" Gradle extension to plugins so that they can use it's vended
|
||||
// compile/target/min sdk values.
|
||||
pluginProject.extensions.create("flutter", FlutterExtension)
|
||||
|
||||
// Add plugin dependency to the app project. We only want to add dependency
|
||||
// for dev dependencies in non-release builds.
|
||||
project.afterEvaluate {
|
||||
project.android.buildTypes.all { buildType ->
|
||||
if (!pluginObject.dev_dependency || buildType.name != 'release') {
|
||||
project.dependencies.add("${buildType.name}Api", pluginProject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Closure addEmbeddingDependencyToPlugin = { BuildType buildType ->
|
||||
String flutterBuildMode = FlutterPluginUtils.buildModeFor(buildType)
|
||||
// In AGP 3.5, the embedding must be added as an API implementation,
|
||||
// so java8 features are desugared against the runtime classpath.
|
||||
// For more, see https://github.com/flutter/flutter/issues/40126
|
||||
if (!FlutterPluginUtils.supportsBuildMode(project, flutterBuildMode)) {
|
||||
return
|
||||
}
|
||||
if (!pluginProject.hasProperty("android")) {
|
||||
return
|
||||
}
|
||||
// Copy build types from the app to the plugin.
|
||||
// This allows to build apps with plugins and custom build types or flavors.
|
||||
pluginProject.android.buildTypes {
|
||||
"${buildType.name}" {}
|
||||
}
|
||||
// The embedding is API dependency of the plugin, so the AGP is able to desugar
|
||||
// default method implementations when the interface is implemented by a plugin.
|
||||
//
|
||||
// See https://issuetracker.google.com/139821726, and
|
||||
// https://github.com/flutter/flutter/issues/72185 for more details.
|
||||
FlutterPluginUtils.addApiDependencies(
|
||||
pluginProject,
|
||||
buildType.name,
|
||||
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
|
||||
)
|
||||
}
|
||||
|
||||
// Wait until the Android plugin loaded.
|
||||
pluginProject.afterEvaluate {
|
||||
// Checks if there is a mismatch between the plugin compileSdkVersion and the project compileSdkVersion.
|
||||
if (pluginProject.android.compileSdkVersion > project.android.compileSdkVersion) {
|
||||
project.logger.quiet("Warning: The plugin ${pluginObject.name} requires Android SDK version ${getCompileSdkFromProject(pluginProject)} or higher.")
|
||||
project.logger.quiet("For more information about build configuration, see $kWebsiteDeploymentAndroidBuildConfig.")
|
||||
}
|
||||
|
||||
project.android.buildTypes.all(addEmbeddingDependencyToPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
private void forceNdkDownload(Project gradleProject, String flutterSdkRootPath) {
|
||||
// If the project is already configuring a native build, we don't need to do anything.
|
||||
Boolean forcingNotRequired = gradleProject.android.externalNativeBuild.cmake.path != null
|
||||
if (forcingNotRequired) {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, point to an empty CMakeLists.txt, and ignore associated warnings.
|
||||
gradleProject.android {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
// Respect the existing configuration if it exists - the NDK will already be
|
||||
// downloaded in this case.
|
||||
path = flutterSdkRootPath + "/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
// CMake will print warnings when you try to build an empty project.
|
||||
// These arguments silence the warnings - our project is intentionally
|
||||
// empty.
|
||||
arguments("-Wno-dev", "--no-warn-unused-cli")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints error message and fix for any plugin compileSdkVersion or ndkVersion that are higher than the project. */
|
||||
private void detectLowCompileSdkVersionOrNdkVersion() {
|
||||
project.afterEvaluate {
|
||||
// Default to int max if using a preview version to skip the sdk check.
|
||||
int projectCompileSdkVersion = Integer.MAX_VALUE
|
||||
// Stable versions use ints, legacy preview uses string.
|
||||
if (getCompileSdkFromProject(project).isInteger()) {
|
||||
projectCompileSdkVersion = getCompileSdkFromProject(project) as int
|
||||
}
|
||||
int maxPluginCompileSdkVersion = projectCompileSdkVersion
|
||||
String ndkVersionIfUnspecified = "21.1.6352462" /* The default for AGP 4.1.0 used in old templates. */
|
||||
String projectNdkVersion = project.android.ndkVersion ?: ndkVersionIfUnspecified
|
||||
String maxPluginNdkVersion = projectNdkVersion
|
||||
int numProcessedPlugins = getPluginList(project).size()
|
||||
List<Tuple2<String, String>> pluginsWithHigherSdkVersion = []
|
||||
List<Tuple2<String, String>> pluginsWithDifferentNdkVersion = []
|
||||
|
||||
getPluginList(project).each { pluginObject ->
|
||||
assert(pluginObject.name instanceof String)
|
||||
Project pluginProject = project.rootProject.findProject(":${pluginObject.name}")
|
||||
if (pluginProject == null) {
|
||||
return
|
||||
}
|
||||
pluginProject.afterEvaluate {
|
||||
// Default to int min if using a preview version to skip the sdk check.
|
||||
int pluginCompileSdkVersion = Integer.MIN_VALUE
|
||||
// Stable versions use ints, legacy preview uses string.
|
||||
if (getCompileSdkFromProject(pluginProject).isInteger()) {
|
||||
pluginCompileSdkVersion = getCompileSdkFromProject(pluginProject) as int
|
||||
}
|
||||
|
||||
maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion)
|
||||
if (pluginCompileSdkVersion > projectCompileSdkVersion) {
|
||||
pluginsWithHigherSdkVersion.add(new Tuple(pluginProject.name, pluginCompileSdkVersion))
|
||||
}
|
||||
|
||||
String pluginNdkVersion = pluginProject.android.ndkVersion ?: ndkVersionIfUnspecified
|
||||
maxPluginNdkVersion = VersionUtils.mostRecentSemanticVersion(pluginNdkVersion, maxPluginNdkVersion)
|
||||
if (pluginNdkVersion != projectNdkVersion) {
|
||||
pluginsWithDifferentNdkVersion.add(new Tuple(pluginProject.name, pluginNdkVersion))
|
||||
}
|
||||
|
||||
numProcessedPlugins--
|
||||
if (numProcessedPlugins == 0) {
|
||||
if (maxPluginCompileSdkVersion > projectCompileSdkVersion) {
|
||||
project.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 (Tuple2<String, String> pluginToCompileSdkVersion : pluginsWithHigherSdkVersion) {
|
||||
project.logger.error("- ${pluginToCompileSdkVersion.v1} compiles against Android SDK ${pluginToCompileSdkVersion.v2}")
|
||||
}
|
||||
File buildGradleFile = FlutterPluginUtils.getBuildGradleFileFromProjectDir(project.projectDir, project.logger)
|
||||
project.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}
|
||||
...
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
if (maxPluginNdkVersion != projectNdkVersion) {
|
||||
project.logger.error("Your project is configured with Android NDK $projectNdkVersion, but the following plugin(s) depend on a different Android NDK version:")
|
||||
for (Tuple2<String, String> pluginToNdkVersion : pluginsWithDifferentNdkVersion) {
|
||||
project.logger.error("- ${pluginToNdkVersion.v1} requires Android NDK ${pluginToNdkVersion.v2}")
|
||||
}
|
||||
File buildGradleFile = FlutterPluginUtils.getBuildGradleFileFromProjectDir(project.projectDir, project.logger)
|
||||
project.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}\"
|
||||
...
|
||||
}
|
||||
""".stripIndent())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the portion of the compileSdkVersion string that corresponds to either the numeric
|
||||
* or string version.
|
||||
*/
|
||||
private static String getCompileSdkFromProject(Project gradleProject) {
|
||||
return gradleProject.android.compileSdkVersion.substring(8)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dependencies on other plugin projects to the plugin project.
|
||||
* A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
|
||||
* making the Gradle plugin project A depend on the Gradle plugin project B.
|
||||
*/
|
||||
private void configurePluginDependencies(Map<String, Object> pluginObject) {
|
||||
assert(pluginObject.name instanceof String)
|
||||
Project pluginProject = project.rootProject.findProject(":${pluginObject.name}")
|
||||
if (pluginProject == null) {
|
||||
return
|
||||
}
|
||||
|
||||
project.android.buildTypes.each { buildType ->
|
||||
String flutterBuildMode = FlutterPluginUtils.buildModeFor(buildType)
|
||||
if (flutterBuildMode == "release" && pluginObject.dev_dependency) {
|
||||
// This plugin is a dev dependency will not be included in the
|
||||
// release build, so no need to add its dependencies.
|
||||
return
|
||||
}
|
||||
def dependencies = pluginObject.dependencies
|
||||
assert(dependencies instanceof List<String>)
|
||||
dependencies.each { pluginDependencyName ->
|
||||
if (pluginDependencyName.empty) {
|
||||
return
|
||||
}
|
||||
Project dependencyProject = project.rootProject.findProject(":$pluginDependencyName")
|
||||
if (dependencyProject == null) {
|
||||
return
|
||||
}
|
||||
// Wait for the Android plugin to load and add the dependency to the plugin project.
|
||||
pluginProject.afterEvaluate {
|
||||
pluginProject.dependencies {
|
||||
implementation(dependencyProject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of plugins (as map) that support the Android platform.
|
||||
*
|
||||
@ -758,24 +475,6 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
return pluginList
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of plugins (as map) that support the Android platform and are dependencies of the
|
||||
* Android project excluding dev dependencies.
|
||||
*
|
||||
* The map value contains either the plugins `name` (String),
|
||||
* its `path` (String), or its `dependencies` (List<String>).
|
||||
* See [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/groovy/native_plugin_loader.groovy
|
||||
*/
|
||||
private List<Map<String, Object>> getPluginListWithoutDevDependencies(Project project) {
|
||||
List<Map<String, Object>> pluginListWithoutDevDependencies = []
|
||||
for (Map<String, Object> plugin in getPluginList(project)) {
|
||||
if (!plugin.dev_dependency) {
|
||||
pluginListWithoutDevDependencies += plugin
|
||||
}
|
||||
}
|
||||
return pluginListWithoutDevDependencies
|
||||
}
|
||||
|
||||
// TODO(54566, 48918): Remove in favor of [getPluginList] only, see also
|
||||
// https://github.com/flutter/flutter/blob/1c90ed8b64d9ed8ce2431afad8bc6e6d9acc4556/packages/flutter_tools/lib/src/flutter_plugins.dart#L212
|
||||
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
|
||||
@ -794,28 +493,11 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
|
||||
private String resolveProperty(String name, String defaultValue) {
|
||||
if (localProperties == null) {
|
||||
localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
|
||||
localProperties = FlutterPluginUtils.readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
|
||||
}
|
||||
return project.findProperty(name) ?: localProperties?.getProperty(name, defaultValue)
|
||||
}
|
||||
|
||||
private List<String> getTargetPlatforms() {
|
||||
final String propTargetPlatform = "target-platform"
|
||||
if (!project.hasProperty(propTargetPlatform)) {
|
||||
return FlutterPluginConstants.DEFAULT_PLATFORMS
|
||||
}
|
||||
return project.property(propTargetPlatform).split(",").collect {
|
||||
if (!FlutterPluginConstants.PLATFORM_ARCH_MAP[it]) {
|
||||
throw new GradleException("Invalid platform: $it.")
|
||||
}
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFlutterAppProject() {
|
||||
return project.android.hasProperty("applicationVariants")
|
||||
}
|
||||
|
||||
private void addFlutterTasks(Project project) {
|
||||
if (project.state.failure) {
|
||||
return
|
||||
@ -890,12 +572,12 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
if (project.hasProperty(propValidateDeferredComponents)) {
|
||||
validateDeferredComponentsValue = project.property(propValidateDeferredComponents).toBoolean()
|
||||
}
|
||||
addTaskForJavaVersion(project)
|
||||
if (isFlutterAppProject()) {
|
||||
addTaskForPrintBuildVariants(project)
|
||||
FlutterPluginUtils.addTaskForJavaVersion(project)
|
||||
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||
FlutterPluginUtils.addTaskForPrintBuildVariants(project)
|
||||
addTasksForOutputsAppLinkSettings(project)
|
||||
}
|
||||
List<String> targetPlatforms = getTargetPlatforms()
|
||||
List<String> targetPlatforms = FlutterPluginUtils.getTargetPlatforms(project)
|
||||
def addFlutterDeps = { variant ->
|
||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||
variant.outputs.each { output ->
|
||||
@ -1058,7 +740,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
return copyFlutterAssetsTask
|
||||
} // end def addFlutterDeps
|
||||
if (isFlutterAppProject()) {
|
||||
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||
AbstractAppExtension android = (AbstractAppExtension) project.extensions.findByName("android")
|
||||
android.applicationVariants.configureEach { variant ->
|
||||
Task assembleTask = variant.assembleProvider.get()
|
||||
@ -1110,7 +792,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
String nativeAssetsDir = "${project.layout.buildDirectory.get()}/../native_assets/android/jniLibs/lib/"
|
||||
android.sourceSets.main.jniLibs.srcDir(nativeAssetsDir)
|
||||
configurePlugins(project)
|
||||
detectLowCompileSdkVersionOrNdkVersion()
|
||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, getPluginList(project))
|
||||
return
|
||||
}
|
||||
// Flutter host module project (Add-to-app).
|
||||
@ -1160,6 +842,6 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
configurePlugins(project)
|
||||
detectLowCompileSdkVersionOrNdkVersion()
|
||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, getPluginList(project))
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ object FlutterPluginConstants {
|
||||
const val INTERMEDIATES_DIR = "intermediates"
|
||||
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
||||
const val DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
|
||||
const val WEBSITE_DEPLOYMENT_ANDROID_BUILD_CONFIG = "https://flutter.dev/to/review-gradle-config"
|
||||
|
||||
/** Maps platforms to ABI architectures. */
|
||||
@JvmStatic val PLATFORM_ARCH_MAP =
|
||||
|
@ -1,19 +1,26 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import com.android.build.gradle.AbstractAppExtension
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.builder.model.BuildType
|
||||
import groovy.lang.Closure
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.JavaVersion
|
||||
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.
|
||||
// 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"
|
||||
@ -21,6 +28,7 @@ object FlutterPluginUtils {
|
||||
internal const val PROP_IS_FAST_START = "fast-start"
|
||||
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"
|
||||
|
||||
// ----------------- Methods for string manipulation and comparison. -----------------
|
||||
|
||||
@ -89,6 +97,21 @@ object FlutterPluginUtils {
|
||||
@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
|
||||
@ -193,8 +216,6 @@ object FlutterPluginUtils {
|
||||
)?.toString()
|
||||
?.toBoolean() ?: false
|
||||
|
||||
// TODO(gmackall): @JvmStatic internal fun getCompileSdkFromProject(project: Project): String {}
|
||||
|
||||
/**
|
||||
* TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560
|
||||
*
|
||||
@ -371,4 +392,466 @@ object FlutterPluginUtils {
|
||||
// doesn't support.
|
||||
return project.property(PROP_LOCAL_ENGINE_BUILD_MODE) == flutterBuildMode
|
||||
}
|
||||
|
||||
private 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)!!
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
val projectNdkVersion =
|
||||
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()
|
||||
)
|
||||
)
|
||||
}
|
||||
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/groovy/CMakeLists.txt"
|
||||
)
|
||||
|
||||
// CMake will print warnings when you try to build an empty project.
|
||||
// These arguments silence the warnings - our project is intentionally
|
||||
// empty.
|
||||
gradleProjectAndroidExtension.defaultConfig.externalNativeBuild.cmake
|
||||
.arguments("-Wno-dev", "--no-warn-unused-cli")
|
||||
}
|
||||
|
||||
@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,
|
||||
pluginList: List<Map<String?, Any?>>,
|
||||
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") {
|
||||
getPluginListWithoutDevDependencies(
|
||||
pluginList
|
||||
)
|
||||
} else {
|
||||
pluginList
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of plugins (as map) that support the Android platform and are dependencies of the
|
||||
* Android project excluding dev dependencies.
|
||||
*
|
||||
* The map value contains either the plugins `name` (String),
|
||||
* its `path` (String), or its `dependencies` (List<String>).
|
||||
* See [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/groovy/native_plugin_loader.groovy
|
||||
*/
|
||||
private fun getPluginListWithoutDevDependencies(pluginList: List<Map<String?, Any?>>): List<Map<String?, Any?>> =
|
||||
pluginList.filter { pluginObject -> pluginObject["dev_dependency"] == false }
|
||||
|
||||
/**
|
||||
* Add the dependencies on other plugin projects to the plugin project.
|
||||
* A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
|
||||
* making the Gradle plugin project A depend on the Gradle plugin project B.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmName("configurePluginDependencies")
|
||||
internal fun configurePluginDependencies(
|
||||
project: Project,
|
||||
pluginObject: Map<String?, Any?>
|
||||
) {
|
||||
val pluginName: String =
|
||||
requireNotNull(pluginObject["name"] as? String) {
|
||||
"Missing valid \"name\" property for plugin object: $pluginObject"
|
||||
}
|
||||
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return
|
||||
|
||||
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
||||
val flutterBuildMode: String = buildModeFor(buildType)
|
||||
if (flutterBuildMode == "release" && (pluginObject["dev_dependency"] as? Boolean == true)) {
|
||||
// This plugin is a dev dependency will not be included in the
|
||||
// release build, so no need to add its dependencies.
|
||||
return@forEach
|
||||
}
|
||||
val dependencies = requireNotNull(pluginObject["dependencies"] as? List<*>)
|
||||
dependencies.forEach innerForEach@{ pluginDependencyName ->
|
||||
check(pluginDependencyName is String)
|
||||
if (pluginDependencyName.isEmpty()) {
|
||||
return@innerForEach
|
||||
}
|
||||
|
||||
val dependencyProject =
|
||||
project.rootProject.findProject(":$pluginDependencyName") ?: return@innerForEach
|
||||
pluginProject.afterEvaluate {
|
||||
pluginProject.dependencies.add("implementation", dependencyProject)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs configuration related to the plugin's Gradle [Project], including
|
||||
* 1. Adding the plugin itself as a dependency to the main project.
|
||||
* 2. Adding the main project's build types to the plugin's build types.
|
||||
* 3. Adding a dependency on the Flutter embedding to the plugin.
|
||||
*
|
||||
* Should only be called on plugins that support the Android platform.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmName("configurePluginProject")
|
||||
internal fun configurePluginProject(
|
||||
project: Project,
|
||||
pluginObject: Map<String?, Any?>,
|
||||
engineVersion: String
|
||||
) {
|
||||
// TODO(gmackall): should guard this with a pluginObject.contains().
|
||||
val pluginName =
|
||||
requireNotNull(pluginObject["name"] as? String) { "Plugin name must be a string for plugin object: $pluginObject" }
|
||||
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return
|
||||
|
||||
// Apply the "flutter" Gradle extension to plugins so that they can use it's vended
|
||||
// compile/target/min sdk values.
|
||||
pluginProject.extensions.create("flutter", FlutterExtension::class.java)
|
||||
|
||||
// Add plugin dependency to the app project. We only want to add dependency
|
||||
// for dev dependencies in non-release builds.
|
||||
project.afterEvaluate {
|
||||
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
||||
if (!(pluginObject["dev_dependency"] as Boolean) || buildType.name != "release") {
|
||||
project.dependencies.add("${buildType.name}Api", pluginProject)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until the Android plugin loaded.
|
||||
pluginProject.afterEvaluate {
|
||||
// Checks if there is a mismatch between the plugin compileSdkVersion and the project compileSdkVersion.
|
||||
val projectCompileSdkVersion: String = getCompileSdkFromProject(project)
|
||||
val pluginCompileSdkVersion: String = getCompileSdkFromProject(pluginProject)
|
||||
// TODO(gmackall): This is doing a string comparison, which is odd and also can be wrong
|
||||
// when comparing preview versions (against non preview, and also in the
|
||||
// case of alphabet reset which happened with "Baklava".
|
||||
if (pluginCompileSdkVersion > projectCompileSdkVersion) {
|
||||
project.logger.quiet("Warning: The plugin $pluginName requires Android SDK version $pluginCompileSdkVersion or higher.")
|
||||
project.logger.quiet(
|
||||
"For more information about build configuration, see ${FlutterPluginConstants.WEBSITE_DEPLOYMENT_ANDROID_BUILD_CONFIG}."
|
||||
)
|
||||
}
|
||||
|
||||
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
||||
addEmbeddingDependencyToPlugin(project, pluginProject, buildType, engineVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addEmbeddingDependencyToPlugin(
|
||||
project: Project,
|
||||
pluginProject: Project,
|
||||
buildType: BuildType,
|
||||
engineVersion: String
|
||||
) {
|
||||
val flutterBuildMode: String = buildModeFor(buildType)
|
||||
// TODO(gmackall): this should be safe to remove, as the minimum required AGP is well above
|
||||
// 3.5. We should try to remove it.
|
||||
// In AGP 3.5, the embedding must be added as an API implementation,
|
||||
// so java8 features are desugared against the runtime classpath.
|
||||
// For more, see https://github.com/flutter/flutter/issues/40126
|
||||
if (!supportsBuildMode(pluginProject, flutterBuildMode)) {
|
||||
return
|
||||
}
|
||||
if (!pluginProject.hasProperty("android")) {
|
||||
return
|
||||
}
|
||||
|
||||
// Copy build types from the app to the plugin.
|
||||
// This allows to build apps with plugins and custom build types or flavors.
|
||||
getAndroidExtension(pluginProject).buildTypes.addAll(getAndroidExtension(project).buildTypes)
|
||||
|
||||
// The embedding is API dependency of the plugin, so the AGP is able to desugar
|
||||
// default method implementations when the interface is implemented by a plugin.
|
||||
//
|
||||
// See https://issuetracker.google.com/139821726, and
|
||||
// https://github.com/flutter/flutter/issues/72185 for more details.
|
||||
addApiDependencies(pluginProject, buildType.name, "io.flutter:flutter_embedding_$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(JavaVersion.current())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class PluginVersionPair(
|
||||
val name: String,
|
||||
val version: String
|
||||
)
|
||||
|
@ -1,19 +1,31 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import com.android.build.gradle.AbstractAppExtension
|
||||
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 io.mockk.called
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.NamedDomainObjectContainer
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.UnknownTaskException
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.util.Properties
|
||||
import kotlin.io.path.createDirectory
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
@ -138,7 +150,8 @@ class FlutterPluginUtilsTest {
|
||||
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
||||
settingsGradle.createNewFile()
|
||||
|
||||
val result = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
||||
val result =
|
||||
FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
||||
assertEquals(settingsGradle, result)
|
||||
}
|
||||
|
||||
@ -157,7 +170,8 @@ class FlutterPluginUtilsTest {
|
||||
val mockLogger = mockk<Logger>()
|
||||
every { mockLogger.error(any()) } returns Unit
|
||||
|
||||
val result = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
||||
val result =
|
||||
FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
||||
assertEquals(groovySettingsGradle, result)
|
||||
verify { mockLogger.error(any()) }
|
||||
}
|
||||
@ -173,7 +187,8 @@ class FlutterPluginUtilsTest {
|
||||
val buildGradle = File(projectDir.parent.resolve("app").toFile(), "build.gradle")
|
||||
buildGradle.createNewFile()
|
||||
|
||||
val result = FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
||||
val result =
|
||||
FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
||||
assertEquals(buildGradle, result)
|
||||
}
|
||||
|
||||
@ -192,7 +207,8 @@ class FlutterPluginUtilsTest {
|
||||
val mockLogger = mockk<Logger>()
|
||||
every { mockLogger.error(any()) } returns Unit
|
||||
|
||||
val result = FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
||||
val result =
|
||||
FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
||||
assertEquals(groovyBuildGradle, result)
|
||||
verify { mockLogger.error(any()) }
|
||||
}
|
||||
@ -358,7 +374,11 @@ class FlutterPluginUtilsTest {
|
||||
val variantName = "debug"
|
||||
val dependency = mockk<Any>()
|
||||
|
||||
every { project.configurations.named("api") } throws UnknownTaskException("message", mockk())
|
||||
every { project.configurations.named("api") } throws
|
||||
UnknownTaskException(
|
||||
"message",
|
||||
mockk()
|
||||
)
|
||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
FlutterPluginUtils.addApiDependencies(project, variantName, dependency)
|
||||
@ -415,4 +435,750 @@ class FlutterPluginUtilsTest {
|
||||
val result = FlutterPluginUtils.supportsBuildMode(project, "release")
|
||||
assertEquals(false, result)
|
||||
}
|
||||
|
||||
// getTargetPlatforms
|
||||
@Test
|
||||
fun `getTargetPlatforms the default if property is not set`() {
|
||||
val project = mockk<Project>()
|
||||
every { project.hasProperty(FlutterPluginUtils.PROP_TARGET_PLATFORM) } returns false
|
||||
val result = FlutterPluginUtils.getTargetPlatforms(project)
|
||||
assertEquals(listOf("android-arm", "android-arm64", "android-x64"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTargetPlatforms the value if property is set`() {
|
||||
val project = mockk<Project>()
|
||||
every { project.hasProperty(FlutterPluginUtils.PROP_TARGET_PLATFORM) } returns true
|
||||
every { project.property(FlutterPluginUtils.PROP_TARGET_PLATFORM) } returns "android-arm64,android-arm"
|
||||
val result = FlutterPluginUtils.getTargetPlatforms(project)
|
||||
assertEquals(listOf("android-arm64", "android-arm"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getTargetPlatforms throws GradleException if property is set to invalid value`() {
|
||||
val project = mockk<Project>()
|
||||
every { project.hasProperty(FlutterPluginUtils.PROP_TARGET_PLATFORM) } returns true
|
||||
every { project.property(FlutterPluginUtils.PROP_TARGET_PLATFORM) } returns "android-invalid"
|
||||
val gradleException: GradleException =
|
||||
assertThrows<GradleException> {
|
||||
FlutterPluginUtils.getTargetPlatforms(project)
|
||||
}
|
||||
assertContains(gradleException.message!!, "android-invalid")
|
||||
}
|
||||
|
||||
// readPropertiesIfExist
|
||||
@Test
|
||||
fun `readPropertiesIfExist returns empty Properties when file does not exist`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val propertiesFile = tempDir.resolve("file_that_doesnt_exist.properties")
|
||||
val result = FlutterPluginUtils.readPropertiesIfExist(propertiesFile.toFile())
|
||||
assertEquals(Properties(), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `readPropertiesIfExist returns Properties when file exists`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val propertiesFile = tempDir.resolve("file_that_exists.properties").toFile()
|
||||
propertiesFile.writeText(
|
||||
"""
|
||||
sdk.dir=/Users/someuser/Library/Android/sdk
|
||||
flutter.sdk=/Users/someuser/development/flutter/flutter
|
||||
flutter.buildMode=release
|
||||
flutter.versionName=1.0.0
|
||||
flutter.versionCode=1
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val result = FlutterPluginUtils.readPropertiesIfExist(propertiesFile)
|
||||
assertEquals(5, result.size)
|
||||
assertEquals("/Users/someuser/Library/Android/sdk", result["sdk.dir"])
|
||||
assertEquals("/Users/someuser/development/flutter/flutter", result["flutter.sdk"])
|
||||
assertEquals("release", result["flutter.buildMode"])
|
||||
assertEquals("1.0.0", result["flutter.versionName"])
|
||||
assertEquals("1", result["flutter.versionCode"])
|
||||
}
|
||||
|
||||
// getCompileSdkFromProject
|
||||
@Test
|
||||
fun `getCompileSdkFromProject returns the compileSdk from the project`() {
|
||||
val project = mockk<Project>()
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
val result = FlutterPluginUtils.getCompileSdkFromProject(project)
|
||||
assertEquals("35", result)
|
||||
}
|
||||
|
||||
// detectLowCompileSdkVersionOrNdkVersion
|
||||
@Test
|
||||
fun `detectLowCompileSdkVersionOrNdkVersion logs no warnings when no plugins have higher sdk or ndk`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val projectDir = tempDir.resolve("app").toFile()
|
||||
|
||||
val project = mockk<Project>()
|
||||
val mockLogger = mockk<Logger>()
|
||||
every { project.logger } returns mockLogger
|
||||
every { project.projectDir } returns projectDir
|
||||
val cameraPluginProject = mockk<Project>()
|
||||
val projectActionSlot = slot<Action<Project>>()
|
||||
val cameraPluginProjectActionSlot = slot<Action<Project>>()
|
||||
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.ndkVersion } returns "26.3.11579264"
|
||||
every { project.rootProject.findProject(":${cameraDependency["name"]}") } returns cameraPluginProject
|
||||
every { cameraPluginProject.afterEvaluate(capture(cameraPluginProjectActionSlot)) } returns Unit
|
||||
every { cameraPluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
every { cameraPluginProject.extensions.findByType(BaseExtension::class.java)!!.ndkVersion } returns "26.3.11579264"
|
||||
|
||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, listOf(cameraDependency))
|
||||
|
||||
verify { project.afterEvaluate(capture(projectActionSlot)) }
|
||||
projectActionSlot.captured.execute(project)
|
||||
verify { cameraPluginProject.afterEvaluate(capture(cameraPluginProjectActionSlot)) }
|
||||
cameraPluginProjectActionSlot.captured.execute(cameraPluginProject)
|
||||
|
||||
verify { mockLogger wasNot called }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detectLowCompileSdkVersionOrNdkVersion logs warnings when plugins have higher sdk and ndk`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val buildGradleFile =
|
||||
tempDir
|
||||
.resolve("app")
|
||||
.createDirectory()
|
||||
.resolve("build.gradle")
|
||||
.toFile()
|
||||
buildGradleFile.createNewFile()
|
||||
val projectDir = tempDir.resolve("app").toFile()
|
||||
|
||||
val project = mockk<Project>()
|
||||
val mockLogger = mockk<Logger>()
|
||||
every { project.logger } returns mockLogger
|
||||
every { mockLogger.error(any()) } returns Unit
|
||||
every { project.projectDir } returns projectDir
|
||||
val cameraPluginProject = mockk<Project>()
|
||||
val flutterPluginAndroidLifecycleDependencyPluginProject = mockk<Project>()
|
||||
val projectActionSlot = slot<Action<Project>>()
|
||||
val cameraPluginProjectActionSlot = slot<Action<Project>>()
|
||||
val flutterPluginAndroidLifecycleDependencyPluginProjectActionSlot = slot<Action<Project>>()
|
||||
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-33"
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.ndkVersion } returns "24.3.11579264"
|
||||
every { project.rootProject.findProject(":${cameraDependency["name"]}") } returns cameraPluginProject
|
||||
every { project.rootProject.findProject(":${flutterPluginAndroidLifecycleDependency["name"]}") } returns
|
||||
flutterPluginAndroidLifecycleDependencyPluginProject
|
||||
every { cameraPluginProject.afterEvaluate(capture(cameraPluginProjectActionSlot)) } returns Unit
|
||||
every { cameraPluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
every { cameraPluginProject.extensions.findByType(BaseExtension::class.java)!!.ndkVersion } returns "26.3.11579264"
|
||||
every {
|
||||
flutterPluginAndroidLifecycleDependencyPluginProject.afterEvaluate(
|
||||
capture(
|
||||
flutterPluginAndroidLifecycleDependencyPluginProjectActionSlot
|
||||
)
|
||||
)
|
||||
} returns Unit
|
||||
every {
|
||||
flutterPluginAndroidLifecycleDependencyPluginProject.extensions
|
||||
.findByType(
|
||||
BaseExtension::class.java
|
||||
)!!
|
||||
.compileSdkVersion
|
||||
} returns "android-34"
|
||||
every {
|
||||
flutterPluginAndroidLifecycleDependencyPluginProject.extensions
|
||||
.findByType(
|
||||
BaseExtension::class.java
|
||||
)!!
|
||||
.ndkVersion
|
||||
} returns "25.3.11579264"
|
||||
|
||||
val dependencyList: List<Map<String?, Any?>> =
|
||||
listOf(cameraDependency, flutterPluginAndroidLifecycleDependency)
|
||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(
|
||||
project,
|
||||
dependencyList
|
||||
)
|
||||
|
||||
verify { project.afterEvaluate(capture(projectActionSlot)) }
|
||||
projectActionSlot.captured.execute(project)
|
||||
verify { cameraPluginProject.afterEvaluate(capture(cameraPluginProjectActionSlot)) }
|
||||
cameraPluginProjectActionSlot.captured.execute(cameraPluginProject)
|
||||
verify {
|
||||
flutterPluginAndroidLifecycleDependencyPluginProject.afterEvaluate(
|
||||
capture(
|
||||
flutterPluginAndroidLifecycleDependencyPluginProjectActionSlot
|
||||
)
|
||||
)
|
||||
}
|
||||
flutterPluginAndroidLifecycleDependencyPluginProjectActionSlot.captured.execute(
|
||||
flutterPluginAndroidLifecycleDependencyPluginProject
|
||||
)
|
||||
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"Your project is configured to compile against Android SDK 33, but the " +
|
||||
"following plugin(s) require to be compiled against a higher Android SDK version:"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"- ${cameraDependency["name"]} compiles against Android SDK 35"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"- ${flutterPluginAndroidLifecycleDependency["name"]} compiles against Android SDK 34"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"""
|
||||
Fix this issue by compiling against the highest Android SDK version (they are backward compatible).
|
||||
Add the following to ${buildGradleFile.path}:
|
||||
|
||||
android {
|
||||
compileSdk = 35
|
||||
...
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"Your project is configured with Android NDK 24.3.11579264, but the following plugin(s) depend on a different Android NDK version:"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"- ${cameraDependency["name"]} requires Android NDK 26.3.11579264"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"- ${flutterPluginAndroidLifecycleDependency["name"]} requires Android NDK 25.3.11579264"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
mockLogger.error(
|
||||
"""
|
||||
Fix this issue by using the highest Android NDK version (they are backward compatible).
|
||||
Add the following to ${buildGradleFile.path}:
|
||||
|
||||
android {
|
||||
ndkVersion = "26.3.11579264"
|
||||
...
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detectLowCompileSdkVersionOrNdkVersion throws IllegalArgumentException when plugin has no name`() {
|
||||
val project = mockk<Project>()
|
||||
val projectActionSlot = slot<Action<Project>>()
|
||||
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.ndkVersion } returns "26.3.11579264"
|
||||
|
||||
val pluginWithoutName: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||
pluginWithoutName.remove("name")
|
||||
|
||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(
|
||||
project,
|
||||
listOf(pluginWithoutName)
|
||||
)
|
||||
verify { project.afterEvaluate(capture(projectActionSlot)) }
|
||||
assertThrows<IllegalArgumentException> { projectActionSlot.captured.execute(project) }
|
||||
}
|
||||
|
||||
// forceNdkDownload
|
||||
@Test
|
||||
fun `forceNdkDownload skips projects which are already configuring a native build`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val fakeCmakeFile = tempDir.resolve("CMakeLists.txt").toFile()
|
||||
fakeCmakeFile.createNewFile()
|
||||
val project = mockk<Project>()
|
||||
val mockCmakeOptions = mockk<CmakeOptions>()
|
||||
val mockDefaultConfig = mockk<DefaultConfig>()
|
||||
every {
|
||||
project.extensions
|
||||
.findByType(BaseExtension::class.java)!!
|
||||
.externalNativeBuild.cmake
|
||||
} returns mockCmakeOptions
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig
|
||||
|
||||
every { mockCmakeOptions.path } returns fakeCmakeFile
|
||||
|
||||
FlutterPluginUtils.forceNdkDownload(project, "ignored")
|
||||
|
||||
verify(exactly = 1) {
|
||||
mockCmakeOptions.path
|
||||
}
|
||||
verify(exactly = 0) { mockCmakeOptions.setPath(any()) }
|
||||
verify { mockDefaultConfig wasNot called }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `forceNdkDownload sets externalNativeBuild properties`() {
|
||||
val project = mockk<Project>()
|
||||
val mockCmakeOptions = mockk<CmakeOptions>()
|
||||
val mockDefaultConfig = mockk<DefaultConfig>()
|
||||
every {
|
||||
project.extensions
|
||||
.findByType(BaseExtension::class.java)!!
|
||||
.externalNativeBuild.cmake
|
||||
} returns mockCmakeOptions
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.defaultConfig } returns mockDefaultConfig
|
||||
|
||||
every { mockCmakeOptions.path } returns null
|
||||
every { mockCmakeOptions.path(any()) } returns Unit
|
||||
every { mockDefaultConfig.externalNativeBuild.cmake.arguments(any(), any()) } returns Unit
|
||||
|
||||
val basePath = "/base/path"
|
||||
FlutterPluginUtils.forceNdkDownload(project, basePath)
|
||||
|
||||
verify(exactly = 1) {
|
||||
mockCmakeOptions.path
|
||||
}
|
||||
verify(exactly = 1) { mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt") }
|
||||
verify(exactly = 1) {
|
||||
mockDefaultConfig.externalNativeBuild.cmake.arguments(
|
||||
"-Wno-dev",
|
||||
"--no-warn-unused-cli"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// isFlutterAppProject skipped as it is a wrapper for a single getter that we would have to mock
|
||||
|
||||
// addFlutterDependencies
|
||||
@Test
|
||||
fun `addFlutterDependencies returns early if buildMode is not supported`() {
|
||||
val project = mockk<Project>()
|
||||
val buildType: BuildType = mockk<BuildType>()
|
||||
every { buildType.name } returns "debug"
|
||||
every { buildType.isDebuggable } returns true
|
||||
every { project.hasProperty("local-engine-repo") } returns true
|
||||
every { project.hasProperty("local-engine-build-mode") } returns true
|
||||
every { project.property("local-engine-build-mode") } returns "release"
|
||||
every { project.logger.quiet(any()) } returns Unit
|
||||
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithoutDevDependency,
|
||||
engineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||
)
|
||||
|
||||
verify(exactly = 1) {
|
||||
project.logger.quiet(
|
||||
"Project does not support Flutter build mode: debug, " +
|
||||
"skipping adding flutter dependencies"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `addFlutterDependencies adds libflutter dependency but not embedding dependency when is a flutter app`() {
|
||||
val project = mockk<Project>()
|
||||
val buildType: BuildType = mockk<BuildType>()
|
||||
val engineVersion = exampleEngineVersion
|
||||
every { buildType.name } returns "debug"
|
||||
every { buildType.isDebuggable } returns true
|
||||
every { project.hasProperty("local-engine-repo") } returns false
|
||||
every { project.extensions.findByType(AbstractAppExtension::class.java) } returns mockk<AbstractAppExtension>()
|
||||
every { project.hasProperty("target-platform") } returns false
|
||||
every { project.configurations.named("api") } returns mockk()
|
||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithoutDevDependency,
|
||||
engineVersion = engineVersion
|
||||
)
|
||||
|
||||
verify(exactly = 3) { project.dependencies.add(any(), any()) }
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"debugApi",
|
||||
"io.flutter:armeabi_v7a_debug:$engineVersion"
|
||||
)
|
||||
}
|
||||
verify { project.dependencies.add("debugApi", "io.flutter:arm64_v8a_debug:$engineVersion") }
|
||||
verify { project.dependencies.add("debugApi", "io.flutter:x86_64_debug:$engineVersion") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `addFlutterDependencies adds libflutter and embedding dep when only dep is dev dep in release mode`() {
|
||||
val project = mockk<Project>()
|
||||
val buildType: BuildType = mockk<BuildType>()
|
||||
val engineVersion = exampleEngineVersion
|
||||
every { buildType.name } returns "release"
|
||||
every { buildType.isDebuggable } returns false
|
||||
every { project.hasProperty("local-engine-repo") } returns false
|
||||
every { project.extensions.findByType(AbstractAppExtension::class.java) } returns mockk<AbstractAppExtension>()
|
||||
every { project.hasProperty("target-platform") } returns false
|
||||
every { project.configurations.named("api") } returns mockk()
|
||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
val pluginListWithSingleDevDependency = listOf(devDependency)
|
||||
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithSingleDevDependency,
|
||||
engineVersion = engineVersion
|
||||
)
|
||||
|
||||
verify(exactly = 4) { project.dependencies.add(any(), any()) }
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"releaseApi",
|
||||
"io.flutter:flutter_embedding_release:$engineVersion"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"releaseApi",
|
||||
"io.flutter:armeabi_v7a_release:$engineVersion"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"releaseApi",
|
||||
"io.flutter:arm64_v8a_release:$engineVersion"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"releaseApi",
|
||||
"io.flutter:x86_64_release:$engineVersion"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `addFlutterDependencies adds libflutter dep but not embedding dep when only dep is dev dep in debug mode`() {
|
||||
val project = mockk<Project>()
|
||||
val buildType: BuildType = mockk<BuildType>()
|
||||
val engineVersion = exampleEngineVersion
|
||||
every { buildType.name } returns "debug"
|
||||
every { buildType.isDebuggable } returns true
|
||||
every { project.hasProperty("local-engine-repo") } returns false
|
||||
every { project.extensions.findByType(AbstractAppExtension::class.java) } returns mockk<AbstractAppExtension>()
|
||||
every { project.hasProperty("target-platform") } returns false
|
||||
every { project.configurations.named("api") } returns mockk()
|
||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
val pluginListWithSingleDevDependency = listOf(devDependency)
|
||||
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithSingleDevDependency,
|
||||
engineVersion = engineVersion
|
||||
)
|
||||
|
||||
verify(exactly = 3) { project.dependencies.add(any(), any()) }
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"debugApi",
|
||||
"io.flutter:armeabi_v7a_debug:$engineVersion"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"debugApi",
|
||||
"io.flutter:arm64_v8a_debug:$engineVersion"
|
||||
)
|
||||
}
|
||||
verify {
|
||||
project.dependencies.add(
|
||||
"debugApi",
|
||||
"io.flutter:x86_64_debug:$engineVersion"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// configurePluginDependencies TODO
|
||||
@Test
|
||||
fun `configurePluginDependencies throws IllegalArgumentException when plugin has no name`() {
|
||||
val project = mockk<Project>()
|
||||
val pluginWithoutName: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||
pluginWithoutName.remove("name")
|
||||
assertThrows<IllegalArgumentException> {
|
||||
FlutterPluginUtils.configurePluginDependencies(
|
||||
project = project,
|
||||
pluginObject = pluginWithoutName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `configurePluginDependencies throws IllegalArgumentException when plugin has null dependencies`() {
|
||||
val project = mockk<Project>()
|
||||
val pluginProject = mockk<Project>()
|
||||
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
||||
val pluginWithNullDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||
pluginWithNullDependencies["dependencies"] = null
|
||||
every { project.rootProject.findProject(":${pluginWithNullDependencies["name"]}") } returns pluginProject
|
||||
every {
|
||||
project.extensions
|
||||
.findByType(BaseExtension::class.java)!!
|
||||
.buildTypes
|
||||
.iterator()
|
||||
} returns
|
||||
mutableListOf(
|
||||
mockBuildType
|
||||
).iterator()
|
||||
every { mockBuildType.name } returns "debug"
|
||||
every { mockBuildType.isDebuggable } returns true
|
||||
|
||||
assertThrows<IllegalArgumentException> {
|
||||
FlutterPluginUtils.configurePluginDependencies(
|
||||
project = project,
|
||||
pluginObject = pluginWithNullDependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `configurePluginDependencies adds plugin dependencies`() {
|
||||
val project = mockk<Project>()
|
||||
val pluginProject = mockk<Project>()
|
||||
val pluginDependencyProject = mockk<Project>()
|
||||
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
||||
val pluginWithDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||
pluginWithDependencies["dependencies"] =
|
||||
listOf(flutterPluginAndroidLifecycleDependency["name"])
|
||||
every { project.rootProject.findProject(":${pluginWithDependencies["name"]}") } returns pluginProject
|
||||
every { project.rootProject.findProject(":${flutterPluginAndroidLifecycleDependency["name"]}") } returns pluginDependencyProject
|
||||
every {
|
||||
project.extensions
|
||||
.findByType(BaseExtension::class.java)!!
|
||||
.buildTypes
|
||||
.iterator()
|
||||
} returns
|
||||
mutableListOf(
|
||||
mockBuildType
|
||||
).iterator()
|
||||
every { mockBuildType.name } returns "debug"
|
||||
every { mockBuildType.isDebuggable } returns true
|
||||
val captureActionSlot = slot<Action<Project>>()
|
||||
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||
val mockDependencyHandler = mockk<DependencyHandler>()
|
||||
every { pluginProject.dependencies } returns mockDependencyHandler
|
||||
every { mockDependencyHandler.add(any(), any()) } returns mockk()
|
||||
|
||||
FlutterPluginUtils.configurePluginDependencies(
|
||||
project = project,
|
||||
pluginObject = pluginWithDependencies
|
||||
)
|
||||
|
||||
verify { pluginProject.afterEvaluate(capture(captureActionSlot)) }
|
||||
captureActionSlot.captured.execute(pluginDependencyProject)
|
||||
verify { mockDependencyHandler.add("implementation", pluginDependencyProject) }
|
||||
}
|
||||
|
||||
// configurePluginProject
|
||||
@Test
|
||||
fun `configurePluginProject throws IllegalArgumentException when plugin has no name`() {
|
||||
val project = mockk<Project>()
|
||||
val pluginWithoutName: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||
pluginWithoutName.remove("name")
|
||||
|
||||
assertThrows<IllegalArgumentException> {
|
||||
FlutterPluginUtils.configurePluginProject(
|
||||
project = project,
|
||||
pluginObject = pluginWithoutName,
|
||||
engineVersion = exampleEngineVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `configurePluginProject adds plugin project`() {
|
||||
val project = mockk<Project>()
|
||||
val pluginProject = mockk<Project>()
|
||||
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
||||
val mockLogger = mockk<Logger>()
|
||||
every { project.logger } returns mockLogger
|
||||
every { pluginProject.hasProperty("local-engine-repo") } returns false
|
||||
every { pluginProject.hasProperty("android") } returns true
|
||||
every { mockBuildType.name } returns "debug"
|
||||
every { mockBuildType.isDebuggable } returns true
|
||||
every { project.rootProject.findProject(":${cameraDependency["name"]}") } returns pluginProject
|
||||
every { pluginProject.extensions.create(any(), any<Class<Any>>()) } returns mockk()
|
||||
val captureActionSlot = slot<Action<Project>>()
|
||||
val capturePluginActionSlot = slot<Action<Project>>()
|
||||
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||
|
||||
val mockProjectBuildTypes =
|
||||
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||
val mockPluginProjectBuildTypes =
|
||||
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockProjectBuildTypes
|
||||
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockPluginProjectBuildTypes
|
||||
every { mockPluginProjectBuildTypes.addAll(any()) } returns true
|
||||
every { pluginProject.configurations.named(any<String>()) } returns mockk()
|
||||
every { pluginProject.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
every {
|
||||
project.extensions
|
||||
.findByType(BaseExtension::class.java)!!
|
||||
.buildTypes
|
||||
.iterator()
|
||||
} returns
|
||||
mutableListOf(
|
||||
mockBuildType
|
||||
).iterator() andThen
|
||||
mutableListOf( // can't return the same iterator as it is stateful
|
||||
mockBuildType
|
||||
).iterator()
|
||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||
|
||||
FlutterPluginUtils.configurePluginProject(
|
||||
project = project,
|
||||
pluginObject = cameraDependency,
|
||||
engineVersion = exampleEngineVersion
|
||||
)
|
||||
|
||||
verify { project.afterEvaluate(capture(captureActionSlot)) }
|
||||
verify { pluginProject.afterEvaluate(capture(capturePluginActionSlot)) }
|
||||
captureActionSlot.captured.execute(project)
|
||||
capturePluginActionSlot.captured.execute(pluginProject)
|
||||
verify { pluginProject.extensions.create("flutter", FlutterExtension::class.java) }
|
||||
verify {
|
||||
pluginProject.dependencies.add(
|
||||
"debugApi",
|
||||
"io.flutter:flutter_embedding_debug:$exampleEngineVersion"
|
||||
)
|
||||
}
|
||||
verify { project.dependencies.add("debugApi", pluginProject) }
|
||||
verify { mockLogger wasNot called }
|
||||
verify { mockPluginProjectBuildTypes.addAll(project.extensions.findByType(BaseExtension::class.java)!!.buildTypes) }
|
||||
}
|
||||
|
||||
// addTaskForJavaVersion
|
||||
@Test
|
||||
fun `addTaskForJavaVersion adds task for Java version`() {
|
||||
val project = mockk<Project>()
|
||||
every { project.tasks.register(any(), any<Action<Task>>()) } returns mockk()
|
||||
val captureSlot = slot<Action<Task>>()
|
||||
FlutterPluginUtils.addTaskForJavaVersion(project)
|
||||
verify { project.tasks.register("javaVersion", capture(captureSlot)) }
|
||||
|
||||
val mockTask = mockk<Task>()
|
||||
every { mockTask.description = any() } returns Unit
|
||||
every { mockTask.doLast(any<Action<Task>>()) } returns mockk()
|
||||
captureSlot.captured.execute(mockTask)
|
||||
verify {
|
||||
mockTask.description = "Print the current java version used by gradle. see: " +
|
||||
"https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html"
|
||||
}
|
||||
}
|
||||
|
||||
// addTaskForPrintBuildVariants
|
||||
@Test
|
||||
fun `addTaskForPrintBuildVariants adds task for printing build variants`() {
|
||||
val project = mockk<Project>()
|
||||
every { project.extensions.getByType(AbstractAppExtension::class.java) } returns mockk()
|
||||
every { project.tasks.register(any(), any<Action<Task>>()) } returns mockk()
|
||||
val captureSlot = slot<Action<Task>>()
|
||||
|
||||
FlutterPluginUtils.addTaskForPrintBuildVariants(project)
|
||||
|
||||
verify { project.tasks.register("printBuildVariants", capture(captureSlot)) }
|
||||
val mockTask = mockk<Task>()
|
||||
every { mockTask.description = any() } returns Unit
|
||||
every { mockTask.doLast(any<Action<Task>>()) } returns mockk()
|
||||
|
||||
captureSlot.captured.execute(mockTask)
|
||||
|
||||
verify {
|
||||
mockTask.description = "Prints out all build variants for this Android project"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val exampleEngineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||
|
||||
val devDependency: Map<String?, Any?> =
|
||||
mapOf(
|
||||
Pair("name", "grays_fun_dev_dependency"),
|
||||
Pair(
|
||||
"path",
|
||||
"/Users/someuser/.pub-cache/hosted/pub.dev/grays_fun_dev_dependency-1.1.1/"
|
||||
),
|
||||
Pair("native_build", true),
|
||||
Pair("dependencies", emptyList<String>()),
|
||||
Pair("dev_dependency", true)
|
||||
)
|
||||
|
||||
val cameraDependency: Map<String?, Any?> =
|
||||
mapOf(
|
||||
Pair("name", "camera_android_camerax"),
|
||||
Pair(
|
||||
"path",
|
||||
"/Users/someuser/.pub-cache/hosted/pub.dev/camera_android_camerax-0.6.14+1/"
|
||||
),
|
||||
Pair("native_build", true),
|
||||
Pair("dependencies", emptyList<String>()),
|
||||
Pair("dev_dependency", false)
|
||||
)
|
||||
|
||||
val flutterPluginAndroidLifecycleDependency: Map<String?, Any?> =
|
||||
mapOf(
|
||||
Pair("name", "flutter_plugin_android_lifecycle"),
|
||||
Pair(
|
||||
"path",
|
||||
"/Users/someuser/.pub-cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.27/"
|
||||
),
|
||||
Pair("native_build", true),
|
||||
Pair("dependencies", emptyList<String>()),
|
||||
Pair("dev_dependency", false)
|
||||
)
|
||||
|
||||
val pluginListWithoutDevDependency: List<Map<String?, Any?>> =
|
||||
listOf(
|
||||
cameraDependency,
|
||||
flutterPluginAndroidLifecycleDependency,
|
||||
mapOf(
|
||||
Pair("name", "in_app_purchase_android"),
|
||||
Pair(
|
||||
"path",
|
||||
"/Users/someuser/.pub-cache/hosted/pub.dev/in_app_purchase_android-0.4.0+1/"
|
||||
),
|
||||
Pair("native_build", true),
|
||||
Pair("dependencies", emptyList<String>()),
|
||||
Pair("dev_dependency", false)
|
||||
)
|
||||
)
|
||||
|
||||
val pluginListWithDevDependency: List<Map<String?, Any?>> =
|
||||
listOf(
|
||||
cameraDependency,
|
||||
flutterPluginAndroidLifecycleDependency,
|
||||
devDependency,
|
||||
mapOf(
|
||||
Pair("name", "in_app_purchase_android"),
|
||||
Pair(
|
||||
"path",
|
||||
"/Users/someuser/.pub-cache/hosted/pub.dev/in_app_purchase_android-0.4.0+1/"
|
||||
),
|
||||
Pair("native_build", true),
|
||||
Pair("dependencies", emptyList<String>()),
|
||||
Pair("dev_dependency", false)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,7 @@ void main() {
|
||||
'apk',
|
||||
'--debug',
|
||||
], workingDirectory: exampleAppDir.path);
|
||||
expect(result, const ProcessResultMatcher());
|
||||
expect(
|
||||
exampleAppDir
|
||||
.childDirectory('build')
|
||||
@ -103,6 +104,7 @@ void main() {
|
||||
'apk',
|
||||
'--debug',
|
||||
], workingDirectory: exampleAppDir.path);
|
||||
expect(result, const ProcessResultMatcher());
|
||||
expect(
|
||||
exampleAppDir
|
||||
.childDirectory('build')
|
||||
@ -155,6 +157,7 @@ void main() {
|
||||
'apk',
|
||||
'--debug',
|
||||
], workingDirectory: exampleAppDir.path);
|
||||
expect(result, const ProcessResultMatcher());
|
||||
expect(
|
||||
exampleAppDir
|
||||
.childDirectory('build')
|
||||
|
Loading…
Reference in New Issue
Block a user