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.OutputFile
|
||||||
import com.android.build.gradle.AbstractAppExtension
|
import com.android.build.gradle.AbstractAppExtension
|
||||||
|
import com.android.tools.r8.P
|
||||||
import com.flutter.gradle.AppLinkSettings
|
import com.flutter.gradle.AppLinkSettings
|
||||||
import com.android.build.gradle.api.BaseVariantOutput
|
import com.android.build.gradle.api.BaseVariantOutput
|
||||||
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||||
@ -72,7 +73,7 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
this.project = project
|
this.project = project
|
||||||
|
|
||||||
Project rootProject = project.rootProject
|
Project rootProject = project.rootProject
|
||||||
if (isFlutterAppProject()) {
|
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||||
rootProject.tasks.register("generateLockfiles") {
|
rootProject.tasks.register("generateLockfiles") {
|
||||||
doLast {
|
doLast {
|
||||||
rootProject.subprojects.each { subproject ->
|
rootProject.subprojects.each { subproject ->
|
||||||
@ -135,7 +136,7 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
extension.flutterVersionName = localProperties.getProperty("flutter.versionName", "1.0")
|
extension.flutterVersionName = localProperties.getProperty("flutter.versionName", "1.0")
|
||||||
|
|
||||||
this.addFlutterTasks(project)
|
this.addFlutterTasks(project)
|
||||||
forceNdkDownload(project, flutterRootPath)
|
FlutterPluginUtils.forceNdkDownload(project, flutterRootPath)
|
||||||
|
|
||||||
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
|
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
|
||||||
// Configuring split per ABI allows to generate separate APKs for each abi.
|
// 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]
|
String abiValue = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
|
||||||
project.android {
|
project.android {
|
||||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||||
@ -256,52 +257,6 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
project.android.buildTypes.all(this.&addFlutterDependencies)
|
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
|
// Add a task that can be called on Flutter projects that outputs app link related project
|
||||||
// settings into a json file.
|
// settings into a json file.
|
||||||
//
|
//
|
||||||
@ -428,29 +383,7 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
* 2. libflutter.so
|
* 2. libflutter.so
|
||||||
*/
|
*/
|
||||||
void addFlutterDependencies(BuildType buildType) {
|
void addFlutterDependencies(BuildType buildType) {
|
||||||
String flutterBuildMode = FlutterPluginUtils.buildModeFor(buildType)
|
FlutterPluginUtils.addFlutterDependencies(project, buildType, getPluginList(project), engineVersion)
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -462,8 +395,12 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
*/
|
*/
|
||||||
private void configurePlugins(Project project) {
|
private void configurePlugins(Project project) {
|
||||||
configureLegacyPluginEachProjects(project)
|
configureLegacyPluginEachProjects(project)
|
||||||
getPluginList(project).each(this.&configurePluginProject)
|
getPluginList(project).each { Map<String, Object> plugin ->
|
||||||
getPluginList(project).each(this.&configurePluginDependencies)
|
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.
|
// TODO(54566, 48918): Can remove once the issues are resolved.
|
||||||
@ -516,7 +453,7 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
} else if (FlutterPluginUtils.pluginSupportsAndroidPlatform(pluginProject)) {
|
} else if (FlutterPluginUtils.pluginSupportsAndroidPlatform(pluginProject)) {
|
||||||
// Plugin has a functioning `android` folder and is included successfully, although it's not supported.
|
// 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.
|
// It must be configured nonetheless, to not throw an "Unresolved reference" exception.
|
||||||
configurePluginProject(it)
|
FlutterPluginUtils.configurePluginProject(project, it, engineVersion)
|
||||||
/* groovylint-disable-next-line EmptyElseBlock */
|
/* groovylint-disable-next-line EmptyElseBlock */
|
||||||
} else {
|
} else {
|
||||||
// Plugin has no or an empty `android` folder. No action required.
|
// 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.
|
* Gets the list of plugins (as map) that support the Android platform.
|
||||||
*
|
*
|
||||||
@ -758,24 +475,6 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
return pluginList
|
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
|
// 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
|
// https://github.com/flutter/flutter/blob/1c90ed8b64d9ed8ce2431afad8bc6e6d9acc4556/packages/flutter_tools/lib/src/flutter_plugins.dart#L212
|
||||||
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
|
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
|
||||||
@ -794,28 +493,11 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
|
|
||||||
private String resolveProperty(String name, String defaultValue) {
|
private String resolveProperty(String name, String defaultValue) {
|
||||||
if (localProperties == null) {
|
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)
|
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) {
|
private void addFlutterTasks(Project project) {
|
||||||
if (project.state.failure) {
|
if (project.state.failure) {
|
||||||
return
|
return
|
||||||
@ -890,12 +572,12 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
if (project.hasProperty(propValidateDeferredComponents)) {
|
if (project.hasProperty(propValidateDeferredComponents)) {
|
||||||
validateDeferredComponentsValue = project.property(propValidateDeferredComponents).toBoolean()
|
validateDeferredComponentsValue = project.property(propValidateDeferredComponents).toBoolean()
|
||||||
}
|
}
|
||||||
addTaskForJavaVersion(project)
|
FlutterPluginUtils.addTaskForJavaVersion(project)
|
||||||
if (isFlutterAppProject()) {
|
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||||
addTaskForPrintBuildVariants(project)
|
FlutterPluginUtils.addTaskForPrintBuildVariants(project)
|
||||||
addTasksForOutputsAppLinkSettings(project)
|
addTasksForOutputsAppLinkSettings(project)
|
||||||
}
|
}
|
||||||
List<String> targetPlatforms = getTargetPlatforms()
|
List<String> targetPlatforms = FlutterPluginUtils.getTargetPlatforms(project)
|
||||||
def addFlutterDeps = { variant ->
|
def addFlutterDeps = { variant ->
|
||||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
@ -1058,7 +740,7 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
}
|
}
|
||||||
return copyFlutterAssetsTask
|
return copyFlutterAssetsTask
|
||||||
} // end def addFlutterDeps
|
} // end def addFlutterDeps
|
||||||
if (isFlutterAppProject()) {
|
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||||
AbstractAppExtension android = (AbstractAppExtension) project.extensions.findByName("android")
|
AbstractAppExtension android = (AbstractAppExtension) project.extensions.findByName("android")
|
||||||
android.applicationVariants.configureEach { variant ->
|
android.applicationVariants.configureEach { variant ->
|
||||||
Task assembleTask = variant.assembleProvider.get()
|
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/"
|
String nativeAssetsDir = "${project.layout.buildDirectory.get()}/../native_assets/android/jniLibs/lib/"
|
||||||
android.sourceSets.main.jniLibs.srcDir(nativeAssetsDir)
|
android.sourceSets.main.jniLibs.srcDir(nativeAssetsDir)
|
||||||
configurePlugins(project)
|
configurePlugins(project)
|
||||||
detectLowCompileSdkVersionOrNdkVersion()
|
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, getPluginList(project))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Flutter host module project (Add-to-app).
|
// Flutter host module project (Add-to-app).
|
||||||
@ -1160,6 +842,6 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
configurePlugins(project)
|
configurePlugins(project)
|
||||||
detectLowCompileSdkVersionOrNdkVersion()
|
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, getPluginList(project))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ object FlutterPluginConstants {
|
|||||||
const val INTERMEDIATES_DIR = "intermediates"
|
const val INTERMEDIATES_DIR = "intermediates"
|
||||||
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
||||||
const val DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
|
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. */
|
/** Maps platforms to ABI architectures. */
|
||||||
@JvmStatic val PLATFORM_ARCH_MAP =
|
@JvmStatic val PLATFORM_ARCH_MAP =
|
||||||
|
@ -1,19 +1,26 @@
|
|||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
|
import com.android.build.gradle.AbstractAppExtension
|
||||||
|
import com.android.build.gradle.BaseExtension
|
||||||
import com.android.builder.model.BuildType
|
import com.android.builder.model.BuildType
|
||||||
import groovy.lang.Closure
|
import groovy.lang.Closure
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.JavaVersion
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
import org.gradle.api.UnknownTaskException
|
import org.gradle.api.UnknownTaskException
|
||||||
import org.gradle.api.logging.Logger
|
import org.gradle.api.logging.Logger
|
||||||
import java.io.File
|
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.
|
* A collection of static utility functions used by the Flutter Gradle Plugin.
|
||||||
*/
|
*/
|
||||||
object FlutterPluginUtils {
|
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_SHOULD_SHRINK_RESOURCES = "shrink"
|
||||||
internal const val PROP_SPLIT_PER_ABI = "split-per-abi"
|
internal const val PROP_SPLIT_PER_ABI = "split-per-abi"
|
||||||
internal const val PROP_LOCAL_ENGINE_REPO = "local-engine-repo"
|
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_IS_FAST_START = "fast-start"
|
||||||
internal const val PROP_TARGET = "target"
|
internal const val PROP_TARGET = "target"
|
||||||
internal const val PROP_LOCAL_ENGINE_BUILD_MODE = "local-engine-build-mode"
|
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. -----------------
|
// ----------------- Methods for string manipulation and comparison. -----------------
|
||||||
|
|
||||||
@ -89,6 +97,21 @@ object FlutterPluginUtils {
|
|||||||
@JvmName("formatPlatformString")
|
@JvmName("formatPlatformString")
|
||||||
fun formatPlatformString(platform: String): String = FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]!!.replace("-", "_")
|
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. -----------------
|
// ----------------- Methods that interact primarily with the Gradle project. -----------------
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -193,8 +216,6 @@ object FlutterPluginUtils {
|
|||||||
)?.toString()
|
)?.toString()
|
||||||
?.toBoolean() ?: false
|
?.toBoolean() ?: false
|
||||||
|
|
||||||
// TODO(gmackall): @JvmStatic internal fun getCompileSdkFromProject(project: Project): String {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560
|
* TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560
|
||||||
*
|
*
|
||||||
@ -371,4 +392,466 @@ object FlutterPluginUtils {
|
|||||||
// doesn't support.
|
// doesn't support.
|
||||||
return project.property(PROP_LOCAL_ENGINE_BUILD_MODE) == flutterBuildMode
|
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
|
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 com.android.builder.model.BuildType
|
||||||
|
import io.mockk.called
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.slot
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.NamedDomainObjectContainer
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
import org.gradle.api.UnknownTaskException
|
import org.gradle.api.UnknownTaskException
|
||||||
|
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||||
import org.gradle.api.logging.Logger
|
import org.gradle.api.logging.Logger
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.junit.jupiter.api.io.TempDir
|
import org.junit.jupiter.api.io.TempDir
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.util.Properties
|
||||||
|
import kotlin.io.path.createDirectory
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertContains
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
@ -138,7 +150,8 @@ class FlutterPluginUtilsTest {
|
|||||||
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
||||||
settingsGradle.createNewFile()
|
settingsGradle.createNewFile()
|
||||||
|
|
||||||
val result = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
val result =
|
||||||
|
FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
||||||
assertEquals(settingsGradle, result)
|
assertEquals(settingsGradle, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +170,8 @@ class FlutterPluginUtilsTest {
|
|||||||
val mockLogger = mockk<Logger>()
|
val mockLogger = mockk<Logger>()
|
||||||
every { mockLogger.error(any()) } returns Unit
|
every { mockLogger.error(any()) } returns Unit
|
||||||
|
|
||||||
val result = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
val result =
|
||||||
|
FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
||||||
assertEquals(groovySettingsGradle, result)
|
assertEquals(groovySettingsGradle, result)
|
||||||
verify { mockLogger.error(any()) }
|
verify { mockLogger.error(any()) }
|
||||||
}
|
}
|
||||||
@ -173,7 +187,8 @@ class FlutterPluginUtilsTest {
|
|||||||
val buildGradle = File(projectDir.parent.resolve("app").toFile(), "build.gradle")
|
val buildGradle = File(projectDir.parent.resolve("app").toFile(), "build.gradle")
|
||||||
buildGradle.createNewFile()
|
buildGradle.createNewFile()
|
||||||
|
|
||||||
val result = FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
val result =
|
||||||
|
FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockk())
|
||||||
assertEquals(buildGradle, result)
|
assertEquals(buildGradle, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +207,8 @@ class FlutterPluginUtilsTest {
|
|||||||
val mockLogger = mockk<Logger>()
|
val mockLogger = mockk<Logger>()
|
||||||
every { mockLogger.error(any()) } returns Unit
|
every { mockLogger.error(any()) } returns Unit
|
||||||
|
|
||||||
val result = FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
val result =
|
||||||
|
FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
|
||||||
assertEquals(groovyBuildGradle, result)
|
assertEquals(groovyBuildGradle, result)
|
||||||
verify { mockLogger.error(any()) }
|
verify { mockLogger.error(any()) }
|
||||||
}
|
}
|
||||||
@ -358,7 +374,11 @@ class FlutterPluginUtilsTest {
|
|||||||
val variantName = "debug"
|
val variantName = "debug"
|
||||||
val dependency = mockk<Any>()
|
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()
|
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
|
||||||
FlutterPluginUtils.addApiDependencies(project, variantName, dependency)
|
FlutterPluginUtils.addApiDependencies(project, variantName, dependency)
|
||||||
@ -415,4 +435,750 @@ class FlutterPluginUtilsTest {
|
|||||||
val result = FlutterPluginUtils.supportsBuildMode(project, "release")
|
val result = FlutterPluginUtils.supportsBuildMode(project, "release")
|
||||||
assertEquals(false, result)
|
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',
|
'apk',
|
||||||
'--debug',
|
'--debug',
|
||||||
], workingDirectory: exampleAppDir.path);
|
], workingDirectory: exampleAppDir.path);
|
||||||
|
expect(result, const ProcessResultMatcher());
|
||||||
expect(
|
expect(
|
||||||
exampleAppDir
|
exampleAppDir
|
||||||
.childDirectory('build')
|
.childDirectory('build')
|
||||||
@ -103,6 +104,7 @@ void main() {
|
|||||||
'apk',
|
'apk',
|
||||||
'--debug',
|
'--debug',
|
||||||
], workingDirectory: exampleAppDir.path);
|
], workingDirectory: exampleAppDir.path);
|
||||||
|
expect(result, const ProcessResultMatcher());
|
||||||
expect(
|
expect(
|
||||||
exampleAppDir
|
exampleAppDir
|
||||||
.childDirectory('build')
|
.childDirectory('build')
|
||||||
@ -155,6 +157,7 @@ void main() {
|
|||||||
'apk',
|
'apk',
|
||||||
'--debug',
|
'--debug',
|
||||||
], workingDirectory: exampleAppDir.path);
|
], workingDirectory: exampleAppDir.path);
|
||||||
|
expect(result, const ProcessResultMatcher());
|
||||||
expect(
|
expect(
|
||||||
exampleAppDir
|
exampleAppDir
|
||||||
.childDirectory('build')
|
.childDirectory('build')
|
||||||
|
Loading…
Reference in New Issue
Block a user