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:
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user