[reland] Convert the Flutter Gradle Plugin entirely to Kotlin source (#166676)
Relands https://github.com/flutter/flutter/pull/166114. The original PR failed this postsubmit https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8718287794116896097/+/u/run_engine_dependency_proxy_test/stdout Because ``` Task result: { "success": false, "reason": "Task failed: Expected Android engine maven dependency URL to resolve to https://storage.googleapis.com/download.flutter.io. Got https://storage.googleapis.com//download.flutter.io instead" } ``` which was because apparently in Groovy ```groovy String foo = "" if (foo) { // branch } ``` Evaluates to false (i.e. does not take the branch). So we need to check if the `engineRealm` string is empty, which is what the additional commit does. ## 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
a333757dcf
commit
56e11aed71
@ -34,7 +34,7 @@ gradlePlugin {
|
|||||||
// The "flutterPlugin" name isn't used anywhere.
|
// The "flutterPlugin" name isn't used anywhere.
|
||||||
create("flutterPlugin") {
|
create("flutterPlugin") {
|
||||||
id = "dev.flutter.flutter-gradle-plugin"
|
id = "dev.flutter.flutter-gradle-plugin"
|
||||||
implementationClass = "FlutterPlugin"
|
implementationClass = "com.flutter.gradle.FlutterPlugin"
|
||||||
}
|
}
|
||||||
// The "flutterAppPluginLoaderPlugin" name isn't used anywhere.
|
// The "flutterAppPluginLoaderPlugin" name isn't used anywhere.
|
||||||
create("flutterAppPluginLoaderPlugin") {
|
create("flutterAppPluginLoaderPlugin") {
|
||||||
|
@ -1,722 +0,0 @@
|
|||||||
/* groovylint-disable LineLength, UnnecessaryGString, UnnecessaryGetter */
|
|
||||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
import com.android.build.OutputFile
|
|
||||||
import com.android.build.gradle.AbstractAppExtension
|
|
||||||
import com.android.build.gradle.api.BaseVariantOutput
|
|
||||||
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.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.NativePluginLoaderReflectionBridge
|
|
||||||
import org.gradle.api.file.Directory
|
|
||||||
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
|
||||||
import org.gradle.api.GradleException
|
|
||||||
import org.gradle.api.JavaVersion
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Task
|
|
||||||
import org.gradle.api.UnknownTaskException
|
|
||||||
import org.gradle.api.tasks.Copy
|
|
||||||
import org.gradle.api.tasks.TaskProvider
|
|
||||||
import org.gradle.api.tasks.bundling.Jar
|
|
||||||
import org.gradle.internal.os.OperatingSystem
|
|
||||||
|
|
||||||
|
|
||||||
class FlutterPlugin implements Plugin<Project> {
|
|
||||||
|
|
||||||
private final static String propLocalEngineRepo = "local-engine-repo"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name prefix for flutter builds. This is used to identify gradle tasks
|
|
||||||
* where we expect the flutter tool to provide any error output, and skip the
|
|
||||||
* standard Gradle error output in the FlutterEventLogger. If you change this,
|
|
||||||
* be sure to change any instances of this string in symbols in the code below
|
|
||||||
* to match.
|
|
||||||
*/
|
|
||||||
static final String FLUTTER_BUILD_PREFIX = "flutterBuild"
|
|
||||||
|
|
||||||
private Project project
|
|
||||||
private File flutterRoot
|
|
||||||
private File flutterExecutable
|
|
||||||
private String localEngine
|
|
||||||
private String localEngineHost
|
|
||||||
private String localEngineSrcPath
|
|
||||||
private Properties localProperties
|
|
||||||
private String engineVersion
|
|
||||||
private String engineRealm
|
|
||||||
private List<Map<String, Object>> pluginList
|
|
||||||
private List<Map<String, Object>> pluginDependencies
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flutter Docs Website URLs for help messages.
|
|
||||||
*/
|
|
||||||
private final String kWebsiteDeploymentAndroidBuildConfig = "https://flutter.dev/to/review-gradle-config"
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void apply(Project project) {
|
|
||||||
this.project = project
|
|
||||||
|
|
||||||
Project rootProject = project.rootProject
|
|
||||||
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
|
||||||
rootProject.tasks.register("generateLockfiles") {
|
|
||||||
doLast {
|
|
||||||
rootProject.subprojects.each { subproject ->
|
|
||||||
String gradlew = (OperatingSystem.current().isWindows()) ?
|
|
||||||
"${rootProject.projectDir}/gradlew.bat" : "${rootProject.projectDir}/gradlew"
|
|
||||||
rootProject.exec {
|
|
||||||
workingDir(rootProject.projectDir)
|
|
||||||
executable(gradlew)
|
|
||||||
args(":${subproject.name}:dependencies", "--write-locks")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String flutterRootPath = resolveProperty("flutter.sdk", System.getenv("FLUTTER_ROOT"))
|
|
||||||
if (flutterRootPath == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
|
|
||||||
}
|
|
||||||
flutterRoot = project.file(flutterRootPath)
|
|
||||||
if (!flutterRoot.isDirectory()) {
|
|
||||||
throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
engineRealm = Paths.get(flutterRoot.absolutePath, "bin", "cache", "engine.realm").toFile().text.trim()
|
|
||||||
if (engineRealm) {
|
|
||||||
engineRealm += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the Maven repository.
|
|
||||||
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"
|
|
||||||
rootProject.allprojects {
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
url(repository)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load shared gradle functions
|
|
||||||
project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "scripts", "native_plugin_loader.gradle.kts")
|
|
||||||
|
|
||||||
FlutterExtension extension = project.extensions.create("flutter", FlutterExtension)
|
|
||||||
Properties localProperties = new Properties()
|
|
||||||
File localPropertiesFile = rootProject.file("local.properties")
|
|
||||||
if (localPropertiesFile.exists()) {
|
|
||||||
localPropertiesFile.withReader("UTF-8") { reader ->
|
|
||||||
localProperties.load(reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension.flutterVersionCode = localProperties.getProperty("flutter.versionCode", "1")
|
|
||||||
extension.flutterVersionName = localProperties.getProperty("flutter.versionName", "1.0")
|
|
||||||
|
|
||||||
this.addFlutterTasks(project)
|
|
||||||
FlutterPluginUtils.forceNdkDownload(project, flutterRootPath)
|
|
||||||
|
|
||||||
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
|
|
||||||
// Configuring split per ABI allows to generate separate APKs for each abi.
|
|
||||||
// This is a noop when building a bundle.
|
|
||||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
|
||||||
project.android {
|
|
||||||
splits {
|
|
||||||
abi {
|
|
||||||
// Enables building multiple APKs per ABI.
|
|
||||||
enable(true)
|
|
||||||
// Resets the list of ABIs that Gradle should create APKs for to none.
|
|
||||||
reset()
|
|
||||||
// Specifies that we do not want to also generate a universal APK that includes all ABIs.
|
|
||||||
universalApk(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final String propDeferredComponentNames = "deferred-component-names"
|
|
||||||
if (project.hasProperty(propDeferredComponentNames)) {
|
|
||||||
String[] componentNames = project.property(propDeferredComponentNames).split(",").collect {":${it}"}
|
|
||||||
project.android {
|
|
||||||
dynamicFeatures = componentNames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FlutterPluginUtils.getTargetPlatforms(project).each { targetArch ->
|
|
||||||
String abiValue = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
|
|
||||||
project.android {
|
|
||||||
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
|
||||||
splits {
|
|
||||||
abi {
|
|
||||||
include(abiValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
|
|
||||||
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile()
|
|
||||||
|
|
||||||
// Validate that the provided Gradle, Java, AGP, and KGP versions are all within our
|
|
||||||
// supported range.
|
|
||||||
// TODO(gmackall) Dependency version checking is currently implemented as an additional
|
|
||||||
// Gradle plugin because we can't import it from Groovy code. As part of the Groovy
|
|
||||||
// -> Kotlin migration, we should remove this complexity and perform the checks inside
|
|
||||||
// of the main Flutter Gradle Plugin.
|
|
||||||
// See https://github.com/flutter/flutter/issues/121541#issuecomment-1920363687.
|
|
||||||
final Boolean shouldSkipDependencyChecks = project.hasProperty("skipDependencyChecks") && project.getProperty("skipDependencyChecks")
|
|
||||||
if (!shouldSkipDependencyChecks) {
|
|
||||||
try {
|
|
||||||
DependencyVersionChecker.checkDependencyVersions(project)
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (!project.hasProperty("usesUnsupportedDependencyVersions") || !project.usesUnsupportedDependencyVersions) {
|
|
||||||
// Possible bug in dependency checking code - warn and do not block build.
|
|
||||||
project.logger.error("Warning: Flutter was unable to detect project Gradle, Java, " +
|
|
||||||
"AGP, and KGP versions. Skipping dependency version checking. Error was: "
|
|
||||||
+ e)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// If usesUnsupportedDependencyVersions is set, the exception was thrown by us
|
|
||||||
// in the dependency version checker plugin so re-throw it here.
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use Kotlin source to handle baseApplicationName logic due to Groovy dynamic dispatch bug.
|
|
||||||
BaseApplicationNameHandler.setBaseName(project)
|
|
||||||
|
|
||||||
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
|
|
||||||
"gradle", "flutter_proguard_rules.pro")
|
|
||||||
project.android.buildTypes {
|
|
||||||
// Add profile build type.
|
|
||||||
profile {
|
|
||||||
initWith(debug)
|
|
||||||
if (it.hasProperty("matchingFallbacks")) {
|
|
||||||
matchingFallbacks = ["debug", "release"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO(garyq): Shrinking is only false for multi apk split aot builds, where shrinking is not allowed yet.
|
|
||||||
// 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 (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(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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if (!engineOut.isDirectory()) {
|
|
||||||
throw new GradleException("local-engine-out must point to a local engine build")
|
|
||||||
}
|
|
||||||
localEngine = engineOut.name
|
|
||||||
localEngineSrcPath = engineOut.parentFile.parent
|
|
||||||
|
|
||||||
String engineHostOutPath = project.property("local-engine-host-out")
|
|
||||||
File engineHostOut = project.file(engineHostOutPath)
|
|
||||||
if (!engineHostOut.isDirectory()) {
|
|
||||||
throw new GradleException("local-engine-host-out must point to a local engine host build")
|
|
||||||
}
|
|
||||||
localEngineHost = engineHostOut.name
|
|
||||||
}
|
|
||||||
project.android.buildTypes.all(this.&addFlutterDependencies)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the dependencies required by the Flutter project.
|
|
||||||
* This includes:
|
|
||||||
* 1. The embedding
|
|
||||||
* 2. libflutter.so
|
|
||||||
*/
|
|
||||||
void addFlutterDependencies(BuildType buildType) {
|
|
||||||
FlutterPluginUtils.addFlutterDependencies(project, buildType, getPluginList(project), engineVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the Flutter plugin dependencies.
|
|
||||||
*
|
|
||||||
* The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
|
|
||||||
* the tool generates a `.flutter-plugins-dependencies` file, which contains a map to each plugin location.
|
|
||||||
* Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject.
|
|
||||||
*/
|
|
||||||
private void configurePlugins(Project project) {
|
|
||||||
configureLegacyPluginEachProjects(project)
|
|
||||||
getPluginList(project).each { Map<String, Object> plugin ->
|
|
||||||
FlutterPluginUtils.configurePluginProject(project, plugin, engineVersion)
|
|
||||||
}
|
|
||||||
getPluginList(project).each {Map<String, Object> plugin ->
|
|
||||||
FlutterPluginUtils.configurePluginDependencies(project, plugin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(54566, 48918): Can remove once the issues are resolved.
|
|
||||||
// This means all references to `.flutter-plugins` are then removed and
|
|
||||||
// apps only depend exclusively on the `plugins` property in `.flutter-plugins-dependencies`.
|
|
||||||
/**
|
|
||||||
* Workaround to load non-native plugins for developers who may still use an
|
|
||||||
* old `settings.gradle` which includes all the plugins from the
|
|
||||||
* `.flutter-plugins` file, even if not made for Android.
|
|
||||||
* The settings.gradle then:
|
|
||||||
* 1) tries to add the android plugin implementation, which does not
|
|
||||||
* exist at all, but is also not included successfully
|
|
||||||
* (which does not throw an error and therefore isn't a problem), or
|
|
||||||
* 2) includes the plugin successfully as a valid android plugin
|
|
||||||
* directory exists, even if the surrounding flutter package does not
|
|
||||||
* support the android platform (see e.g. apple_maps_flutter: 1.0.1).
|
|
||||||
* So as it's included successfully it expects to be added as API.
|
|
||||||
* This is only possible by taking all plugins into account, which
|
|
||||||
* only appear on the `dependencyGraph` and in the `.flutter-plugins` file.
|
|
||||||
* So in summary the plugins are currently selected from the `dependencyGraph`
|
|
||||||
* and filtered then with the [doesSupportAndroidPlatform] method instead of
|
|
||||||
* just using the `plugins.android` list.
|
|
||||||
*/
|
|
||||||
static private void configureLegacyPluginEachProjects(Project project) {
|
|
||||||
try {
|
|
||||||
// Read the contents of the settings.gradle file.
|
|
||||||
// Remove block/line comments
|
|
||||||
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: " +
|
|
||||||
"${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.")
|
|
||||||
project.logger.quiet("In an upcoming stable release support for this file will be completely removed and your build will fail.")
|
|
||||||
project.logger.quiet("See https:/flutter.dev/to/flutter-plugins-configuration.")
|
|
||||||
List<Map<String, Object>> deps = getPluginDependencies(project)
|
|
||||||
List<String> plugins = getPluginList(project).collect { it.name as String }
|
|
||||||
deps.removeIf { plugins.contains(it.name) }
|
|
||||||
deps.each {
|
|
||||||
Project pluginProject = project.rootProject.findProject(":${it.name}")
|
|
||||||
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 (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.
|
|
||||||
FlutterPluginUtils.configurePluginProject(project, it, engineVersion)
|
|
||||||
/* groovylint-disable-next-line EmptyElseBlock */
|
|
||||||
} else {
|
|
||||||
// Plugin has no or an empty `android` folder. No action required.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the list of plugins (as map) that support the Android platform.
|
|
||||||
*
|
|
||||||
* The map value contains either the plugins `name` (String),
|
|
||||||
* its `path` (String), or its `dependencies` (List<String>).
|
|
||||||
* See [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/scripts/native_plugin_loader.gradle.kts
|
|
||||||
*/
|
|
||||||
private List<Map<String, Object>> getPluginList(Project project) {
|
|
||||||
if (pluginList == null) {
|
|
||||||
pluginList = NativePluginLoaderReflectionBridge.getPlugins(project.ext, FlutterPluginUtils.getFlutterSourceDirectory(project))
|
|
||||||
}
|
|
||||||
return pluginList
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(54566, 48918): Remove in favor of [getPluginList] only, see also
|
|
||||||
// https://github.com/flutter/flutter/blob/1c90ed8b64d9ed8ce2431afad8bc6e6d9acc4556/packages/flutter_tools/lib/src/flutter_plugins.dart#L212
|
|
||||||
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
|
|
||||||
private List<Map<String, Object>> getPluginDependencies(Project project) {
|
|
||||||
if (pluginDependencies == null) {
|
|
||||||
Map meta = NativePluginLoaderReflectionBridge.getDependenciesMetadata(project.ext, FlutterPluginUtils.getFlutterSourceDirectory(project))
|
|
||||||
if (meta == null) {
|
|
||||||
pluginDependencies = []
|
|
||||||
} else {
|
|
||||||
assert(meta.dependencyGraph instanceof List<Map>)
|
|
||||||
pluginDependencies = meta.dependencyGraph as List<Map<String, Object>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pluginDependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
private String resolveProperty(String name, String defaultValue) {
|
|
||||||
if (localProperties == null) {
|
|
||||||
localProperties = FlutterPluginUtils.readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
|
|
||||||
}
|
|
||||||
return project.findProperty(name) ?: localProperties?.getProperty(name, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addFlutterTasks(Project project) {
|
|
||||||
if (project.state.failure) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
String[] fileSystemRootsValue = null
|
|
||||||
final String propFileSystemRoots = "filesystem-roots"
|
|
||||||
if (project.hasProperty(propFileSystemRoots)) {
|
|
||||||
fileSystemRootsValue = project.property(propFileSystemRoots).split("\\|")
|
|
||||||
}
|
|
||||||
String fileSystemSchemeValue = null
|
|
||||||
final String propFileSystemScheme = "filesystem-scheme"
|
|
||||||
if (project.hasProperty(propFileSystemScheme)) {
|
|
||||||
fileSystemSchemeValue = project.property(propFileSystemScheme)
|
|
||||||
}
|
|
||||||
Boolean trackWidgetCreationValue = true
|
|
||||||
final String propTrackWidgetCreation = "track-widget-creation"
|
|
||||||
if (project.hasProperty(propTrackWidgetCreation)) {
|
|
||||||
trackWidgetCreationValue = project.property(propTrackWidgetCreation).toBoolean()
|
|
||||||
}
|
|
||||||
String frontendServerStarterPathValue = null
|
|
||||||
final String propFrontendServerStarterPath = "frontend-server-starter-path"
|
|
||||||
if (project.hasProperty(propFrontendServerStarterPath)) {
|
|
||||||
frontendServerStarterPathValue = project.property(propFrontendServerStarterPath)
|
|
||||||
}
|
|
||||||
String extraFrontEndOptionsValue = null
|
|
||||||
final String propExtraFrontEndOptions = "extra-front-end-options"
|
|
||||||
if (project.hasProperty(propExtraFrontEndOptions)) {
|
|
||||||
extraFrontEndOptionsValue = project.property(propExtraFrontEndOptions)
|
|
||||||
}
|
|
||||||
String extraGenSnapshotOptionsValue = null
|
|
||||||
final String propExtraGenSnapshotOptions = "extra-gen-snapshot-options"
|
|
||||||
if (project.hasProperty(propExtraGenSnapshotOptions)) {
|
|
||||||
extraGenSnapshotOptionsValue = project.property(propExtraGenSnapshotOptions)
|
|
||||||
}
|
|
||||||
String splitDebugInfoValue = null
|
|
||||||
final String propSplitDebugInfo = "split-debug-info"
|
|
||||||
if (project.hasProperty(propSplitDebugInfo)) {
|
|
||||||
splitDebugInfoValue = project.property(propSplitDebugInfo)
|
|
||||||
}
|
|
||||||
Boolean dartObfuscationValue = false
|
|
||||||
final String propDartObfuscation = "dart-obfuscation"
|
|
||||||
if (project.hasProperty(propDartObfuscation)) {
|
|
||||||
dartObfuscationValue = project.property(propDartObfuscation).toBoolean()
|
|
||||||
}
|
|
||||||
Boolean treeShakeIconsOptionsValue = false
|
|
||||||
final String propTreeShakeIcons = "tree-shake-icons"
|
|
||||||
if (project.hasProperty(propTreeShakeIcons)) {
|
|
||||||
treeShakeIconsOptionsValue = project.property(propTreeShakeIcons).toBoolean()
|
|
||||||
}
|
|
||||||
String dartDefinesValue = null
|
|
||||||
final String propDartDefines = "dart-defines"
|
|
||||||
if (project.hasProperty(propDartDefines)) {
|
|
||||||
dartDefinesValue = project.property(propDartDefines)
|
|
||||||
}
|
|
||||||
String performanceMeasurementFileValue
|
|
||||||
final String propPerformanceMeasurementFile = "performance-measurement-file"
|
|
||||||
if (project.hasProperty(propPerformanceMeasurementFile)) {
|
|
||||||
performanceMeasurementFileValue = project.property(propPerformanceMeasurementFile)
|
|
||||||
}
|
|
||||||
String codeSizeDirectoryValue
|
|
||||||
final String propCodeSizeDirectory = "code-size-directory"
|
|
||||||
if (project.hasProperty(propCodeSizeDirectory)) {
|
|
||||||
codeSizeDirectoryValue = project.property(propCodeSizeDirectory)
|
|
||||||
}
|
|
||||||
Boolean deferredComponentsValue = false
|
|
||||||
final String propDeferredComponents = "deferred-components"
|
|
||||||
if (project.hasProperty(propDeferredComponents)) {
|
|
||||||
deferredComponentsValue = project.property(propDeferredComponents).toBoolean()
|
|
||||||
}
|
|
||||||
Boolean validateDeferredComponentsValue = true
|
|
||||||
final String propValidateDeferredComponents = "validate-deferred-components"
|
|
||||||
if (project.hasProperty(propValidateDeferredComponents)) {
|
|
||||||
validateDeferredComponentsValue = project.property(propValidateDeferredComponents).toBoolean()
|
|
||||||
}
|
|
||||||
FlutterPluginUtils.addTaskForJavaVersion(project)
|
|
||||||
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
|
||||||
FlutterPluginUtils.addTaskForPrintBuildVariants(project)
|
|
||||||
FlutterPluginUtils.addTasksForOutputsAppLinkSettings(project)
|
|
||||||
}
|
|
||||||
List<String> targetPlatforms = FlutterPluginUtils.getTargetPlatforms(project)
|
|
||||||
def addFlutterDeps = { variant ->
|
|
||||||
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
|
|
||||||
// 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 = FlutterPluginConstants.ABI_VERSION[output.getFilter(OutputFile.ABI)]
|
|
||||||
if (abiVersionCode != null) {
|
|
||||||
output.versionCodeOverride =
|
|
||||||
abiVersionCode * 1000 + variant.versionCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build an AAR when this property is defined.
|
|
||||||
boolean isBuildingAar = project.hasProperty("is-plugin")
|
|
||||||
// In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project.
|
|
||||||
// `:flutter` is used as a subproject when these tasks exists and the build isn't building an AAR.
|
|
||||||
Task packageAssets
|
|
||||||
Task cleanPackageAssets
|
|
||||||
try {
|
|
||||||
packageAssets = project.tasks.named("package${variant.name.capitalize()}Assets").get()
|
|
||||||
} catch (UnknownTaskException ignored) {
|
|
||||||
packageAssets = null
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
cleanPackageAssets = project.tasks.named("cleanPackage${variant.name.capitalize()}Assets").get()
|
|
||||||
} catch (UnknownTaskException ignored) {
|
|
||||||
cleanPackageAssets = null
|
|
||||||
}
|
|
||||||
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
|
|
||||||
|
|
||||||
String variantBuildMode = FlutterPluginUtils.buildModeFor(variant.buildType)
|
|
||||||
String flavorValue = variant.getFlavorName()
|
|
||||||
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`
|
|
||||||
// original value. You either need to hoist the value
|
|
||||||
// into a separate variable `verbose verboseValue` or prefix with
|
|
||||||
// `this` (`verbose this.isVerbose()`).
|
|
||||||
TaskProvider<FlutterTask> compileTaskProvider = project.tasks.register(taskName , FlutterTask) {
|
|
||||||
flutterRoot(this.flutterRoot)
|
|
||||||
flutterExecutable(this.flutterExecutable)
|
|
||||||
buildMode(variantBuildMode)
|
|
||||||
minSdkVersion(variant.mergedFlavor.minSdkVersion.apiLevel)
|
|
||||||
localEngine(this.localEngine)
|
|
||||||
localEngineHost(this.localEngineHost)
|
|
||||||
localEngineSrcPath(this.localEngineSrcPath)
|
|
||||||
targetPath(FlutterPluginUtils.getFlutterTarget(project))
|
|
||||||
verbose(FlutterPluginUtils.isProjectVerbose(project))
|
|
||||||
fastStart(FlutterPluginUtils.isProjectFastStart(project))
|
|
||||||
fileSystemRoots(fileSystemRootsValue)
|
|
||||||
fileSystemScheme(fileSystemSchemeValue)
|
|
||||||
trackWidgetCreation(trackWidgetCreationValue)
|
|
||||||
targetPlatformValues = targetPlatforms
|
|
||||||
sourceDir(FlutterPluginUtils.getFlutterSourceDirectory(project))
|
|
||||||
intermediateDir(project.file(project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/")))
|
|
||||||
frontendServerStarterPath(frontendServerStarterPathValue)
|
|
||||||
extraFrontEndOptions(extraFrontEndOptionsValue)
|
|
||||||
extraGenSnapshotOptions(extraGenSnapshotOptionsValue)
|
|
||||||
splitDebugInfo(splitDebugInfoValue)
|
|
||||||
treeShakeIcons(treeShakeIconsOptionsValue)
|
|
||||||
dartObfuscation(dartObfuscationValue)
|
|
||||||
dartDefines(dartDefinesValue)
|
|
||||||
performanceMeasurementFile(performanceMeasurementFileValue)
|
|
||||||
codeSizeDirectory(codeSizeDirectoryValue)
|
|
||||||
deferredComponents(deferredComponentsValue)
|
|
||||||
validateDeferredComponents(validateDeferredComponentsValue)
|
|
||||||
flavor(flavorValue)
|
|
||||||
}
|
|
||||||
Task compileTask = compileTaskProvider.get()
|
|
||||||
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 = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform]
|
|
||||||
from("${compileTask.intermediateDir}/${abi}") {
|
|
||||||
include("*.so")
|
|
||||||
// Move `app.so` to `lib/<abi>/libapp.so`
|
|
||||||
rename { String filename ->
|
|
||||||
return "lib/${abi}/lib${filename}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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 = "${FlutterPluginUtils.getFlutterSourceDirectory(project)}/build"
|
|
||||||
String nativeAssetsDir = "${buildDir}/native_assets/android/jniLibs/lib"
|
|
||||||
from("${nativeAssetsDir}/${abi}") {
|
|
||||||
include("*.so")
|
|
||||||
rename { String filename ->
|
|
||||||
return "lib/${abi}/${filename}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Task packJniLibsTask = packJniLibsTaskProvider.get()
|
|
||||||
FlutterPluginUtils.addApiDependencies(project, variant.name, project.files {
|
|
||||||
packJniLibsTask
|
|
||||||
})
|
|
||||||
TaskProvider<Copy> copyFlutterAssetsTaskProvider = project.tasks.register(
|
|
||||||
"copyFlutterAssets${variant.name.capitalize()}" , Copy
|
|
||||||
) {
|
|
||||||
dependsOn(compileTask)
|
|
||||||
with(compileTask.assets)
|
|
||||||
String currentGradleVersion = project.getGradle().getGradleVersion()
|
|
||||||
|
|
||||||
// See https://docs.gradle.org/current/javadoc/org/gradle/api/file/ConfigurableFilePermissions.html
|
|
||||||
// See https://github.com/flutter/flutter/pull/50047
|
|
||||||
if (FlutterPluginUtils.compareVersionStrings(currentGradleVersion, "8.3") >= 0) {
|
|
||||||
filePermissions {
|
|
||||||
user {
|
|
||||||
read = true
|
|
||||||
write = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// See https://docs.gradle.org/8.2/dsl/org.gradle.api.tasks.Copy.html#org.gradle.api.tasks.Copy:fileMode
|
|
||||||
// See https://github.com/flutter/flutter/pull/50047
|
|
||||||
fileMode(0644)
|
|
||||||
}
|
|
||||||
if (isUsedAsSubproject) {
|
|
||||||
dependsOn(packageAssets)
|
|
||||||
dependsOn(cleanPackageAssets)
|
|
||||||
into(packageAssets.outputDir)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// `variant.mergeAssets` will be removed at the end of 2019.
|
|
||||||
def mergeAssets = variant.hasProperty("mergeAssetsProvider") ?
|
|
||||||
variant.mergeAssetsProvider.get() : variant.mergeAssets
|
|
||||||
dependsOn(mergeAssets)
|
|
||||||
dependsOn("clean${mergeAssets.name.capitalize()}")
|
|
||||||
mergeAssets.mustRunAfter("clean${mergeAssets.name.capitalize()}")
|
|
||||||
into(mergeAssets.outputDir)
|
|
||||||
}
|
|
||||||
Task copyFlutterAssetsTask = copyFlutterAssetsTaskProvider.get()
|
|
||||||
if (!isUsedAsSubproject) {
|
|
||||||
def variantOutput = variant.outputs.first()
|
|
||||||
def processResources = variantOutput.hasProperty(FlutterPluginConstants.PROP_PROCESS_RESOURCES_PROVIDER) ?
|
|
||||||
variantOutput.processResourcesProvider.get() : variantOutput.processResources
|
|
||||||
processResources.dependsOn(copyFlutterAssetsTask)
|
|
||||||
}
|
|
||||||
// The following tasks use the output of copyFlutterAssetsTask,
|
|
||||||
// so it's necessary to declare it as an dependency since Gradle 8.
|
|
||||||
// See https://docs.gradle.org/8.1/userguide/validation_problems.html#implicit_dependency.
|
|
||||||
def tasksToCheck = [
|
|
||||||
"compress${variant.name.capitalize()}Assets",
|
|
||||||
"bundle${variant.name.capitalize()}Aar",
|
|
||||||
"bundle${variant.name.capitalize()}LocalLintAar"
|
|
||||||
]
|
|
||||||
tasksToCheck.each { taskTocheck ->
|
|
||||||
try {
|
|
||||||
project.tasks.named(taskTocheck).configure { task ->
|
|
||||||
task.dependsOn(copyFlutterAssetsTask)
|
|
||||||
}
|
|
||||||
} catch (UnknownTaskException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return copyFlutterAssetsTask
|
|
||||||
} // end def addFlutterDeps
|
|
||||||
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
|
||||||
AbstractAppExtension android = (AbstractAppExtension) project.extensions.findByName("android")
|
|
||||||
android.applicationVariants.configureEach { variant ->
|
|
||||||
Task assembleTask = variant.assembleProvider.get()
|
|
||||||
if (!FlutterPluginUtils.shouldConfigureFlutterTask(project, assembleTask)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Task copyFlutterAssetsTask = addFlutterDeps(variant)
|
|
||||||
BaseVariantOutput variantOutput = variant.outputs.first()
|
|
||||||
ProcessAndroidResources processResources = variantOutput.hasProperty(FlutterPluginConstants.PROP_PROCESS_RESOURCES_PROVIDER) ?
|
|
||||||
variantOutput.processResourcesProvider.get() : variantOutput.processResources
|
|
||||||
processResources.dependsOn(copyFlutterAssetsTask)
|
|
||||||
|
|
||||||
// Copy the output APKs into a known location, so `flutter run` or `flutter build apk`
|
|
||||||
// can discover them. By default, this is `<app-dir>/build/app/outputs/flutter-apk/<filename>.apk`.
|
|
||||||
//
|
|
||||||
// The filename consists of `app<-abi>?<-flavor-name>?-<build-mode>.apk`.
|
|
||||||
// Where:
|
|
||||||
// * `abi` can be `armeabi-v7a|arm64-v8a|x86|x86_64` only if the flag `split-per-abi` is set.
|
|
||||||
// * `flavor-name` is the flavor used to build the app in lower case if the assemble task is called.
|
|
||||||
// * `build-mode` can be `release|debug|profile`.
|
|
||||||
variant.outputs.each { output ->
|
|
||||||
assembleTask.doLast {
|
|
||||||
PackageAndroidArtifact packageApplicationProvider = variant.packageApplicationProvider.get()
|
|
||||||
Directory outputDirectory = packageApplicationProvider.outputDirectory.get()
|
|
||||||
String outputDirectoryStr = outputDirectory.toString()
|
|
||||||
String filename = "app"
|
|
||||||
String abi = output.getFilter(OutputFile.ABI)
|
|
||||||
if (abi != null && !abi.isEmpty()) {
|
|
||||||
filename += "-${abi}"
|
|
||||||
}
|
|
||||||
if (variant.flavorName != null && !variant.flavorName.isEmpty()) {
|
|
||||||
filename += "-${variant.flavorName.toLowerCase()}"
|
|
||||||
}
|
|
||||||
filename += "-${FlutterPluginUtils.buildModeFor(variant.buildType)}"
|
|
||||||
project.copy {
|
|
||||||
from new File("$outputDirectoryStr/${output.outputFileName}")
|
|
||||||
into new File("${project.layout.buildDirectory.dir("outputs/flutter-apk").get()}")
|
|
||||||
rename {
|
|
||||||
return "${filename}.apk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Copy the native assets created by build.dart and placed here by flutter assemble.
|
|
||||||
// This path is not flavor specific and must only be added once.
|
|
||||||
// If support for flavors is added to native assets, then they must only be added
|
|
||||||
// once per flavor; see https://github.com/dart-lang/native/issues/1359.
|
|
||||||
String nativeAssetsDir = "${project.layout.buildDirectory.get()}/../native_assets/android/jniLibs/lib/"
|
|
||||||
android.sourceSets.main.jniLibs.srcDir(nativeAssetsDir)
|
|
||||||
configurePlugins(project)
|
|
||||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, getPluginList(project))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Flutter host module project (Add-to-app).
|
|
||||||
String hostAppProjectName = project.rootProject.hasProperty("flutter.hostAppProjectName") ? project.rootProject.property("flutter.hostAppProjectName") : "app"
|
|
||||||
Project appProject = project.rootProject.findProject(":${hostAppProjectName}")
|
|
||||||
assert(appProject != null) : "Project :${hostAppProjectName} doesn't exist. To customize the host app project name, set `flutter.hostAppProjectName=<project-name>` in gradle.properties."
|
|
||||||
// Wait for the host app project configuration.
|
|
||||||
appProject.afterEvaluate {
|
|
||||||
assert(appProject.android != null)
|
|
||||||
project.android.libraryVariants.all { libraryVariant ->
|
|
||||||
Task copyFlutterAssetsTask
|
|
||||||
appProject.android.applicationVariants.all { appProjectVariant ->
|
|
||||||
Task appAssembleTask = appProjectVariant.assembleProvider.get()
|
|
||||||
if (!FlutterPluginUtils.shouldConfigureFlutterTask(project, appAssembleTask)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Find a compatible application variant in the host app.
|
|
||||||
//
|
|
||||||
// For example, consider a host app that defines the following variants:
|
|
||||||
// | ----------------- | ----------------------------- |
|
|
||||||
// | Build Variant | Flutter Equivalent Variant |
|
|
||||||
// | ----------------- | ----------------------------- |
|
|
||||||
// | freeRelease | release |
|
|
||||||
// | freeDebug | debug |
|
|
||||||
// | freeDevelop | debug |
|
|
||||||
// | profile | profile |
|
|
||||||
// | ----------------- | ----------------------------- |
|
|
||||||
//
|
|
||||||
// This mapping is based on the following rules:
|
|
||||||
// 1. If the host app build variant name is `profile` then the equivalent
|
|
||||||
// Flutter variant is `profile`.
|
|
||||||
// 2. If the host app build variant is debuggable
|
|
||||||
// (e.g. `buildType.debuggable = true`), then the equivalent Flutter
|
|
||||||
// variant is `debug`.
|
|
||||||
// 3. Otherwise, the equivalent Flutter variant is `release`.
|
|
||||||
String variantBuildMode = FlutterPluginUtils.buildModeFor(libraryVariant.buildType)
|
|
||||||
if (FlutterPluginUtils.buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
copyFlutterAssetsTask = copyFlutterAssetsTask ?: addFlutterDeps(libraryVariant)
|
|
||||||
Task mergeAssets = project
|
|
||||||
.tasks
|
|
||||||
.findByPath(":${hostAppProjectName}:merge${appProjectVariant.name.capitalize()}Assets")
|
|
||||||
assert(mergeAssets)
|
|
||||||
mergeAssets.dependsOn(copyFlutterAssetsTask)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configurePlugins(project)
|
|
||||||
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(project, getPluginList(project))
|
|
||||||
}
|
|
||||||
}
|
|
743
packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
Normal file
743
packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
Normal file
@ -0,0 +1,743 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package com.flutter.gradle
|
||||||
|
|
||||||
|
import com.android.build.VariantOutput
|
||||||
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
|
import com.android.build.gradle.AbstractAppExtension
|
||||||
|
import com.android.build.gradle.BaseExtension
|
||||||
|
import com.android.build.gradle.LibraryExtension
|
||||||
|
import com.android.build.gradle.api.ApkVariantOutput
|
||||||
|
import com.android.build.gradle.api.BaseVariant
|
||||||
|
import com.android.build.gradle.api.BaseVariantOutput
|
||||||
|
import com.android.build.gradle.tasks.PackageAndroidArtifact
|
||||||
|
import com.android.build.gradle.tasks.ProcessAndroidResources
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils.readPropertiesIfExist
|
||||||
|
import com.flutter.gradle.plugins.PluginHandler
|
||||||
|
import com.flutter.gradle.tasks.FlutterTask
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.Plugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.UnknownTaskException
|
||||||
|
import org.gradle.api.file.Directory
|
||||||
|
import org.gradle.api.tasks.Copy
|
||||||
|
import org.gradle.api.tasks.TaskProvider
|
||||||
|
import org.gradle.api.tasks.bundling.Jar
|
||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
|
class FlutterPlugin : Plugin<Project> {
|
||||||
|
private var project: Project? = null
|
||||||
|
private var flutterRoot: File? = null
|
||||||
|
private var flutterExecutable: File? = null
|
||||||
|
private var localEngine: String? = null
|
||||||
|
private var localEngineHost: String? = null
|
||||||
|
private var localEngineSrcPath: String? = null
|
||||||
|
private var localProperties: Properties? = null
|
||||||
|
private var engineVersion: String? = null
|
||||||
|
private var engineRealm: String? = null
|
||||||
|
private var pluginHandler: PluginHandler? = null
|
||||||
|
|
||||||
|
override fun apply(project: Project) {
|
||||||
|
this.project = project
|
||||||
|
|
||||||
|
val rootProject = project.rootProject
|
||||||
|
if (FlutterPluginUtils.isFlutterAppProject(project)) {
|
||||||
|
addTaskForLockfileGeneration(rootProject)
|
||||||
|
}
|
||||||
|
|
||||||
|
val flutterRootSystemVal: String? = System.getenv("FLUTTER_ROOT")
|
||||||
|
val flutterRootPath: String =
|
||||||
|
resolveProperty("flutter.sdk", flutterRootSystemVal)
|
||||||
|
?: throw GradleException(
|
||||||
|
"Flutter SDK not found. Define location with flutter.sdk in the " +
|
||||||
|
"local.properties file or with a FLUTTER_ROOT environment variable."
|
||||||
|
)
|
||||||
|
|
||||||
|
flutterRoot = project.file(flutterRootPath)
|
||||||
|
if (!flutterRoot!!.isDirectory) {
|
||||||
|
throw GradleException("flutter.sdk must point to the Flutter SDK directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
engineVersion =
|
||||||
|
if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
|
||||||
|
"+" // Match any version since there's only one.
|
||||||
|
} else {
|
||||||
|
val engineStampPath =
|
||||||
|
Paths.get(flutterRoot!!.absolutePath, "bin", "cache", "engine.stamp")
|
||||||
|
val engineStampContent = engineStampPath.toFile().readText().trim()
|
||||||
|
"1.0.0-$engineStampContent"
|
||||||
|
}
|
||||||
|
|
||||||
|
engineRealm =
|
||||||
|
Paths
|
||||||
|
.get(flutterRoot!!.absolutePath, "bin", "cache", "engine.realm")
|
||||||
|
.toFile()
|
||||||
|
.readText()
|
||||||
|
.trim()
|
||||||
|
if (engineRealm!!.isNotEmpty()) {
|
||||||
|
engineRealm += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the Maven repository.
|
||||||
|
val hostedRepository: String =
|
||||||
|
System.getenv(FlutterPluginConstants.FLUTTER_STORAGE_BASE_URL)
|
||||||
|
?: FlutterPluginConstants.DEFAULT_MAVEN_HOST
|
||||||
|
val repository: String? =
|
||||||
|
if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
|
||||||
|
project.property(PROP_LOCAL_ENGINE_REPO) as String?
|
||||||
|
} else {
|
||||||
|
"$hostedRepository/${engineRealm}download.flutter.io"
|
||||||
|
}
|
||||||
|
rootProject.allprojects {
|
||||||
|
repositories.maven {
|
||||||
|
url = uri(repository!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.apply {
|
||||||
|
from(
|
||||||
|
Paths.get(
|
||||||
|
flutterRoot!!.absolutePath,
|
||||||
|
"packages",
|
||||||
|
"flutter_tools",
|
||||||
|
"gradle",
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"scripts",
|
||||||
|
"native_plugin_loader.gradle.kts"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val flutterExtension: FlutterExtension =
|
||||||
|
project.extensions.create("flutter", FlutterExtension::class.java)
|
||||||
|
|
||||||
|
// TODO(gmackall): is this actually a different properties file than the previous one?
|
||||||
|
val rootProjectLocalProperties = Properties()
|
||||||
|
val rootProjectLocalPropertiesFile = rootProject.file("local.properties")
|
||||||
|
if (rootProjectLocalPropertiesFile.exists()) {
|
||||||
|
rootProjectLocalPropertiesFile.reader(StandardCharsets.UTF_8).use { reader ->
|
||||||
|
rootProjectLocalProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flutterExtension.flutterVersionCode =
|
||||||
|
rootProjectLocalProperties.getProperty("flutter.versionCode", "1")
|
||||||
|
flutterExtension.flutterVersionName =
|
||||||
|
rootProjectLocalProperties.getProperty("flutter.versionName", "1.0")
|
||||||
|
|
||||||
|
this.addFlutterTasks(project)
|
||||||
|
FlutterPluginUtils.forceNdkDownload(project, flutterRootPath)
|
||||||
|
|
||||||
|
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
|
||||||
|
// Configuring split per ABI allows to generate separate APKs for each abi.
|
||||||
|
// This is a noop when building a bundle.
|
||||||
|
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||||
|
FlutterPluginUtils.getAndroidExtension(project).splits.abi {
|
||||||
|
isEnable = true
|
||||||
|
reset()
|
||||||
|
isUniversalApk = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val propDeferredComponentNames: String = "deferred-component-names"
|
||||||
|
val deferredComponentNamesValue: String? =
|
||||||
|
project.findProperty(propDeferredComponentNames) as? String
|
||||||
|
if (deferredComponentNamesValue != null) {
|
||||||
|
val componentNames: Set<String> =
|
||||||
|
deferredComponentNamesValue
|
||||||
|
.split(',')
|
||||||
|
.map { ":$it" }
|
||||||
|
.toSet()
|
||||||
|
// TODO(gmackall): Unify the types we use for the android extension. This is yet
|
||||||
|
// another type we need unfortunately.
|
||||||
|
val androidExtensionAsApplicationExtension =
|
||||||
|
project.extensions.getByType(ApplicationExtension::class.java)
|
||||||
|
// TODO(gmackall): Should we clear here? I think this is equivalent to what we used to
|
||||||
|
// do, but unsure. Can't use a closure.
|
||||||
|
androidExtensionAsApplicationExtension.dynamicFeatures.clear()
|
||||||
|
androidExtensionAsApplicationExtension.dynamicFeatures.addAll(componentNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
FlutterPluginUtils.getTargetPlatforms(project).forEach { targetArch ->
|
||||||
|
val abiValue: String? = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetArch]
|
||||||
|
val androidExtension: BaseExtension = FlutterPluginUtils.getAndroidExtension(project)
|
||||||
|
androidExtension.splits.abi.include(abiValue!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
val flutterExecutableName = getExecutableNameForPlatform("flutter")
|
||||||
|
flutterExecutable =
|
||||||
|
Paths.get(flutterRoot!!.absolutePath, "bin", flutterExecutableName).toFile()
|
||||||
|
|
||||||
|
// Validate that the provided Gradle, Java, AGP, and KGP versions are all within our
|
||||||
|
// supported range.
|
||||||
|
val shouldSkipDependencyChecks: Boolean =
|
||||||
|
project.hasProperty("skipDependencyChecks") &&
|
||||||
|
(
|
||||||
|
project.properties["skipDependencyChecks"] as? Boolean
|
||||||
|
?: false
|
||||||
|
)
|
||||||
|
if (!shouldSkipDependencyChecks) {
|
||||||
|
try {
|
||||||
|
DependencyVersionChecker.checkDependencyVersions(project)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (!project.hasProperty("usesUnsupportedDependencyVersions") ||
|
||||||
|
!(project.properties["usesUnsupportedDependencyVersions"] as Boolean)
|
||||||
|
) {
|
||||||
|
// Possible bug in dependency checking code - warn and do not block build.
|
||||||
|
project.logger.error(
|
||||||
|
"Warning: Flutter was unable to detect project Gradle, Java, " +
|
||||||
|
"AGP, and KGP versions. Skipping dependency version checking. Error was: " +
|
||||||
|
e
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// If usesUnsupportedDependencyVersions is set, the exception was thrown by us
|
||||||
|
// in the dependency version checker plugin so re-throw it here.
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseApplicationNameHandler.setBaseName(project)
|
||||||
|
val flutterProguardRules: String =
|
||||||
|
Paths
|
||||||
|
.get(
|
||||||
|
flutterRoot!!.absolutePath,
|
||||||
|
"packages",
|
||||||
|
"flutter_tools",
|
||||||
|
"gradle",
|
||||||
|
"flutter_proguard_rules.pro"
|
||||||
|
).toString()
|
||||||
|
// TODO(gmackall): reconsider getting the android extension every time
|
||||||
|
FlutterPluginUtils.getAndroidExtension(project).buildTypes {
|
||||||
|
// Add profile build type.
|
||||||
|
create("profile") {
|
||||||
|
initWith(getByName("debug"))
|
||||||
|
// TODO(gmackall): do we need to clear?
|
||||||
|
this.matchingFallbacks.clear()
|
||||||
|
this.matchingFallbacks.addAll(listOf("debug", "release"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(garyq): Shrinking is only false for multi apk split aot builds, where shrinking is not allowed yet.
|
||||||
|
// 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 (FlutterPluginUtils.shouldShrinkResources(project)) {
|
||||||
|
getByName("release") {
|
||||||
|
isMinifyEnabled = true
|
||||||
|
// Enables resource shrinking, which is performed by the Android Gradle plugin.
|
||||||
|
// The resource shrinker can't be used for libraries.
|
||||||
|
isShrinkResources = FlutterPluginUtils.isBuiltAsApp(project)
|
||||||
|
// Fallback to `android/app/proguard-rules.pro`.
|
||||||
|
// This way, custom Proguard rules can be configured as needed.
|
||||||
|
proguardFiles(
|
||||||
|
FlutterPluginUtils
|
||||||
|
.getAndroidExtension(project)
|
||||||
|
.getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
flutterProguardRules,
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FlutterPluginUtils.shouldProjectUseLocalEngine(project)) {
|
||||||
|
// This is required to pass the local engine to flutter build aot.
|
||||||
|
val engineOutPath: String = project.properties["local-engine-out"] as String
|
||||||
|
val engineOut: File = project.file(engineOutPath)
|
||||||
|
if (!engineOut.isDirectory) {
|
||||||
|
throw GradleException("local-engine-out must point to a local engine build")
|
||||||
|
}
|
||||||
|
localEngine = engineOut.name
|
||||||
|
localEngineSrcPath = engineOut.parentFile.parent
|
||||||
|
|
||||||
|
val engineHostOutPath: String = project.properties["local-engine-host-out"] as String
|
||||||
|
val engineHostOut: File = project.file(engineHostOutPath)
|
||||||
|
if (!engineHostOut.isDirectory) {
|
||||||
|
throw GradleException("local-engine-host-out must point to a local engine host build")
|
||||||
|
}
|
||||||
|
localEngineHost = engineHostOut.name
|
||||||
|
}
|
||||||
|
FlutterPluginUtils.getAndroidExtension(project).buildTypes.all {
|
||||||
|
addFlutterDependencies(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFlutterDependencies(buildType: com.android.builder.model.BuildType) {
|
||||||
|
FlutterPluginUtils.addFlutterDependencies(
|
||||||
|
project!!,
|
||||||
|
buildType,
|
||||||
|
getPluginHandler(project!!),
|
||||||
|
engineVersion!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getExecutableNameForPlatform(baseExecutableName: String): String =
|
||||||
|
if (OperatingSystem.current().isWindows) "$baseExecutableName.bat" else baseExecutableName
|
||||||
|
|
||||||
|
private fun resolveProperty(
|
||||||
|
propertyName: String,
|
||||||
|
defaultValue: String?
|
||||||
|
): String? {
|
||||||
|
if (localProperties == null) {
|
||||||
|
localProperties =
|
||||||
|
readPropertiesIfExist(File(project!!.projectDir.parentFile, "local.properties"))
|
||||||
|
}
|
||||||
|
return project?.findProperty(propertyName) as? String ?: localProperties!!.getProperty(
|
||||||
|
propertyName,
|
||||||
|
defaultValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTaskForLockfileGeneration(rootProject: Project) {
|
||||||
|
rootProject.tasks.register("generateLockfiles") {
|
||||||
|
doLast {
|
||||||
|
rootProject.subprojects.forEach { subproject ->
|
||||||
|
val gradlew: String =
|
||||||
|
getExecutableNameForPlatform("${rootProject.projectDir}/gradlew")
|
||||||
|
rootProject.exec {
|
||||||
|
workingDir(rootProject.projectDir)
|
||||||
|
executable(gradlew)
|
||||||
|
args(":${subproject.name}:dependencies", "--write-locks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFlutterTasks(projectToAddTasksTo: Project) {
|
||||||
|
if (projectToAddTasksTo.state.failure != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
FlutterPluginUtils.addTaskForJavaVersion(projectToAddTasksTo)
|
||||||
|
if (FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo)) {
|
||||||
|
FlutterPluginUtils.addTaskForPrintBuildVariants(projectToAddTasksTo)
|
||||||
|
FlutterPluginUtils.addTasksForOutputsAppLinkSettings(projectToAddTasksTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetPlatforms: List<String> =
|
||||||
|
FlutterPluginUtils.getTargetPlatforms(projectToAddTasksTo)
|
||||||
|
|
||||||
|
val flutterPlugin = this
|
||||||
|
|
||||||
|
if (FlutterPluginUtils.isFlutterAppProject(projectToAddTasksTo)) {
|
||||||
|
// TODO(gmackall): I think this can be BaseExtension, with findByType.
|
||||||
|
val android: AbstractAppExtension =
|
||||||
|
projectToAddTasksTo.extensions.findByName("android") as AbstractAppExtension
|
||||||
|
android.applicationVariants.configureEach {
|
||||||
|
val variant = this
|
||||||
|
val assembleTask = variant.assembleProvider.get()
|
||||||
|
if (!FlutterPluginUtils.shouldConfigureFlutterTask(
|
||||||
|
projectToAddTasksTo,
|
||||||
|
assembleTask
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return@configureEach
|
||||||
|
}
|
||||||
|
val copyFlutterAssetsTask: Task =
|
||||||
|
addFlutterDeps(variant, flutterPlugin, targetPlatforms)
|
||||||
|
val variantOutput: BaseVariantOutput = variant.outputs.first()
|
||||||
|
val processResources: ProcessAndroidResources =
|
||||||
|
try {
|
||||||
|
variantOutput.processResourcesProvider.get()
|
||||||
|
} catch (e: UnknownTaskException) {
|
||||||
|
variantOutput.processResources
|
||||||
|
}
|
||||||
|
processResources.dependsOn(copyFlutterAssetsTask)
|
||||||
|
|
||||||
|
// Copy the output APKs into a known location, so `flutter run` or `flutter build apk`
|
||||||
|
// can discover them. By default, this is `<app-dir>/build/app/outputs/flutter-apk/<filename>.apk`.
|
||||||
|
//
|
||||||
|
// The filename consists of `app<-abi>?<-flavor-name>?-<build-mode>.apk`.
|
||||||
|
// Where:
|
||||||
|
// * `abi` can be `armeabi-v7a|arm64-v8a|x86|x86_64` only if the flag `split-per-abi` is set.
|
||||||
|
// * `flavor-name` is the flavor used to build the app in lower case if the assemble task is called.
|
||||||
|
// * `build-mode` can be `release|debug|profile`.
|
||||||
|
variant.outputs.forEach { output ->
|
||||||
|
assembleTask.doLast {
|
||||||
|
output as ApkVariantOutput
|
||||||
|
val packageApplicationProvider: PackageAndroidArtifact =
|
||||||
|
variant.packageApplicationProvider.get()
|
||||||
|
val outputDirectory: Directory =
|
||||||
|
packageApplicationProvider.outputDirectory.get()
|
||||||
|
val outputDirectoryStr: String = outputDirectory.toString()
|
||||||
|
var filename = "app"
|
||||||
|
val abi = output.getFilter(VariantOutput.FilterType.ABI)
|
||||||
|
if (abi != null && abi.isNotEmpty()) {
|
||||||
|
filename += "-$abi"
|
||||||
|
}
|
||||||
|
if (variant.flavorName != null && variant.flavorName.isNotEmpty()) {
|
||||||
|
filename += "-${variant.flavorName.toLowerCase()}"
|
||||||
|
}
|
||||||
|
filename += "-${FlutterPluginUtils.buildModeFor(variant.buildType)}"
|
||||||
|
projectToAddTasksTo.copy {
|
||||||
|
from(File("$outputDirectoryStr/${output.outputFileName}"))
|
||||||
|
into(
|
||||||
|
File(
|
||||||
|
"${
|
||||||
|
projectToAddTasksTo.layout.buildDirectory.dir("outputs/flutter-apk")
|
||||||
|
.get()
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rename { "$filename.apk" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Copy the native assets created by build.dart and placed here by flutter assemble.
|
||||||
|
// This path is not flavor specific and must only be added once.
|
||||||
|
// If support for flavors is added to native assets, then they must only be added
|
||||||
|
// once per flavor; see https://github.com/dart-lang/native/issues/1359.
|
||||||
|
val nativeAssetsDir: String =
|
||||||
|
"${projectToAddTasksTo.layout.buildDirectory.get()}/../native_assets/android/jniLibs/lib/"
|
||||||
|
android.sourceSets
|
||||||
|
.getByName("main")
|
||||||
|
.jniLibs
|
||||||
|
.srcDir(nativeAssetsDir)
|
||||||
|
getPluginHandler(projectToAddTasksTo!!).configurePlugins(engineVersion!!)
|
||||||
|
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(
|
||||||
|
projectToAddTasksTo,
|
||||||
|
getPluginHandler(projectToAddTasksTo).getPluginList()
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Flutter host module project (Add-to-app).
|
||||||
|
val hostAppProjectName: String? =
|
||||||
|
if (projectToAddTasksTo.rootProject.hasProperty("flutter.hostAppProjectName")) {
|
||||||
|
projectToAddTasksTo.rootProject.property(
|
||||||
|
"flutter.hostAppProjectName"
|
||||||
|
) as? String
|
||||||
|
} else {
|
||||||
|
"app"
|
||||||
|
}
|
||||||
|
val appProject: Project? =
|
||||||
|
projectToAddTasksTo.rootProject.findProject(":$hostAppProjectName")
|
||||||
|
check(appProject != null) {
|
||||||
|
"Project :$hostAppProjectName doesn't exist. To customize the host app project name, set `flutter.hostAppProjectName=<project-name>` in gradle.properties."
|
||||||
|
}
|
||||||
|
// Wait for the host app project configuration.
|
||||||
|
appProject.afterEvaluate {
|
||||||
|
val androidLibraryExtension =
|
||||||
|
projectToAddTasksTo.extensions.findByType(LibraryExtension::class.java)
|
||||||
|
check(androidLibraryExtension != null)
|
||||||
|
androidLibraryExtension.libraryVariants.all libraryVariantAll@{
|
||||||
|
val libraryVariant = this
|
||||||
|
var copyFlutterAssetsTask: Task? = null
|
||||||
|
val androidAppExtension =
|
||||||
|
appProject.extensions.findByName("android") as? AbstractAppExtension
|
||||||
|
check(androidAppExtension != null)
|
||||||
|
androidAppExtension.applicationVariants.all applicationVariantAll@{
|
||||||
|
val appProjectVariant = this
|
||||||
|
val appAssembleTask: Task = appProjectVariant.assembleProvider.get()
|
||||||
|
if (!FlutterPluginUtils.shouldConfigureFlutterTask(project, appAssembleTask)) {
|
||||||
|
return@applicationVariantAll
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a compatible application variant in the host app.
|
||||||
|
//
|
||||||
|
// For example, consider a host app that defines the following variants:
|
||||||
|
// | ----------------- | ----------------------------- |
|
||||||
|
// | Build Variant | Flutter Equivalent Variant |
|
||||||
|
// | ----------------- | ----------------------------- |
|
||||||
|
// | freeRelease | release |
|
||||||
|
// | freeDebug | debug |
|
||||||
|
// | freeDevelop | debug |
|
||||||
|
// | profile | profile |
|
||||||
|
// | ----------------- | ----------------------------- |
|
||||||
|
//
|
||||||
|
// This mapping is based on the following rules:
|
||||||
|
// 1. If the host app build variant name is `profile` then the equivalent
|
||||||
|
// Flutter variant is `profile`.
|
||||||
|
// 2. If the host app build variant is debuggable
|
||||||
|
// (e.g. `buildType.debuggable = true`), then the equivalent Flutter
|
||||||
|
// variant is `debug`.
|
||||||
|
// 3. Otherwise, the equivalent Flutter variant is `release`.
|
||||||
|
val variantBuildMode: String =
|
||||||
|
FlutterPluginUtils.buildModeFor(libraryVariant.buildType)
|
||||||
|
if (FlutterPluginUtils.buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
|
||||||
|
return@applicationVariantAll
|
||||||
|
}
|
||||||
|
copyFlutterAssetsTask = copyFlutterAssetsTask ?: addFlutterDeps(
|
||||||
|
libraryVariant,
|
||||||
|
flutterPlugin,
|
||||||
|
targetPlatforms
|
||||||
|
)
|
||||||
|
val mergeAssets =
|
||||||
|
projectToAddTasksTo
|
||||||
|
.tasks
|
||||||
|
.findByPath(":$hostAppProjectName:merge${appProjectVariant.name.capitalize()}Assets")
|
||||||
|
check(mergeAssets != null)
|
||||||
|
mergeAssets.dependsOn(copyFlutterAssetsTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getPluginHandler(projectToAddTasksTo).configurePlugins(engineVersion!!)
|
||||||
|
FlutterPluginUtils.detectLowCompileSdkVersionOrNdkVersion(
|
||||||
|
projectToAddTasksTo,
|
||||||
|
getPluginHandler(projectToAddTasksTo).getPluginList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPluginHandler(project: Project): PluginHandler {
|
||||||
|
if (this.pluginHandler == null) {
|
||||||
|
this.pluginHandler = PluginHandler(project)
|
||||||
|
}
|
||||||
|
return this.pluginHandler!!
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PROP_LOCAL_ENGINE_REPO: String = "local-engine-repo"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name prefix for flutter builds. This is used to identify gradle tasks
|
||||||
|
* where we expect the flutter tool to provide any error output, and skip the
|
||||||
|
* standard Gradle error output in the FlutterEventLogger. If you change this,
|
||||||
|
* be sure to change any instances of this string in symbols in the code below
|
||||||
|
* to match.
|
||||||
|
*/
|
||||||
|
const val FLUTTER_BUILD_PREFIX: String = "flutterBuild"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a task by name, returning null if the task does not exist.
|
||||||
|
*/
|
||||||
|
private fun findTaskOrNull(
|
||||||
|
project: Project,
|
||||||
|
taskName: String
|
||||||
|
): Task? =
|
||||||
|
try {
|
||||||
|
project.tasks.named(taskName).get()
|
||||||
|
} catch (ignored: UnknownTaskException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFlutterDeps(
|
||||||
|
variant: BaseVariant,
|
||||||
|
flutterPlugin: FlutterPlugin,
|
||||||
|
targetPlatforms: List<String>
|
||||||
|
): Task {
|
||||||
|
// Shorthand
|
||||||
|
val project: Project = flutterPlugin.project!!
|
||||||
|
|
||||||
|
val fileSystemRootsValue: Array<String>? =
|
||||||
|
project
|
||||||
|
.findProperty("filesystem-roots")
|
||||||
|
?.toString()
|
||||||
|
?.split("\\|")
|
||||||
|
?.toTypedArray()
|
||||||
|
val fileSystemSchemeValue: String? =
|
||||||
|
project.findProperty("filesystem-scheme")?.toString()
|
||||||
|
val trackWidgetCreationValue: Boolean =
|
||||||
|
project.findProperty("track-widget-creation")?.toString()?.toBoolean() ?: true
|
||||||
|
val frontendServerStarterPathValue: String? =
|
||||||
|
project.findProperty("frontend-server-starter-path")?.toString()
|
||||||
|
val extraFrontEndOptionsValue: String? =
|
||||||
|
project.findProperty("extra-front-end-options")?.toString()
|
||||||
|
val extraGenSnapshotOptionsValue: String? =
|
||||||
|
project.findProperty("extra-gen-snapshot-options")?.toString()
|
||||||
|
val splitDebugInfoValue: String? = project.findProperty("split-debug-info")?.toString()
|
||||||
|
val dartObfuscationValue: Boolean =
|
||||||
|
project.findProperty("dart-obfuscation")?.toString()?.toBoolean() ?: false
|
||||||
|
val treeShakeIconsOptionsValue: Boolean =
|
||||||
|
project.findProperty("tree-shake-icons")?.toString()?.toBoolean() ?: false
|
||||||
|
val dartDefinesValue: String? = project.findProperty("dart-defines")?.toString()
|
||||||
|
val performanceMeasurementFileValue: String? =
|
||||||
|
project.findProperty("performance-measurement-file")?.toString()
|
||||||
|
val codeSizeDirectoryValue: String? =
|
||||||
|
project.findProperty("code-size-directory")?.toString()
|
||||||
|
val deferredComponentsValue: Boolean =
|
||||||
|
project.findProperty("deferred-components")?.toString()?.toBoolean() ?: false
|
||||||
|
val validateDeferredComponentsValue: Boolean =
|
||||||
|
project.findProperty("validate-deferred-components")?.toString()?.toBoolean() ?: true
|
||||||
|
|
||||||
|
if (FlutterPluginUtils.shouldProjectSplitPerAbi(project)) {
|
||||||
|
variant.outputs.forEach { output ->
|
||||||
|
// need to force this as the API does not return the right thing for our use.
|
||||||
|
output as ApkVariantOutput
|
||||||
|
val filterIdentifier: String =
|
||||||
|
output.getFilter(VariantOutput.FilterType.ABI)
|
||||||
|
val abiVersionCode: Int? = FlutterPluginConstants.ABI_VERSION[filterIdentifier]
|
||||||
|
if (abiVersionCode != null) {
|
||||||
|
output.versionCodeOverride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build an AAR when this property is defined.
|
||||||
|
val isBuildingAar: Boolean = project.hasProperty("is-plugin")
|
||||||
|
// In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project.
|
||||||
|
// `:flutter` is used as a subproject when these tasks exists and the build isn't building an AAR.
|
||||||
|
// TODO(gmackall): I think this is just always null? Which is great news! Consider removing.
|
||||||
|
val packageAssets: Task? =
|
||||||
|
findTaskOrNull(
|
||||||
|
project,
|
||||||
|
"package${FlutterPluginUtils.capitalize(variant.name)}Assets"
|
||||||
|
)
|
||||||
|
val cleanPackageAssets: Task? =
|
||||||
|
findTaskOrNull(
|
||||||
|
project,
|
||||||
|
"cleanPackage${FlutterPluginUtils.capitalize(variant.name)}Assets"
|
||||||
|
)
|
||||||
|
|
||||||
|
val isUsedAsSubproject: Boolean =
|
||||||
|
packageAssets != null && cleanPackageAssets != null && !isBuildingAar
|
||||||
|
|
||||||
|
val variantBuildMode: String = FlutterPluginUtils.buildModeFor(variant.buildType)
|
||||||
|
val flavorValue: String = variant.flavorName
|
||||||
|
val taskName: String =
|
||||||
|
FlutterPluginUtils.toCamelCase(
|
||||||
|
listOf(
|
||||||
|
"compile",
|
||||||
|
FLUTTER_BUILD_PREFIX,
|
||||||
|
variant.name
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// The task provider below will shadow a lot of the variable names, so provide this reference
|
||||||
|
// to access them within that scope.
|
||||||
|
|
||||||
|
// 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`
|
||||||
|
// original value. You either need to hoist the value
|
||||||
|
// into a separate variable `verbose verboseValue` or prefix with
|
||||||
|
// `this` (`verbose this.isVerbose()`).
|
||||||
|
val compileTaskProvider: TaskProvider<FlutterTask> =
|
||||||
|
project.tasks.register(taskName, FlutterTask::class.java) {
|
||||||
|
flutterRoot = flutterPlugin.flutterRoot
|
||||||
|
flutterExecutable = flutterPlugin.flutterExecutable
|
||||||
|
buildMode = variantBuildMode
|
||||||
|
minSdkVersion = variant.mergedFlavor.minSdkVersion!!.apiLevel
|
||||||
|
localEngine = flutterPlugin.localEngine
|
||||||
|
localEngineHost = flutterPlugin.localEngineHost
|
||||||
|
localEngineSrcPath = flutterPlugin.localEngineSrcPath
|
||||||
|
targetPath = FlutterPluginUtils.getFlutterTarget(project)
|
||||||
|
verbose = FlutterPluginUtils.isProjectVerbose(project)
|
||||||
|
fastStart = FlutterPluginUtils.isProjectFastStart(project)
|
||||||
|
fileSystemRoots = fileSystemRootsValue
|
||||||
|
fileSystemScheme = fileSystemSchemeValue
|
||||||
|
trackWidgetCreation = trackWidgetCreationValue
|
||||||
|
targetPlatformValues = targetPlatforms
|
||||||
|
sourceDir = FlutterPluginUtils.getFlutterSourceDirectory(project)
|
||||||
|
intermediateDir =
|
||||||
|
project.file(
|
||||||
|
project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/")
|
||||||
|
)
|
||||||
|
frontendServerStarterPath = frontendServerStarterPathValue
|
||||||
|
extraFrontEndOptions = extraFrontEndOptionsValue
|
||||||
|
extraGenSnapshotOptions = extraGenSnapshotOptionsValue
|
||||||
|
splitDebugInfo = splitDebugInfoValue
|
||||||
|
treeShakeIcons = treeShakeIconsOptionsValue
|
||||||
|
dartObfuscation = dartObfuscationValue
|
||||||
|
dartDefines = dartDefinesValue
|
||||||
|
performanceMeasurementFile = performanceMeasurementFileValue
|
||||||
|
codeSizeDirectory = codeSizeDirectoryValue
|
||||||
|
deferredComponents = deferredComponentsValue
|
||||||
|
validateDeferredComponents = validateDeferredComponentsValue
|
||||||
|
flavor = flavorValue
|
||||||
|
}
|
||||||
|
val compileTask: FlutterTask = compileTaskProvider.get()
|
||||||
|
val libJar: File =
|
||||||
|
project.file(
|
||||||
|
project.layout.buildDirectory.dir("${FlutterPluginConstants.INTERMEDIATES_DIR}/flutter/${variant.name}/libs.jar")
|
||||||
|
)
|
||||||
|
val packJniLibsTaskProvider: TaskProvider<Jar> =
|
||||||
|
project.tasks.register<Jar>(
|
||||||
|
"packJniLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}",
|
||||||
|
Jar::class.java
|
||||||
|
) {
|
||||||
|
destinationDirectory.set(libJar.parentFile)
|
||||||
|
archiveFileName.set(libJar.name)
|
||||||
|
dependsOn(compileTask)
|
||||||
|
targetPlatforms.forEach { targetPlatform ->
|
||||||
|
val abi: String? = FlutterPluginConstants.PLATFORM_ARCH_MAP[targetPlatform]
|
||||||
|
from("${compileTask.intermediateDir}/$abi") {
|
||||||
|
include("*.so")
|
||||||
|
// Move `app.so` to `lib/<abi>/libapp.so`
|
||||||
|
rename { filename: String -> "lib/$abi/lib$filename" }
|
||||||
|
}
|
||||||
|
// 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/'.
|
||||||
|
val buildDir: String =
|
||||||
|
"${FlutterPluginUtils.getFlutterSourceDirectory(project)}/build"
|
||||||
|
val nativeAssetsDir: String =
|
||||||
|
"$buildDir/native_assets/android/jniLibs/lib"
|
||||||
|
from("$nativeAssetsDir/$abi") {
|
||||||
|
include("*.so")
|
||||||
|
rename { filename: String -> "lib/$abi/$filename" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val packJniLibsTask: Task = packJniLibsTaskProvider.get()
|
||||||
|
FlutterPluginUtils.addApiDependencies(
|
||||||
|
project,
|
||||||
|
variant.name,
|
||||||
|
project.files({
|
||||||
|
packJniLibsTask
|
||||||
|
})
|
||||||
|
)
|
||||||
|
val copyFlutterAssetsTaskProvider: TaskProvider<Copy> =
|
||||||
|
project.tasks.register(
|
||||||
|
"copyFlutterAssets${variant.name.capitalize()}",
|
||||||
|
Copy::class.java
|
||||||
|
) {
|
||||||
|
dependsOn(compileTask)
|
||||||
|
with(compileTask.assets)
|
||||||
|
// TODO(gmackall): Replace with filePermissions.user.read/write = true once
|
||||||
|
// minimum supported Gradle version is 8.3.
|
||||||
|
fileMode = 420 // corresponds to unix 0644 in base 8
|
||||||
|
if (isUsedAsSubproject) {
|
||||||
|
// TODO(gmackall): above is always false, can delete
|
||||||
|
dependsOn(packageAssets)
|
||||||
|
dependsOn(cleanPackageAssets)
|
||||||
|
into(packageAssets!!.outputs)
|
||||||
|
}
|
||||||
|
val mergeAssets =
|
||||||
|
try {
|
||||||
|
variant.mergeAssetsProvider.get()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
variant.mergeAssets
|
||||||
|
}
|
||||||
|
dependsOn(mergeAssets)
|
||||||
|
dependsOn("clean${mergeAssets.name.capitalize()}")
|
||||||
|
mergeAssets.mustRunAfter("clean${mergeAssets.name.capitalize()}")
|
||||||
|
into(mergeAssets.outputDir)
|
||||||
|
}
|
||||||
|
val copyFlutterAssetsTask: Task = copyFlutterAssetsTaskProvider.get()
|
||||||
|
if (!isUsedAsSubproject) {
|
||||||
|
val variantOutput: BaseVariantOutput = variant.outputs.first()
|
||||||
|
val processResources =
|
||||||
|
try {
|
||||||
|
variantOutput.processResourcesProvider.get()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
variantOutput.processResources
|
||||||
|
}
|
||||||
|
processResources.dependsOn(copyFlutterAssetsTask)
|
||||||
|
}
|
||||||
|
// The following tasks use the output of copyFlutterAssetsTask,
|
||||||
|
// so it's necessary to declare it as an dependency since Gradle 8.
|
||||||
|
// See https://docs.gradle.org/8.1/userguide/validation_problems.html#implicit_dependency.
|
||||||
|
val tasksToCheck =
|
||||||
|
listOf(
|
||||||
|
"compress${variant.name.capitalize()}Assets",
|
||||||
|
"bundle${variant.name.capitalize()}Aar",
|
||||||
|
"bundle${variant.name.capitalize()}LocalLintAar"
|
||||||
|
)
|
||||||
|
tasksToCheck.forEach { taskTocheck ->
|
||||||
|
try {
|
||||||
|
project.tasks.named(taskTocheck).configure {
|
||||||
|
dependsOn(copyFlutterAssetsTask)
|
||||||
|
}
|
||||||
|
} catch (ignored: UnknownTaskException) {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copyFlutterAssetsTask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,6 @@ object FlutterPluginConstants {
|
|||||||
const val INTERMEDIATES_DIR = "intermediates"
|
const val INTERMEDIATES_DIR = "intermediates"
|
||||||
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
const val FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
||||||
const val DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
|
const val DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"
|
||||||
const val WEBSITE_DEPLOYMENT_ANDROID_BUILD_CONFIG = "https://flutter.dev/to/review-gradle-config"
|
|
||||||
|
|
||||||
/** Maps platforms to ABI architectures. */
|
/** Maps platforms to ABI architectures. */
|
||||||
@JvmStatic val PLATFORM_ARCH_MAP =
|
@JvmStatic val PLATFORM_ARCH_MAP =
|
||||||
@ -42,7 +41,7 @@ object FlutterPluginConstants {
|
|||||||
* Otherwise, the Play Store will complain that the APK variants have the same version.
|
* Otherwise, the Play Store will complain that the APK variants have the same version.
|
||||||
*/
|
*/
|
||||||
@JvmStatic val ABI_VERSION =
|
@JvmStatic val ABI_VERSION =
|
||||||
mapOf<String, Int>( // Explicit type for clarity, though inferred
|
mapOf<String, Int>(
|
||||||
ARCH_ARM32 to 1,
|
ARCH_ARM32 to 1,
|
||||||
ARCH_ARM64 to 2,
|
ARCH_ARM64 to 2,
|
||||||
ARCH_X86 to 3,
|
ARCH_X86 to 3,
|
||||||
|
@ -10,6 +10,7 @@ import com.android.build.gradle.api.ApplicationVariant
|
|||||||
import com.android.build.gradle.api.BaseVariantOutput
|
import com.android.build.gradle.api.BaseVariantOutput
|
||||||
import com.android.build.gradle.tasks.ProcessAndroidResources
|
import com.android.build.gradle.tasks.ProcessAndroidResources
|
||||||
import com.android.builder.model.BuildType
|
import com.android.builder.model.BuildType
|
||||||
|
import com.flutter.gradle.plugins.PluginHandler
|
||||||
import groovy.lang.Closure
|
import groovy.lang.Closure
|
||||||
import groovy.util.Node
|
import groovy.util.Node
|
||||||
import groovy.util.XmlParser
|
import groovy.util.XmlParser
|
||||||
@ -139,19 +140,6 @@ object FlutterPluginUtils {
|
|||||||
|
|
||||||
// TODO(54566): Can remove this function and its call sites once resolved.
|
// 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
|
* Returns the Gradle settings script for the build. When both Groovy and
|
||||||
* Kotlin variants exist, then Groovy (settings.gradle) is preferred over
|
* Kotlin variants exist, then Groovy (settings.gradle) is preferred over
|
||||||
@ -406,7 +394,7 @@ object FlutterPluginUtils {
|
|||||||
return project.property(PROP_LOCAL_ENGINE_BUILD_MODE) == flutterBuildMode
|
return project.property(PROP_LOCAL_ENGINE_BUILD_MODE) == flutterBuildMode
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAndroidExtension(project: Project): BaseExtension {
|
internal fun getAndroidExtension(project: Project): BaseExtension {
|
||||||
// Common supertype of the android extension types.
|
// Common supertype of the android extension types.
|
||||||
// But maybe this should be https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/dsl/TestedExtension.
|
// But maybe this should be https://developer.android.com/reference/tools/gradle-api/8.7/com/android/build/api/dsl/TestedExtension.
|
||||||
return project.extensions.findByType(BaseExtension::class.java)!!
|
return project.extensions.findByType(BaseExtension::class.java)!!
|
||||||
@ -611,7 +599,7 @@ object FlutterPluginUtils {
|
|||||||
|
|
||||||
// Otherwise, point to an empty CMakeLists.txt, and ignore associated warnings.
|
// Otherwise, point to an empty CMakeLists.txt, and ignore associated warnings.
|
||||||
gradleProjectAndroidExtension.externalNativeBuild.cmake.path(
|
gradleProjectAndroidExtension.externalNativeBuild.cmake.path(
|
||||||
"$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt"
|
"$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AGP defaults to outputting build artifacts in `android/app/.cxx`. This directory is a
|
// AGP defaults to outputting build artifacts in `android/app/.cxx`. This directory is a
|
||||||
@ -656,7 +644,7 @@ object FlutterPluginUtils {
|
|||||||
internal fun addFlutterDependencies(
|
internal fun addFlutterDependencies(
|
||||||
project: Project,
|
project: Project,
|
||||||
buildType: BuildType,
|
buildType: BuildType,
|
||||||
pluginList: List<Map<String?, Any?>>,
|
pluginHandler: PluginHandler,
|
||||||
engineVersion: String
|
engineVersion: String
|
||||||
) {
|
) {
|
||||||
val flutterBuildMode: String = buildModeFor(buildType)
|
val flutterBuildMode: String = buildModeFor(buildType)
|
||||||
@ -676,11 +664,9 @@ object FlutterPluginUtils {
|
|||||||
// embedding.
|
// embedding.
|
||||||
val pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency: List<Map<String?, Any?>> =
|
val pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency: List<Map<String?, Any?>> =
|
||||||
if (flutterBuildMode == "release") {
|
if (flutterBuildMode == "release") {
|
||||||
getPluginListWithoutDevDependencies(
|
pluginHandler.getPluginListWithoutDevDependencies()
|
||||||
pluginList
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
pluginList
|
pluginHandler.getPluginList()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isFlutterAppProject(project) || pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency.isEmpty()) {
|
if (!isFlutterAppProject(project) || pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency.isEmpty()) {
|
||||||
@ -702,143 +688,6 @@ object FlutterPluginUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the list of plugins (as map) that support the Android platform and are dependencies of the
|
|
||||||
* Android project excluding dev dependencies.
|
|
||||||
*
|
|
||||||
* The map value contains either the plugins `name` (String),
|
|
||||||
* its `path` (String), or its `dependencies` (List<String>).
|
|
||||||
* See [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/scripts/native_plugin_loader.gradle.kts
|
|
||||||
*/
|
|
||||||
private fun getPluginListWithoutDevDependencies(pluginList: List<Map<String?, Any?>>): List<Map<String?, Any?>> =
|
|
||||||
pluginList.filter { pluginObject -> pluginObject["dev_dependency"] == false }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the dependencies on other plugin projects to the plugin project.
|
|
||||||
* A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
|
|
||||||
* making the Gradle plugin project A depend on the Gradle plugin project B.
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@JvmName("configurePluginDependencies")
|
|
||||||
internal fun configurePluginDependencies(
|
|
||||||
project: Project,
|
|
||||||
pluginObject: Map<String?, Any?>
|
|
||||||
) {
|
|
||||||
val pluginName: String =
|
|
||||||
requireNotNull(pluginObject["name"] as? String) {
|
|
||||||
"Missing valid \"name\" property for plugin object: $pluginObject"
|
|
||||||
}
|
|
||||||
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return
|
|
||||||
|
|
||||||
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
|
||||||
val flutterBuildMode: String = buildModeFor(buildType)
|
|
||||||
if (flutterBuildMode == "release" && (pluginObject["dev_dependency"] as? Boolean == true)) {
|
|
||||||
// This plugin is a dev dependency will not be included in the
|
|
||||||
// release build, so no need to add its dependencies.
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
val dependencies = requireNotNull(pluginObject["dependencies"] as? List<*>)
|
|
||||||
dependencies.forEach innerForEach@{ pluginDependencyName ->
|
|
||||||
check(pluginDependencyName is String)
|
|
||||||
if (pluginDependencyName.isEmpty()) {
|
|
||||||
return@innerForEach
|
|
||||||
}
|
|
||||||
|
|
||||||
val dependencyProject =
|
|
||||||
project.rootProject.findProject(":$pluginDependencyName") ?: return@innerForEach
|
|
||||||
pluginProject.afterEvaluate {
|
|
||||||
pluginProject.dependencies.add("implementation", dependencyProject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs configuration related to the plugin's Gradle [Project], including
|
|
||||||
* 1. Adding the plugin itself as a dependency to the main project.
|
|
||||||
* 2. Adding the main project's build types to the plugin's build types.
|
|
||||||
* 3. Adding a dependency on the Flutter embedding to the plugin.
|
|
||||||
*
|
|
||||||
* Should only be called on plugins that support the Android platform.
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@JvmName("configurePluginProject")
|
|
||||||
internal fun configurePluginProject(
|
|
||||||
project: Project,
|
|
||||||
pluginObject: Map<String?, Any?>,
|
|
||||||
engineVersion: String
|
|
||||||
) {
|
|
||||||
// TODO(gmackall): should guard this with a pluginObject.contains().
|
|
||||||
val pluginName =
|
|
||||||
requireNotNull(pluginObject["name"] as? String) { "Plugin name must be a string for plugin object: $pluginObject" }
|
|
||||||
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return
|
|
||||||
|
|
||||||
// Apply the "flutter" Gradle extension to plugins so that they can use it's vended
|
|
||||||
// compile/target/min sdk values.
|
|
||||||
pluginProject.extensions.create("flutter", FlutterExtension::class.java)
|
|
||||||
|
|
||||||
// Add plugin dependency to the app project. We only want to add dependency
|
|
||||||
// for dev dependencies in non-release builds.
|
|
||||||
project.afterEvaluate {
|
|
||||||
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
|
||||||
if (!(pluginObject["dev_dependency"] as Boolean) || buildType.name != "release") {
|
|
||||||
project.dependencies.add("${buildType.name}Api", pluginProject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until the Android plugin loaded.
|
|
||||||
pluginProject.afterEvaluate {
|
|
||||||
// Checks if there is a mismatch between the plugin compileSdkVersion and the project compileSdkVersion.
|
|
||||||
val projectCompileSdkVersion: String = getCompileSdkFromProject(project)
|
|
||||||
val pluginCompileSdkVersion: String = getCompileSdkFromProject(pluginProject)
|
|
||||||
// TODO(gmackall): This is doing a string comparison, which is odd and also can be wrong
|
|
||||||
// when comparing preview versions (against non preview, and also in the
|
|
||||||
// case of alphabet reset which happened with "Baklava".
|
|
||||||
if (pluginCompileSdkVersion > projectCompileSdkVersion) {
|
|
||||||
project.logger.quiet("Warning: The plugin $pluginName requires Android SDK version $pluginCompileSdkVersion or higher.")
|
|
||||||
project.logger.quiet(
|
|
||||||
"For more information about build configuration, see ${FlutterPluginConstants.WEBSITE_DEPLOYMENT_ANDROID_BUILD_CONFIG}."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
|
||||||
addEmbeddingDependencyToPlugin(project, pluginProject, buildType, engineVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addEmbeddingDependencyToPlugin(
|
|
||||||
project: Project,
|
|
||||||
pluginProject: Project,
|
|
||||||
buildType: BuildType,
|
|
||||||
engineVersion: String
|
|
||||||
) {
|
|
||||||
val flutterBuildMode: String = buildModeFor(buildType)
|
|
||||||
// TODO(gmackall): this should be safe to remove, as the minimum required AGP is well above
|
|
||||||
// 3.5. We should try to remove it.
|
|
||||||
// In AGP 3.5, the embedding must be added as an API implementation,
|
|
||||||
// so java8 features are desugared against the runtime classpath.
|
|
||||||
// For more, see https://github.com/flutter/flutter/issues/40126
|
|
||||||
if (!supportsBuildMode(pluginProject, flutterBuildMode)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!pluginProject.hasProperty("android")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy build types from the app to the plugin.
|
|
||||||
// This allows to build apps with plugins and custom build types or flavors.
|
|
||||||
getAndroidExtension(pluginProject).buildTypes.addAll(getAndroidExtension(project).buildTypes)
|
|
||||||
|
|
||||||
// The embedding is API dependency of the plugin, so the AGP is able to desugar
|
|
||||||
// default method implementations when the interface is implemented by a plugin.
|
|
||||||
//
|
|
||||||
// See https://issuetracker.google.com/139821726, and
|
|
||||||
// https://github.com/flutter/flutter/issues/72185 for more details.
|
|
||||||
addApiDependencies(pluginProject, buildType.name, "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------ Task adders (a subset of the above category)
|
// ------------------ Task adders (a subset of the above category)
|
||||||
|
|
||||||
// Add a task that can be called on flutter projects that prints the Java version used in Gradle.
|
// Add a task that can be called on flutter projects that prints the Java version used in Gradle.
|
||||||
|
@ -17,24 +17,21 @@ import java.io.File
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
object NativePluginLoaderReflectionBridge {
|
object NativePluginLoaderReflectionBridge {
|
||||||
private var nativePluginLoader: Any? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstraction to hide reflection from calling sites. See ../scripts/native_plugin_loader.gradle.kts.
|
* An abstraction to hide reflection from calling sites. See ../scripts/native_plugin_loader.gradle.kts.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
|
||||||
fun getPlugins(
|
fun getPlugins(
|
||||||
extraProperties: ExtraPropertiesExtension,
|
extraProperties: ExtraPropertiesExtension,
|
||||||
flutterProjectRoot: File
|
flutterProjectRoot: File
|
||||||
): List<Map<String, Any>> {
|
): List<Map<String?, Any?>> {
|
||||||
nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
val nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val pluginList: List<Map<String, Any>> =
|
val pluginList: List<Map<String?, Any?>> =
|
||||||
nativePluginLoader!!::class
|
nativePluginLoader::class
|
||||||
.members
|
.members
|
||||||
.firstOrNull { it.name == "getPlugins" }
|
.firstOrNull { it.name == "getPlugins" }
|
||||||
?.call(nativePluginLoader, flutterProjectRoot) as List<Map<String, Any>>
|
?.call(nativePluginLoader, flutterProjectRoot) as List<Map<String?, Any?>>
|
||||||
|
|
||||||
return pluginList
|
return pluginList
|
||||||
}
|
}
|
||||||
@ -42,16 +39,15 @@ object NativePluginLoaderReflectionBridge {
|
|||||||
/**
|
/**
|
||||||
* An abstraction to hide reflection from calling sites. See ../scripts/native_plugin_loader.gradle.kts.
|
* An abstraction to hide reflection from calling sites. See ../scripts/native_plugin_loader.gradle.kts.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
|
||||||
fun getDependenciesMetadata(
|
fun getDependenciesMetadata(
|
||||||
extraProperties: ExtraPropertiesExtension,
|
extraProperties: ExtraPropertiesExtension,
|
||||||
flutterProjectRoot: File
|
flutterProjectRoot: File
|
||||||
): Map<String, Any> {
|
): Map<String, Any> {
|
||||||
nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
val nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val dependenciesMetadata: Map<String, Any> =
|
val dependenciesMetadata: Map<String, Any> =
|
||||||
nativePluginLoader!!::class
|
nativePluginLoader::class
|
||||||
.members
|
.members
|
||||||
.firstOrNull { it.name == "dependenciesMetadata" }
|
.firstOrNull { it.name == "dependenciesMetadata" }
|
||||||
?.call(nativePluginLoader, flutterProjectRoot) as Map<String, Any>
|
?.call(nativePluginLoader, flutterProjectRoot) as Map<String, Any>
|
||||||
|
@ -0,0 +1,326 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package com.flutter.gradle.plugins
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import com.android.builder.model.BuildType
|
||||||
|
import com.flutter.gradle.FlutterExtension
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils.addApiDependencies
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils.buildModeFor
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils.getAndroidExtension
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils.getCompileSdkFromProject
|
||||||
|
import com.flutter.gradle.FlutterPluginUtils.supportsBuildMode
|
||||||
|
import com.flutter.gradle.NativePluginLoaderReflectionBridge
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles interactions with the flutter plugins (not Gradle plugins) used by the Flutter project,
|
||||||
|
* such as retrieving them as a list and configuring them as Gradle dependencies of the main Gradle
|
||||||
|
* project.
|
||||||
|
*/
|
||||||
|
class PluginHandler(
|
||||||
|
val project: Project
|
||||||
|
) {
|
||||||
|
private var pluginList: List<Map<String?, Any?>>? = null
|
||||||
|
private var pluginDependencies: List<Map<String?, Any?>>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of plugins (as map) that support the Android platform.
|
||||||
|
*
|
||||||
|
* The map contains the following key - value pairs:
|
||||||
|
* `name` - the plugins name (String),
|
||||||
|
* `path` - it's path (String),
|
||||||
|
* `dependencies` - a list of its dependencies names (List<String>)
|
||||||
|
* `dev_dependency` - a boolean indicating whether the plugin is a dev dependency (Boolean)
|
||||||
|
* `native_build` - a boolean indicating whether the plugin has native code (Boolean)
|
||||||
|
*
|
||||||
|
* This format is defined in packages/flutter_tools/lib/src/flutter_plugins.dart, in the
|
||||||
|
* _createPluginMapOfPlatform method.
|
||||||
|
* See also [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/scripts/native_plugin_loader.gradle.kts
|
||||||
|
*/
|
||||||
|
internal fun getPluginList(): List<Map<String?, Any?>> {
|
||||||
|
if (pluginList == null) {
|
||||||
|
pluginList =
|
||||||
|
NativePluginLoaderReflectionBridge.getPlugins(
|
||||||
|
project.extraProperties,
|
||||||
|
FlutterPluginUtils.getFlutterSourceDirectory(project)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return pluginList!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(54566, 48918): Remove in favor of [getPluginList] only, see also
|
||||||
|
// https://github.com/flutter/flutter/blob/1c90ed8b64d9ed8ce2431afad8bc6e6d9acc4556/packages/flutter_tools/lib/src/flutter_plugins.dart#L212
|
||||||
|
|
||||||
|
/** Gets the plugins dependencies from `.flutter-plugins-dependencies`. */
|
||||||
|
private fun getPluginDependencies(): List<Map<String?, Any?>> {
|
||||||
|
if (pluginDependencies == null) {
|
||||||
|
val meta: Map<String, Any> =
|
||||||
|
NativePluginLoaderReflectionBridge.getDependenciesMetadata(
|
||||||
|
project.extraProperties,
|
||||||
|
FlutterPluginUtils.getFlutterSourceDirectory(project)
|
||||||
|
)
|
||||||
|
check(meta["dependencyGraph"] is List<*>)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
pluginDependencies = meta["dependencyGraph"] as List<Map<String?, Any?>>
|
||||||
|
}
|
||||||
|
return pluginDependencies!!
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(54566, 48918): Can remove once the issues are resolved.
|
||||||
|
// This means all references to `.flutter-plugins` are then removed and
|
||||||
|
// apps only depend exclusively on the `plugins` property in `.flutter-plugins-dependencies`.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Workaround to load non-native plugins for developers who may still use an
|
||||||
|
* old `settings.gradle` which includes all the plugins from the
|
||||||
|
* `.flutter-plugins` file, even if not made for Android.
|
||||||
|
* The settings.gradle then:
|
||||||
|
* 1) tries to add the android plugin implementation, which does not
|
||||||
|
* exist at all, but is also not included successfully
|
||||||
|
* (which does not throw an error and therefore isn't a problem), or
|
||||||
|
* 2) includes the plugin successfully as a valid android plugin
|
||||||
|
* directory exists, even if the surrounding flutter package does not
|
||||||
|
* support the android platform (see e.g. apple_maps_flutter: 1.0.1).
|
||||||
|
* So as it's included successfully it expects to be added as API.
|
||||||
|
* This is only possible by taking all plugins into account, which
|
||||||
|
* only appear on the `dependencyGraph` and in the `.flutter-plugins` file.
|
||||||
|
* So in summary the plugins are currently selected from the `dependencyGraph`
|
||||||
|
* and filtered then with the [doesSupportAndroidPlatform] method instead of
|
||||||
|
* just using the `plugins.android` list.
|
||||||
|
*/
|
||||||
|
private fun configureLegacyPluginEachProjects(engineVersionValue: String) {
|
||||||
|
try {
|
||||||
|
// Read the contents of the settings.gradle file.
|
||||||
|
// Remove block/line comments
|
||||||
|
var settingsText =
|
||||||
|
FlutterPluginUtils
|
||||||
|
.getSettingsGradleFileFromProjectDir(
|
||||||
|
project.projectDir,
|
||||||
|
project.logger
|
||||||
|
).readText(StandardCharsets.UTF_8)
|
||||||
|
settingsText =
|
||||||
|
settingsText
|
||||||
|
.replace(Regex("""(?s)/\*.*?\*/"""), "")
|
||||||
|
.replace(Regex("""(?m)//.*$"""), "")
|
||||||
|
if (!settingsText.contains("'.flutter-plugins'")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (ignored: FileNotFoundException) {
|
||||||
|
throw 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(
|
||||||
|
legacyFlutterPluginsWarning
|
||||||
|
)
|
||||||
|
val deps: List<Map<String?, Any?>> = getPluginDependencies()
|
||||||
|
val pluginsNameSet = HashSet<String>()
|
||||||
|
getPluginList().mapTo(pluginsNameSet) { plugin -> plugin["name"] as String }
|
||||||
|
deps.filterNot { plugin -> pluginsNameSet.contains(plugin["name"]) }
|
||||||
|
deps.forEach { plugin: Map<String?, Any?> ->
|
||||||
|
val pluginProject = project.rootProject.findProject(":${plugin["name"]}")
|
||||||
|
if (pluginProject == null) {
|
||||||
|
// Plugin was not included in `settings.gradle`, but is listed in `.flutter-plugins`.
|
||||||
|
project.logger.error(
|
||||||
|
"Plugin project :${plugin["name"]} listed, but not found. Please fix your settings.gradle/settings.gradle.kts."
|
||||||
|
)
|
||||||
|
} else if (pluginSupportsAndroidPlatform(project)) {
|
||||||
|
// 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(project, plugin, engineVersionValue)
|
||||||
|
} else {
|
||||||
|
// Plugin has no or an empty `android` folder. No action required.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun configurePlugins(engineVersionValue: String) {
|
||||||
|
configureLegacyPluginEachProjects(engineVersionValue)
|
||||||
|
val pluginList: List<Map<String?, Any?>> = getPluginList()
|
||||||
|
pluginList.forEach { plugin: Map<String?, Any?> ->
|
||||||
|
configurePluginProject(
|
||||||
|
project,
|
||||||
|
plugin,
|
||||||
|
engineVersionValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pluginList.forEach { plugin: Map<String?, Any?> ->
|
||||||
|
configurePluginDependencies(project, plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of plugins (as map) that support the Android platform and are dependencies of the
|
||||||
|
* Android project excluding dev dependencies.
|
||||||
|
*
|
||||||
|
* The map value contains either the plugins `name` (String),
|
||||||
|
* its `path` (String), or its `dependencies` (List<String>).
|
||||||
|
* See [NativePluginLoader#getPlugins] in packages/flutter_tools/gradle/src/main/scripts/native_plugin_loader.gradle.kts
|
||||||
|
*/
|
||||||
|
internal fun getPluginListWithoutDevDependencies(): List<Map<String?, Any?>> =
|
||||||
|
getPluginList().filter { pluginObject -> pluginObject["dev_dependency"] == false }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Flutter Docs Website URLs for help messages.
|
||||||
|
*/
|
||||||
|
private const val WEBSITE_DEPLOYMENT_ANDROID_BUILD_CONFIG = "https://flutter.dev/to/review-gradle-config"
|
||||||
|
|
||||||
|
@VisibleForTesting internal val legacyFlutterPluginsWarning =
|
||||||
|
"""
|
||||||
|
Warning: This project is still reading the deprecated '.flutter-plugins. file.
|
||||||
|
In an upcoming stable release support for this file will be completely removed and your build will fail.
|
||||||
|
See https:/flutter.dev/to/flutter-plugins-configuration.
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs configuration related to the plugin's Gradle [Project], including
|
||||||
|
* 1. Adding the plugin itself as a dependency to the main project.
|
||||||
|
* 2. Adding the main project's build types to the plugin's build types.
|
||||||
|
* 3. Adding a dependency on the Flutter embedding to the plugin.
|
||||||
|
*
|
||||||
|
* Should only be called on plugins that support the Android platform.
|
||||||
|
*/
|
||||||
|
private fun configurePluginProject(
|
||||||
|
project: Project,
|
||||||
|
pluginObject: Map<String?, Any?>,
|
||||||
|
engineVersion: String
|
||||||
|
) {
|
||||||
|
val pluginName =
|
||||||
|
requireNotNull(pluginObject["name"] as? String) { "Plugin name must be a string for plugin object: $pluginObject" }
|
||||||
|
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return
|
||||||
|
|
||||||
|
// Apply the "flutter" Gradle extension to plugins so that they can use it's vended
|
||||||
|
// compile/target/min sdk values.
|
||||||
|
pluginProject.extensions.create("flutter", FlutterExtension::class.java)
|
||||||
|
|
||||||
|
// Add plugin dependency to the app project. We only want to add dependency
|
||||||
|
// for dev dependencies in non-release builds.
|
||||||
|
project.afterEvaluate {
|
||||||
|
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
||||||
|
if (!(pluginObject["dev_dependency"] as Boolean) || buildType.name != "release") {
|
||||||
|
project.dependencies.add("${buildType.name}Api", pluginProject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the Android plugin loaded.
|
||||||
|
pluginProject.afterEvaluate {
|
||||||
|
// Checks if there is a mismatch between the plugin compileSdkVersion and the project compileSdkVersion.
|
||||||
|
val projectCompileSdkVersion: String = getCompileSdkFromProject(project)
|
||||||
|
val pluginCompileSdkVersion: String = getCompileSdkFromProject(pluginProject)
|
||||||
|
// TODO(gmackall): This is doing a string comparison, which is odd and also can be wrong
|
||||||
|
// when comparing preview versions (against non preview, and also in the
|
||||||
|
// case of alphabet reset which happened with "Baklava".
|
||||||
|
if (pluginCompileSdkVersion > projectCompileSdkVersion) {
|
||||||
|
project.logger.quiet(
|
||||||
|
"Warning: The plugin $pluginName requires Android SDK version $pluginCompileSdkVersion or higher."
|
||||||
|
)
|
||||||
|
project.logger.quiet(
|
||||||
|
"For more information about build configuration, see ${WEBSITE_DEPLOYMENT_ANDROID_BUILD_CONFIG}."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
||||||
|
addEmbeddingDependencyToPlugin(project, pluginProject, buildType, engineVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addEmbeddingDependencyToPlugin(
|
||||||
|
project: Project,
|
||||||
|
pluginProject: Project,
|
||||||
|
buildType: BuildType,
|
||||||
|
engineVersion: String
|
||||||
|
) {
|
||||||
|
val flutterBuildMode: String = buildModeFor(buildType)
|
||||||
|
// TODO(gmackall): this should be safe to remove, as the minimum required AGP is well above
|
||||||
|
// 3.5. We should try to remove it.
|
||||||
|
// In AGP 3.5, the embedding must be added as an API implementation,
|
||||||
|
// so java8 features are desugared against the runtime classpath.
|
||||||
|
// For more, see https://github.com/flutter/flutter/issues/40126
|
||||||
|
if (!supportsBuildMode(pluginProject, flutterBuildMode)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!pluginProject.hasProperty("android")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy build types from the app to the plugin.
|
||||||
|
// This allows to build apps with plugins and custom build types or flavors.
|
||||||
|
getAndroidExtension(pluginProject).buildTypes.addAll(getAndroidExtension(project).buildTypes)
|
||||||
|
|
||||||
|
// The embedding is API dependency of the plugin, so the AGP is able to desugar
|
||||||
|
// default method implementations when the interface is implemented by a plugin.
|
||||||
|
//
|
||||||
|
// See https://issuetracker.google.com/139821726, and
|
||||||
|
// https://github.com/flutter/flutter/issues/72185 for more details.
|
||||||
|
addApiDependencies(pluginProject, buildType.name, "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if the given project is a plugin project having an `android` directory
|
||||||
|
* containing a `build.gradle` or `build.gradle.kts` file.
|
||||||
|
*/
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the dependencies on other plugin projects to the plugin project.
|
||||||
|
* A plugin A can depend on plugin B. As a result, this dependency must be surfaced by
|
||||||
|
* making the Gradle plugin project A depend on the Gradle plugin project B.
|
||||||
|
*/
|
||||||
|
private fun configurePluginDependencies(
|
||||||
|
project: Project,
|
||||||
|
pluginObject: Map<String?, Any?>
|
||||||
|
) {
|
||||||
|
val pluginName: String =
|
||||||
|
requireNotNull(pluginObject["name"] as? String) {
|
||||||
|
"Missing valid \"name\" property for plugin object: $pluginObject"
|
||||||
|
}
|
||||||
|
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return
|
||||||
|
|
||||||
|
getAndroidExtension(project).buildTypes.forEach { buildType ->
|
||||||
|
val flutterBuildMode: String = buildModeFor(buildType)
|
||||||
|
if (flutterBuildMode == "release" && (pluginObject["dev_dependency"] as? Boolean == true)) {
|
||||||
|
// This plugin is a dev dependency will not be included in the
|
||||||
|
// release build, so no need to add its dependencies.
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val dependencies = requireNotNull(pluginObject["dependencies"] as? List<*>)
|
||||||
|
dependencies.forEach innerForEach@{ pluginDependencyName ->
|
||||||
|
check(pluginDependencyName is String)
|
||||||
|
if (pluginDependencyName.isEmpty()) {
|
||||||
|
return@innerForEach
|
||||||
|
}
|
||||||
|
|
||||||
|
val dependencyProject =
|
||||||
|
project.rootProject.findProject(":$pluginDependencyName") ?: return@innerForEach
|
||||||
|
pluginProject.afterEvaluate {
|
||||||
|
// this.dependencies.add("implementation", dependencyProject)
|
||||||
|
pluginProject.dependencies.add("implementation", dependencyProject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle.tasks
|
||||||
|
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle.tasks
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle.tasks
|
||||||
|
|
||||||
import org.gradle.api.file.CopySpec
|
import org.gradle.api.file.CopySpec
|
||||||
import org.gradle.api.file.FileCollection
|
import org.gradle.api.file.FileCollection
|
@ -2,8 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle.tasks
|
||||||
|
|
||||||
|
import com.flutter.gradle.FlutterPluginConstants
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.file.CopySpec
|
import org.gradle.api.file.CopySpec
|
||||||
import org.gradle.api.file.FileCollection
|
import org.gradle.api.file.FileCollection
|
@ -72,9 +72,63 @@ class NativePluginLoader {
|
|||||||
*/
|
*/
|
||||||
fun getDependenciesMetadata(flutterSourceDirectory: File): Map<String, Any>? {
|
fun getDependenciesMetadata(flutterSourceDirectory: File): Map<String, Any>? {
|
||||||
// Consider a `.flutter-plugins-dependencies` file with the following content:
|
// Consider a `.flutter-plugins-dependencies` file with the following content:
|
||||||
// { ... (example content as in the original Groovy code) ... }
|
// {
|
||||||
|
// "plugins": {
|
||||||
|
// "android": [
|
||||||
|
// {
|
||||||
|
// "name": "plugin-a",
|
||||||
|
// "path": "/path/to/plugin-a",
|
||||||
|
// "dependencies": ["plugin-b", "plugin-c"],
|
||||||
|
// "native_build": true
|
||||||
|
// "dev_dependency": false
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "plugin-b",
|
||||||
|
// "path": "/path/to/plugin-b",
|
||||||
|
// "dependencies": ["plugin-c"],
|
||||||
|
// "native_build": true
|
||||||
|
// "dev_dependency": false
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "plugin-c",
|
||||||
|
// "path": "/path/to/plugin-c",
|
||||||
|
// "dependencies": [],
|
||||||
|
// "native_build": true
|
||||||
|
// "dev_dependency": false
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "plugin-d",
|
||||||
|
// "path": "/path/to/plugin-d",
|
||||||
|
// "dependencies": [],
|
||||||
|
// "native_build": true
|
||||||
|
// "dev_dependency": true
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// "dependencyGraph": [
|
||||||
|
// {
|
||||||
|
// "name": "plugin-a",
|
||||||
|
// "dependencies": ["plugin-b","plugin-c"]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "plugin-b",
|
||||||
|
// "dependencies": ["plugin-c"]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "plugin-c",
|
||||||
|
// "dependencies": []
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "name": "plugin-d",
|
||||||
|
// "dependencies": []
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
// This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
|
// This means, `plugin-a` depends on `plugin-b` and `plugin-c`.
|
||||||
// ... (rest of the comment as in the original Groovy code) ...
|
// `plugin-b` depends on `plugin-c`.
|
||||||
|
// `plugin-c` doesn't depend on anything.
|
||||||
|
// `plugin-d` also doesn't depend on anything, but it is a dev
|
||||||
|
// dependency to the Flutter project, so it is marked as such.
|
||||||
if (parsedFlutterPluginsDependencies != null) {
|
if (parsedFlutterPluginsDependencies != null) {
|
||||||
return parsedFlutterPluginsDependencies
|
return parsedFlutterPluginsDependencies
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import com.android.build.api.dsl.ApplicationDefaultConfig
|
import com.android.build.api.dsl.ApplicationDefaultConfig
|
||||||
import com.android.build.api.dsl.ApplicationExtension
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
import com.flutter.gradle.BaseApplicationNameHandler.GRADLE_BASE_APPLICATION_NAME_PROPERTY
|
import com.flutter.gradle.BaseApplicationNameHandler.GRADLE_BASE_APPLICATION_NAME_PROPERTY
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import org.gradle.internal.impldep.org.junit.Assert.assertThrows
|
import org.gradle.internal.impldep.org.junit.Assert.assertThrows
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import com.android.build.api.AndroidPluginVersion
|
import com.android.build.api.AndroidPluginVersion
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.flutter.gradle
|
||||||
|
|
||||||
|
import com.android.build.api.dsl.ApplicationDefaultConfig
|
||||||
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
|
import com.android.build.gradle.AbstractAppExtension
|
||||||
|
import com.android.build.gradle.BaseExtension
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.file.Directory
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class FlutterPluginTest {
|
||||||
|
@Test
|
||||||
|
fun `FlutterPlugin apply() adds expected tasks`(
|
||||||
|
@TempDir tempDir: Path
|
||||||
|
) {
|
||||||
|
val projectDir = tempDir.resolve("project-dir").resolve("android").resolve("app")
|
||||||
|
projectDir.toFile().mkdirs()
|
||||||
|
val settingsFile = projectDir.parent.resolve("settings.gradle")
|
||||||
|
settingsFile.writeText("empty for now")
|
||||||
|
val fakeFlutterSdkDir = tempDir.resolve("fake-flutter-sdk")
|
||||||
|
fakeFlutterSdkDir.toFile().mkdirs()
|
||||||
|
val fakeCacheDir = fakeFlutterSdkDir.resolve("bin").resolve("cache")
|
||||||
|
fakeCacheDir.toFile().mkdirs()
|
||||||
|
val fakeEngineStampFile = fakeCacheDir.resolve("engine.stamp")
|
||||||
|
fakeEngineStampFile.writeText(FAKE_ENGINE_STAMP)
|
||||||
|
val fakeEngineRealmFile = fakeCacheDir.resolve("engine.realm")
|
||||||
|
fakeEngineRealmFile.writeText(FAKE_ENGINE_REALM)
|
||||||
|
val project = mockk<Project>(relaxed = true)
|
||||||
|
val mockAbstractAppExtension = mockk<AbstractAppExtension>(relaxed = true)
|
||||||
|
every { project.extensions.findByType(AbstractAppExtension::class.java) } returns mockAbstractAppExtension
|
||||||
|
every { project.extensions.getByType(AbstractAppExtension::class.java) } returns mockAbstractAppExtension
|
||||||
|
every { project.extensions.findByName("android") } returns mockAbstractAppExtension
|
||||||
|
every { project.projectDir } returns projectDir.toFile()
|
||||||
|
every { project.findProperty("flutter.sdk") } returns fakeFlutterSdkDir.toString()
|
||||||
|
every { project.file(fakeFlutterSdkDir.toString()) } returns fakeFlutterSdkDir.toFile()
|
||||||
|
val flutterExtension = FlutterExtension()
|
||||||
|
every { project.extensions.create("flutter", any<Class<*>>()) } returns flutterExtension
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns flutterExtension
|
||||||
|
val mockBaseExtension = mockk<BaseExtension>(relaxed = true)
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java) } returns mockBaseExtension
|
||||||
|
val mockApplicationExtension = mockk<ApplicationExtension>(relaxed = true)
|
||||||
|
every { project.extensions.findByType(ApplicationExtension::class.java) } returns mockApplicationExtension
|
||||||
|
val mockApplicationDefaultConfig = mockk<ApplicationDefaultConfig>(relaxed = true)
|
||||||
|
every { mockApplicationExtension.defaultConfig } returns mockApplicationDefaultConfig
|
||||||
|
every { project.rootProject } returns project
|
||||||
|
every { project.state.failure } returns null
|
||||||
|
val mockDirectory = mockk<Directory>(relaxed = true)
|
||||||
|
every { project.layout.buildDirectory.get() } returns mockDirectory
|
||||||
|
val mockAndroidSourceSet = mockk<com.android.build.gradle.api.AndroidSourceSet>(relaxed = true)
|
||||||
|
every { mockAbstractAppExtension.sourceSets.getByName("main") } returns mockAndroidSourceSet
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
|
||||||
|
listOf()
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.file(flutterExtension.source!!) } returns mockk()
|
||||||
|
val flutterPlugin = FlutterPlugin()
|
||||||
|
flutterPlugin.apply(project)
|
||||||
|
|
||||||
|
verify { project.tasks.register("generateLockfiles", any()) }
|
||||||
|
verify { project.tasks.register("javaVersion", any()) }
|
||||||
|
verify { project.tasks.register("printBuildVariants", any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FAKE_ENGINE_STAMP = "901b0f1afe77c3555abee7b86a26aaa37f131379"
|
||||||
|
const val FAKE_ENGINE_REALM = "made_up_realm"
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import com.android.build.gradle.AbstractAppExtension
|
import com.android.build.gradle.AbstractAppExtension
|
||||||
@ -8,25 +12,26 @@ import com.android.build.gradle.internal.dsl.CmakeOptions
|
|||||||
import com.android.build.gradle.internal.dsl.DefaultConfig
|
import com.android.build.gradle.internal.dsl.DefaultConfig
|
||||||
import com.android.build.gradle.tasks.ProcessAndroidResources
|
import com.android.build.gradle.tasks.ProcessAndroidResources
|
||||||
import com.android.builder.model.BuildType
|
import com.android.builder.model.BuildType
|
||||||
|
import com.flutter.gradle.plugins.PluginHandler
|
||||||
import io.mockk.called
|
import io.mockk.called
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import org.gradle.api.Action
|
import org.gradle.api.Action
|
||||||
import org.gradle.api.DomainObjectCollection
|
import org.gradle.api.DomainObjectCollection
|
||||||
import org.gradle.api.DomainObjectSet
|
import org.gradle.api.DomainObjectSet
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.NamedDomainObjectContainer
|
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
import org.gradle.api.UnknownTaskException
|
import org.gradle.api.UnknownTaskException
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
|
||||||
import org.gradle.api.file.Directory
|
import org.gradle.api.file.Directory
|
||||||
import org.gradle.api.file.DirectoryProperty
|
import org.gradle.api.file.DirectoryProperty
|
||||||
import org.gradle.api.logging.Logger
|
import org.gradle.api.logging.Logger
|
||||||
import org.gradle.api.tasks.TaskContainer
|
import org.gradle.api.tasks.TaskContainer
|
||||||
import org.gradle.api.tasks.TaskProvider
|
import org.gradle.api.tasks.TaskProvider
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.junit.jupiter.api.io.TempDir
|
import org.junit.jupiter.api.io.TempDir
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -36,12 +41,10 @@ import kotlin.io.path.createDirectory
|
|||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertContains
|
import kotlin.test.assertContains
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class FlutterPluginUtilsTest {
|
class FlutterPluginUtilsTest {
|
||||||
companion object {
|
companion object {
|
||||||
val exampleEngineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
const val EXAMPLE_ENGINE_VERSION = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||||
|
|
||||||
val devDependency: Map<String?, Any?> =
|
val devDependency: Map<String?, Any?> =
|
||||||
mapOf(
|
mapOf(
|
||||||
@ -219,45 +222,6 @@ class FlutterPluginUtilsTest {
|
|||||||
assertEquals(true, result)
|
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
|
// settingsGradleFile
|
||||||
@Test
|
@Test
|
||||||
fun `settingsGradleFile returns groovy settings gradle file when it exists`(
|
fun `settingsGradleFile returns groovy settings gradle file when it exists`(
|
||||||
@ -872,7 +836,7 @@ class FlutterPluginUtilsTest {
|
|||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
mockCmakeOptions.path
|
mockCmakeOptions.path
|
||||||
}
|
}
|
||||||
verify(exactly = 1) { mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt") }
|
verify(exactly = 1) { mockCmakeOptions.path("$basePath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt") }
|
||||||
verify(exactly = 1) { mockCmakeOptions.buildStagingDirectory(any()) }
|
verify(exactly = 1) { mockCmakeOptions.buildStagingDirectory(any()) }
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
mockDefaultConfig.externalNativeBuild.cmake.arguments(
|
mockDefaultConfig.externalNativeBuild.cmake.arguments(
|
||||||
@ -888,6 +852,12 @@ class FlutterPluginUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `addFlutterDependencies returns early if buildMode is not supported`() {
|
fun `addFlutterDependencies returns early if buildMode is not supported`() {
|
||||||
val project = mockk<Project>()
|
val project = mockk<Project>()
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns pluginListWithoutDevDependency
|
||||||
val buildType: BuildType = mockk<BuildType>()
|
val buildType: BuildType = mockk<BuildType>()
|
||||||
every { buildType.name } returns "debug"
|
every { buildType.name } returns "debug"
|
||||||
every { buildType.isDebuggable } returns true
|
every { buildType.isDebuggable } returns true
|
||||||
@ -899,7 +869,7 @@ class FlutterPluginUtilsTest {
|
|||||||
FlutterPluginUtils.addFlutterDependencies(
|
FlutterPluginUtils.addFlutterDependencies(
|
||||||
project = project,
|
project = project,
|
||||||
buildType = buildType,
|
buildType = buildType,
|
||||||
pluginList = pluginListWithoutDevDependency,
|
pluginHandler = pluginHandler,
|
||||||
engineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
engineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -914,8 +884,14 @@ class FlutterPluginUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `addFlutterDependencies adds libflutter dependency but not embedding dependency when is a flutter app`() {
|
fun `addFlutterDependencies adds libflutter dependency but not embedding dependency when is a flutter app`() {
|
||||||
val project = mockk<Project>()
|
val project = mockk<Project>()
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns pluginListWithoutDevDependency
|
||||||
val buildType: BuildType = mockk<BuildType>()
|
val buildType: BuildType = mockk<BuildType>()
|
||||||
val engineVersion = exampleEngineVersion
|
val engineVersion = EXAMPLE_ENGINE_VERSION
|
||||||
every { buildType.name } returns "debug"
|
every { buildType.name } returns "debug"
|
||||||
every { buildType.isDebuggable } returns true
|
every { buildType.isDebuggable } returns true
|
||||||
every { project.hasProperty("local-engine-repo") } returns false
|
every { project.hasProperty("local-engine-repo") } returns false
|
||||||
@ -927,7 +903,7 @@ class FlutterPluginUtilsTest {
|
|||||||
FlutterPluginUtils.addFlutterDependencies(
|
FlutterPluginUtils.addFlutterDependencies(
|
||||||
project = project,
|
project = project,
|
||||||
buildType = buildType,
|
buildType = buildType,
|
||||||
pluginList = pluginListWithoutDevDependency,
|
pluginHandler = pluginHandler,
|
||||||
engineVersion = engineVersion
|
engineVersion = engineVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -945,8 +921,15 @@ class FlutterPluginUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `addFlutterDependencies adds libflutter and embedding dep when only dep is dev dep in release mode`() {
|
fun `addFlutterDependencies adds libflutter and embedding dep when only dep is dev dep in release mode`() {
|
||||||
val project = mockk<Project>()
|
val project = mockk<Project>()
|
||||||
|
val pluginListWithSingleDevDependency = listOf(devDependency)
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns pluginListWithSingleDevDependency
|
||||||
val buildType: BuildType = mockk<BuildType>()
|
val buildType: BuildType = mockk<BuildType>()
|
||||||
val engineVersion = exampleEngineVersion
|
val engineVersion = EXAMPLE_ENGINE_VERSION
|
||||||
every { buildType.name } returns "release"
|
every { buildType.name } returns "release"
|
||||||
every { buildType.isDebuggable } returns false
|
every { buildType.isDebuggable } returns false
|
||||||
every { project.hasProperty("local-engine-repo") } returns false
|
every { project.hasProperty("local-engine-repo") } returns false
|
||||||
@ -955,12 +938,10 @@ class FlutterPluginUtilsTest {
|
|||||||
every { project.configurations.named("api") } returns mockk()
|
every { project.configurations.named("api") } returns mockk()
|
||||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
|
||||||
val pluginListWithSingleDevDependency = listOf(devDependency)
|
|
||||||
|
|
||||||
FlutterPluginUtils.addFlutterDependencies(
|
FlutterPluginUtils.addFlutterDependencies(
|
||||||
project = project,
|
project = project,
|
||||||
buildType = buildType,
|
buildType = buildType,
|
||||||
pluginList = pluginListWithSingleDevDependency,
|
pluginHandler = pluginHandler,
|
||||||
engineVersion = engineVersion
|
engineVersion = engineVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -994,8 +975,15 @@ class FlutterPluginUtilsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `addFlutterDependencies adds libflutter dep but not embedding dep when only dep is dev dep in debug mode`() {
|
fun `addFlutterDependencies adds libflutter dep but not embedding dep when only dep is dev dep in debug mode`() {
|
||||||
val project = mockk<Project>()
|
val project = mockk<Project>()
|
||||||
|
val pluginListWithSingleDevDependency = listOf(devDependency)
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns pluginListWithSingleDevDependency
|
||||||
val buildType: BuildType = mockk<BuildType>()
|
val buildType: BuildType = mockk<BuildType>()
|
||||||
val engineVersion = exampleEngineVersion
|
val engineVersion = EXAMPLE_ENGINE_VERSION
|
||||||
every { buildType.name } returns "debug"
|
every { buildType.name } returns "debug"
|
||||||
every { buildType.isDebuggable } returns true
|
every { buildType.isDebuggable } returns true
|
||||||
every { project.hasProperty("local-engine-repo") } returns false
|
every { project.hasProperty("local-engine-repo") } returns false
|
||||||
@ -1004,12 +992,10 @@ class FlutterPluginUtilsTest {
|
|||||||
every { project.configurations.named("api") } returns mockk()
|
every { project.configurations.named("api") } returns mockk()
|
||||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
|
||||||
val pluginListWithSingleDevDependency = listOf(devDependency)
|
|
||||||
|
|
||||||
FlutterPluginUtils.addFlutterDependencies(
|
FlutterPluginUtils.addFlutterDependencies(
|
||||||
project = project,
|
project = project,
|
||||||
buildType = buildType,
|
buildType = buildType,
|
||||||
pluginList = pluginListWithSingleDevDependency,
|
pluginHandler = pluginHandler,
|
||||||
engineVersion = engineVersion
|
engineVersion = engineVersion
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1034,168 +1020,6 @@ class FlutterPluginUtilsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// configurePluginDependencies TODO
|
|
||||||
@Test
|
|
||||||
fun `configurePluginDependencies throws IllegalArgumentException when plugin has no name`() {
|
|
||||||
val project = mockk<Project>()
|
|
||||||
val pluginWithoutName: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
|
||||||
pluginWithoutName.remove("name")
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
FlutterPluginUtils.configurePluginDependencies(
|
|
||||||
project = project,
|
|
||||||
pluginObject = pluginWithoutName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `configurePluginDependencies throws IllegalArgumentException when plugin has null dependencies`() {
|
|
||||||
val project = mockk<Project>()
|
|
||||||
val pluginProject = mockk<Project>()
|
|
||||||
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
|
||||||
val pluginWithNullDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
|
||||||
pluginWithNullDependencies["dependencies"] = null
|
|
||||||
every { project.rootProject.findProject(":${pluginWithNullDependencies["name"]}") } returns pluginProject
|
|
||||||
every {
|
|
||||||
project.extensions
|
|
||||||
.findByType(BaseExtension::class.java)!!
|
|
||||||
.buildTypes
|
|
||||||
.iterator()
|
|
||||||
} returns
|
|
||||||
mutableListOf(
|
|
||||||
mockBuildType
|
|
||||||
).iterator()
|
|
||||||
every { mockBuildType.name } returns "debug"
|
|
||||||
every { mockBuildType.isDebuggable } returns true
|
|
||||||
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
FlutterPluginUtils.configurePluginDependencies(
|
|
||||||
project = project,
|
|
||||||
pluginObject = pluginWithNullDependencies
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `configurePluginDependencies adds plugin dependencies`() {
|
|
||||||
val project = mockk<Project>()
|
|
||||||
val pluginProject = mockk<Project>()
|
|
||||||
val pluginDependencyProject = mockk<Project>()
|
|
||||||
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
|
||||||
val pluginWithDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
|
||||||
pluginWithDependencies["dependencies"] =
|
|
||||||
listOf(flutterPluginAndroidLifecycleDependency["name"])
|
|
||||||
every { project.rootProject.findProject(":${pluginWithDependencies["name"]}") } returns pluginProject
|
|
||||||
every { project.rootProject.findProject(":${flutterPluginAndroidLifecycleDependency["name"]}") } returns pluginDependencyProject
|
|
||||||
every {
|
|
||||||
project.extensions
|
|
||||||
.findByType(BaseExtension::class.java)!!
|
|
||||||
.buildTypes
|
|
||||||
.iterator()
|
|
||||||
} returns
|
|
||||||
mutableListOf(
|
|
||||||
mockBuildType
|
|
||||||
).iterator()
|
|
||||||
every { mockBuildType.name } returns "debug"
|
|
||||||
every { mockBuildType.isDebuggable } returns true
|
|
||||||
val captureActionSlot = slot<Action<Project>>()
|
|
||||||
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
|
||||||
val mockDependencyHandler = mockk<DependencyHandler>()
|
|
||||||
every { pluginProject.dependencies } returns mockDependencyHandler
|
|
||||||
every { mockDependencyHandler.add(any(), any()) } returns mockk()
|
|
||||||
|
|
||||||
FlutterPluginUtils.configurePluginDependencies(
|
|
||||||
project = project,
|
|
||||||
pluginObject = pluginWithDependencies
|
|
||||||
)
|
|
||||||
|
|
||||||
verify { pluginProject.afterEvaluate(capture(captureActionSlot)) }
|
|
||||||
captureActionSlot.captured.execute(pluginDependencyProject)
|
|
||||||
verify { mockDependencyHandler.add("implementation", pluginDependencyProject) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// configurePluginProject
|
|
||||||
@Test
|
|
||||||
fun `configurePluginProject throws IllegalArgumentException when plugin has no name`() {
|
|
||||||
val project = mockk<Project>()
|
|
||||||
val pluginWithoutName: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
|
||||||
pluginWithoutName.remove("name")
|
|
||||||
|
|
||||||
assertThrows<IllegalArgumentException> {
|
|
||||||
FlutterPluginUtils.configurePluginProject(
|
|
||||||
project = project,
|
|
||||||
pluginObject = pluginWithoutName,
|
|
||||||
engineVersion = exampleEngineVersion
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `configurePluginProject adds plugin project`() {
|
|
||||||
val project = mockk<Project>()
|
|
||||||
val pluginProject = mockk<Project>()
|
|
||||||
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
|
||||||
val mockLogger = mockk<Logger>()
|
|
||||||
every { project.logger } returns mockLogger
|
|
||||||
every { pluginProject.hasProperty("local-engine-repo") } returns false
|
|
||||||
every { pluginProject.hasProperty("android") } returns true
|
|
||||||
every { mockBuildType.name } returns "debug"
|
|
||||||
every { mockBuildType.isDebuggable } returns true
|
|
||||||
every { project.rootProject.findProject(":${cameraDependency["name"]}") } returns pluginProject
|
|
||||||
every { pluginProject.extensions.create(any(), any<Class<Any>>()) } returns mockk()
|
|
||||||
val captureActionSlot = slot<Action<Project>>()
|
|
||||||
val capturePluginActionSlot = slot<Action<Project>>()
|
|
||||||
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
|
||||||
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
|
||||||
|
|
||||||
val mockProjectBuildTypes =
|
|
||||||
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
|
||||||
val mockPluginProjectBuildTypes =
|
|
||||||
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
|
||||||
every { project.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockProjectBuildTypes
|
|
||||||
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockPluginProjectBuildTypes
|
|
||||||
every { mockPluginProjectBuildTypes.addAll(any()) } returns true
|
|
||||||
every { pluginProject.configurations.named(any<String>()) } returns mockk()
|
|
||||||
every { pluginProject.dependencies.add(any(), any()) } returns mockk()
|
|
||||||
|
|
||||||
every {
|
|
||||||
project.extensions
|
|
||||||
.findByType(BaseExtension::class.java)!!
|
|
||||||
.buildTypes
|
|
||||||
.iterator()
|
|
||||||
} returns
|
|
||||||
mutableListOf(
|
|
||||||
mockBuildType
|
|
||||||
).iterator() andThen
|
|
||||||
mutableListOf( // can't return the same iterator as it is stateful
|
|
||||||
mockBuildType
|
|
||||||
).iterator()
|
|
||||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
|
||||||
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
|
||||||
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
|
||||||
|
|
||||||
FlutterPluginUtils.configurePluginProject(
|
|
||||||
project = project,
|
|
||||||
pluginObject = cameraDependency,
|
|
||||||
engineVersion = exampleEngineVersion
|
|
||||||
)
|
|
||||||
|
|
||||||
verify { project.afterEvaluate(capture(captureActionSlot)) }
|
|
||||||
verify { pluginProject.afterEvaluate(capture(capturePluginActionSlot)) }
|
|
||||||
captureActionSlot.captured.execute(project)
|
|
||||||
capturePluginActionSlot.captured.execute(pluginProject)
|
|
||||||
verify { pluginProject.extensions.create("flutter", FlutterExtension::class.java) }
|
|
||||||
verify {
|
|
||||||
pluginProject.dependencies.add(
|
|
||||||
"debugApi",
|
|
||||||
"io.flutter:flutter_embedding_debug:$exampleEngineVersion"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
verify { project.dependencies.add("debugApi", pluginProject) }
|
|
||||||
verify { mockLogger wasNot called }
|
|
||||||
verify { mockPluginProjectBuildTypes.addAll(project.extensions.findByType(BaseExtension::class.java)!!.buildTypes) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// addTaskForJavaVersion
|
// addTaskForJavaVersion
|
||||||
@Test
|
@Test
|
||||||
fun `addTaskForJavaVersion adds task for Java version`() {
|
fun `addTaskForJavaVersion adds task for Java version`() {
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
package com.flutter.gradle
|
package com.flutter.gradle
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
@ -0,0 +1,443 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package com.flutter.gradle.plugins
|
||||||
|
|
||||||
|
import com.android.build.gradle.BaseExtension
|
||||||
|
import com.flutter.gradle.FlutterExtension
|
||||||
|
import com.flutter.gradle.FlutterPluginUtilsTest.Companion.EXAMPLE_ENGINE_VERSION
|
||||||
|
import com.flutter.gradle.FlutterPluginUtilsTest.Companion.cameraDependency
|
||||||
|
import com.flutter.gradle.FlutterPluginUtilsTest.Companion.flutterPluginAndroidLifecycleDependency
|
||||||
|
import com.flutter.gradle.FlutterPluginUtilsTest.Companion.pluginListWithDevDependency
|
||||||
|
import com.flutter.gradle.FlutterPluginUtilsTest.Companion.pluginListWithoutDevDependency
|
||||||
|
import com.flutter.gradle.NativePluginLoaderReflectionBridge
|
||||||
|
import io.mockk.called
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkObject
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.gradle.api.Action
|
||||||
|
import org.gradle.api.NamedDomainObjectContainer
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.logging.Logger
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||||
|
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 PluginHandlerTest {
|
||||||
|
// getPluginListWithoutDevDependencies
|
||||||
|
@Test
|
||||||
|
fun `getPluginListWithoutDevDependencies removes dev dependencies from list`() {
|
||||||
|
val project = mockk<Project>()
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
every {
|
||||||
|
NativePluginLoaderReflectionBridge.getPlugins(
|
||||||
|
any(),
|
||||||
|
any()
|
||||||
|
)
|
||||||
|
} returns pluginListWithDevDependency
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
|
||||||
|
val result = pluginHandler.getPluginListWithoutDevDependencies()
|
||||||
|
assertEquals(pluginListWithoutDevDependency, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPluginListWithoutDevDependencies does not modify list without dev dependencies`() {
|
||||||
|
val project = mockk<Project>()
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
every {
|
||||||
|
NativePluginLoaderReflectionBridge.getPlugins(
|
||||||
|
any(),
|
||||||
|
any()
|
||||||
|
)
|
||||||
|
} returns pluginListWithoutDevDependency
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
|
||||||
|
val result = pluginHandler.getPluginListWithoutDevDependencies()
|
||||||
|
assertEquals(pluginListWithoutDevDependency, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPluginList skipped as it is a wrapper around a single reflection call
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
PluginHandler.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 {
|
||||||
|
PluginHandler.pluginSupportsAndroidPlatform(mockProject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `configurePlugins throws IllegalArgumentException when plugin has no name`(
|
||||||
|
@TempDir tempDir: Path
|
||||||
|
) {
|
||||||
|
val project = mockk<Project>()
|
||||||
|
|
||||||
|
// configuration for configureLegacyPluginEachProjects
|
||||||
|
val projectDir = tempDir.resolve("my-plugin")
|
||||||
|
projectDir.toFile().mkdirs()
|
||||||
|
every { project.projectDir } returns projectDir.toFile()
|
||||||
|
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
||||||
|
settingsGradle.createNewFile()
|
||||||
|
val mockLogger = mockk<Logger>()
|
||||||
|
every { project.logger } returns mockLogger
|
||||||
|
|
||||||
|
val pluginWithoutName: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||||
|
pluginWithoutName.remove("name")
|
||||||
|
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
|
||||||
|
listOf(
|
||||||
|
pluginWithoutName
|
||||||
|
)
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
pluginHandler.configurePlugins(
|
||||||
|
engineVersionValue = EXAMPLE_ENGINE_VERSION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `configurePlugins adds plugin project and configures its dependencies`(
|
||||||
|
@TempDir tempDir: Path
|
||||||
|
) {
|
||||||
|
val project = mockk<Project>()
|
||||||
|
|
||||||
|
// configuration for configureLegacyPluginEachProjects
|
||||||
|
val projectDir = tempDir.resolve("my-plugin")
|
||||||
|
projectDir.toFile().mkdirs()
|
||||||
|
every { project.projectDir } returns projectDir.toFile()
|
||||||
|
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
||||||
|
settingsGradle.createNewFile()
|
||||||
|
val mockLogger = mockk<Logger>()
|
||||||
|
every { project.logger } returns mockLogger
|
||||||
|
|
||||||
|
val pluginProject = mockk<Project>()
|
||||||
|
val pluginDependencyProject = mockk<Project>()
|
||||||
|
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
||||||
|
every { pluginProject.hasProperty("local-engine-repo") } returns false
|
||||||
|
every { pluginProject.hasProperty("android") } returns true
|
||||||
|
every { mockBuildType.name } returns "debug"
|
||||||
|
every { mockBuildType.isDebuggable } returns true
|
||||||
|
every { project.rootProject.findProject(":${cameraDependency["name"]}") } returns pluginProject
|
||||||
|
every { project.rootProject.findProject(":${flutterPluginAndroidLifecycleDependency["name"]}") } returns pluginDependencyProject
|
||||||
|
every { pluginProject.extensions.create(any(), any<Class<Any>>()) } returns mockk()
|
||||||
|
val captureActionSlot = slot<Action<Project>>()
|
||||||
|
val capturePluginActionSlot = mutableListOf<Action<Project>>()
|
||||||
|
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||||
|
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||||
|
|
||||||
|
val mockProjectBuildTypes =
|
||||||
|
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||||
|
val mockPluginProjectBuildTypes =
|
||||||
|
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockProjectBuildTypes
|
||||||
|
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockPluginProjectBuildTypes
|
||||||
|
every { mockPluginProjectBuildTypes.addAll(any()) } returns true
|
||||||
|
every { pluginProject.configurations.named(any<String>()) } returns mockk()
|
||||||
|
every { pluginProject.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
|
||||||
|
every {
|
||||||
|
project.extensions
|
||||||
|
.findByType(BaseExtension::class.java)!!
|
||||||
|
.buildTypes
|
||||||
|
.iterator()
|
||||||
|
} returns
|
||||||
|
mutableListOf(
|
||||||
|
mockBuildType
|
||||||
|
).iterator() andThen
|
||||||
|
mutableListOf( // can't return the same iterator as it is stateful
|
||||||
|
mockBuildType
|
||||||
|
).iterator() andThen
|
||||||
|
mutableListOf( // and again
|
||||||
|
mockBuildType
|
||||||
|
).iterator()
|
||||||
|
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||||
|
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||||
|
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
val pluginWithDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||||
|
pluginWithDependencies["dependencies"] =
|
||||||
|
listOf(flutterPluginAndroidLifecycleDependency["name"])
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
|
||||||
|
listOf(
|
||||||
|
pluginWithDependencies
|
||||||
|
)
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
|
||||||
|
pluginHandler.configurePlugins(
|
||||||
|
engineVersionValue = EXAMPLE_ENGINE_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
verify { project.afterEvaluate(capture(captureActionSlot)) }
|
||||||
|
verify { pluginProject.afterEvaluate(capture(capturePluginActionSlot)) }
|
||||||
|
captureActionSlot.captured.execute(project)
|
||||||
|
capturePluginActionSlot[0].execute(pluginProject)
|
||||||
|
capturePluginActionSlot[1].execute(pluginProject)
|
||||||
|
verify { pluginProject.extensions.create("flutter", FlutterExtension::class.java) }
|
||||||
|
verify {
|
||||||
|
pluginProject.dependencies.add(
|
||||||
|
"debugApi",
|
||||||
|
"io.flutter:flutter_embedding_debug:$EXAMPLE_ENGINE_VERSION"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify { project.dependencies.add("debugApi", pluginProject) }
|
||||||
|
verify { mockLogger wasNot called }
|
||||||
|
verify { mockPluginProjectBuildTypes.addAll(project.extensions.findByType(BaseExtension::class.java)!!.buildTypes) }
|
||||||
|
|
||||||
|
verify { pluginProject.dependencies.add("implementation", pluginDependencyProject) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `configurePlugins throws IllegalArgumentException when plugin has null dependencies`(
|
||||||
|
@TempDir tempDir: Path
|
||||||
|
) {
|
||||||
|
val project = mockk<Project>()
|
||||||
|
|
||||||
|
// configuration for configureLegacyPluginEachProjects
|
||||||
|
val projectDir = tempDir.resolve("my-plugin")
|
||||||
|
projectDir.toFile().mkdirs()
|
||||||
|
every { project.projectDir } returns projectDir.toFile()
|
||||||
|
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
||||||
|
settingsGradle.createNewFile()
|
||||||
|
val mockLogger = mockk<Logger>()
|
||||||
|
every { project.logger } returns mockLogger
|
||||||
|
|
||||||
|
val pluginProject = mockk<Project>()
|
||||||
|
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
||||||
|
every { pluginProject.hasProperty("local-engine-repo") } returns false
|
||||||
|
every { pluginProject.hasProperty("android") } returns true
|
||||||
|
every { mockBuildType.name } returns "debug"
|
||||||
|
every { mockBuildType.isDebuggable } returns true
|
||||||
|
val pluginWithNullDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||||
|
pluginWithNullDependencies["dependencies"] = null
|
||||||
|
every { project.rootProject.findProject(":${pluginWithNullDependencies["name"]}") } returns pluginProject
|
||||||
|
every { pluginProject.extensions.create(any(), any<Class<Any>>()) } returns mockk()
|
||||||
|
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||||
|
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||||
|
|
||||||
|
val mockProjectBuildTypes =
|
||||||
|
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||||
|
val mockPluginProjectBuildTypes =
|
||||||
|
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockProjectBuildTypes
|
||||||
|
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockPluginProjectBuildTypes
|
||||||
|
every { mockPluginProjectBuildTypes.addAll(any()) } returns true
|
||||||
|
every { pluginProject.configurations.named(any<String>()) } returns mockk()
|
||||||
|
every { pluginProject.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
|
||||||
|
every {
|
||||||
|
project.extensions
|
||||||
|
.findByType(BaseExtension::class.java)!!
|
||||||
|
.buildTypes
|
||||||
|
.iterator()
|
||||||
|
} returns
|
||||||
|
mutableListOf(
|
||||||
|
mockBuildType
|
||||||
|
).iterator() andThen
|
||||||
|
mutableListOf( // can't return the same iterator as it is stateful
|
||||||
|
mockBuildType
|
||||||
|
).iterator() andThen
|
||||||
|
mutableListOf( // and again
|
||||||
|
mockBuildType
|
||||||
|
).iterator()
|
||||||
|
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||||
|
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||||
|
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
|
||||||
|
listOf(
|
||||||
|
pluginWithNullDependencies
|
||||||
|
)
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
|
||||||
|
assertThrows<IllegalArgumentException> {
|
||||||
|
pluginHandler.configurePlugins(
|
||||||
|
engineVersionValue = EXAMPLE_ENGINE_VERSION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `configurePlugins works for old flutter-plugins file`(
|
||||||
|
@TempDir tempDir: Path
|
||||||
|
) {
|
||||||
|
val project = mockk<Project>()
|
||||||
|
|
||||||
|
// configuration for configureLegacyPluginEachProjects
|
||||||
|
val projectDir = tempDir.resolve("my-plugin")
|
||||||
|
projectDir.toFile().mkdirs()
|
||||||
|
every { project.projectDir } returns projectDir.toFile()
|
||||||
|
val settingsGradle = File(projectDir.parent.toFile(), "settings.gradle")
|
||||||
|
settingsGradle.createNewFile()
|
||||||
|
settingsGradle.writeText("def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')")
|
||||||
|
val mockLogger = mockk<Logger>()
|
||||||
|
every { project.logger } returns mockLogger
|
||||||
|
every { mockLogger.quiet(any()) } returns Unit
|
||||||
|
|
||||||
|
val pluginProject = mockk<Project>()
|
||||||
|
val pluginDependencyProject = mockk<Project>()
|
||||||
|
val mockBuildType = mockk<com.android.build.gradle.internal.dsl.BuildType>()
|
||||||
|
every { pluginProject.hasProperty("local-engine-repo") } returns false
|
||||||
|
every { pluginProject.hasProperty("android") } returns true
|
||||||
|
every { mockBuildType.name } returns "debug"
|
||||||
|
every { mockBuildType.isDebuggable } returns true
|
||||||
|
every { project.rootProject.findProject(":${cameraDependency["name"]}") } returns pluginProject
|
||||||
|
every { project.rootProject.findProject(":${flutterPluginAndroidLifecycleDependency["name"]}") } returns pluginDependencyProject
|
||||||
|
every { pluginProject.extensions.create(any(), any<Class<Any>>()) } returns mockk()
|
||||||
|
val captureActionSlot = slot<Action<Project>>()
|
||||||
|
val capturePluginActionSlot = mutableListOf<Action<Project>>()
|
||||||
|
every { project.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||||
|
every { pluginProject.afterEvaluate(any<Action<Project>>()) } returns Unit
|
||||||
|
|
||||||
|
val mockProjectBuildTypes =
|
||||||
|
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||||
|
val mockPluginProjectBuildTypes =
|
||||||
|
mockk<NamedDomainObjectContainer<com.android.build.gradle.internal.dsl.BuildType>>()
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockProjectBuildTypes
|
||||||
|
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.buildTypes } returns mockPluginProjectBuildTypes
|
||||||
|
every { mockPluginProjectBuildTypes.addAll(any()) } returns true
|
||||||
|
every { pluginProject.configurations.named(any<String>()) } returns mockk()
|
||||||
|
every { pluginProject.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
|
||||||
|
every {
|
||||||
|
project.extensions
|
||||||
|
.findByType(BaseExtension::class.java)!!
|
||||||
|
.buildTypes
|
||||||
|
.iterator()
|
||||||
|
} returns
|
||||||
|
mutableListOf(
|
||||||
|
mockBuildType
|
||||||
|
).iterator() andThen
|
||||||
|
mutableListOf( // can't return the same iterator as it is stateful
|
||||||
|
mockBuildType
|
||||||
|
).iterator() andThen
|
||||||
|
mutableListOf( // and again
|
||||||
|
mockBuildType
|
||||||
|
).iterator()
|
||||||
|
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||||
|
every { project.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||||
|
every { pluginProject.extensions.findByType(BaseExtension::class.java)!!.compileSdkVersion } returns "android-35"
|
||||||
|
|
||||||
|
val pluginHandler = PluginHandler(project)
|
||||||
|
mockkObject(NativePluginLoaderReflectionBridge)
|
||||||
|
// mock return of NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
val pluginWithDependencies: MutableMap<String?, Any?> = cameraDependency.toMutableMap()
|
||||||
|
pluginWithDependencies["dependencies"] =
|
||||||
|
listOf(flutterPluginAndroidLifecycleDependency["name"])
|
||||||
|
every { NativePluginLoaderReflectionBridge.getPlugins(any(), any()) } returns
|
||||||
|
listOf(
|
||||||
|
pluginWithDependencies
|
||||||
|
)
|
||||||
|
// mock method calls that are invoked by the args to NativePluginLoaderReflectionBridge.getPlugins
|
||||||
|
every { project.extraProperties } returns mockk()
|
||||||
|
every { project.extensions.findByType(FlutterExtension::class.java) } returns FlutterExtension()
|
||||||
|
every { project.file(any()) } returns mockk()
|
||||||
|
|
||||||
|
val dependencyGraph =
|
||||||
|
listOf<Map<String?, Any?>>(
|
||||||
|
mapOf(
|
||||||
|
"name" to cameraDependency["name"],
|
||||||
|
"dependencies" to listOf(flutterPluginAndroidLifecycleDependency["name"])
|
||||||
|
),
|
||||||
|
mapOf(
|
||||||
|
"name" to flutterPluginAndroidLifecycleDependency["name"],
|
||||||
|
"dependencies" to listOf<String>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
every { NativePluginLoaderReflectionBridge.getDependenciesMetadata(any(), any()) } returns
|
||||||
|
mapOf("dependencyGraph" to dependencyGraph)
|
||||||
|
|
||||||
|
pluginHandler.configurePlugins(
|
||||||
|
engineVersionValue = EXAMPLE_ENGINE_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
verify { project.afterEvaluate(capture(captureActionSlot)) }
|
||||||
|
verify { pluginProject.afterEvaluate(capture(capturePluginActionSlot)) }
|
||||||
|
captureActionSlot.captured.execute(project)
|
||||||
|
capturePluginActionSlot[0].execute(pluginProject)
|
||||||
|
capturePluginActionSlot[1].execute(pluginProject)
|
||||||
|
verify { pluginProject.extensions.create("flutter", FlutterExtension::class.java) }
|
||||||
|
verify {
|
||||||
|
pluginProject.dependencies.add(
|
||||||
|
"debugApi",
|
||||||
|
"io.flutter:flutter_embedding_debug:$EXAMPLE_ENGINE_VERSION"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify { project.dependencies.add("debugApi", pluginProject) }
|
||||||
|
verify { mockPluginProjectBuildTypes.addAll(project.extensions.findByType(BaseExtension::class.java)!!.buildTypes) }
|
||||||
|
|
||||||
|
verify { pluginProject.dependencies.add("implementation", pluginDependencyProject) }
|
||||||
|
verify { mockLogger.quiet(PluginHandler.legacyFlutterPluginsWarning) }
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
package com.flutter.gradle
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package com.flutter.gradle.tasks
|
||||||
|
|
||||||
|
import com.flutter.gradle.DependencyVersionChecker
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
@ -1,5 +1,10 @@
|
|||||||
package com.flutter.gradle
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package com.flutter.gradle.tasks
|
||||||
|
|
||||||
|
import com.flutter.gradle.FlutterPluginConstants
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
@ -42,7 +42,7 @@ import 'migrations/top_level_gradle_build_file_migration.dart';
|
|||||||
|
|
||||||
/// The regex to grab variant names from printBuildVariants gradle task
|
/// The regex to grab variant names from printBuildVariants gradle task
|
||||||
///
|
///
|
||||||
/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
|
/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/kotlin/FlutterPluginUtils.kt
|
||||||
///
|
///
|
||||||
/// The expected output from the task should be similar to:
|
/// The expected output from the task should be similar to:
|
||||||
///
|
///
|
||||||
|
@ -917,7 +917,7 @@ const String kAndroidArchs = 'AndroidArchs';
|
|||||||
///
|
///
|
||||||
/// If not provided, defaults to `minSdkVersion` from gradle_utils.dart.
|
/// If not provided, defaults to `minSdkVersion` from gradle_utils.dart.
|
||||||
///
|
///
|
||||||
/// This is passed in by flutter.groovy's invocation of `flutter assemble`.
|
/// This is passed in by the Flutter Gradle plugin's invocation of `flutter assemble`.
|
||||||
///
|
///
|
||||||
/// For more info, see:
|
/// For more info, see:
|
||||||
/// https://developer.android.com/ndk/guides/sdk-versions#minsdkversion
|
/// https://developer.android.com/ndk/guides/sdk-versions#minsdkversion
|
||||||
|
Loading…
x
Reference in New Issue
Block a user