Create a FlutterPluginUtils.kt, and port static methods from FlutterPlugin there (#165239)

Creates a `FlutterPluginUtils.kt` object to contain static methods used
by the Flutter Gradle plugin, and moves over (porting them from Groovy
to Kotlin)
1. Methods that were already static in `FlutterPlugin`.
2. Methods that were not static but were only accessing the `project`
member. These methods were made static by changing their signature to
take the project as an argument.

This doesn't get all of the methods of type 2. Specifically, I'm
planning on leaving functions that interact with the Android Gradle
plugin for a different PR, as this PR was getting long enough and those
pieces will be a lot more brittle anyways (so I'd like to be able to
revert them on their own).

## 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].
- [x] 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.
- [ ] 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:
Gray Mackall
2025-03-17 17:14:28 -07:00
committed by GitHub
parent 1d954f4e96
commit 879d80fde4
5 changed files with 825 additions and 288 deletions

View File

@@ -15,6 +15,7 @@ import com.flutter.gradle.BaseFlutterTask
import com.flutter.gradle.Deeplink
import com.flutter.gradle.DependencyVersionChecker
import com.flutter.gradle.FlutterExtension
import com.flutter.gradle.FlutterPluginUtils
import com.flutter.gradle.IntentFilterCheck
import com.flutter.gradle.VersionUtils
import groovy.xml.QName
@@ -145,7 +146,7 @@ class FlutterPlugin implements Plugin<Project> {
throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
}
engineVersion = useLocalEngine()
engineVersion = FlutterPluginUtils.shouldProjectUseLocalEngine(project)
? "+" // Match any version since there's only one.
: "1.0.0-" + Paths.get(flutterRoot.absolutePath, "bin", "cache", "engine.stamp").toFile().text.trim()
@@ -156,7 +157,7 @@ class FlutterPlugin implements Plugin<Project> {
// Configure the Maven repository.
String hostedRepository = System.getenv("FLUTTER_STORAGE_BASE_URL") ?: DEFAULT_MAVEN_HOST
String repository = useLocalEngine()
String repository = FlutterPluginUtils.shouldProjectUseLocalEngine(project)
? project.property(propLocalEngineRepo)
: "$hostedRepository/${engineRealm}download.flutter.io"
rootProject.allprojects {
@@ -188,7 +189,7 @@ class FlutterPlugin implements Plugin<Project> {
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
// Configuring split per ABI allows to generate separate APKs for each abi.
// This is a noop when building a bundle.
if (shouldSplitPerAbi()) {
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
project.android {
splits {
abi {
@@ -213,7 +214,7 @@ class FlutterPlugin implements Plugin<Project> {
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android {
if (shouldSplitPerAbi()) {
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
splits {
abi {
include(abiValue)
@@ -269,14 +270,14 @@ class FlutterPlugin implements Plugin<Project> {
// This limitation has been removed experimentally in gradle plugin version 4.2, so we can remove
// this check when we upgrade to 4.2+ gradle. Currently, deferred components apps may see
// increased app size due to this.
if (shouldShrinkResources(project)) {
if (FlutterPluginUtils.shouldShrinkResources(project)) {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled(true)
// Enables resource shrinking, which is performed by the Android Gradle plugin.
// The resource shrinker can't be used for libraries.
shrinkResources(isBuiltAsApp(project))
shrinkResources(FlutterPluginUtils.isBuiltAsApp(project))
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles(project.android.getDefaultProguardFile("proguard-android-optimize.txt"), flutterProguardRules, "proguard-rules.pro")
@@ -284,7 +285,7 @@ class FlutterPlugin implements Plugin<Project> {
}
}
if (useLocalEngine()) {
if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
// This is required to pass the local engine to flutter build aot.
String engineOutPath = project.property("local-engine-out")
File engineOut = project.file(engineOutPath)
@@ -304,21 +305,6 @@ class FlutterPlugin implements Plugin<Project> {
project.android.buildTypes.all(this.&addFlutterDependencies)
}
private static Boolean shouldShrinkResources(Project project) {
final String propShrink = "shrink"
if (project.hasProperty(propShrink)) {
return project.property(propShrink).toBoolean()
}
return true
}
private static String toCamelCase(List<String> parts) {
if (parts.empty) {
return ""
}
return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}"
}
private static Properties readPropertiesIfExist(File propertiesFile) {
Properties result = new Properties()
if (propertiesFile.exists()) {
@@ -327,24 +313,6 @@ class FlutterPlugin implements Plugin<Project> {
return result
}
private static Boolean isBuiltAsApp(Project project) {
// Projects are built as applications when the they use the `com.android.application`
// plugin.
return project.plugins.hasPlugin("com.android.application")
}
private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) {
String configuration
// `compile` dependencies are now `api` dependencies.
try{
project.getConfigurations().named("api")
configuration = "${variantName}Api"
} catch(UnknownTaskException ignored) {
configuration = "${variantName}Compile"
}
project.dependencies.add(configuration, dependency, config)
}
// 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.
@@ -502,20 +470,6 @@ class FlutterPlugin implements Plugin<Project> {
}
}
/**
* Returns a Flutter build mode suitable for the specified Android buildType.
*
* @return "debug", "profile", or "release" (fall-back).
*/
private static String buildModeFor(BuildType buildType) {
if (buildType.name == "profile") {
return "profile"
} else if (buildType.debuggable) {
return "debug"
}
return "release"
}
/**
* Adds the dependencies required by the Flutter project.
* This includes:
@@ -523,8 +477,8 @@ class FlutterPlugin implements Plugin<Project> {
* 2. libflutter.so
*/
void addFlutterDependencies(BuildType buildType) {
String flutterBuildMode = buildModeFor(buildType)
if (!supportsBuildMode(flutterBuildMode)) {
String flutterBuildMode = FlutterPluginUtils.buildModeFor(buildType)
if (!FlutterPluginUtils.supportsBuildMode(project, flutterBuildMode)) {
return
}
// The embedding is set as an API dependency in a Flutter plugin.
@@ -536,14 +490,14 @@ class FlutterPlugin implements Plugin<Project> {
// embedding.
List<Map<String, Object>> pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency = flutterBuildMode == "release" ? getPluginListWithoutDevDependencies(project) : getPluginList(project);
if (!isFlutterAppProject() || pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency.size() == 0) {
addApiDependencies(project, buildType.name,
FlutterPluginUtils.addApiDependencies(project, buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
}
List<String> platforms = getTargetPlatforms().collect()
platforms.each { platform ->
String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
// Add the `libflutter.so` dependency.
addApiDependencies(project, buildType.name,
FlutterPluginUtils.addApiDependencies(project, buildType.name,
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")
}
}
@@ -586,14 +540,15 @@ class FlutterPlugin implements Plugin<Project> {
try {
// Read the contents of the settings.gradle file.
// Remove block/line comments
String settingsText = settingsGradleFile(project).text
String settingsText = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(project.projectDir, project.logger).text
settingsText = settingsText.replaceAll(/(?s)\/\*.*?\*\//, '').replaceAll(/(?m)\/\/.*$/, '')
if (!settingsText.contains("'.flutter-plugins'")) {
return
}
} catch (FileNotFoundException ignored) {
throw new GradleException("settings.gradle/settings.gradle.kts does not exist: ${settingsGradleFile(project).absolutePath}")
throw new GradleException("settings.gradle/settings.gradle.kts does not exist: " +
"${FlutterPluginUtils.getSettingsGradleFileFromProjectDir(project.projectDir, project.logger).absolutePath}")
}
// TODO(matanlurey): https://github.com/flutter/flutter/issues/48918.
project.logger.quiet("Warning: This project is still reading the deprecated '.flutter-plugins. file.")
@@ -607,7 +562,7 @@ class FlutterPlugin implements Plugin<Project> {
if (pluginProject == null) {
// Plugin was not included in `settings.gradle`, but is listed in `.flutter-plugins`.
project.logger.error("Plugin project :${it.name} listed, but not found. Please fix your settings.gradle/settings.gradle.kts.")
} else if (pluginSupportsAndroidPlatform(pluginProject)) {
} else if (FlutterPluginUtils.pluginSupportsAndroidPlatform(pluginProject)) {
// Plugin has a functioning `android` folder and is included successfully, although it's not supported.
// It must be configured nonetheless, to not throw an "Unresolved reference" exception.
configurePluginProject(it)
@@ -618,53 +573,6 @@ class FlutterPlugin implements Plugin<Project> {
}
}
// TODO(54566): Can remove this function and its call sites once resolved.
/**
* Returns `true` if the given project is a plugin project having an `android` directory
* containing a `build.gradle` or `build.gradle.kts` file.
*/
private static Boolean pluginSupportsAndroidPlatform(Project project) {
File buildGradle = new File(project.projectDir.parentFile, "android" + File.separator + "build.gradle")
File buildGradleKts = new File(project.projectDir.parentFile, "android" + File.separator + "build.gradle.kts")
return buildGradle.exists() || buildGradleKts.exists()
}
/**
* Returns the Gradle build script for the build. When both Groovy and
* Kotlin variants exist, then Groovy (build.gradle) is preferred over
* Kotlin (build.gradle.kts). This is the same behavior as Gradle 8.5.
*/
private static File buildGradleFile(Project project) {
File buildGradle = new File(project.projectDir.parentFile, "app" + File.separator + "build.gradle")
File buildGradleKts = new File(project.projectDir.parentFile, "app" + File.separator + "build.gradle.kts")
if (buildGradle.exists() && buildGradleKts.exists()) {
project.logger.error(
"Both build.gradle and build.gradle.kts exist, so " +
"build.gradle.kts is ignored. This is likely a mistake."
)
}
return buildGradle.exists() ? buildGradle : buildGradleKts
}
/**
* Returns the Gradle settings script for the build. When both Groovy and
* Kotlin variants exist, then Groovy (settings.gradle) is preferred over
* Kotlin (settings.gradle.kts). This is the same behavior as Gradle 8.5.
*/
private static File settingsGradleFile(Project project) {
File settingsGradle = new File(project.projectDir.parentFile, "settings.gradle")
File settingsGradleKts = new File(project.projectDir.parentFile, "settings.gradle.kts")
if (settingsGradle.exists() && settingsGradleKts.exists()) {
project.logger.error(
"Both settings.gradle and settings.gradle.kts exist, so " +
"settings.gradle.kts is ignored. This is likely a mistake."
)
}
return settingsGradle.exists() ? settingsGradle : settingsGradleKts
}
/** Adds the plugin project dependency to the app project. */
private void configurePluginProject(Map<String, Object> pluginObject) {
assert(pluginObject.name instanceof String)
@@ -687,11 +595,11 @@ class FlutterPlugin implements Plugin<Project> {
}
Closure addEmbeddingDependencyToPlugin = { BuildType buildType ->
String flutterBuildMode = buildModeFor(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 (!supportsBuildMode(flutterBuildMode)) {
if (!FlutterPluginUtils.supportsBuildMode(project, flutterBuildMode)) {
return
}
if (!pluginProject.hasProperty("android")) {
@@ -707,7 +615,7 @@ class FlutterPlugin implements Plugin<Project> {
//
// See https://issuetracker.google.com/139821726, and
// https://github.com/flutter/flutter/issues/72185 for more details.
addApiDependencies(
FlutterPluginUtils.addApiDependencies(
pluginProject,
buildType.name,
"io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion"
@@ -805,9 +713,10 @@ class FlutterPlugin implements Plugin<Project> {
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(project).path}:
Add the following to ${buildGradleFile.path}:
android {
compileSdk = ${maxPluginCompileSdkVersion}
@@ -820,9 +729,10 @@ class FlutterPlugin implements Plugin<Project> {
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(project).path}:
Add the following to ${buildGradleFile.path}:
android {
ndkVersion = \"${maxPluginNdkVersion}\"
@@ -857,7 +767,7 @@ class FlutterPlugin implements Plugin<Project> {
}
project.android.buildTypes.each { buildType ->
String flutterBuildMode = buildModeFor(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.
@@ -892,7 +802,7 @@ class FlutterPlugin implements Plugin<Project> {
*/
private List<Map<String, Object>> getPluginList(Project project) {
if (pluginList == null) {
pluginList = project.ext.nativePluginLoader.getPlugins(getFlutterSourceDirectory())
pluginList = project.ext.nativePluginLoader.getPlugins(FlutterPluginUtils.getFlutterSourceDirectory(project))
}
return pluginList
}
@@ -920,7 +830,7 @@ class FlutterPlugin implements Plugin<Project> {
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
private List<Map<String, Object>> getPluginDependencies(Project project) {
if (pluginDependencies == null) {
Map meta = project.ext.nativePluginLoader.getDependenciesMetadata(getFlutterSourceDirectory())
Map meta = project.ext.nativePluginLoader.getDependenciesMetadata(FlutterPluginUtils.getFlutterSourceDirectory(project))
if (meta == null) {
pluginDependencies = []
} else {
@@ -951,115 +861,6 @@ class FlutterPlugin implements Plugin<Project> {
}
}
private Boolean shouldSplitPerAbi() {
return project.findProperty("split-per-abi")?.toBoolean() ?: false
}
private Boolean useLocalEngine() {
return project.hasProperty(propLocalEngineRepo)
}
private Boolean isVerbose() {
return project.findProperty("verbose")?.toBoolean() ?: false
}
/** Whether to build the debug app in "fast-start" mode. */
private Boolean isFastStart() {
return project.findProperty("fast-start")?.toBoolean() ?: false
}
/**
* Returns true if the build mode is supported by the current call to Gradle.
* This only relevant when using a local engine. Because the engine
* is built for a specific mode, the call to Gradle must match that mode.
*/
private Boolean supportsBuildMode(String flutterBuildMode) {
if (!useLocalEngine()) {
return true
}
final String propLocalEngineBuildMode = "local-engine-build-mode"
assert(project.hasProperty(propLocalEngineBuildMode))
// Don't configure dependencies for a build mode that the local engine
// doesn't support.
return project.property(propLocalEngineBuildMode) == flutterBuildMode
}
/**
* Gets the directory that contains the Flutter source code.
* This is the directory containing the `android/` directory.
*/
private File getFlutterSourceDirectory() {
if (project.flutter.source == null) {
throw new GradleException("Must provide Flutter source directory")
}
return project.file(project.flutter.source)
}
/**
* Gets the target file. This is typically `lib/main.dart`.
*/
private String getFlutterTarget() {
String target = project.flutter.target ?: "lib/main.dart"
final String propTarget = "target"
if (project.hasProperty(propTarget)) {
target = project.property(propTarget)
}
return target
}
// TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560
/**
* In AGP 4.0, the Android linter task depends on the JAR tasks that generate `libapp.so`.
* When building APKs, this causes an issue where building release requires the debug JAR,
* but Gradle won't build debug.
*
* To workaround this issue, only configure the JAR task that is required given the task
* from the command line.
*
* The AGP team said that this issue is fixed in Gradle 7.0, which isn't released at the
* time of adding this code. Once released, this can be removed. However, after updating to
* AGP/Gradle 7.2.0/7.5, removing this hack still causes build failures. Further
* investigation necessary to remove this.
*
* Tested cases:
* * `./gradlew assembleRelease`
* * `./gradlew app:assembleRelease.`
* * `./gradlew assemble{flavorName}Release`
* * `./gradlew app:assemble{flavorName}Release`
* * `./gradlew assemble.`
* * `./gradlew app:assemble.`
* * `./gradlew bundle.`
* * `./gradlew bundleRelease.`
* * `./gradlew app:bundleRelease.`
*
* Related issues:
* https://issuetracker.google.com/issues/158060799
* https://issuetracker.google.com/issues/158753935
*/
private boolean shouldConfigureFlutterTask(Task assembleTask) {
List<String> cliTasksNames = project.gradle.startParameter.taskNames
if (cliTasksNames.size() != 1 || !cliTasksNames.first().contains("assemble")) {
return true
}
String taskName = cliTasksNames.first().split(":").last()
if (taskName == "assemble") {
return true
}
if (taskName == assembleTask.name) {
return true
}
if (taskName.endsWith("Release") && assembleTask.name.endsWith("Release")) {
return true
}
if (taskName.endsWith("Debug") && assembleTask.name.endsWith("Debug")) {
return true
}
if (taskName.endsWith("Profile") && assembleTask.name.endsWith("Profile")) {
return true
}
return false
}
private boolean isFlutterAppProject() {
return project.android.hasProperty("applicationVariants")
}
@@ -1145,7 +946,7 @@ class FlutterPlugin implements Plugin<Project> {
}
List<String> targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
variant.outputs.each { output ->
// Assigns the new version code to versionCodeOverride, which changes the version code
// for only the output APK, not for the variant itself. Skipping this step simply
@@ -1176,9 +977,9 @@ class FlutterPlugin implements Plugin<Project> {
}
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
String variantBuildMode = buildModeFor(variant.buildType)
String variantBuildMode = FlutterPluginUtils.buildModeFor(variant.buildType)
String flavorValue = variant.getFlavorName()
String taskName = toCamelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])
String taskName = FlutterPluginUtils.toCamelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])
// Be careful when configuring task below, Groovy has bizarre
// scoping rules: writing `verbose isVerbose()` means calling
// `isVerbose` on the task itself - which would return `verbose`
@@ -1193,14 +994,14 @@ class FlutterPlugin implements Plugin<Project> {
localEngine(this.localEngine)
localEngineHost(this.localEngineHost)
localEngineSrcPath(this.localEngineSrcPath)
targetPath(getFlutterTarget())
verbose(this.isVerbose())
fastStart(this.isFastStart())
targetPath(FlutterPluginUtils.getFlutterTarget(project))
verbose(FlutterPluginUtils.isProjectVerbose(project))
fastStart(FlutterPluginUtils.isProjectFastStart(project))
fileSystemRoots(fileSystemRootsValue)
fileSystemScheme(fileSystemSchemeValue)
trackWidgetCreation(trackWidgetCreationValue)
targetPlatformValues = targetPlatforms
sourceDir(getFlutterSourceDirectory())
sourceDir(FlutterPluginUtils.getFlutterSourceDirectory(project))
intermediateDir(project.file(project.layout.buildDirectory.dir("$INTERMEDIATES_DIR/flutter/${variant.name}/")))
frontendServerStarterPath(frontendServerStarterPathValue)
extraFrontEndOptions(extraFrontEndOptionsValue)
@@ -1232,7 +1033,7 @@ class FlutterPlugin implements Plugin<Project> {
}
// Copy the native assets created by build.dart and placed in build/native_assets by flutter assemble.
// The `$project.layout.buildDirectory` is '.android/Flutter/build/' instead of 'build/'.
String buildDir = "${getFlutterSourceDirectory()}/build"
String buildDir = "${FlutterPluginUtils.getFlutterSourceDirectory(project)}/build"
String nativeAssetsDir = "${buildDir}/native_assets/android/jniLibs/lib"
from("${nativeAssetsDir}/${abi}") {
include("*.so")
@@ -1243,7 +1044,7 @@ class FlutterPlugin implements Plugin<Project> {
}
}
Task packJniLibsTask = packJniLibsTaskProvider.get()
addApiDependencies(project, variant.name, project.files {
FlutterPluginUtils.addApiDependencies(project, variant.name, project.files {
packJniLibsTask
})
TaskProvider<Copy> copyFlutterAssetsTaskProvider = project.tasks.register(
@@ -1255,7 +1056,7 @@ class FlutterPlugin implements Plugin<Project> {
// See https://docs.gradle.org/current/javadoc/org/gradle/api/file/ConfigurableFilePermissions.html
// See https://github.com/flutter/flutter/pull/50047
if (compareVersionStrings(currentGradleVersion, "8.3") >= 0) {
if (FlutterPluginUtils.compareVersionStrings(currentGradleVersion, "8.3") >= 0) {
filePermissions {
user {
read = true
@@ -1310,7 +1111,7 @@ class FlutterPlugin implements Plugin<Project> {
AbstractAppExtension android = (AbstractAppExtension) project.extensions.findByName("android")
android.applicationVariants.configureEach { variant ->
Task assembleTask = variant.assembleProvider.get()
if (!shouldConfigureFlutterTask(assembleTask)) {
if (!FlutterPluginUtils.shouldConfigureFlutterTask(project, assembleTask)) {
return
}
Task copyFlutterAssetsTask = addFlutterDeps(variant)
@@ -1340,7 +1141,7 @@ class FlutterPlugin implements Plugin<Project> {
if (variant.flavorName != null && !variant.flavorName.isEmpty()) {
filename += "-${variant.flavorName.toLowerCase()}"
}
filename += "-${buildModeFor(variant.buildType)}"
filename += "-${FlutterPluginUtils.buildModeFor(variant.buildType)}"
project.copy {
from new File("$outputDirectoryStr/${output.outputFileName}")
into new File("${project.layout.buildDirectory.dir("outputs/flutter-apk").get()}")
@@ -1372,7 +1173,7 @@ class FlutterPlugin implements Plugin<Project> {
Task copyFlutterAssetsTask
appProject.android.applicationVariants.all { appProjectVariant ->
Task appAssembleTask = appProjectVariant.assembleProvider.get()
if (!shouldConfigureFlutterTask(appAssembleTask)) {
if (!FlutterPluginUtils.shouldConfigureFlutterTask(project, appAssembleTask)) {
return
}
// Find a compatible application variant in the host app.
@@ -1394,8 +1195,8 @@ class FlutterPlugin implements Plugin<Project> {
// (e.g. `buildType.debuggable = true`), then the equivalent Flutter
// variant is `debug`.
// 3. Otherwise, the equivalent Flutter variant is `release`.
String variantBuildMode = buildModeFor(libraryVariant.buildType)
if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
String variantBuildMode = FlutterPluginUtils.buildModeFor(libraryVariant.buildType)
if (FlutterPluginUtils.buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
return
}
copyFlutterAssetsTask = copyFlutterAssetsTask ?: addFlutterDeps(libraryVariant)
@@ -1410,50 +1211,6 @@ class FlutterPlugin implements Plugin<Project> {
configurePlugins(project)
detectLowCompileSdkVersionOrNdkVersion()
}
// compareTo implementation of version strings in the format of ints and periods
// Requires non null objects.
// Will not crash on RC candidate strings but considers all RC candidates the same version.
static int compareVersionStrings(String firstString, String secondString) {
List firstVersion = firstString.tokenize(".")
List secondVersion = secondString.tokenize(".")
int commonIndices = Math.min(firstVersion.size(), secondVersion.size())
for (int i = 0; i < commonIndices; i++) {
String firstAtIndex = firstVersion[i]
String secondAtIndex = secondVersion[i]
int firstInt = 0
int secondInt = 0
try {
if (firstAtIndex.contains("-")) {
// Strip any chars after "-". For example "8.6-rc-2"
firstAtIndex = firstAtIndex.substring(0, firstAtIndex.indexOf('-'))
}
firstInt = firstAtIndex.toInteger()
} catch (NumberFormatException nfe) {
println(nfe)
}
try {
if (firstAtIndex.contains("-")) {
// Strip any chars after "-". For example "8.6-rc-2"
secondAtIndex = secondAtIndex.substring(0, secondAtIndex.indexOf('-'))
}
secondInt = secondAtIndex.toInteger()
} catch (NumberFormatException nfe) {
println(nfe)
}
if (firstInt != secondInt) {
// <=> in groovy delegates to compareTo
return firstInt <=> secondInt
}
}
// If we got this far then all the common indices are identical, so whichever version is longer must be more recent
return firstVersion.size() <=> secondVersion.size()
}
}
class FlutterTask extends BaseFlutterTask {
@@ -1525,5 +1282,4 @@ class FlutterTask extends BaseFlutterTask {
void build() {
buildBundle()
}
}

View File

@@ -164,9 +164,9 @@ object DependencyVersionChecker {
}
}
private fun generateAssembleTaskName(it: Variant) = "$ASSEMBLE_PREFIX${it.name.capitalize()}"
private fun generateAssembleTaskName(it: Variant) = "$ASSEMBLE_PREFIX${FlutterPluginUtils.capitalize(it.name)}"
private fun generateMinSdkCheckTaskName(it: Variant) = "${it.name.capitalize()}$MIN_SDK_CHECK_TASK_POSTFIX"
private fun generateMinSdkCheckTaskName(it: Variant) = "${FlutterPluginUtils.capitalize(it.name)}$MIN_SDK_CHECK_TASK_POSTFIX"
private fun getMinSdkVersion(
project: Project,

View File

@@ -39,7 +39,7 @@ open class FlutterExtension {
* Specifies the relative directory to the Flutter project directory.
* In an app project, this is ../.. since the app's Gradle build file is under android/app.
*/
var source: String = "../.."
var source: String? = "../.."
/** Allows to override the target file. Otherwise, the target is lib/main.dart. */
var target: String? = null

View File

@@ -0,0 +1,370 @@
package com.flutter.gradle
import com.android.builder.model.BuildType
import groovy.lang.Closure
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.UnknownTaskException
import org.gradle.api.logging.Logger
import java.io.File
/**
* A collection of static utility functions used by the Flutter Gradle Plugin.
*/
object FlutterPluginUtils {
// Gradle properties.
internal const val PROP_SHOULD_SHRINK_RESOURCES = "shrink"
internal const val PROP_SPLIT_PER_ABI = "split-per-abi"
internal const val PROP_LOCAL_ENGINE_REPO = "localEngineRepo"
internal const val PROP_IS_VERBOSE = "verbose"
internal const val PROP_IS_FAST_START = "fast-start"
internal const val PROP_TARGET = "target"
internal const val PROP_LOCAL_ENGINE_BUILD_MODE = "local-engine-build-mode"
// ----------------- Methods for string manipulation and comparison. -----------------
@JvmStatic
fun toCamelCase(parts: List<String>): String {
if (parts.isEmpty()) {
return ""
}
return parts[0] +
parts.drop(1).joinToString("") { capitalize(it) }
}
// Kotlin's capitalize function is deprecated, but the suggested replacement uses syntax that
// our minimum version doesn't support yet. Centralize the use to one place, so that when our
// minimum version does support the replacement we can replace by changing a single line.
@JvmStatic
@Suppress("DEPRECATION")
internal fun capitalize(string: String): String = string.capitalize()
// compareTo implementation of version strings in the format of ints and periods
// Will not crash on RC candidate strings but considers all RC candidates the same version.
// Returns -1 if firstString < secondString, 0 if firstString == secondString, 1 if firstString > secondString
@JvmStatic
@JvmName("compareVersionStrings")
internal fun compareVersionStrings(
firstString: String,
secondString: String
): Int {
val firstVersion = firstString.split(".")
val secondVersion = secondString.split(".")
val commonIndices = minOf(firstVersion.size, secondVersion.size)
for (i in 0 until commonIndices) {
var firstAtIndex = firstVersion[i]
var secondAtIndex = secondVersion[i]
var firstInt = 0
var secondInt = 0
// Strip any chars after "-". For example "8.6-rc-2"
firstAtIndex = firstAtIndex.substringBefore("-")
try {
firstInt = firstAtIndex.toInt()
} catch (nfe: NumberFormatException) {
println(nfe)
}
secondAtIndex = secondAtIndex.substringBefore("-")
try {
secondInt = secondAtIndex.toInt()
} catch (nfe: NumberFormatException) {
println(nfe)
}
val comparisonResult = firstInt.compareTo(secondInt)
if (comparisonResult != 0) {
return comparisonResult
}
}
// If we got this far then all the common indices are identical, so whichever version is longer must be more recent
return firstVersion.size.compareTo(secondVersion.size)
}
// ----------------- Methods that interact primarily with the Gradle project. -----------------
@JvmStatic
@JvmName("shouldShrinkResources")
fun shouldShrinkResources(project: Project): Boolean {
if (project.hasProperty(PROP_SHOULD_SHRINK_RESOURCES)) {
val propertyValue = project.property(PROP_SHOULD_SHRINK_RESOURCES)
return propertyValue.toString().toBoolean()
}
return true
}
// TODO(54566): Can remove this function and its call sites once resolved.
/**
* Returns `true` if the given project is a plugin project having an `android` directory
* containing a `build.gradle` or `build.gradle.kts` file.
*/
@JvmStatic
@JvmName("pluginSupportsAndroidPlatform")
internal fun pluginSupportsAndroidPlatform(project: Project): Boolean {
val buildGradle = File(File(project.projectDir.parentFile, "android"), "build.gradle")
val buildGradleKts =
File(File(project.projectDir.parentFile, "android"), "build.gradle.kts")
return buildGradle.exists() || buildGradleKts.exists()
}
/**
* Returns the Gradle settings script for the build. When both Groovy and
* Kotlin variants exist, then Groovy (settings.gradle) is preferred over
* Kotlin (settings.gradle.kts). This is the same behavior as Gradle 8.5.
*/
@JvmStatic
@JvmName("getSettingsGradleFileFromProjectDir")
internal fun getSettingsGradleFileFromProjectDir(
projectDirectory: File,
logger: Logger
): File {
val settingsGradle = File(projectDirectory.parentFile, "settings.gradle")
val settingsGradleKts = File(projectDirectory.parentFile, "settings.gradle.kts")
if (settingsGradle.exists() && settingsGradleKts.exists()) {
logger.error(
"""
Both settings.gradle and settings.gradle.kts exist, so
settings.gradle.kts is ignored. This is likely a mistake.
""".trimIndent()
)
}
return if (settingsGradle.exists()) settingsGradle else settingsGradleKts
}
/**
* Returns the Gradle build script for the build. When both Groovy and
* Kotlin variants exist, then Groovy (build.gradle) is preferred over
* Kotlin (build.gradle.kts). This is the same behavior as Gradle 8.5.
*/
@JvmStatic
@JvmName("getBuildGradleFileFromProjectDir")
internal fun getBuildGradleFileFromProjectDir(
projectDirectory: File,
logger: Logger
): File {
val buildGradle = File(File(projectDirectory.parentFile, "app"), "build.gradle")
val buildGradleKts = File(File(projectDirectory.parentFile, "app"), "build.gradle.kts")
if (buildGradle.exists() && buildGradleKts.exists()) {
logger.error(
"""
Both build.gradle and build.gradle.kts exist, so
build.gradle.kts is ignored. This is likely a mistake.
""".trimIndent()
)
}
return if (buildGradle.exists()) buildGradle else buildGradleKts
}
@JvmStatic
@JvmName("shouldProjectSplitPerAbi")
internal fun shouldProjectSplitPerAbi(project: Project): Boolean =
project
.findProperty(
PROP_SPLIT_PER_ABI
)?.toString()
?.toBoolean() ?: false
@JvmStatic
@JvmName("shouldProjectUseLocalEngine")
internal fun shouldProjectUseLocalEngine(project: Project): Boolean = project.hasProperty(PROP_LOCAL_ENGINE_REPO)
@JvmStatic
@JvmName("isProjectVerbose")
internal fun isProjectVerbose(project: Project): Boolean = project.findProperty(PROP_IS_VERBOSE)?.toString()?.toBoolean() ?: false
/** Whether to build the debug app in "fast-start" mode. */
@JvmStatic
@JvmName("isProjectFastStart")
internal fun isProjectFastStart(project: Project): Boolean =
project
.findProperty(
PROP_IS_FAST_START
)?.toString()
?.toBoolean() ?: false
// TODO(gmackall): @JvmStatic internal fun getCompileSdkFromProject(project: Project): String {}
/**
* TODO: Remove this AGP hack. https://github.com/flutter/flutter/issues/109560
*
* In AGP 4.0, the Android linter task depends on the JAR tasks that generate `libapp.so`.
* When building APKs, this causes an issue where building release requires the debug JAR,
* but Gradle won't build debug.
*
* To workaround this issue, only configure the JAR task that is required given the task
* from the command line.
*
* The AGP team said that this issue is fixed in Gradle 7.0, which isn't released at the
* time of adding this code. Once released, this can be removed. However, after updating to
* AGP/Gradle 7.2.0/7.5, removing this hack still causes build failures. Further
* investigation necessary to remove this.
*
* Tested cases:
* * `./gradlew assembleRelease`
* * `./gradlew app:assembleRelease.`
* * `./gradlew assemble{flavorName}Release`
* * `./gradlew app:assemble{flavorName}Release`
* * `./gradlew assemble.`
* * `./gradlew app:assemble.`
* * `./gradlew bundle.`
* * `./gradlew bundleRelease.`
* * `./gradlew app:bundleRelease.`
*
* Related issues:
* https://issuetracker.google.com/issues/158060799
* https://issuetracker.google.com/issues/158753935
*/
@JvmStatic
@JvmName("shouldConfigureFlutterTask")
internal fun shouldConfigureFlutterTask(
project: Project,
assembleTask: Task
): Boolean {
val cliTasksNames = project.gradle.startParameter.taskNames
if (cliTasksNames.size != 1 || !cliTasksNames.first().contains("assemble")) {
return true
}
val taskName = cliTasksNames.first().split(":").last()
if (taskName == "assemble") {
return true
}
if (taskName == assembleTask.name) {
return true
}
if (taskName.endsWith("Release") && assembleTask.name.endsWith("Release")) {
return true
}
if (taskName.endsWith("Debug") && assembleTask.name.endsWith("Debug")) {
return true
}
if (taskName.endsWith("Profile") && assembleTask.name.endsWith("Profile")) {
return true
}
return false
}
private fun getFlutterExtensionOrNull(project: Project): FlutterExtension? = project.extensions.findByType(FlutterExtension::class.java)
/**
* Gets the directory that contains the Flutter source code.
* This is the directory containing the `android/` directory.
*/
@JvmStatic
@JvmName("getFlutterSourceDirectory")
internal fun getFlutterSourceDirectory(project: Project): File {
val flutterExtension = getFlutterExtensionOrNull(project)
// TODO(gmackall): clean up this NPE that is still around from the Groovy conversion.
if (flutterExtension!!.source == null) {
throw GradleException("Flutter source directory not set.")
}
return project.file(flutterExtension.source!!)
}
/**
* Gets the target file. This is typically `lib/main.dart`.
*
* Returns
* 1. the value of the `target` property, if it exists
* 2. the target value set in the FlutterExtension, if it exists
* 3. `lib/main.dart` otherwise
*/
@JvmStatic
@JvmName("getFlutterTarget")
internal fun getFlutterTarget(project: Project): String {
if (project.hasProperty(PROP_TARGET)) {
return project.property(PROP_TARGET).toString()
}
val target: String = getFlutterExtensionOrNull(project)!!.target ?: "lib/main.dart"
return target
}
@JvmStatic
@JvmName("isBuiltAsApp")
internal fun isBuiltAsApp(project: Project): Boolean {
// Projects are built as applications when the they use the `com.android.application`
// plugin.
return project.plugins.hasPlugin("com.android.application")
}
// Optional parameters don't work when Groovy makes calls into Kotlin, so provide an additional
// signature for the 3 argument version.
@JvmStatic
@JvmName("addApiDependencies")
internal fun addApiDependencies(
project: Project,
variantName: String,
dependency: Any
) {
addApiDependencies(project, variantName, dependency, null)
}
@JvmStatic
@JvmName("addApiDependencies")
internal fun addApiDependencies(
project: Project,
variantName: String,
dependency: Any,
config: Closure<Any>?
) {
var configuration: String
try {
project.configurations.named("api")
configuration = "${variantName}Api"
} catch (ignored: UnknownTaskException) {
// TODO(gmackall): The docs say the above should actually be an UnknownDomainObjectException.
configuration = "${variantName}Compile"
}
if (config == null) {
project.dependencies.add(
configuration,
dependency
)
} else {
project.dependencies.add(configuration, dependency, config)
}
}
/**
* Returns a Flutter build mode suitable for the specified Android buildType.
*
* @return "debug", "profile", or "release" (fall-back).
*/
@JvmStatic
@JvmName("buildModeFor")
internal fun buildModeFor(buildType: BuildType): String {
if (buildType.name == "profile") {
return "profile"
} else if (buildType.isDebuggable) {
return "debug"
}
return "release"
}
/**
* Returns true if the build mode is supported by the current call to Gradle.
* This only relevant when using a local engine. Because the engine
* is built for a specific mode, the call to Gradle must match that mode.
*/
@JvmStatic
@JvmName("supportsBuildMode")
internal fun supportsBuildMode(
project: Project,
flutterBuildMode: String
): Boolean {
if (!shouldProjectUseLocalEngine(project)) {
return true
}
check(project.hasProperty(PROP_LOCAL_ENGINE_BUILD_MODE)) { "Project must have property '$PROP_LOCAL_ENGINE_BUILD_MODE'" }
// Don't configure dependencies for a build mode that the local engine
// doesn't support.
return project.property(PROP_LOCAL_ENGINE_BUILD_MODE) == flutterBuildMode
}
}

View File

@@ -0,0 +1,411 @@
package com.flutter.gradle
import com.android.builder.model.BuildType
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.UnknownTaskException
import org.gradle.api.logging.Logger
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.io.TempDir
import java.io.File
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class FlutterPluginUtilsTest {
// toCamelCase
@Test
fun `toCamelCase converts a list of strings to camel case`() {
val result = FlutterPluginUtils.toCamelCase(listOf("hello", "world"))
assertEquals("helloWorld", result)
}
@Test
fun `toCamelCase handles empty list`() {
val result = FlutterPluginUtils.toCamelCase(emptyList())
assertEquals("", result)
}
@Test
fun `toCamelCase handles single-element list`() {
val result = FlutterPluginUtils.toCamelCase(listOf("hello"))
assertEquals("hello", result)
}
// compareVersionStrings
@Test
fun `compareVersionStrings compares last element of version string correctly`() {
val result = FlutterPluginUtils.compareVersionStrings("1.2.3", "1.2.4")
assertEquals(-1, result)
}
@Test
fun `compareVersionStrings compares middle element of version string correctly`() {
val result = FlutterPluginUtils.compareVersionStrings("1.2.3", "1.1.4")
assertEquals(1, result)
}
@Test
fun `compareVersionStrings compares first element of version string correctly`() {
val result = FlutterPluginUtils.compareVersionStrings("1.2.3", "2.2.4")
assertEquals(-1, result)
}
@Test
fun `compareVersionStrings considers rc candidates the same`() {
val result = FlutterPluginUtils.compareVersionStrings("1.2.3-rc", "1.2.3")
assertEquals(0, result)
}
// shouldShrinkResources
@Test
fun `shouldShrinkResources returns true by default`() {
val project = mockk<Project>()
every { project.hasProperty(any()) } returns false
val result = FlutterPluginUtils.shouldShrinkResources(project)
assertEquals(true, result)
}
@Test
fun `shouldShrinkResources returns true when property is set to true`() {
val project = mockk<Project>()
every { project.hasProperty(FlutterPluginUtils.PROP_SHOULD_SHRINK_RESOURCES) } returns true
every { project.property(FlutterPluginUtils.PROP_SHOULD_SHRINK_RESOURCES) } returns true
val result = FlutterPluginUtils.shouldShrinkResources(project)
assertEquals(true, result)
}
// pluginSupportsAndroidPlatform
@Test
fun `pluginSupportsAndroidPlatform returns true when android directory exists with gradle build file`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("my-plugin")
projectDir.toFile().mkdirs()
val androidDir = tempDir.resolve("android")
androidDir.toFile().mkdirs()
File(androidDir.toFile(), "build.gradle").createNewFile()
val mockProject =
mockk<Project> {
every { this@mockk.projectDir } returns projectDir.toFile()
}
assertTrue {
FlutterPluginUtils.pluginSupportsAndroidPlatform(mockProject)
} // Replace YourClass with the actual class containing the method
}
@Test
fun `pluginSupportsAndroidPlatform returns false when gradle build file does not exist`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("my-plugin")
projectDir.toFile().mkdirs()
val mockProject =
mockk<Project> {
every { this@mockk.projectDir } returns projectDir.toFile()
}
assertFalse {
FlutterPluginUtils.pluginSupportsAndroidPlatform(mockProject)
} // Replace YourClass with the actual class containing the method
}
// settingsGradleFile
@Test
fun `settingsGradleFile returns groovy settings gradle file when it exists`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("android").resolve("app")
projectDir.toFile().mkdirs()
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
settingsGradle.createNewFile()
val result = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockk())
assertEquals(settingsGradle, result)
}
@Test
fun `settingsGradleFile returns groovy settings file and logs when both groovy and kotlin exist`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("android").resolve("app")
projectDir.toFile().mkdirs()
val groovySettingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
groovySettingsGradle.createNewFile()
val kotlinSettingsGradle = File(projectDir.parent.toFile(), "settings.gradle.kts")
kotlinSettingsGradle.createNewFile()
val mockLogger = mockk<Logger>()
every { mockLogger.error(any()) } returns Unit
val result = FlutterPluginUtils.getSettingsGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
assertEquals(groovySettingsGradle, result)
verify { mockLogger.error(any()) }
}
// buildGradleFile
@Test
fun `buildGradleFile returns groovy build gradle file when it exists`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("android").resolve("app")
projectDir.toFile().mkdirs()
val buildGradle = File(projectDir.parent.resolve("app").toFile(), "build.gradle")
buildGradle.createNewFile()
val result = FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockk())
assertEquals(buildGradle, result)
}
@Test
fun `buildGradleFile returns groovy build file and logs when both groovy and kotlin exist`(
@TempDir tempDir: Path
) {
val projectDir = tempDir.resolve("android").resolve("app")
projectDir.toFile().mkdirs()
val groovyBuildGradle = File(projectDir.parent.resolve("app").toFile(), "build.gradle")
groovyBuildGradle.createNewFile()
val kotlinBuildGradle = File(projectDir.parent.resolve("app").toFile(), "build.gradle.kts")
kotlinBuildGradle.createNewFile()
val mockLogger = mockk<Logger>()
every { mockLogger.error(any()) } returns Unit
val result = FlutterPluginUtils.getBuildGradleFileFromProjectDir(projectDir.toFile(), mockLogger)
assertEquals(groovyBuildGradle, result)
verify { mockLogger.error(any()) }
}
// shouldProjectSplitPerAbi
@Test
fun `shouldProjectSplitPerAbi returns false by default`() {
val project = mockk<Project>()
every { project.findProperty(FlutterPluginUtils.PROP_SPLIT_PER_ABI) } returns null
val result = FlutterPluginUtils.shouldProjectSplitPerAbi(project)
assertEquals(false, result)
}
@Test
fun `shouldProjectSplitPerAbi returns true when property is set to true`() {
val project = mockk<Project>()
every { project.findProperty(FlutterPluginUtils.PROP_SPLIT_PER_ABI) } returns "true"
val result = FlutterPluginUtils.shouldProjectSplitPerAbi(project)
assertEquals(true, result)
}
// shouldProjectUseLocalEngine skipped as it is a wrapper for a single getter
// isProjectVerbose
@Test
fun `isProjectVerbose returns false by default`() {
val project = mockk<Project>()
every { project.findProperty(FlutterPluginUtils.PROP_IS_VERBOSE) } returns null
val result = FlutterPluginUtils.isProjectVerbose(project)
assertEquals(false, result)
}
// isProjectVerbose
@Test
fun `isProjectVerbose returns true when the property is set to true`() {
val project = mockk<Project>()
every { project.findProperty(FlutterPluginUtils.PROP_IS_VERBOSE) } returns true
val result = FlutterPluginUtils.isProjectVerbose(project)
assertEquals(true, result)
}
// isProjectFastStart
@Test
fun `isProjectFastStart returns false by default`() {
val project = mockk<Project>()
every { project.findProperty(FlutterPluginUtils.PROP_IS_FAST_START) } returns null
val result = FlutterPluginUtils.isProjectFastStart(project)
assertEquals(false, result)
}
@Test
fun `isProjectFastStart returns true when the property is set to true`() {
val project = mockk<Project>()
every { project.findProperty(FlutterPluginUtils.PROP_IS_FAST_START) } returns true
val result = FlutterPluginUtils.isProjectFastStart(project)
assertEquals(true, result)
}
// shouldConfigureFlutterTask
@Test
fun `shouldConfigureFlutterTask returns true for assemble task`() {
val project = mockk<Project>()
val assembleTask = mockk<Task>()
every { project.gradle.startParameter.taskNames } returns listOf("assemble")
val result = FlutterPluginUtils.shouldConfigureFlutterTask(project, assembleTask)
assertEquals(true, result)
}
@Test
fun `shouldConfigureFlutterTask returns true when taskname and assembleTask end with Release`() {
val project = mockk<Project>()
val assembleTask = mockk<Task>()
every { project.gradle.startParameter.taskNames } returns listOf("assembleRelease")
every { assembleTask.name } returns "assembleSomethingElseRelease"
val result = FlutterPluginUtils.shouldConfigureFlutterTask(project, assembleTask)
assertEquals(true, result)
}
// getFlutterSourceDirectory
@Test
fun `getFlutterSourceDirectory returns the flutter source directory`() {
val flutterExtension = FlutterExtension()
val project = mockk<Project>()
flutterExtension.source = "my/flutter/source/directory"
every { project.extensions.findByType(FlutterExtension::class.java) } returns flutterExtension
every { project.file(any()) } returns mockk()
FlutterPluginUtils.getFlutterSourceDirectory(project)
verify { project.file("my/flutter/source/directory") }
}
@Test
fun `getFlutterSourceDirectory throws exception when flutter source directory is not set`() {
val flutterExtension = FlutterExtension()
val project = mockk<Project>()
flutterExtension.source = null
every { project.extensions.findByType(FlutterExtension::class.java) } returns flutterExtension
assertThrows<GradleException> {
FlutterPluginUtils.getFlutterSourceDirectory(project)
}
}
// getFlutterTarget
@Test
fun `getFlutterTarget returns the target when the project property is set`() {
val project = mockk<Project>()
every { project.hasProperty(FlutterPluginUtils.PROP_TARGET) } returns true
every { project.property(FlutterPluginUtils.PROP_TARGET) } returns "my/target"
val result = FlutterPluginUtils.getFlutterTarget(project)
assertEquals("my/target", result)
}
@Test
fun `getFlutterTarget returns the target from the FlutterExtension when it is set and project property is not set`() {
val flutterExtension = FlutterExtension()
val project = mockk<Project>()
flutterExtension.target = "my/target"
every { project.hasProperty(FlutterPluginUtils.PROP_TARGET) } returns false
every { project.extensions.findByType(FlutterExtension::class.java) } returns flutterExtension
val result = FlutterPluginUtils.getFlutterTarget(project)
assertEquals(flutterExtension.target, result)
}
@Test
fun `getFlutterTarget returns the default target when it is not set`() {
val project = mockk<Project>()
every { project.hasProperty(FlutterPluginUtils.PROP_TARGET) } returns false
every { project.extensions.findByType(FlutterExtension::class.java)!!.target } returns null
val result = FlutterPluginUtils.getFlutterTarget(project)
assertEquals("lib/main.dart", result)
}
// isBuiltAsApp skipped as it is a wrapper for a single getter
// addApiDependencies
@Test
fun `addApiDependencies adds the dependency with the correct name when no UnknownTaskException`() {
val project = mockk<Project>()
val variantName = "debug"
val dependency = mockk<Any>()
every { project.configurations.named("api") } returns mockk()
every { project.dependencies.add(any(), any()) } returns mockk()
FlutterPluginUtils.addApiDependencies(project, variantName, dependency)
verify { project.dependencies.add("debugApi", dependency) }
}
@Test
fun `addApiDependencies adds the dependency with the correct name when UnknownTaskException`() {
val project = mockk<Project>()
val variantName = "debug"
val dependency = mockk<Any>()
every { project.configurations.named("api") } throws UnknownTaskException("message", mockk())
every { project.dependencies.add(any(), any()) } returns mockk()
FlutterPluginUtils.addApiDependencies(project, variantName, dependency)
verify { project.dependencies.add("debugCompile", dependency) }
}
// buildModeFor
@Test
fun `buildModeFor returns profile if the BuildType has name profile`() {
val buildType = mockk<BuildType>()
every { buildType.name } returns "profile"
val result = FlutterPluginUtils.buildModeFor(buildType)
assertEquals("profile", result)
}
@Test
fun `buildModeFor returns debug if the BuildType is debuggable`() {
val buildType = mockk<BuildType>()
every { buildType.name } returns "something random"
every { buildType.isDebuggable } returns true
val result = FlutterPluginUtils.buildModeFor(buildType)
assertEquals("debug", result)
}
@Test
fun `buildModeFor returns release if the BuildType is not debuggable and not named profile`() {
val buildType = mockk<BuildType>()
every { buildType.isDebuggable } returns false
every { buildType.name } returns "something random"
val result = FlutterPluginUtils.buildModeFor(buildType)
assertEquals("release", result)
}
// supportsBuildMode
@Test
fun `supportsBuildMode returns true if project should not use local engine`() {
val project = mockk<Project>()
every { project.hasProperty(FlutterPluginUtils.PROP_LOCAL_ENGINE_REPO) } returns false
val result = FlutterPluginUtils.supportsBuildMode(project, "debug")
assertEquals(true, result)
}
@Test
fun `supportsBuildMode returns false if project should use local engine and build mode does not match`() {
val project = mockk<Project>()
every { project.hasProperty(FlutterPluginUtils.PROP_LOCAL_ENGINE_REPO) } returns true
every { project.hasProperty(FlutterPluginUtils.PROP_LOCAL_ENGINE_BUILD_MODE) } returns true
every { project.property(FlutterPluginUtils.PROP_LOCAL_ENGINE_BUILD_MODE) } returns "debug"
val result = FlutterPluginUtils.supportsBuildMode(project, "release")
assertEquals(false, result)
}
}