Port FlutterTask
from Groovy to Kotlin (#165244)
1. Ports `FlutterTask` from groovy to kotlin 2. Refactors `BaseFlutterTask` a bit, to follow a paradigm that methods on `BaseFlutterTask` and `FlutterTask` will contain no logic, and instead be set equal to a method with the same name in `BaseFlutterTaskHelper` and `FlutterTaskHelper` respectively, and that both of those helpers will be stateless. This allows us to somewhat get around the fact that you cannot create a `Task` instance in testing, because testing the logic of the task is the same as testing the logic of the helper. 3. Also creates a `FlutterPluginConstants` object to hold the constants from `FlutterPlugin`. I think ideally this will be collapsed back in to `FlutterPlugin` when the conversion is completed, but it needs to be moved for now so it can be referenced in Kotlin code (this pr needs it). Fixes https://github.com/flutter/flutter/issues/162112 ## 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. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Gray Mackall <mackall@google.com>
This commit is contained in:
parent
f7d11ddecc
commit
bd166030e0
@ -11,10 +11,11 @@ import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||
import com.android.build.gradle.tasks.ProcessAndroidResources
|
||||
import com.android.builder.model.BuildType
|
||||
import com.flutter.gradle.BaseApplicationNameHandler
|
||||
import com.flutter.gradle.BaseFlutterTask
|
||||
import com.flutter.gradle.Deeplink
|
||||
import com.flutter.gradle.DependencyVersionChecker
|
||||
import com.flutter.gradle.FlutterExtension
|
||||
import com.flutter.gradle.FlutterPluginConstants
|
||||
import com.flutter.gradle.FlutterTask
|
||||
import com.flutter.gradle.FlutterPluginUtils
|
||||
import com.flutter.gradle.IntentFilterCheck
|
||||
import com.flutter.gradle.VersionUtils
|
||||
@ -29,14 +30,7 @@ import org.gradle.api.Project
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.UnknownTaskException
|
||||
import org.gradle.api.file.CopySpec
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.Copy
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.bundling.Jar
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
@ -44,49 +38,6 @@ import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
class FlutterPlugin implements Plugin<Project> {
|
||||
|
||||
private static final String DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
|
||||
|
||||
/** The platforms that can be passed to the `--Ptarget-platform` flag. */
|
||||
private static final String PLATFORM_ARM32 = "android-arm"
|
||||
private static final String PLATFORM_ARM64 = "android-arm64"
|
||||
private static final String PLATFORM_X86 = "android-x86"
|
||||
private static final String PLATFORM_X86_64 = "android-x64"
|
||||
|
||||
/** The ABI architectures supported by Flutter. */
|
||||
private static final String ARCH_ARM32 = "armeabi-v7a"
|
||||
private static final String ARCH_ARM64 = "arm64-v8a"
|
||||
private static final String ARCH_X86 = "x86"
|
||||
private static final String ARCH_X86_64 = "x86_64"
|
||||
|
||||
private static final String INTERMEDIATES_DIR = "intermediates"
|
||||
|
||||
/** Maps platforms to ABI architectures. */
|
||||
private static final Map PLATFORM_ARCH_MAP = [
|
||||
(PLATFORM_ARM32) : ARCH_ARM32,
|
||||
(PLATFORM_ARM64) : ARCH_ARM64,
|
||||
(PLATFORM_X86) : ARCH_X86,
|
||||
(PLATFORM_X86_64) : ARCH_X86_64,
|
||||
]
|
||||
|
||||
/**
|
||||
* The version code that gives each ABI a value.
|
||||
* For each APK variant, use the following versions to override the version of the Universal APK.
|
||||
* Otherwise, the Play Store will complain that the APK variants have the same version.
|
||||
*/
|
||||
private static final Map<String, Integer> ABI_VERSION = [
|
||||
(ARCH_ARM32) : 1,
|
||||
(ARCH_ARM64) : 2,
|
||||
(ARCH_X86) : 3,
|
||||
(ARCH_X86_64) : 4,
|
||||
]
|
||||
|
||||
/** When split is enabled, multiple APKs are generated per each ABI. */
|
||||
private static final List DEFAULT_PLATFORMS = [
|
||||
PLATFORM_ARM32,
|
||||
PLATFORM_ARM64,
|
||||
PLATFORM_X86_64,
|
||||
]
|
||||
|
||||
private final static String propLocalEngineRepo = "local-engine-repo"
|
||||
private final static String propProcessResourcesProvider = "processResourcesProvider"
|
||||
|
||||
@ -156,7 +107,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
|
||||
// Configure the Maven repository.
|
||||
String hostedRepository = System.getenv("FLUTTER_STORAGE_BASE_URL") ?: DEFAULT_MAVEN_HOST
|
||||
String hostedRepository = System.getenv(FlutterPluginConstants.FLUTTER_STORAGE_BASE_URL) ?: FlutterPluginConstants.DEFAULT_MAVEN_HOST
|
||||
String repository = FlutterPluginUtils.shouldProjectUseLocalEngine(project)
|
||||
? project.property(propLocalEngineRepo)
|
||||
: "$hostedRepository/${engineRealm}download.flutter.io"
|
||||
@ -212,7 +163,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
|
||||
getTargetPlatforms().each { targetArch ->
|
||||
String abiValue = PLATFORM_ARCH_MAP[targetArch]
|
||||
String abiValue = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
|
||||
project.android {
|
||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||
splits {
|
||||
@ -495,7 +446,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
List<String> platforms = getTargetPlatforms().collect()
|
||||
platforms.each { platform ->
|
||||
String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
|
||||
String arch = FlutterPluginUtils.formatPlatformString(platform)
|
||||
// Add the `libflutter.so` dependency.
|
||||
FlutterPluginUtils.addApiDependencies(project, buildType.name,
|
||||
"io.flutter:${arch}_$flutterBuildMode:$engineVersion")
|
||||
@ -851,10 +802,10 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
private List<String> getTargetPlatforms() {
|
||||
final String propTargetPlatform = "target-platform"
|
||||
if (!project.hasProperty(propTargetPlatform)) {
|
||||
return DEFAULT_PLATFORMS
|
||||
return FlutterPluginConstants.DEFAULT_PLATFORMS
|
||||
}
|
||||
return project.property(propTargetPlatform).split(",").collect {
|
||||
if (!PLATFORM_ARCH_MAP[it]) {
|
||||
if (!FlutterPluginConstants.PLATFORM_ARCH_MAP[it]) {
|
||||
throw new GradleException("Invalid platform: $it.")
|
||||
}
|
||||
return it
|
||||
@ -952,7 +903,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
// for only the output APK, not for the variant itself. Skipping this step simply
|
||||
// causes Gradle to use the value of variant.versionCode for the APK.
|
||||
// For more, see https://developer.android.com/studio/build/configure-apk-splits
|
||||
Integer abiVersionCode = ABI_VERSION[output.getFilter(OutputFile.ABI)]
|
||||
Integer abiVersionCode = FlutterPluginConstants.ABI_VERSION[output.getFilter(OutputFile.ABI)]
|
||||
if (abiVersionCode != null) {
|
||||
output.versionCodeOverride =
|
||||
abiVersionCode * 1000 + variant.versionCode
|
||||
@ -1002,7 +953,7 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
trackWidgetCreation(trackWidgetCreationValue)
|
||||
targetPlatformValues = targetPlatforms
|
||||
sourceDir(FlutterPluginUtils.getFlutterSourceDirectory(project))
|
||||
intermediateDir(project.file(project.layout.buildDirectory.dir("$INTERMEDIATES_DIR/flutter/${variant.name}/")))
|
||||
intermediateDir(project.file(project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/")))
|
||||
frontendServerStarterPath(frontendServerStarterPathValue)
|
||||
extraFrontEndOptions(extraFrontEndOptionsValue)
|
||||
extraGenSnapshotOptions(extraGenSnapshotOptionsValue)
|
||||
@ -1017,13 +968,13 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
flavor(flavorValue)
|
||||
}
|
||||
Task compileTask = compileTaskProvider.get()
|
||||
File libJar = project.file(project.layout.buildDirectory.dir("$INTERMEDIATES_DIR/flutter/${variant.name}/libs.jar"))
|
||||
File libJar = project.file(project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/libs.jar"))
|
||||
TaskProvider<Jar> packJniLibsTaskProvider = project.tasks.register("packJniLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", Jar) {
|
||||
destinationDirectory = libJar.parentFile
|
||||
archiveFileName = libJar.name
|
||||
dependsOn(compileTask)
|
||||
targetPlatforms.each { targetPlatform ->
|
||||
String abi = PLATFORM_ARCH_MAP[targetPlatform]
|
||||
String abi = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform]
|
||||
from("${compileTask.intermediateDir}/${abi}") {
|
||||
include("*.so")
|
||||
// Move `app.so` to `lib/<abi>/libapp.so`
|
||||
@ -1212,74 +1163,3 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
detectLowCompileSdkVersionOrNdkVersion()
|
||||
}
|
||||
}
|
||||
|
||||
class FlutterTask extends BaseFlutterTask {
|
||||
|
||||
@OutputDirectory
|
||||
File getOutputDirectory() {
|
||||
return intermediateDir
|
||||
}
|
||||
|
||||
@Internal
|
||||
String getAssetsDirectory() {
|
||||
return "${outputDirectory}/flutter_assets"
|
||||
}
|
||||
|
||||
@Internal
|
||||
CopySpec getAssets() {
|
||||
return project.copySpec {
|
||||
from("${intermediateDir}")
|
||||
include("flutter_assets/**") // the working dir and its files
|
||||
}
|
||||
}
|
||||
|
||||
@Internal
|
||||
CopySpec getSnapshots() {
|
||||
return project.copySpec {
|
||||
from("${intermediateDir}")
|
||||
|
||||
if (buildMode == "release" || buildMode == "profile") {
|
||||
targetPlatformValues.each {
|
||||
include("${PLATFORM_ARCH_MAP[targetArch]}/app.so")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileCollection readDependencies(File dependenciesFile, Boolean inputs) {
|
||||
if (dependenciesFile.exists()) {
|
||||
// Dependencies file has Makefile syntax:
|
||||
// <target> <files>: <source> <files> <separated> <by> <non-escaped space>
|
||||
String depText = dependenciesFile.text
|
||||
// So we split list of files by non-escaped(by backslash) space,
|
||||
def matcher = depText.split(": ")[inputs ? 1 : 0] =~ /(\\ |\S)+/
|
||||
// then we replace all escaped spaces with regular spaces
|
||||
def depList = matcher.collect{ it[0].replaceAll("\\\\ ", " ") }
|
||||
return project.files(depList)
|
||||
}
|
||||
return project.files()
|
||||
}
|
||||
|
||||
@InputFiles
|
||||
FileCollection getSourceFiles() {
|
||||
FileCollection sources = project.files()
|
||||
for (File depfile in getDependenciesFiles()) {
|
||||
sources += readDependencies(depfile, true)
|
||||
}
|
||||
return sources + project.files("pubspec.yaml")
|
||||
}
|
||||
|
||||
@OutputFiles
|
||||
FileCollection getOutputFiles() {
|
||||
FileCollection sources = project.files()
|
||||
for (File depfile in getDependenciesFiles()) {
|
||||
sources += readDependencies(depfile, false)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
void build() {
|
||||
buildBundle()
|
||||
}
|
||||
}
|
||||
|
@ -5,15 +5,20 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.Optional
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import java.io.File
|
||||
|
||||
abstract class BaseFlutterTask : DefaultTask() {
|
||||
// IMPORTANT: Do not add logic to the methods in this class directly,
|
||||
// instead add logic to [BaseFlutterTaskHelper].
|
||||
|
||||
/**
|
||||
* Base implementation of a Gradle task. Gradle tasks can not be instantiated for testing,
|
||||
* so this class delegates all logic to [BaseFlutterTaskHelper].
|
||||
*/
|
||||
open class BaseFlutterTask : DefaultTask() {
|
||||
@Internal
|
||||
var flutterRoot: File? = null
|
||||
|
||||
@ -132,21 +137,12 @@ abstract class BaseFlutterTask : DefaultTask() {
|
||||
* @return the dependency file(s) based on the current intermediate directory path.
|
||||
*/
|
||||
@OutputFiles
|
||||
fun getDependenciesFiles(): FileCollection {
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask = this)
|
||||
val depFiles = helper.getDependenciesFiles()
|
||||
return depFiles
|
||||
}
|
||||
fun getDependenciesFiles() = BaseFlutterTaskHelper.getDependenciesFiles(baseFlutterTask = this)
|
||||
|
||||
/**
|
||||
* Builds a Flutter Android application bundle by verifying the Flutter source directory,
|
||||
* creating an intermediate build directory if necessary, and running flutter assemble by
|
||||
* configuring and executing with a set of build configurations.
|
||||
*/
|
||||
fun buildBundle() {
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask = this)
|
||||
helper.checkPreConditions()
|
||||
logging.captureStandardError(LogLevel.ERROR)
|
||||
project.exec(helper.createExecSpecActionFromTask())
|
||||
}
|
||||
fun buildBundle() = BaseFlutterTaskHelper.buildBundle(baseFlutterTask = this)
|
||||
}
|
||||
|
@ -4,15 +4,19 @@ import androidx.annotation.VisibleForTesting
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.process.ExecSpec
|
||||
import java.nio.file.Paths
|
||||
|
||||
class BaseFlutterTaskHelper(
|
||||
private var baseFlutterTask: BaseFlutterTask
|
||||
) {
|
||||
/**
|
||||
* Stateless object to contain the logic used in [BaseFlutterTask]. Any required state should be stored
|
||||
* on [BaseFlutterTask] instead, while any logic needed by [BaseFlutterTask] should be added here.
|
||||
*/
|
||||
object BaseFlutterTaskHelper {
|
||||
@VisibleForTesting
|
||||
internal var gradleErrorMessage = "Invalid Flutter source directory: ${baseFlutterTask.sourceDir}"
|
||||
internal fun getGradleErrorMessage(baseFlutterTask: BaseFlutterTask): String =
|
||||
"Invalid Flutter source directory: ${baseFlutterTask.sourceDir}"
|
||||
|
||||
/**
|
||||
* Gets the dependency file(s) that tracks the dependencies or input files used for a specific
|
||||
@ -22,7 +26,7 @@ class BaseFlutterTaskHelper(
|
||||
*/
|
||||
@OutputFiles
|
||||
@VisibleForTesting
|
||||
internal fun getDependenciesFiles(): FileCollection {
|
||||
internal fun getDependenciesFiles(baseFlutterTask: BaseFlutterTask): FileCollection {
|
||||
var depfiles: FileCollection = baseFlutterTask.project.files()
|
||||
|
||||
// TODO(jesswon): During cleanup determine if .../flutter_build.d is ever a directory and refactor accordingly
|
||||
@ -38,9 +42,9 @@ class BaseFlutterTaskHelper(
|
||||
* @throws GradleException if sourceDir is null or is not a directory
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal fun checkPreConditions() {
|
||||
internal fun checkPreConditions(baseFlutterTask: BaseFlutterTask) {
|
||||
if (baseFlutterTask.sourceDir == null || !baseFlutterTask.sourceDir!!.isDirectory) {
|
||||
throw GradleException(gradleErrorMessage)
|
||||
throw GradleException(getGradleErrorMessage(baseFlutterTask))
|
||||
}
|
||||
baseFlutterTask.intermediateDir!!.mkdirs()
|
||||
}
|
||||
@ -77,8 +81,7 @@ class BaseFlutterTaskHelper(
|
||||
*
|
||||
* @return an Action<ExecSpec> of build processes and options to be executed.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal fun createExecSpecActionFromTask(): Action<ExecSpec> =
|
||||
internal fun createExecSpecActionFromTask(baseFlutterTask: BaseFlutterTask): Action<ExecSpec> =
|
||||
Action<ExecSpec> {
|
||||
executable(baseFlutterTask.flutterExecutable!!.absolutePath)
|
||||
workingDir(baseFlutterTask.sourceDir)
|
||||
@ -146,4 +149,10 @@ class BaseFlutterTaskHelper(
|
||||
args("-dMinSdkVersion=${baseFlutterTask.minSdkVersion}")
|
||||
args(generateRuleNames(baseFlutterTask))
|
||||
}
|
||||
|
||||
fun buildBundle(baseFlutterTask: BaseFlutterTask) {
|
||||
checkPreConditions(baseFlutterTask)
|
||||
baseFlutterTask.logging.captureStandardError(LogLevel.ERROR)
|
||||
baseFlutterTask.project.exec(createExecSpecActionFromTask(baseFlutterTask = baseFlutterTask))
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
// TODO(gmackall): this should be collapsed back into the core FlutterPlugin once the Groovy to
|
||||
// kotlin conversion is complete.
|
||||
object FlutterPluginConstants {
|
||||
/** The platforms that can be passed to the `--Ptarget-platform` flag. */
|
||||
private const val PLATFORM_ARM32 = "android-arm"
|
||||
private const val PLATFORM_ARM64 = "android-arm64"
|
||||
private const val PLATFORM_X86 = "android-x86"
|
||||
private const val PLATFORM_X86_64 = "android-x64"
|
||||
|
||||
/** The ABI architectures supported by Flutter. */
|
||||
private const val ARCH_ARM32 = "armeabi-v7a"
|
||||
private const val ARCH_ARM64 = "arm64-v8a"
|
||||
private const val ARCH_X86 = "x86"
|
||||
private const val ARCH_X86_64 = "x86_64"
|
||||
|
||||
const val INTERMEDIATES_DIR = "intermediates"
|
||||
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
||||
const val DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
|
||||
|
||||
/** Maps platforms to ABI architectures. */
|
||||
@JvmStatic val PLATFORM_ARCH_MAP =
|
||||
mapOf(
|
||||
PLATFORM_ARM32 to ARCH_ARM32,
|
||||
PLATFORM_ARM64 to ARCH_ARM64,
|
||||
PLATFORM_X86 to ARCH_X86,
|
||||
PLATFORM_X86_64 to ARCH_X86_64
|
||||
)
|
||||
|
||||
/**
|
||||
* The version code that gives each ABI a value.
|
||||
* For each APK variant, use the following versions to override the version of the Universal APK.
|
||||
* Otherwise, the Play Store will complain that the APK variants have the same version.
|
||||
*/
|
||||
@JvmStatic val ABI_VERSION =
|
||||
mapOf<String, Int>( // Explicit type for clarity, though inferred
|
||||
ARCH_ARM32 to 1,
|
||||
ARCH_ARM64 to 2,
|
||||
ARCH_X86 to 3,
|
||||
ARCH_X86_64 to 4
|
||||
)
|
||||
|
||||
/** When split is enabled, multiple APKs are generated per each ABI. */
|
||||
@JvmStatic val DEFAULT_PLATFORMS =
|
||||
listOf(
|
||||
PLATFORM_ARM32,
|
||||
PLATFORM_ARM64,
|
||||
PLATFORM_X86_64
|
||||
)
|
||||
}
|
@ -85,6 +85,10 @@ object FlutterPluginUtils {
|
||||
return firstVersion.size.compareTo(secondVersion.size)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmName("formatPlatformString")
|
||||
fun formatPlatformString(platform: String): String = FlutterPluginConstants.PLATFORM_ARCH_MAP[platform]!!.replace("-", "_")
|
||||
|
||||
// ----------------- Methods that interact primarily with the Gradle project. -----------------
|
||||
|
||||
@JvmStatic
|
||||
|
46
packages/flutter_tools/gradle/src/main/kotlin/FlutterTask.kt
Normal file
46
packages/flutter_tools/gradle/src/main/kotlin/FlutterTask.kt
Normal file
@ -0,0 +1,46 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import org.gradle.api.file.CopySpec
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
import org.gradle.api.tasks.Internal
|
||||
import org.gradle.api.tasks.OutputDirectory
|
||||
import org.gradle.api.tasks.OutputFiles
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
|
||||
// IMPORTANT: Do not add logic to the methods in this class directly,
|
||||
// instead add logic to [FlutterTaskHelper].
|
||||
|
||||
/**
|
||||
* Flutter's implementation of a Gradle task. Gradle tasks can not be instantiated for testing,
|
||||
* so this class delegates all logic to [FlutterTaskHelper].
|
||||
*/
|
||||
abstract class FlutterTask : BaseFlutterTask() {
|
||||
@get:OutputDirectory
|
||||
val outputDirectory: File?
|
||||
get() = FlutterTaskHelper.getOutputDirectory(flutterTask = this)
|
||||
|
||||
@get:Internal
|
||||
val assetsDirectory: String
|
||||
get() = FlutterTaskHelper.getAssetsDirectory(flutterTask = this)
|
||||
|
||||
@get:Internal
|
||||
val assets: CopySpec
|
||||
get() = FlutterTaskHelper.getAssets(project, flutterTask = this)
|
||||
|
||||
@get:Internal
|
||||
val snapshots: CopySpec
|
||||
get() = FlutterTaskHelper.getSnapshots(project, flutterTask = this)
|
||||
|
||||
@get:InputFiles
|
||||
val sourceFiles: FileCollection
|
||||
get() = FlutterTaskHelper.getSourceFiles(project, flutterTask = this)
|
||||
|
||||
@get:OutputFiles
|
||||
val outputFiles: FileCollection
|
||||
get() = FlutterTaskHelper.getOutputFiles(project, flutterTask = this)
|
||||
|
||||
@TaskAction
|
||||
fun build() = FlutterTaskHelper.build(flutterTask = this)
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.CopySpec
|
||||
import org.gradle.api.file.FileCollection
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Stateless object to contain the logic used in [FlutterTask]. Any required state should be stored
|
||||
* on [FlutterTask] instead, while any logic needed by [FlutterTask] should be added here.
|
||||
*/
|
||||
object FlutterTaskHelper {
|
||||
const val FLUTTER_ASSETS_INCLUDE_DIRECTORY = "flutter_assets/**"
|
||||
|
||||
internal fun getOutputDirectory(flutterTask: FlutterTask): File? = flutterTask.intermediateDir
|
||||
|
||||
internal fun getAssetsDirectory(flutterTask: FlutterTask): String = "${flutterTask.outputDirectory}/flutter_assets"
|
||||
|
||||
internal fun getAssets(
|
||||
project: Project,
|
||||
flutterTask: FlutterTask
|
||||
): CopySpec =
|
||||
project.copySpec {
|
||||
from("${flutterTask.intermediateDir}")
|
||||
include(FLUTTER_ASSETS_INCLUDE_DIRECTORY) // the working dir and its files
|
||||
}
|
||||
|
||||
internal fun getSnapshots(
|
||||
project: Project,
|
||||
flutterTask: FlutterTask
|
||||
): CopySpec =
|
||||
project.copySpec {
|
||||
from("${flutterTask.intermediateDir}")
|
||||
if (flutterTask.buildMode == "release" || flutterTask.buildMode == "profile") {
|
||||
flutterTask.targetPlatformValues!!.forEach { targetArch ->
|
||||
include("${FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]}/app.so")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readDependencies(
|
||||
project: Project,
|
||||
dependenciesFile: File,
|
||||
inputs: Boolean
|
||||
): FileCollection {
|
||||
if (dependenciesFile.exists()) {
|
||||
// Dependencies file has Makefile syntax:
|
||||
// <target> <files>: <source> <files> <separated> <by> <non-escaped space>
|
||||
val depText = dependenciesFile.readText()
|
||||
// So we split list of files by non-escaped(by backslash) space,
|
||||
val parts = depText.split(": ")
|
||||
val fileString = parts[if (inputs) 1 else 0]
|
||||
val matcher = Regex("""(\\ |\S)+""").findAll(fileString)
|
||||
// then we replace all escaped spaces with regular spaces
|
||||
val depList =
|
||||
matcher.map { it.value.replace("\\\\ ", " ") }.toList()
|
||||
return project.files(depList)
|
||||
}
|
||||
return project.files()
|
||||
}
|
||||
|
||||
internal fun getSourceFiles(
|
||||
project: Project,
|
||||
flutterTask: FlutterTask
|
||||
): FileCollection {
|
||||
var sources: FileCollection = project.files()
|
||||
flutterTask.getDependenciesFiles().forEach { dependenciesFile ->
|
||||
sources += readDependencies(project, dependenciesFile, true)
|
||||
}
|
||||
return sources + project.files("pubspec.yaml")
|
||||
}
|
||||
|
||||
internal fun getOutputFiles(
|
||||
project: Project,
|
||||
flutterTask: FlutterTask
|
||||
): FileCollection {
|
||||
var outputs: FileCollection = project.files()
|
||||
flutterTask.getDependenciesFiles().forEach { dependenciesFile ->
|
||||
outputs += readDependencies(project, dependenciesFile, false)
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
internal fun build(flutterTask: FlutterTask) {
|
||||
flutterTask.buildBundle()
|
||||
}
|
||||
}
|
@ -3,7 +3,11 @@ package com.flutter.gradle
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.logging.LoggingManager
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.gradle.process.ProcessForkOptions
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
@ -48,13 +52,11 @@ class BaseFlutterTaskHelperTest {
|
||||
val baseFlutterTask = mockk<BaseFlutterTask>()
|
||||
every { baseFlutterTask.sourceDir } returns null
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
|
||||
val gradleException =
|
||||
assertFailsWith<GradleException> { helper.checkPreConditions() }
|
||||
assertFailsWith<GradleException> { BaseFlutterTaskHelper.checkPreConditions(baseFlutterTask) }
|
||||
assert(
|
||||
gradleException.message ==
|
||||
helper.gradleErrorMessage
|
||||
BaseFlutterTaskHelper.getGradleErrorMessage(baseFlutterTask)
|
||||
)
|
||||
}
|
||||
|
||||
@ -64,13 +66,11 @@ class BaseFlutterTaskHelperTest {
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
every { baseFlutterTask.sourceDir!!.isDirectory } returns false
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
|
||||
val gradleException =
|
||||
assertFailsWith<GradleException> { helper.checkPreConditions() }
|
||||
assertFailsWith<GradleException> { BaseFlutterTaskHelper.checkPreConditions(baseFlutterTask) }
|
||||
assert(
|
||||
gradleException.message ==
|
||||
helper.gradleErrorMessage
|
||||
BaseFlutterTaskHelper.getGradleErrorMessage(baseFlutterTask)
|
||||
)
|
||||
}
|
||||
|
||||
@ -86,8 +86,7 @@ class BaseFlutterTaskHelperTest {
|
||||
// There is already an intermediate directory, so there is no need to create it.
|
||||
every { baseFlutterTask.intermediateDir!!.mkdirs() } returns false
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
assertDoesNotThrow { helper.checkPreConditions() }
|
||||
assertDoesNotThrow { BaseFlutterTaskHelper.checkPreConditions(baseFlutterTask) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -100,8 +99,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
every { baseFlutterTask.buildMode } returns buildModeString
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val ruleNamesList = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
assertEquals(ruleNamesList, listOf("debug_android_application"))
|
||||
}
|
||||
@ -118,8 +116,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { baseFlutterTask.deferredComponents } returns true
|
||||
every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val ruleNamesList = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
assertEquals(
|
||||
ruleNamesList,
|
||||
@ -142,8 +139,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { baseFlutterTask.deferredComponents } returns false
|
||||
every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val ruleNamesList = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
assertEquals(
|
||||
ruleNamesList,
|
||||
@ -166,8 +162,7 @@ class BaseFlutterTaskHelperTest {
|
||||
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
|
||||
// before creating a BaseFlutterTaskHelper object.
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
|
||||
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
|
||||
|
||||
// Mock return values of properties.
|
||||
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
|
||||
@ -216,7 +211,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
|
||||
|
||||
// Generate rule names for verification and can only be generated after buildMode is mocked.
|
||||
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
|
||||
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
|
||||
@ -267,8 +262,7 @@ class BaseFlutterTaskHelperTest {
|
||||
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
|
||||
// before creating a BaseFlutterTaskHelper object.
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
|
||||
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
|
||||
|
||||
// Mock return values of properties.
|
||||
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
|
||||
@ -317,7 +311,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
|
||||
|
||||
// Generate rule names for verification and can only be generated after buildMode is mocked.
|
||||
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
|
||||
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
|
||||
@ -353,8 +347,7 @@ class BaseFlutterTaskHelperTest {
|
||||
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
|
||||
// before creating a BaseFlutterTaskHelper object.
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
|
||||
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
|
||||
|
||||
// Mock return values of properties.
|
||||
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
|
||||
@ -402,7 +395,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
|
||||
|
||||
// Generate rule names for verification and can only be generated after buildMode is mocked.
|
||||
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
|
||||
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
|
||||
@ -453,8 +446,7 @@ class BaseFlutterTaskHelperTest {
|
||||
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
|
||||
// before creating a BaseFlutterTaskHelper object.
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
|
||||
val execSpecActionFromTask = BaseFlutterTaskHelper.createExecSpecActionFromTask(baseFlutterTask)
|
||||
|
||||
// Mock return values of properties.
|
||||
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
|
||||
@ -503,7 +495,7 @@ class BaseFlutterTaskHelperTest {
|
||||
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
|
||||
|
||||
// Generate rule names for verification and can only be generated after buildMode is mocked.
|
||||
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
|
||||
val ruleNamesList: List<String> = BaseFlutterTaskHelper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
|
||||
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
|
||||
@ -541,4 +533,46 @@ class BaseFlutterTaskHelperTest {
|
||||
verify { mockExecSpec.args("-dMinSdkVersion=${BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST}") }
|
||||
verify { mockExecSpec.args(ruleNamesList) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildBundle calls the correct methods`() {
|
||||
val baseFlutterTask = mockk<BaseFlutterTask>()
|
||||
val mockLoggingManager = mockk<LoggingManager>()
|
||||
val mockFile = mockk<File>()
|
||||
val mockProject = mockk<Project>()
|
||||
|
||||
// When baseFlutterTask.sourceDir is null, an exception is thrown. We mock its return value
|
||||
// before creating a BaseFlutterTaskHelper object.
|
||||
every { baseFlutterTask.sourceDir } returns mockFile
|
||||
every { mockFile.isDirectory } returns true
|
||||
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
|
||||
every { baseFlutterTask.logging } returns mockLoggingManager
|
||||
every { mockLoggingManager.captureStandardError(any()) } returns mockLoggingManager
|
||||
every { baseFlutterTask.project } returns mockProject
|
||||
every { mockProject.exec(any<Action<ExecSpec>>()) } returns mockk()
|
||||
|
||||
BaseFlutterTaskHelper.buildBundle(baseFlutterTask)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDependencyFiles returns a FileCollection of dependency file(s)`() {
|
||||
val baseFlutterTask = mockk<BaseFlutterTask>()
|
||||
val project = mockk<Project>()
|
||||
val configFileCollection = mockk<ConfigurableFileCollection>()
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
|
||||
every { baseFlutterTask.project } returns project
|
||||
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
|
||||
|
||||
val projectIntermediary = baseFlutterTask.project
|
||||
val interDirFile = baseFlutterTask.intermediateDir
|
||||
|
||||
every { projectIntermediary.files() } returns configFileCollection
|
||||
every { projectIntermediary.files("$interDirFile/flutter_build.d") } returns configFileCollection
|
||||
every { configFileCollection.plus(configFileCollection) } returns configFileCollection
|
||||
|
||||
BaseFlutterTaskHelper.getDependenciesFiles(baseFlutterTask)
|
||||
verify { projectIntermediary.files() }
|
||||
verify { projectIntermediary.files("${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}/flutter_build.d") }
|
||||
}
|
||||
}
|
||||
|
@ -1,147 +0,0 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import com.flutter.gradle.BaseFlutterTaskHelperTest.BaseFlutterTaskPropertiesTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.process.ExecSpec
|
||||
import org.gradle.process.ProcessForkOptions
|
||||
import org.junit.jupiter.api.assertDoesNotThrow
|
||||
import kotlin.test.Test
|
||||
|
||||
class BaseFlutterTaskTest {
|
||||
@Test
|
||||
fun `getDependencyFiles returns a FileCollection of dependency file(s)`() {
|
||||
val baseFlutterTask = mockk<BaseFlutterTask>()
|
||||
val project = mockk<Project>()
|
||||
val configFileCollection = mockk<ConfigurableFileCollection>()
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
|
||||
every { baseFlutterTask.project } returns project
|
||||
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
|
||||
|
||||
val projectIntermediary = baseFlutterTask.project
|
||||
val interDirFile = baseFlutterTask.intermediateDir
|
||||
|
||||
every { projectIntermediary.files() } returns configFileCollection
|
||||
every { projectIntermediary.files("$interDirFile/flutter_build.d") } returns configFileCollection
|
||||
every { configFileCollection.plus(configFileCollection) } returns configFileCollection
|
||||
|
||||
helper.getDependenciesFiles()
|
||||
verify { projectIntermediary.files() }
|
||||
verify { projectIntermediary.files("${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}/flutter_build.d") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildBundle builds a Flutter application bundle for Android`() {
|
||||
val buildModeString = "debug"
|
||||
|
||||
// Create necessary mocks.
|
||||
val baseFlutterTask = mockk<BaseFlutterTask>()
|
||||
val mockExecSpec = mockk<ExecSpec>()
|
||||
val mockProcessForkOptions = mockk<ProcessForkOptions>()
|
||||
|
||||
// Check preconditions
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
every { baseFlutterTask.sourceDir!!.isDirectory } returns true
|
||||
|
||||
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
|
||||
every { baseFlutterTask.intermediateDir!!.mkdirs() } returns false
|
||||
|
||||
val helper = BaseFlutterTaskHelper(baseFlutterTask)
|
||||
assertDoesNotThrow { helper.checkPreConditions() }
|
||||
|
||||
// Create action to be executed.
|
||||
val execSpecActionFromTask = helper.createExecSpecActionFromTask()
|
||||
|
||||
// Mock return values of properties.
|
||||
every { baseFlutterTask.flutterExecutable } returns BaseFlutterTaskPropertiesTest.flutterExecutableTest
|
||||
every {
|
||||
baseFlutterTask.flutterExecutable!!.absolutePath
|
||||
} returns BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST
|
||||
every { baseFlutterTask.sourceDir } returns BaseFlutterTaskPropertiesTest.sourceDirTest
|
||||
|
||||
every { baseFlutterTask.localEngine } returns BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_TEST
|
||||
every { baseFlutterTask.localEngineSrcPath } returns BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_SRC_PATH_TEST
|
||||
|
||||
every { baseFlutterTask.localEngineHost } returns BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_HOST_TEST
|
||||
every { baseFlutterTask.verbose } returns true
|
||||
every { baseFlutterTask.intermediateDir } returns BaseFlutterTaskPropertiesTest.intermediateDirFileTest
|
||||
every { baseFlutterTask.performanceMeasurementFile } returns BaseFlutterTaskPropertiesTest.PERFORMANCE_MEASUREMENT_FILE_TEST
|
||||
|
||||
every { baseFlutterTask.fastStart } returns true
|
||||
every { baseFlutterTask.buildMode } returns buildModeString
|
||||
every { baseFlutterTask.flutterRoot } returns BaseFlutterTaskPropertiesTest.flutterRootTest
|
||||
every { baseFlutterTask.flutterRoot!!.absolutePath } returns BaseFlutterTaskPropertiesTest.FLUTTER_ROOT_ABSOLUTE_PATH_TEST
|
||||
|
||||
every { baseFlutterTask.trackWidgetCreation } returns true
|
||||
every { baseFlutterTask.splitDebugInfo } returns BaseFlutterTaskPropertiesTest.SPLIT_DEBUG_INFO_TEST
|
||||
every { baseFlutterTask.treeShakeIcons } returns true
|
||||
|
||||
every { baseFlutterTask.dartObfuscation } returns true
|
||||
every { baseFlutterTask.dartDefines } returns BaseFlutterTaskPropertiesTest.DART_DEFINES_TEST
|
||||
every { baseFlutterTask.bundleSkSLPath } returns BaseFlutterTaskPropertiesTest.BUNDLE_SK_SL_PATH_TEST
|
||||
|
||||
every { baseFlutterTask.codeSizeDirectory } returns BaseFlutterTaskPropertiesTest.CODE_SIZE_DIRECTORY_TEST
|
||||
every { baseFlutterTask.flavor } returns BaseFlutterTaskPropertiesTest.FLAVOR_TEST
|
||||
every { baseFlutterTask.extraGenSnapshotOptions } returns BaseFlutterTaskPropertiesTest.EXTRA_GEN_SNAPSHOT_OPTIONS_TEST
|
||||
|
||||
every { baseFlutterTask.frontendServerStarterPath } returns BaseFlutterTaskPropertiesTest.FRONTEND_SERVER_STARTER_PATH_TEST
|
||||
every { baseFlutterTask.extraFrontEndOptions } returns BaseFlutterTaskPropertiesTest.EXTRA_FRONTEND_OPTIONS_TEST
|
||||
|
||||
every { baseFlutterTask.targetPlatformValues } returns BaseFlutterTaskPropertiesTest.targetPlatformValuesList
|
||||
|
||||
every { baseFlutterTask.minSdkVersion } returns BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST
|
||||
|
||||
// Mock the method calls. We collapse all the args mock calls into four calls.
|
||||
every { mockExecSpec.executable(any<String>()) } returns mockExecSpec
|
||||
every { mockExecSpec.workingDir(any()) } returns mockProcessForkOptions
|
||||
every { mockExecSpec.args(any<String>(), any()) } returns mockExecSpec
|
||||
every { mockExecSpec.args(any<String>(), any()) } returns mockExecSpec
|
||||
every { mockExecSpec.args(any<String>()) } returns mockExecSpec
|
||||
every { mockExecSpec.args(any<List<String>>()) } returns mockExecSpec
|
||||
|
||||
// Generate rule names for verification and can only be generated after buildMode is mocked.
|
||||
val ruleNamesList: List<String> = helper.generateRuleNames(baseFlutterTask)
|
||||
|
||||
// The exec function will be deprecated in gradle 8.11 and will be removed in gradle 9.0
|
||||
// https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.kotlin.dsl/-kotlin-script/exec.html?query=abstract%20fun%20exec(configuration:%20Action%3CExecSpec%3E):%20ExecResult
|
||||
// The actions are executed.
|
||||
execSpecActionFromTask.execute(mockExecSpec)
|
||||
|
||||
// After execution, we verify the functions are actually being
|
||||
// called with the expected argument passed in.
|
||||
verify { mockExecSpec.executable(BaseFlutterTaskPropertiesTest.FLUTTER_EXECUTABLE_ABSOLUTE_PATH_TEST) }
|
||||
verify { mockExecSpec.workingDir(BaseFlutterTaskPropertiesTest.sourceDirTest) }
|
||||
verify { mockExecSpec.args("--local-engine", BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_TEST) }
|
||||
verify { mockExecSpec.args("--local-engine-src-path", BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_SRC_PATH_TEST) }
|
||||
verify { mockExecSpec.args("--local-engine-host", BaseFlutterTaskPropertiesTest.LOCAL_ENGINE_HOST_TEST) }
|
||||
verify { mockExecSpec.args("--verbose") }
|
||||
verify { mockExecSpec.args("assemble") }
|
||||
verify { mockExecSpec.args("--no-version-check") }
|
||||
verify { mockExecSpec.args("--depfile", "${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}/flutter_build.d") }
|
||||
verify { mockExecSpec.args("--output", "${BaseFlutterTaskPropertiesTest.intermediateDirFileTest}") }
|
||||
verify { mockExecSpec.args("--performance-measurement-file=${BaseFlutterTaskPropertiesTest.PERFORMANCE_MEASUREMENT_FILE_TEST}") }
|
||||
verify { mockExecSpec.args("-dTargetFile=${BaseFlutterTaskPropertiesTest.FLUTTER_TARGET_FILE_PATH}") }
|
||||
verify { mockExecSpec.args("-dTargetPlatform=android") }
|
||||
verify { mockExecSpec.args("-dBuildMode=$buildModeString") }
|
||||
verify { mockExecSpec.args("-dTrackWidgetCreation=${true}") }
|
||||
verify { mockExecSpec.args("-dSplitDebugInfo=${BaseFlutterTaskPropertiesTest.SPLIT_DEBUG_INFO_TEST}") }
|
||||
verify { mockExecSpec.args("-dTreeShakeIcons=true") }
|
||||
verify { mockExecSpec.args("-dDartObfuscation=true") }
|
||||
verify { mockExecSpec.args("--DartDefines=${BaseFlutterTaskPropertiesTest.DART_DEFINES_TEST}") }
|
||||
verify { mockExecSpec.args("-dBundleSkSLPath=${BaseFlutterTaskPropertiesTest.BUNDLE_SK_SL_PATH_TEST}") }
|
||||
verify { mockExecSpec.args("-dCodeSizeDirectory=${BaseFlutterTaskPropertiesTest.CODE_SIZE_DIRECTORY_TEST}") }
|
||||
verify { mockExecSpec.args("-dFlavor=${BaseFlutterTaskPropertiesTest.FLAVOR_TEST}") }
|
||||
verify { mockExecSpec.args("--ExtraGenSnapshotOptions=${BaseFlutterTaskPropertiesTest.EXTRA_GEN_SNAPSHOT_OPTIONS_TEST}") }
|
||||
verify { mockExecSpec.args("-dFrontendServerStarterPath=${BaseFlutterTaskPropertiesTest.FRONTEND_SERVER_STARTER_PATH_TEST}") }
|
||||
verify { mockExecSpec.args("--ExtraFrontEndOptions=${BaseFlutterTaskPropertiesTest.EXTRA_FRONTEND_OPTIONS_TEST}") }
|
||||
verify { mockExecSpec.args("-dAndroidArchs=${BaseFlutterTaskPropertiesTest.TARGET_PLATFORM_VALUES_JOINED_LIST}") }
|
||||
verify { mockExecSpec.args("-dMinSdkVersion=${BaseFlutterTaskPropertiesTest.MIN_SDK_VERSION_TEST}") }
|
||||
verify { mockExecSpec.args(ruleNamesList) }
|
||||
}
|
||||
}
|
@ -63,6 +63,13 @@ class FlutterPluginUtilsTest {
|
||||
assertEquals(0, result)
|
||||
}
|
||||
|
||||
// formatPlatformString
|
||||
@Test
|
||||
fun `formatPlatformString returns correct string`() {
|
||||
val result = FlutterPluginUtils.formatPlatformString("android-arm64")
|
||||
assertEquals("arm64_v8a", result)
|
||||
}
|
||||
|
||||
// shouldShrinkResources
|
||||
@Test
|
||||
fun `shouldShrinkResources returns true by default`() {
|
||||
|
@ -0,0 +1,174 @@
|
||||
package com.flutter.gradle
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.CopySpec
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.Test
|
||||
|
||||
class FlutterTaskHelperTest {
|
||||
@Test
|
||||
fun `getAssetsDirectory returns correct path`() {
|
||||
val flutterTask = mockk<FlutterTask>()
|
||||
val mockFile = mockk<File>()
|
||||
val flutterTaskOutputDirectory = "/path/to/assets"
|
||||
val expectedPath = "$flutterTaskOutputDirectory/flutter_assets"
|
||||
|
||||
every { flutterTask.outputDirectory } returns mockFile
|
||||
every { mockFile.toString() } returns flutterTaskOutputDirectory
|
||||
val result = FlutterTaskHelper.getAssetsDirectory(flutterTask)
|
||||
assert(result == expectedPath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAssets returns correct CopySpec`() {
|
||||
val project = mockk<Project>()
|
||||
val flutterTask = mockk<FlutterTask>()
|
||||
val mockFile = mockk<File>()
|
||||
val mockCopySpec = mockk<CopySpec>()
|
||||
val copySpecActionSlot = slot<Action<in CopySpec>>()
|
||||
val fakeFromPath = "/path/to/intermediate"
|
||||
|
||||
every { flutterTask.intermediateDir } returns mockFile
|
||||
every { mockFile.toString() } returns fakeFromPath
|
||||
every { project.copySpec(capture(copySpecActionSlot)) } returns mockk()
|
||||
|
||||
FlutterTaskHelper.getAssets(project, flutterTask)
|
||||
every { mockCopySpec.from(fakeFromPath) } returns mockCopySpec
|
||||
every { mockCopySpec.include(FlutterTaskHelper.FLUTTER_ASSETS_INCLUDE_DIRECTORY) } returns mockCopySpec
|
||||
copySpecActionSlot.captured.execute(mockCopySpec)
|
||||
verify { mockCopySpec.from(fakeFromPath) }
|
||||
verify { mockCopySpec.include(FlutterTaskHelper.FLUTTER_ASSETS_INCLUDE_DIRECTORY) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSnapshots returns correct CopySpec for release build`() {
|
||||
val project = mockk<Project>()
|
||||
val flutterTask = mockk<FlutterTask>()
|
||||
val mockCopySpec = mockk<CopySpec>()
|
||||
val copySpecActionSlot = slot<Action<in CopySpec>>()
|
||||
val fakeIntermediateDirectory = mockk<File>()
|
||||
val fakeIntermediateDirectoryPath = "/path/to/intermediate"
|
||||
|
||||
every { flutterTask.intermediateDir } returns fakeIntermediateDirectory
|
||||
every { flutterTask.buildMode } returns "release"
|
||||
every { flutterTask.targetPlatformValues } returns listOf("arm64-v8a", "x64")
|
||||
every { fakeIntermediateDirectory.toString() } returns fakeIntermediateDirectoryPath
|
||||
every { project.copySpec(capture(copySpecActionSlot)) } returns mockk()
|
||||
|
||||
FlutterTaskHelper.getSnapshots(project, flutterTask)
|
||||
every { mockCopySpec.from(fakeIntermediateDirectoryPath) } returns mockCopySpec
|
||||
every { mockCopySpec.include(any<String>()) } returns mockCopySpec
|
||||
copySpecActionSlot.captured.execute(mockCopySpec)
|
||||
|
||||
verify { mockCopySpec.from(fakeIntermediateDirectoryPath) }
|
||||
verify { mockCopySpec.include("${FlutterPluginConstants.PLATFORM_ARCH_MAP["arm64-v8a"]}/app.so") }
|
||||
verify { mockCopySpec.include("${FlutterPluginConstants.PLATFORM_ARCH_MAP["x64"]}/app.so") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSnapshots returns correct CopySpec for debug build`() {
|
||||
val project = mockk<Project>()
|
||||
val flutterTask = mockk<FlutterTask>()
|
||||
val mockCopySpec = mockk<CopySpec>()
|
||||
val copySpecActionSlot = slot<Action<in CopySpec>>()
|
||||
val fakeIntermediateDirectory = mockk<File>()
|
||||
val fakeIntermediateDirectoryPath = "/path/to/intermediate"
|
||||
|
||||
every { flutterTask.intermediateDir } returns fakeIntermediateDirectory
|
||||
every { flutterTask.buildMode } returns "debug"
|
||||
every { flutterTask.targetPlatformValues } returns listOf("arm64-v8a", "x64")
|
||||
every { fakeIntermediateDirectory.toString() } returns fakeIntermediateDirectoryPath
|
||||
every { project.copySpec(capture(copySpecActionSlot)) } returns mockk()
|
||||
|
||||
FlutterTaskHelper.getSnapshots(project, flutterTask)
|
||||
every { mockCopySpec.from(fakeIntermediateDirectoryPath) } returns mockCopySpec
|
||||
every { mockCopySpec.include(any<String>()) } returns mockCopySpec
|
||||
copySpecActionSlot.captured.execute(mockCopySpec)
|
||||
|
||||
verify { mockCopySpec.from(fakeIntermediateDirectoryPath) }
|
||||
verify(exactly = 0) { mockCopySpec.include(any<String>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getSourceFiles returns files when dependenciesFile exists`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val mockProjectFileCollection = mockk<ConfigurableFileCollection>(relaxed = true)
|
||||
val mockDependenciesFileCollection = mockk<FileCollection>()
|
||||
val project = mockk<Project>()
|
||||
val mockFlutterTask = mockk<FlutterTask>()
|
||||
|
||||
every { project.files() } returns mockProjectFileCollection
|
||||
every { project.files(any()) } returns mockProjectFileCollection
|
||||
|
||||
every { mockFlutterTask.intermediateDir } returns tempDir.toFile()
|
||||
every { mockFlutterTask.getDependenciesFiles() } returns mockDependenciesFileCollection
|
||||
val dependenciesFile =
|
||||
tempDir
|
||||
.resolve("${mockFlutterTask.intermediateDir}/flutter_build.d")
|
||||
.toFile()
|
||||
dependenciesFile.writeText(
|
||||
" ${tempDir.toFile().path}/pre/delimiter/one ${tempDir.toFile().path}/pre/delimiter/two: ${tempDir.toFile().path}/post/delimiter/one ${tempDir.toFile().path}/post/delimiter/two"
|
||||
)
|
||||
every { mockDependenciesFileCollection.iterator() } returns (mutableListOf(dependenciesFile).iterator())
|
||||
|
||||
FlutterTaskHelper.getSourceFiles(project, mockFlutterTask)
|
||||
|
||||
verify {
|
||||
project.files(
|
||||
listOf(
|
||||
"${tempDir.toFile().path}/post/delimiter/one",
|
||||
"${tempDir.toFile().path}/post/delimiter/two"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
verify { project.files("pubspec.yaml") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getOutputFiles returns files when dependenciesFile exists`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val mockProjectFileCollection = mockk<ConfigurableFileCollection>(relaxed = true)
|
||||
val mockDependenciesFileCollection = mockk<FileCollection>()
|
||||
val project = mockk<Project>()
|
||||
val mockFlutterTask = mockk<FlutterTask>()
|
||||
|
||||
every { project.files() } returns mockProjectFileCollection
|
||||
every { project.files(any()) } returns mockProjectFileCollection
|
||||
|
||||
every { mockFlutterTask.intermediateDir } returns tempDir.toFile()
|
||||
every { mockFlutterTask.getDependenciesFiles() } returns mockDependenciesFileCollection
|
||||
val dependenciesFile =
|
||||
tempDir
|
||||
.resolve("${mockFlutterTask.intermediateDir}/flutter_build.d")
|
||||
.toFile()
|
||||
dependenciesFile.writeText(
|
||||
" ${tempDir.toFile().path}/pre/delimiter/one ${tempDir.toFile().path}/pre/delimiter/two: ${tempDir.toFile().path}/post/delimiter/one ${tempDir.toFile().path}/post/delimiter/two"
|
||||
)
|
||||
every { mockDependenciesFileCollection.iterator() } returns (mutableListOf(dependenciesFile).iterator())
|
||||
|
||||
FlutterTaskHelper.getOutputFiles(project, mockFlutterTask)
|
||||
|
||||
verify {
|
||||
project.files(
|
||||
listOf(
|
||||
"${tempDir.toFile().path}/pre/delimiter/one",
|
||||
"${tempDir.toFile().path}/pre/delimiter/two"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
verify(exactly = 0) { project.files("pubspec.yaml") }
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user