[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.
|
||||
create("flutterPlugin") {
|
||||
id = "dev.flutter.flutter-gradle-plugin"
|
||||
implementationClass = "FlutterPlugin"
|
||||
implementationClass = "com.flutter.gradle.FlutterPlugin"
|
||||
}
|
||||
// The "flutterAppPluginLoaderPlugin" name isn't used anywhere.
|
||||
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 FLUTTER_STORAGE_BASE_URL = "FLUTTER_STORAGE_BASE_URL"
|
||||
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. */
|
||||
@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.
|
||||
*/
|
||||
@JvmStatic val ABI_VERSION =
|
||||
mapOf<String, Int>( // Explicit type for clarity, though inferred
|
||||
mapOf<String, Int>(
|
||||
ARCH_ARM32 to 1,
|
||||
ARCH_ARM64 to 2,
|
||||
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.tasks.ProcessAndroidResources
|
||||
import com.android.builder.model.BuildType
|
||||
import com.flutter.gradle.plugins.PluginHandler
|
||||
import groovy.lang.Closure
|
||||
import groovy.util.Node
|
||||
import groovy.util.XmlParser
|
||||
@ -139,19 +140,6 @@ object FlutterPluginUtils {
|
||||
|
||||
// TODO(54566): Can remove this function and its call sites once resolved.
|
||||
|
||||
/**
|
||||
* Returns `true` if the given project is a plugin project having an `android` directory
|
||||
* containing a `build.gradle` or `build.gradle.kts` file.
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmName("pluginSupportsAndroidPlatform")
|
||||
internal fun pluginSupportsAndroidPlatform(project: Project): Boolean {
|
||||
val buildGradle = File(File(project.projectDir.parentFile, "android"), "build.gradle")
|
||||
val buildGradleKts =
|
||||
File(File(project.projectDir.parentFile, "android"), "build.gradle.kts")
|
||||
return buildGradle.exists() || buildGradleKts.exists()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Gradle settings script for the build. When both Groovy and
|
||||
* Kotlin variants exist, then Groovy (settings.gradle) is preferred over
|
||||
@ -406,7 +394,7 @@ object FlutterPluginUtils {
|
||||
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.
|
||||
// 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)!!
|
||||
@ -611,7 +599,7 @@ object FlutterPluginUtils {
|
||||
|
||||
// Otherwise, point to an empty CMakeLists.txt, and ignore associated warnings.
|
||||
gradleProjectAndroidExtension.externalNativeBuild.cmake.path(
|
||||
"$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/groovy/CMakeLists.txt"
|
||||
"$flutterSdkRootPath/packages/flutter_tools/gradle/src/main/scripts/CMakeLists.txt"
|
||||
)
|
||||
|
||||
// AGP defaults to outputting build artifacts in `android/app/.cxx`. This directory is a
|
||||
@ -656,7 +644,7 @@ object FlutterPluginUtils {
|
||||
internal fun addFlutterDependencies(
|
||||
project: Project,
|
||||
buildType: BuildType,
|
||||
pluginList: List<Map<String?, Any?>>,
|
||||
pluginHandler: PluginHandler,
|
||||
engineVersion: String
|
||||
) {
|
||||
val flutterBuildMode: String = buildModeFor(buildType)
|
||||
@ -676,11 +664,9 @@ object FlutterPluginUtils {
|
||||
// embedding.
|
||||
val pluginsThatIncludeFlutterEmbeddingAsTransitiveDependency: List<Map<String?, Any?>> =
|
||||
if (flutterBuildMode == "release") {
|
||||
getPluginListWithoutDevDependencies(
|
||||
pluginList
|
||||
)
|
||||
pluginHandler.getPluginListWithoutDevDependencies()
|
||||
} else {
|
||||
pluginList
|
||||
pluginHandler.getPluginList()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
private var nativePluginLoader: Any? = null
|
||||
|
||||
/**
|
||||
* An abstraction to hide reflection from calling sites. See ../scripts/native_plugin_loader.gradle.kts.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPlugins(
|
||||
extraProperties: ExtraPropertiesExtension,
|
||||
flutterProjectRoot: File
|
||||
): List<Map<String, Any>> {
|
||||
nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
||||
): List<Map<String?, Any?>> {
|
||||
val nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val pluginList: List<Map<String, Any>> =
|
||||
nativePluginLoader!!::class
|
||||
val pluginList: List<Map<String?, Any?>> =
|
||||
nativePluginLoader::class
|
||||
.members
|
||||
.firstOrNull { it.name == "getPlugins" }
|
||||
?.call(nativePluginLoader, flutterProjectRoot) as List<Map<String, Any>>
|
||||
?.call(nativePluginLoader, flutterProjectRoot) as List<Map<String?, Any?>>
|
||||
|
||||
return pluginList
|
||||
}
|
||||
@ -42,16 +39,15 @@ object NativePluginLoaderReflectionBridge {
|
||||
/**
|
||||
* An abstraction to hide reflection from calling sites. See ../scripts/native_plugin_loader.gradle.kts.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getDependenciesMetadata(
|
||||
extraProperties: ExtraPropertiesExtension,
|
||||
flutterProjectRoot: File
|
||||
): Map<String, Any> {
|
||||
nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
||||
val nativePluginLoader = extraProperties.get("nativePluginLoader")!!
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val dependenciesMetadata: Map<String, Any> =
|
||||
nativePluginLoader!!::class
|
||||
nativePluginLoader::class
|
||||
.members
|
||||
.firstOrNull { it.name == "dependenciesMetadata" }
|
||||
?.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
|
||||
// found in the LICENSE file.
|
||||
|
||||
package com.flutter.gradle
|
||||
package com.flutter.gradle.tasks
|
||||
|
||||
import org.gradle.api.DefaultTask
|
||||
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
|
||||
// found in the LICENSE file.
|
||||
|
||||
package com.flutter.gradle
|
||||
package com.flutter.gradle.tasks
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import org.gradle.api.Action
|
@ -2,7 +2,7 @@
|
||||
// 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.tasks
|
||||
|
||||
import org.gradle.api.file.CopySpec
|
||||
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
|
||||
// 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.file.CopySpec
|
||||
import org.gradle.api.file.FileCollection
|
@ -72,9 +72,63 @@ class NativePluginLoader {
|
||||
*/
|
||||
fun getDependenciesMetadata(flutterSourceDirectory: File): Map<String, Any>? {
|
||||
// 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`.
|
||||
// ... (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) {
|
||||
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
|
||||
|
||||
import com.android.build.api.dsl.ApplicationDefaultConfig
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.tasks.ProcessAndroidResources
|
||||
import com.android.builder.model.BuildType
|
||||
import com.flutter.gradle.plugins.PluginHandler
|
||||
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.DomainObjectCollection
|
||||
import org.gradle.api.DomainObjectSet
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.NamedDomainObjectContainer
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.UnknownTaskException
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.file.Directory
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.jetbrains.kotlin.gradle.plugin.extraProperties
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
@ -36,12 +41,10 @@ import kotlin.io.path.createDirectory
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class FlutterPluginUtilsTest {
|
||||
companion object {
|
||||
val exampleEngineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||
const val EXAMPLE_ENGINE_VERSION = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||
|
||||
val devDependency: Map<String?, Any?> =
|
||||
mapOf(
|
||||
@ -219,45 +222,6 @@ class FlutterPluginUtilsTest {
|
||||
assertEquals(true, result)
|
||||
}
|
||||
|
||||
// pluginSupportsAndroidPlatform
|
||||
@Test
|
||||
fun `pluginSupportsAndroidPlatform returns true when android directory exists with gradle build file`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val projectDir = tempDir.resolve("my-plugin")
|
||||
projectDir.toFile().mkdirs()
|
||||
|
||||
val androidDir = tempDir.resolve("android")
|
||||
androidDir.toFile().mkdirs()
|
||||
File(androidDir.toFile(), "build.gradle").createNewFile()
|
||||
|
||||
val mockProject =
|
||||
mockk<Project> {
|
||||
every { this@mockk.projectDir } returns projectDir.toFile()
|
||||
}
|
||||
|
||||
assertTrue {
|
||||
FlutterPluginUtils.pluginSupportsAndroidPlatform(mockProject)
|
||||
} // Replace YourClass with the actual class containing the method
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pluginSupportsAndroidPlatform returns false when gradle build file does not exist`(
|
||||
@TempDir tempDir: Path
|
||||
) {
|
||||
val projectDir = tempDir.resolve("my-plugin")
|
||||
projectDir.toFile().mkdirs()
|
||||
|
||||
val mockProject =
|
||||
mockk<Project> {
|
||||
every { this@mockk.projectDir } returns projectDir.toFile()
|
||||
}
|
||||
|
||||
assertFalse {
|
||||
FlutterPluginUtils.pluginSupportsAndroidPlatform(mockProject)
|
||||
} // Replace YourClass with the actual class containing the method
|
||||
}
|
||||
|
||||
// settingsGradleFile
|
||||
@Test
|
||||
fun `settingsGradleFile returns groovy settings gradle file when it exists`(
|
||||
@ -872,7 +836,7 @@ class FlutterPluginUtilsTest {
|
||||
verify(exactly = 1) {
|
||||
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) {
|
||||
mockDefaultConfig.externalNativeBuild.cmake.arguments(
|
||||
@ -888,6 +852,12 @@ class FlutterPluginUtilsTest {
|
||||
@Test
|
||||
fun `addFlutterDependencies returns early if buildMode is not supported`() {
|
||||
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>()
|
||||
every { buildType.name } returns "debug"
|
||||
every { buildType.isDebuggable } returns true
|
||||
@ -899,7 +869,7 @@ class FlutterPluginUtilsTest {
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithoutDevDependency,
|
||||
pluginHandler = pluginHandler,
|
||||
engineVersion = "1.0.0-e0676b47c7550ecdc0f0c4fa759201449b2c5f23"
|
||||
)
|
||||
|
||||
@ -914,8 +884,14 @@ class FlutterPluginUtilsTest {
|
||||
@Test
|
||||
fun `addFlutterDependencies adds libflutter dependency but not embedding dependency when is a flutter app`() {
|
||||
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 engineVersion = exampleEngineVersion
|
||||
val engineVersion = EXAMPLE_ENGINE_VERSION
|
||||
every { buildType.name } returns "debug"
|
||||
every { buildType.isDebuggable } returns true
|
||||
every { project.hasProperty("local-engine-repo") } returns false
|
||||
@ -927,7 +903,7 @@ class FlutterPluginUtilsTest {
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithoutDevDependency,
|
||||
pluginHandler = pluginHandler,
|
||||
engineVersion = engineVersion
|
||||
)
|
||||
|
||||
@ -945,8 +921,15 @@ class FlutterPluginUtilsTest {
|
||||
@Test
|
||||
fun `addFlutterDependencies adds libflutter and embedding dep when only dep is dev dep in release mode`() {
|
||||
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 engineVersion = exampleEngineVersion
|
||||
val engineVersion = EXAMPLE_ENGINE_VERSION
|
||||
every { buildType.name } returns "release"
|
||||
every { buildType.isDebuggable } 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.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
val pluginListWithSingleDevDependency = listOf(devDependency)
|
||||
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithSingleDevDependency,
|
||||
pluginHandler = pluginHandler,
|
||||
engineVersion = engineVersion
|
||||
)
|
||||
|
||||
@ -994,8 +975,15 @@ class FlutterPluginUtilsTest {
|
||||
@Test
|
||||
fun `addFlutterDependencies adds libflutter dep but not embedding dep when only dep is dev dep in debug mode`() {
|
||||
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 engineVersion = exampleEngineVersion
|
||||
val engineVersion = EXAMPLE_ENGINE_VERSION
|
||||
every { buildType.name } returns "debug"
|
||||
every { buildType.isDebuggable } returns true
|
||||
every { project.hasProperty("local-engine-repo") } returns false
|
||||
@ -1004,12 +992,10 @@ class FlutterPluginUtilsTest {
|
||||
every { project.configurations.named("api") } returns mockk()
|
||||
every { project.dependencies.add(any(), any()) } returns mockk()
|
||||
|
||||
val pluginListWithSingleDevDependency = listOf(devDependency)
|
||||
|
||||
FlutterPluginUtils.addFlutterDependencies(
|
||||
project = project,
|
||||
buildType = buildType,
|
||||
pluginList = pluginListWithSingleDevDependency,
|
||||
pluginHandler = pluginHandler,
|
||||
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
|
||||
@Test
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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.mockk
|
||||
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.mockk
|
||||
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 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:
|
||||
///
|
||||
|
@ -917,7 +917,7 @@ const String kAndroidArchs = 'AndroidArchs';
|
||||
///
|
||||
/// 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:
|
||||
/// https://developer.android.com/ndk/guides/sdk-versions#minsdkversion
|
||||
|
Loading…
x
Reference in New Issue
Block a user