mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
921 lines
37 KiB
Groovy
921 lines
37 KiB
Groovy
// Copyright 2019 The Chromium 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 static groovy.io.FileType.FILES
|
|
|
|
import com.android.builder.model.AndroidProject
|
|
import com.android.build.OutputFile
|
|
import java.nio.file.Path
|
|
import java.nio.file.Paths
|
|
import java.util.regex.Matcher
|
|
import java.util.regex.Pattern
|
|
import org.apache.tools.ant.taskdefs.condition.Os
|
|
import org.gradle.api.DefaultTask
|
|
import org.gradle.api.GradleException
|
|
import org.gradle.api.Project
|
|
import org.gradle.api.Plugin
|
|
import org.gradle.api.Task
|
|
import org.gradle.api.file.CopySpec
|
|
import org.gradle.api.file.FileCollection
|
|
import org.gradle.api.tasks.Copy
|
|
import org.gradle.api.tasks.InputFiles
|
|
import org.gradle.api.tasks.OutputDirectory
|
|
import org.gradle.api.tasks.TaskAction
|
|
import org.gradle.api.tasks.bundling.Jar
|
|
|
|
buildscript {
|
|
repositories {
|
|
google()
|
|
jcenter()
|
|
}
|
|
dependencies {
|
|
classpath 'com.android.tools.build:gradle:3.5.0'
|
|
}
|
|
}
|
|
|
|
android {
|
|
compileOptions {
|
|
sourceCompatibility 1.8
|
|
targetCompatibility 1.8
|
|
}
|
|
}
|
|
|
|
apply plugin: FlutterPlugin
|
|
|
|
class FlutterPlugin implements Plugin<Project> {
|
|
private static final String MAVEN_REPO = "http://download.flutter.io";
|
|
|
|
// The platforms that can be passed to the `--Ptarget-platform` flag.
|
|
private static final String PLATFORM_ARM32 = "android-arm";
|
|
private static final String PLATFORM_ARM64 = "android-arm64";
|
|
private static final String PLATFORM_X86 = "android-x86";
|
|
private static final String PLATFORM_X86_64 = "android-x64";
|
|
|
|
// The ABI architectures.
|
|
private static final String ARCH_ARM32 = "armeabi-v7a";
|
|
private static final String ARCH_ARM64 = "arm64-v8a";
|
|
private static final String ARCH_X86 = "x86";
|
|
private static final String ARCH_X86_64 = "x86_64";
|
|
|
|
// Maps platforms to ABI architectures.
|
|
private static final Map PLATFORM_ARCH_MAP = [
|
|
(PLATFORM_ARM32) : ARCH_ARM32,
|
|
(PLATFORM_ARM64) : ARCH_ARM64,
|
|
(PLATFORM_X86) : ARCH_X86,
|
|
(PLATFORM_X86_64) : ARCH_X86_64,
|
|
]
|
|
|
|
// The version code that gives each ABI a value.
|
|
// For each APK variant, use the following versions to override the version of the Universal APK.
|
|
// Otherwise, the Play Store will complain that the APK variants have the same version.
|
|
private static final Map ABI_VERSION = [
|
|
(ARCH_ARM32) : 1,
|
|
(ARCH_ARM64) : 2,
|
|
(ARCH_X86) : 3,
|
|
(ARCH_X86_64) : 4,
|
|
]
|
|
|
|
// When split is enabled, multiple APKs are generated per each ABI.
|
|
private static final List DEFAULT_PLATFORMS = [
|
|
PLATFORM_ARM32,
|
|
PLATFORM_ARM64,
|
|
]
|
|
|
|
// 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 Map baseJar = [:]
|
|
private File flutterRoot
|
|
private File flutterExecutable
|
|
private String localEngine
|
|
private String localEngineSrcPath
|
|
private Properties localProperties
|
|
private File flutterJar
|
|
private String engineVersion
|
|
|
|
@Override
|
|
void apply(Project project) {
|
|
this.project = project
|
|
|
|
project.extensions.create("flutter", FlutterExtension)
|
|
project.afterEvaluate this.&addFlutterTasks
|
|
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
|
|
// Configuring split per ABI allows to generate separate APKs for each abi.
|
|
// This is a noop when building a bundle.
|
|
if (shouldSplitPerAbi()) {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
getTargetPlatforms().each { targetArch ->
|
|
String abiValue = PLATFORM_ARCH_MAP[targetArch]
|
|
project.android {
|
|
if (shouldSplitPerAbi()) {
|
|
splits {
|
|
abi {
|
|
include abiValue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
String flutterRootPath = resolveProperty("flutter.sdk", System.env.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 = Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version")
|
|
.toFile().text.trim()
|
|
|
|
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
|
|
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
|
|
|
|
// Add custom build types.
|
|
project.android.buildTypes {
|
|
profile {
|
|
initWith debug
|
|
if (it.hasProperty("matchingFallbacks")) {
|
|
matchingFallbacks = ["debug", "release"]
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shouldShrinkResources(project)) {
|
|
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
|
|
"gradle", "flutter_proguard_rules.pro")
|
|
project.android.buildTypes {
|
|
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.
|
|
// NOTE: The resource shrinker can't be used for libraries.
|
|
shrinkResources 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.txt"), flutterProguardRules, "proguard-rules.pro"
|
|
}
|
|
}
|
|
}
|
|
|
|
if (useLocalEngine()) {
|
|
String engineOutPath = project.property('localEngineOut')
|
|
File engineOut = project.file(engineOutPath)
|
|
if (!engineOut.isDirectory()) {
|
|
throw new GradleException('localEngineOut must point to a local engine build')
|
|
}
|
|
Path baseEnginePath = Paths.get(engineOut.absolutePath)
|
|
flutterJar = baseEnginePath.resolve("flutter.jar").toFile()
|
|
if (!flutterJar.isFile()) {
|
|
throw new GradleException("Local engine jar not found: $flutterJar")
|
|
}
|
|
localEngine = engineOut.name
|
|
localEngineSrcPath = engineOut.parentFile.parent
|
|
// The local engine is built for one of the build type.
|
|
// However, we use the same engine for each of the build types.
|
|
project.android.buildTypes.each {
|
|
addApiDependencies(project, it.name, project.files {
|
|
flutterJar
|
|
})
|
|
}
|
|
} else {
|
|
project.android.buildTypes.each this.&addFlutterDependencies
|
|
project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the dependencies required by the Flutter project.
|
|
* This includes:
|
|
* 1. The embedding
|
|
* 2. libflutter.so
|
|
*/
|
|
void addFlutterDependencies(buildType) {
|
|
project.rootProject.allprojects {
|
|
repositories {
|
|
maven {
|
|
url MAVEN_REPO
|
|
}
|
|
}
|
|
}
|
|
String flutterBuildMode = buildModeFor(buildType)
|
|
// Add the embedding dependency.
|
|
addApiDependencies(project, buildType.name,
|
|
"io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion")
|
|
|
|
List<String> platforms = getTargetPlatforms().collect()
|
|
// Debug mode includes x86 and x64, which are commonly used in emulators.
|
|
if (flutterBuildMode == "debug") {
|
|
platforms.add("android-x86")
|
|
platforms.add("android-x64")
|
|
}
|
|
platforms.each { platform ->
|
|
String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
|
|
// Add the `libflutter.so` dependency.
|
|
addApiDependencies(project, buildType.name,
|
|
"io.flutter:${arch}_$flutterBuildMode:1.0.0-$engineVersion")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the directory where the plugins are built.
|
|
*/
|
|
private File getPluginBuildDir() {
|
|
// Module projects specify this flag to include plugins in the same repo as the module project.
|
|
if (project.ext.has("pluginBuildDir")) {
|
|
return project.ext.get("pluginBuildDir")
|
|
}
|
|
return project.buildDir
|
|
}
|
|
|
|
/**
|
|
* Configures the Flutter plugin dependencies.
|
|
*
|
|
* The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
|
|
* the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location.
|
|
* Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject.
|
|
*/
|
|
private void configurePlugins() {
|
|
if (!buildPluginAsAar()) {
|
|
getPluginList().each this.&configurePluginProject
|
|
return
|
|
}
|
|
if (useLocalEngine()) {
|
|
throw new GradleException("Local engine isn't supported when building the plugins as AAR")
|
|
}
|
|
List<Project> projects = [project]
|
|
// Module projects set the `hostProjects` extra property in `include_flutter.groovy`.
|
|
// This is required to set the local repository in each host app project.
|
|
if (project.ext.has("hostProjects")) {
|
|
projects.addAll(project.ext.get("hostProjects"))
|
|
}
|
|
// Configure the repository for the plugins.
|
|
projects.each { hostProject ->
|
|
hostProject.repositories {
|
|
maven {
|
|
url "${getPluginBuildDir()}/outputs/repo"
|
|
}
|
|
}
|
|
}
|
|
getPluginList().each { pluginName, pluginPath ->
|
|
configurePluginAar(pluginName, pluginPath, project)
|
|
}
|
|
}
|
|
|
|
private static final Pattern GROUP_PATTERN = ~/group\s+\'(.+)\'/
|
|
private static final Pattern PROJECT_NAME_PATTERN = ~/rootProject\.name\s+=\s+\'(.+)\'/
|
|
|
|
// Adds the plugin AAR dependency to the app project.
|
|
private void configurePluginAar(String pluginName, String pluginPath, Project project) {
|
|
// Extract the group id from the plugin's build.gradle.
|
|
// This is `group '<group-id>'`
|
|
File pluginBuildFile = project.file(Paths.get(pluginPath, "android", "build.gradle"));
|
|
if (!pluginBuildFile.exists()) {
|
|
throw new GradleException("Plugin $pluginName doesn't have the required file $pluginBuildFile.")
|
|
}
|
|
|
|
Matcher groupParts = GROUP_PATTERN.matcher(pluginBuildFile.text)
|
|
assert groupParts.count == 1
|
|
assert groupParts.hasGroup()
|
|
String groupId = groupParts[0][1]
|
|
|
|
// Extract the artifact name from the plugin's settings.gradle.
|
|
// This is `rootProject.name = '<artifact-name>'`
|
|
File pluginSettings = project.file(Paths.get(pluginPath, "android", "settings.gradle"));
|
|
if (!pluginSettings.exists()) {
|
|
throw new GradleException("Plugin $pluginName doesn't have the required file $pluginSettings.")
|
|
}
|
|
Matcher projectNameParts = PROJECT_NAME_PATTERN.matcher(pluginSettings.text)
|
|
assert projectNameParts.count == 1
|
|
assert projectNameParts.hasGroup()
|
|
String artifactId = "${projectNameParts[0][1]}_release"
|
|
|
|
assert !groupId.empty
|
|
project.dependencies.add("api", "$groupId:$artifactId:+")
|
|
}
|
|
|
|
// Adds the plugin project dependency to the app project .
|
|
private void configurePluginProject(String name, String _) {
|
|
Project pluginProject = project.rootProject.findProject(":$name")
|
|
if (pluginProject == null) {
|
|
project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
|
|
return
|
|
}
|
|
// Add plugin dependency to the app project.
|
|
project.dependencies {
|
|
if (project.getConfigurations().findByName("implementation")) {
|
|
implementation pluginProject
|
|
} else {
|
|
compile pluginProject
|
|
}
|
|
}
|
|
Closure addEmbeddingCompileOnlyDependency = { buildType ->
|
|
String flutterBuildMode = buildModeFor(buildType)
|
|
// In AGP 3.5, the embedding must be added as an API implementation,
|
|
// so java8 features are desugared against the runtime classpath.
|
|
// For more, see https://github.com/flutter/flutter/issues/40126
|
|
if (flutterJar) {
|
|
addApiDependencies(
|
|
pluginProject,
|
|
buildType.name,
|
|
project.files {
|
|
flutterJar
|
|
}
|
|
)
|
|
return
|
|
}
|
|
addApiDependencies(
|
|
pluginProject,
|
|
buildType.name,
|
|
"io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion",
|
|
{
|
|
// We only need to expose io.flutter.plugin.*
|
|
// No need for the embedding transitive dependencies.
|
|
transitive = false
|
|
}
|
|
)
|
|
}
|
|
pluginProject.afterEvaluate {
|
|
pluginProject.android.buildTypes {
|
|
profile {
|
|
initWith debug
|
|
}
|
|
}
|
|
pluginProject.android.buildTypes.each addEmbeddingCompileOnlyDependency
|
|
pluginProject.android.buildTypes.whenObjectAdded addEmbeddingCompileOnlyDependency
|
|
}
|
|
}
|
|
|
|
private Properties getPluginList() {
|
|
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
|
|
Properties allPlugins = readPropertiesIfExist(pluginsFile)
|
|
Properties androidPlugins = new Properties()
|
|
allPlugins.each { name, path ->
|
|
File editableAndroidProject = new File(path, 'android' + File.separator + 'build.gradle')
|
|
if (editableAndroidProject.exists()) {
|
|
androidPlugins.setProperty(name, path)
|
|
}
|
|
// TODO(amirh): log an error if this plugin was specified to be an Android
|
|
// plugin according to the new schema, and was missing a build.gradle file.
|
|
// https://github.com/flutter/flutter/issues/40784
|
|
}
|
|
return androidPlugins
|
|
}
|
|
|
|
private static String toCammelCase(List<String> parts) {
|
|
if (parts.empty) {
|
|
return ""
|
|
}
|
|
return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}"
|
|
}
|
|
|
|
private String resolveProperty(String name, String defaultValue) {
|
|
if (localProperties == null) {
|
|
localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
|
|
}
|
|
String result
|
|
if (project.hasProperty(name)) {
|
|
result = project.property(name)
|
|
}
|
|
if (result == null) {
|
|
result = localProperties.getProperty(name)
|
|
}
|
|
if (result == null) {
|
|
result = defaultValue
|
|
}
|
|
return result
|
|
}
|
|
|
|
private static Properties readPropertiesIfExist(File propertiesFile) {
|
|
Properties result = new Properties()
|
|
if (propertiesFile.exists()) {
|
|
propertiesFile.withReader('UTF-8') { reader -> result.load(reader) }
|
|
}
|
|
return result
|
|
}
|
|
|
|
private List<String> getTargetPlatforms() {
|
|
if (!project.hasProperty('target-platform')) {
|
|
return DEFAULT_PLATFORMS
|
|
}
|
|
return project.property('target-platform').split(',').collect {
|
|
if (!PLATFORM_ARCH_MAP[it]) {
|
|
throw new GradleException("Invalid platform: $it.")
|
|
}
|
|
return it
|
|
}
|
|
}
|
|
|
|
private Boolean shouldSplitPerAbi() {
|
|
if (project.hasProperty('split-per-abi')) {
|
|
return project.property('split-per-abi').toBoolean()
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private Boolean useLocalEngine() {
|
|
return project.hasProperty('localEngineOut')
|
|
}
|
|
|
|
private Boolean isVerbose() {
|
|
if (project.hasProperty('verbose')) {
|
|
return project.property('verbose').toBoolean()
|
|
}
|
|
return false
|
|
}
|
|
|
|
private static Boolean shouldShrinkResources(Project project) {
|
|
if (project.hasProperty("shrink")) {
|
|
return project.property("shrink").toBoolean()
|
|
}
|
|
return false
|
|
}
|
|
|
|
private static Boolean isBuiltAsApp(Project project) {
|
|
// Projects are built as applications when the they use the `com.android.application`
|
|
// plugin.
|
|
return project.plugins.hasPlugin("com.android.application");
|
|
}
|
|
|
|
private static Boolean buildPluginAsAar() {
|
|
return System.getProperty('build-plugins-as-aars') == 'true'
|
|
}
|
|
|
|
private void addCompileOnlyDependency(Project project, String variantName, Object dependency, Closure config = null) {
|
|
if (project.state.failure) {
|
|
return
|
|
}
|
|
String configuration;
|
|
if (project.getConfigurations().findByName("compileOnly")) {
|
|
configuration = "${variantName}CompileOnly";
|
|
} else {
|
|
configuration = "${variantName}Provided";
|
|
}
|
|
project.dependencies.add(configuration, dependency, config)
|
|
}
|
|
|
|
private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) {
|
|
String configuration;
|
|
// `compile` dependencies are now `api` dependencies.
|
|
if (project.getConfigurations().findByName("api")) {
|
|
configuration = "${variantName}Api";
|
|
} else {
|
|
configuration = "${variantName}Compile";
|
|
}
|
|
project.dependencies.add(configuration, dependency, config)
|
|
}
|
|
|
|
/**
|
|
* Returns a Flutter build mode suitable for the specified Android buildType.
|
|
*
|
|
* Note: The BuildType DSL type is not public, and is therefore omitted from the signature.
|
|
*
|
|
* @return "debug", "profile", or "release" (fall-back).
|
|
*/
|
|
private static String buildModeFor(buildType) {
|
|
if (buildType.name == "profile") {
|
|
return "profile"
|
|
} else if (buildType.debuggable) {
|
|
return "debug"
|
|
}
|
|
return "release"
|
|
}
|
|
|
|
private static String getEngineArtifactDirName(buildType, targetArch) {
|
|
if (buildType.name == "profile") {
|
|
return "${targetArch}-profile"
|
|
} else if (buildType.debuggable) {
|
|
return "${targetArch}"
|
|
}
|
|
return "${targetArch}-release"
|
|
}
|
|
|
|
private void addFlutterTasks(Project project) {
|
|
if (project.state.failure) {
|
|
return
|
|
}
|
|
if (project.flutter.source == null) {
|
|
throw new GradleException("Must provide Flutter source directory")
|
|
}
|
|
String target = project.flutter.target
|
|
if (target == null) {
|
|
target = 'lib/main.dart'
|
|
}
|
|
if (project.hasProperty('target')) {
|
|
target = project.property('target')
|
|
}
|
|
String[] fileSystemRootsValue = null
|
|
if (project.hasProperty('filesystem-roots')) {
|
|
fileSystemRootsValue = project.property('filesystem-roots').split('\\|')
|
|
}
|
|
String fileSystemSchemeValue = null
|
|
if (project.hasProperty('filesystem-scheme')) {
|
|
fileSystemSchemeValue = project.property('filesystem-scheme')
|
|
}
|
|
Boolean trackWidgetCreationValue = false
|
|
if (project.hasProperty('track-widget-creation')) {
|
|
trackWidgetCreationValue = project.property('track-widget-creation').toBoolean()
|
|
}
|
|
String compilationTraceFilePathValue = null
|
|
if (project.hasProperty('compilation-trace-file')) {
|
|
compilationTraceFilePathValue = project.property('compilation-trace-file')
|
|
}
|
|
Boolean createPatchValue = false
|
|
if (project.hasProperty('patch')) {
|
|
createPatchValue = project.property('patch').toBoolean()
|
|
}
|
|
Integer buildNumberValue = null
|
|
if (project.hasProperty('build-number')) {
|
|
buildNumberValue = project.property('build-number').toInteger()
|
|
}
|
|
String baselineDirValue = null
|
|
if (project.hasProperty('baseline-dir')) {
|
|
baselineDirValue = project.property('baseline-dir')
|
|
}
|
|
String extraFrontEndOptionsValue = null
|
|
if (project.hasProperty('extra-front-end-options')) {
|
|
extraFrontEndOptionsValue = project.property('extra-front-end-options')
|
|
}
|
|
String extraGenSnapshotOptionsValue = null
|
|
if (project.hasProperty('extra-gen-snapshot-options')) {
|
|
extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
|
|
}
|
|
def targetPlatforms = getTargetPlatforms()
|
|
def addFlutterDeps = { variant ->
|
|
if (shouldSplitPerAbi()) {
|
|
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
|
|
def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))
|
|
if (abiVersionCode != null) {
|
|
output.versionCodeOverride =
|
|
abiVersionCode * 1000 + variant.versionCode
|
|
}
|
|
}
|
|
}
|
|
String variantBuildMode = buildModeFor(variant.buildType)
|
|
List<FlutterTask> compileTasks = targetPlatforms.collect { targetArch ->
|
|
String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name, targetArch.replace('android-', '')])
|
|
project.tasks.create(name: taskName, type: FlutterTask) {
|
|
flutterRoot this.flutterRoot
|
|
flutterExecutable this.flutterExecutable
|
|
buildMode variantBuildMode
|
|
localEngine this.localEngine
|
|
localEngineSrcPath this.localEngineSrcPath
|
|
abi PLATFORM_ARCH_MAP[targetArch]
|
|
targetPath target
|
|
verbose isVerbose()
|
|
fileSystemRoots fileSystemRootsValue
|
|
fileSystemScheme fileSystemSchemeValue
|
|
trackWidgetCreation trackWidgetCreationValue
|
|
compilationTraceFilePath compilationTraceFilePathValue
|
|
createPatch createPatchValue
|
|
buildNumber buildNumberValue
|
|
baselineDir baselineDirValue
|
|
targetPlatform targetArch
|
|
sourceDir project.file(project.flutter.source)
|
|
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${targetArch}")
|
|
extraFrontEndOptions extraFrontEndOptionsValue
|
|
extraGenSnapshotOptions extraGenSnapshotOptionsValue
|
|
}
|
|
}
|
|
File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
|
|
Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
|
|
destinationDir libJar.parentFile
|
|
archiveName libJar.name
|
|
dependsOn compileTasks
|
|
compileTasks.each { compileTask ->
|
|
from(compileTask.intermediateDir) {
|
|
include '*.so'
|
|
// Move `app.so` to `lib/<abi>/libapp.so`
|
|
rename { String filename ->
|
|
return "lib/${compileTask.abi}/lib${filename}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
addApiDependencies(project, variant.name, project.files {
|
|
packFlutterAppAotTask
|
|
})
|
|
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
|
|
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
|
|
// In add to app scenarios, :flutter is a subproject of another Android app.
|
|
// We know that :flutter is used as a subproject when these tasks exist.
|
|
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets
|
|
Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
|
|
dependsOn compileTasks
|
|
compileTasks.each { flutterTask ->
|
|
// Add flutter_assets.
|
|
with flutterTask.assets
|
|
}
|
|
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
|
|
}
|
|
if (!isUsedAsSubproject) {
|
|
variant.mergeResources.dependsOn(copyFlutterAssetsTask)
|
|
return
|
|
}
|
|
// Flutter module included as a subproject in add to app.
|
|
Project appProject = project.rootProject.findProject(':app')
|
|
assert appProject != null
|
|
appProject.afterEvaluate {
|
|
assert appProject.android != null
|
|
appProject.android.applicationVariants.all { appProjectVariant ->
|
|
// 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 | relese |
|
|
// | 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`.
|
|
if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
|
|
return
|
|
}
|
|
Task mergeAssets = project
|
|
.tasks
|
|
.findByPath(":app:merge${appProjectVariant.name.capitalize()}Assets")
|
|
assert mergeAssets
|
|
mergeAssets.dependsOn(copyFlutterAssetsTask)
|
|
}
|
|
}
|
|
}
|
|
if (project.android.hasProperty("applicationVariants")) {
|
|
project.android.applicationVariants.all addFlutterDeps
|
|
} else {
|
|
project.android.libraryVariants.all addFlutterDeps
|
|
}
|
|
configurePlugins()
|
|
}
|
|
}
|
|
|
|
class FlutterExtension {
|
|
String source
|
|
String target
|
|
}
|
|
|
|
abstract class BaseFlutterTask extends DefaultTask {
|
|
File flutterRoot
|
|
File flutterExecutable
|
|
String buildMode
|
|
String localEngine
|
|
String localEngineSrcPath
|
|
@Input
|
|
String targetPath
|
|
@Optional @Input
|
|
Boolean verbose
|
|
@Optional @Input
|
|
String[] fileSystemRoots
|
|
@Optional @Input
|
|
String fileSystemScheme
|
|
@Input
|
|
Boolean trackWidgetCreation
|
|
@Optional @Input
|
|
String compilationTraceFilePath
|
|
@Optional @Input
|
|
Boolean createPatch
|
|
@Optional @Input
|
|
Integer buildNumber
|
|
@Optional @Input
|
|
String baselineDir
|
|
@Optional @Input
|
|
String targetPlatform
|
|
@Input
|
|
String abi
|
|
File sourceDir
|
|
File intermediateDir
|
|
@Optional @Input
|
|
String extraFrontEndOptions
|
|
@Optional @Input
|
|
String extraGenSnapshotOptions
|
|
|
|
@OutputFiles
|
|
FileCollection getDependenciesFiles() {
|
|
FileCollection depfiles = project.files()
|
|
|
|
// Include the kernel compiler depfile, since kernel compile is the
|
|
// first stage of AOT build in this mode, and it includes all the Dart
|
|
// sources.
|
|
depfiles += project.files("${intermediateDir}/kernel_compile.d")
|
|
|
|
// Include Core JIT kernel compiler depfile, since kernel compile is
|
|
// the first stage of JIT builds in this mode, and it includes all the
|
|
// Dart sources.
|
|
depfiles += project.files("${intermediateDir}/snapshot_blob.bin.d")
|
|
return depfiles
|
|
}
|
|
|
|
void buildBundle() {
|
|
if (!sourceDir.isDirectory()) {
|
|
throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
|
|
}
|
|
|
|
intermediateDir.mkdirs()
|
|
|
|
if (buildMode == "profile" || buildMode == "release") {
|
|
project.exec {
|
|
executable flutterExecutable.absolutePath
|
|
workingDir sourceDir
|
|
if (localEngine != null) {
|
|
args "--local-engine", localEngine
|
|
args "--local-engine-src-path", localEngineSrcPath
|
|
}
|
|
args "build", "aot"
|
|
args "--suppress-analytics"
|
|
args "--quiet"
|
|
args "--target", targetPath
|
|
args "--output-dir", "${intermediateDir}"
|
|
args "--target-platform", "${targetPlatform}"
|
|
if (trackWidgetCreation) {
|
|
args "--track-widget-creation"
|
|
}
|
|
if (extraFrontEndOptions != null) {
|
|
args "--extra-front-end-options", "${extraFrontEndOptions}"
|
|
}
|
|
if (extraGenSnapshotOptions != null) {
|
|
args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
|
|
}
|
|
args "--${buildMode}"
|
|
}
|
|
}
|
|
|
|
project.exec {
|
|
executable flutterExecutable.absolutePath
|
|
workingDir sourceDir
|
|
|
|
if (localEngine != null) {
|
|
args "--local-engine", localEngine
|
|
args "--local-engine-src-path", localEngineSrcPath
|
|
}
|
|
args "build", "bundle"
|
|
args "--target", targetPath
|
|
args "--target-platform", "${targetPlatform}"
|
|
if (verbose) {
|
|
args "--verbose"
|
|
}
|
|
if (fileSystemRoots != null) {
|
|
for (root in fileSystemRoots) {
|
|
args "--filesystem-root", root
|
|
}
|
|
}
|
|
if (fileSystemScheme != null) {
|
|
args "--filesystem-scheme", fileSystemScheme
|
|
}
|
|
if (trackWidgetCreation) {
|
|
args "--track-widget-creation"
|
|
}
|
|
if (compilationTraceFilePath != null) {
|
|
args "--compilation-trace-file", compilationTraceFilePath
|
|
}
|
|
if (createPatch) {
|
|
args "--patch"
|
|
args "--build-number", project.android.defaultConfig.versionCode
|
|
if (buildNumber != null) {
|
|
assert buildNumber == project.android.defaultConfig.versionCode
|
|
}
|
|
}
|
|
if (baselineDir != null) {
|
|
args "--baseline-dir", baselineDir
|
|
}
|
|
if (extraFrontEndOptions != null) {
|
|
args "--extra-front-end-options", "${extraFrontEndOptions}"
|
|
}
|
|
if (extraGenSnapshotOptions != null) {
|
|
args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
|
|
}
|
|
if (buildMode == "release" || buildMode == "profile") {
|
|
args "--precompiled"
|
|
} else {
|
|
args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
|
|
}
|
|
args "--asset-dir", "${intermediateDir}/flutter_assets"
|
|
if (buildMode == "debug") {
|
|
args "--debug"
|
|
}
|
|
if (buildMode == "profile") {
|
|
args "--profile"
|
|
}
|
|
if (buildMode == "release") {
|
|
args "--release"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class FlutterTask extends BaseFlutterTask {
|
|
@OutputDirectory
|
|
File getOutputDirectory() {
|
|
return intermediateDir
|
|
}
|
|
|
|
CopySpec getAssets() {
|
|
return project.copySpec {
|
|
from "${intermediateDir}"
|
|
include "flutter_assets/**" // the working dir and its files
|
|
}
|
|
}
|
|
|
|
CopySpec getSnapshots() {
|
|
return project.copySpec {
|
|
from "${intermediateDir}"
|
|
|
|
if (buildMode == 'release' || buildMode == 'profile') {
|
|
include "app.so"
|
|
}
|
|
}
|
|
}
|
|
|
|
FileCollection readDependencies(File dependenciesFile) {
|
|
if (dependenciesFile.exists()) {
|
|
try {
|
|
// Dependencies file has Makefile syntax:
|
|
// <target> <files>: <source> <files> <separated> <by> <non-escaped space>
|
|
String depText = dependenciesFile.text
|
|
// So we split list of files by non-escaped(by backslash) space,
|
|
def matcher = depText.split(': ')[1] =~ /(\\ |[^\s])+/
|
|
// then we replace all escaped spaces with regular spaces
|
|
def depList = matcher.collect{it[0].replaceAll("\\\\ ", " ")}
|
|
return project.files(depList)
|
|
} catch (Exception e) {
|
|
logger.error("Error reading dependency file ${dependenciesFile}: ${e}")
|
|
}
|
|
}
|
|
return project.files()
|
|
}
|
|
|
|
@InputFiles
|
|
FileCollection getSourceFiles() {
|
|
FileCollection sources = project.files()
|
|
for (File depfile in getDependenciesFiles()) {
|
|
sources += readDependencies(depfile)
|
|
}
|
|
if (!sources.isEmpty()) {
|
|
// We have a dependencies file. Add a dependency on gen_snapshot as well, since the
|
|
// snapshots have to be rebuilt if it changes.
|
|
sources += readDependencies(project.file("${intermediateDir}/gen_snapshot.d"))
|
|
sources += readDependencies(project.file("${intermediateDir}/frontend_server.d"))
|
|
if (localEngineSrcPath != null) {
|
|
sources += project.files("$localEngineSrcPath/$localEngine")
|
|
}
|
|
// Finally, add a dependency on pubspec.yaml as well.
|
|
return sources + project.files('pubspec.yaml')
|
|
}
|
|
// No dependencies file (or problems parsing it). Fall back to source files.
|
|
return project.fileTree(
|
|
dir: sourceDir,
|
|
exclude: ['android', 'ios'],
|
|
include: ['**/*.dart', 'pubspec.yaml']
|
|
)
|
|
}
|
|
|
|
@TaskAction
|
|
void build() {
|
|
buildBundle()
|
|
}
|
|
}
|