| // 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 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.2.1' |
| } |
| } |
| |
| android { |
| compileOptions { |
| sourceCompatibility 1.8 |
| targetCompatibility 1.8 |
| } |
| } |
| |
| apply plugin: FlutterPlugin |
| |
| class FlutterPlugin implements Plugin<Project> { |
| // 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 Map baseJar = [:] |
| private File flutterRoot |
| private File flutterExecutable |
| private String localEngine |
| private String localEngineSrcPath |
| private Properties localProperties |
| private File flutterJar |
| |
| @Override |
| void apply(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 (splitPerAbi(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 |
| } |
| } |
| } |
| } |
| getTargetPlatforms(project).each { targetArch -> |
| String abiValue = PLATFORM_ARCH_MAP[targetArch] |
| project.android { |
| packagingOptions { |
| // Prevent the ELF library from getting corrupted. |
| doNotStrip "*/${abiValue}/libapp.so" |
| } |
| if (splitPerAbi(project)) { |
| splits { |
| abi { |
| include abiValue |
| } |
| } |
| } |
| } |
| } |
| |
| // Add custom build types |
| project.android.buildTypes { |
| profile { |
| initWith debug |
| if (it.hasProperty('matchingFallbacks')) { |
| matchingFallbacks = ['debug', 'release'] |
| } |
| } |
| } |
| |
| String flutterRootPath = resolveProperty(project, "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") |
| } |
| |
| String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" |
| flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); |
| |
| if (useLocalEngine(project)) { |
| 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 { |
| String basePlatformArch = getBasePlatform(project) |
| // This is meant to include the compiled classes only, however it will include `libflutter.so` as well. |
| Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine") |
| File debugJar = baseEnginePath.resolve("${basePlatformArch}").resolve("flutter.jar").toFile() |
| baseJar["debug"] = debugJar |
| if (!debugJar.isFile()) { |
| project.exec { |
| executable flutterExecutable.absolutePath |
| args "--suppress-analytics" |
| args "precache" |
| } |
| if (!debugJar.isFile()) { |
| throw new GradleException("Unable to find flutter.jar in SDK: ${debugJar}") |
| } |
| } |
| baseJar["profile"] = baseEnginePath.resolve("${basePlatformArch}-profile").resolve("flutter.jar").toFile() |
| baseJar["release"] = baseEnginePath.resolve("${basePlatformArch}-release").resolve("flutter.jar").toFile() |
| |
| // Add flutter.jar dependencies to all <buildType>Api configurations, including custom ones |
| // added after applying the Flutter plugin. |
| project.android.buildTypes.each { |
| def buildMode = buildModeFor(it) |
| addApiDependencies(project, it.name, project.files { |
| baseJar[buildMode] |
| }) |
| } |
| project.android.buildTypes.whenObjectAdded { |
| def buildMode = buildModeFor(it) |
| addApiDependencies(project, it.name, project.files { |
| baseJar[buildMode] |
| }) |
| } |
| } |
| } |
| |
| /** |
| * Returns the directory where the plugins are built. |
| */ |
| private File getPluginBuildDir(Project project) { |
| // 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 |
| } |
| |
| private Properties getPluginList(Project project) { |
| File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins') |
| return readPropertiesIfExist(pluginsFile) |
| } |
| |
| private void addPluginTasks(Project project) { |
| Properties plugins = getPluginList(project) |
| project.android.buildTypes.each { buildType -> |
| plugins.each { name, path -> |
| String buildModeValue = buildType.debuggable ? "debug" : "release" |
| List<String> taskNameParts = ["build", "plugin", buildModeValue] |
| taskNameParts.addAll(name.split("_")) |
| String taskName = toCammelCase(taskNameParts) |
| // Build types can be extended. For example, a build type can extend the `debug` mode. |
| // In such cases, prevent creating the same task. |
| if (project.tasks.findByName(taskName) == null) { |
| project.tasks.create(name: taskName, type: FlutterPluginTask) { |
| flutterExecutable this.flutterExecutable |
| buildMode buildModeValue |
| verbose isVerbose(project) |
| pluginDir project.file(path) |
| sourceDir project.file(project.flutter.source) |
| intermediateDir getPluginBuildDir(project) |
| } |
| } |
| } |
| } |
| } |
| |
| private void buildPlugins(Project project, Set buildTypes) { |
| 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")) |
| } |
| projects.each { hostProject -> |
| hostProject.repositories { |
| maven { |
| url "${getPluginBuildDir(project)}/outputs/repo" |
| } |
| } |
| } |
| buildTypes.each { buildType -> |
| project.tasks.withType(FlutterPluginTask).all { pluginTask -> |
| String buildMode = buildType.debuggable ? "debug" : "release" |
| if (pluginTask.buildMode != buildMode) { |
| return |
| } |
| pluginTask.execute() |
| pluginTask.intermediateDir.eachFileRecurse(FILES) { file -> |
| if (file.name != "maven-metadata.xml") { |
| return |
| } |
| def mavenMetadata = new XmlParser().parse(file) |
| String groupId = mavenMetadata.groupId.text() |
| String artifactId = mavenMetadata.artifactId.text() |
| |
| if (!artifactId.endsWith(buildMode)) { |
| return |
| } |
| // Add the plugin dependency based on the Maven metadata. |
| addApiDependencies(project, buildType.name, "$groupId:$artifactId:+@aar", { |
| transitive = true |
| }) |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns a set with the build type names that apply to the given list of tasks |
| * required to configure the plugin dependencies. |
| */ |
| private Set getBuildTypesForTasks(Project project, List<String> tasksToExecute) { |
| Set buildTypes = [] |
| tasksToExecute.each { task -> |
| project.android.buildTypes.each { buildType -> |
| if (task == "androidDependencies" || task.endsWith("dependencies")) { |
| // The tasks to query the dependencies includes all the build types. |
| buildTypes.add(buildType) |
| } else if (task.endsWith("assemble")) { |
| // The `assemble` task includes all the build types. |
| buildTypes.add(buildType) |
| } else if (task.endsWith(buildType.name.capitalize())) { |
| buildTypes.add(buildType) |
| } |
| } |
| } |
| return buildTypes |
| } |
| |
| private static String toCammelCase(List<String> parts) { |
| if (parts.empty) { |
| return "" |
| } |
| return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}" |
| } |
| |
| private String resolveProperty(Project project, 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 static List<String> getTargetPlatforms(Project project) { |
| 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 static Boolean splitPerAbi(Project project) { |
| if (project.hasProperty('split-per-abi')) { |
| return project.property('split-per-abi').toBoolean() |
| } |
| return false; |
| } |
| |
| private static Boolean useLocalEngine(Project project) { |
| return project.hasProperty('localEngineOut') |
| } |
| |
| private static Boolean isVerbose(Project project) { |
| if (project.hasProperty('verbose')) { |
| return project.property('verbose').toBoolean() |
| } |
| return false |
| } |
| |
| private static Boolean buildPluginAsAar() { |
| return System.getProperty('build-plugins-as-aars') == 'true' |
| } |
| |
| /** |
| * Returns the platform that is used to extract the `libflutter.so` and the .class files. |
| * |
| * Note: This is only needed to add the .class files. |
| * Unfortunately, the engine artifacts include the .class and libflutter.so files. |
| */ |
| private static String getBasePlatform(Project project) { |
| if (PLATFORM_ARM64 in getTargetPlatforms(project)) { |
| return PLATFORM_ARM64; |
| } |
| return PLATFORM_ARM32; |
| } |
| |
| private void addFlutterJarCompileOnlyDependency(Project project, String variantName, FileCollection files) { |
| if (project.state.failure) { |
| return |
| } |
| String configuration; |
| if (project.getConfigurations().findByName("compileOnly")) { |
| configuration = "${variantName}CompileOnly"; |
| } else { |
| configuration = "${variantName}Provided"; |
| } |
| project.dependencies.add(configuration, files) |
| } |
| |
| 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(project) |
| def addFlutterDeps = { variant -> |
| if (splitPerAbi(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 |
| def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI)) |
| if (abiVersionCode != null) { |
| output.versionCodeOverride = |
| abiVersionCode * 1000 + variant.versionCode |
| } |
| } |
| } |
| |
| String flutterBuildMode = buildModeFor(variant.buildType) |
| if (flutterBuildMode == 'debug' && project.tasks.findByName("${FLUTTER_BUILD_PREFIX}X86Jar")) { |
| Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac") |
| if (task) { |
| task.dependsOn project.flutterBuildX86Jar |
| } |
| task = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin") |
| if (task) { |
| task.dependsOn project.flutterBuildX86Jar |
| } |
| } |
| |
| def compileTasks = targetPlatforms.collect { targetArch -> |
| String abiValue = PLATFORM_ARCH_MAP[targetArch] |
| String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name, targetArch.replace('android-', '')]) |
| FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { |
| flutterRoot this.flutterRoot |
| flutterExecutable this.flutterExecutable |
| buildMode flutterBuildMode |
| localEngine this.localEngine |
| localEngineSrcPath this.localEngineSrcPath |
| abi abiValue |
| targetPath target |
| verbose isVerbose(project) |
| 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 |
| } |
| } |
| |
| def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar") |
| def libFlutterPlatforms = targetPlatforms.collect() |
| // x86/x86_64 native library used for debugging only, for now. |
| if (flutterBuildMode == 'debug') { |
| libFlutterPlatforms.add('android-x86') |
| libFlutterPlatforms.add('android-x64') |
| } |
| Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { |
| destinationDir libJar.parentFile |
| archiveName libJar.name |
| libFlutterPlatforms.each { targetArch -> |
| // This check prevents including `libflutter.so` twice, since it's included in the base platform jar. |
| // Unfortunately, the `pickFirst` setting in `packagingOptions` does not work when the project `:flutter` |
| // is included as an implementation dependency, which causes duplicated `libflutter.so`. |
| if (getBasePlatform(project) == targetArch) { |
| return |
| } |
| // Don't include `libflutter.so` for other architectures when a local engine is specified. |
| if (useLocalEngine(project)) { |
| return |
| } |
| def engineArtifactSubdir = getEngineArtifactDirName(variant.buildType, targetArch); |
| // Include `libflutter.so`. |
| // TODO(blasten): The libs should be outside `flutter.jar` when the artifacts are downloaded. |
| from(project.zipTree("${flutterRoot}/bin/cache/artifacts/engine/${engineArtifactSubdir}/flutter.jar")) { |
| include 'lib/**' |
| } |
| } |
| dependsOn compileTasks |
| // Add the ELF library. |
| compileTasks.each { compileTask -> |
| from(compileTask.intermediateDir) { |
| include '*.so' |
| rename { String filename -> |
| return "lib/${compileTask.abi}/lib${filename}" |
| } |
| } |
| } |
| } |
| // Include the snapshots and libflutter.so in `lib/`. |
| addApiDependencies(project, variant.name, project.files { |
| packFlutterSnapshotsAndLibsTask |
| }) |
| |
| // We know that the flutter app is a subproject in another Android app when these tasks exist. |
| Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets") |
| Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets") |
| Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) { |
| dependsOn compileTasks |
| if (packageAssets && cleanPackageAssets) { |
| dependsOn packageAssets |
| dependsOn cleanPackageAssets |
| into packageAssets.outputDir |
| } else { |
| dependsOn variant.mergeAssets |
| dependsOn "clean${variant.mergeAssets.name.capitalize()}" |
| variant.mergeAssets.mustRunAfter("clean${variant.mergeAssets.name.capitalize()}") |
| into variant.mergeAssets.outputDir |
| } |
| compileTasks.each { flutterTask -> |
| with flutterTask.assets |
| } |
| } |
| variant.outputs.first().processResources.dependsOn(copyFlutterAssetsTask) |
| } |
| if (project.android.hasProperty("applicationVariants")) { |
| project.android.applicationVariants.all addFlutterDeps |
| } else { |
| project.android.libraryVariants.all addFlutterDeps |
| } |
| |
| if (buildPluginAsAar()) { |
| addPluginTasks(project) |
| |
| List<String> tasksToExecute = project.gradle.startParameter.taskNames |
| Set buildTypes = getBuildTypesForTasks(project, tasksToExecute) |
| if (tasksToExecute.contains("clean")) { |
| // Because the plugins are built during configuration, the task "clean" |
| // cannot run in conjunction with an assembly task. |
| if (!buildTypes.empty) { |
| throw new GradleException("Can't run the clean task along with other assemble tasks") |
| } |
| } |
| // Build plugins when a task "assembly*" will be called later. |
| if (!buildTypes.empty) { |
| // Build the plugin during configuration. |
| // This is required when Jetifier is enabled, otherwise the implementation dependency |
| // cannot be added. |
| buildPlugins(project, buildTypes) |
| } |
| } else { |
| getPluginList(project).each { name, _ -> |
| def pluginProject = project.rootProject.findProject(":$name") |
| if (pluginProject != null) { |
| project.dependencies { |
| if (project.getConfigurations().findByName("implementation")) { |
| implementation pluginProject |
| } else { |
| compile pluginProject |
| } |
| } |
| pluginProject.afterEvaluate { |
| pluginProject.android.buildTypes { |
| profile { |
| initWith debug |
| } |
| } |
| pluginProject.android.buildTypes.each { |
| def buildMode = buildModeFor(it) |
| addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] )) |
| } |
| pluginProject.android.buildTypes.whenObjectAdded { |
| def buildMode = buildModeFor(it) |
| addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] )) |
| } |
| } |
| } else { |
| project.logger.error("Plugin project :$name not found. Please update settings.gradle.") |
| } |
| } |
| } |
| } |
| } |
| |
| 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() |
| } |
| } |
| |
| class FlutterPluginTask extends DefaultTask { |
| File flutterExecutable |
| @Optional @Input |
| Boolean verbose |
| @Input |
| String buildMode |
| @Input |
| File pluginDir |
| @Input |
| File intermediateDir |
| File sourceDir |
| |
| @InputFiles |
| FileCollection getSourceFiles() { |
| return project.fileTree( |
| dir: sourceDir, |
| exclude: ["android", "ios"], |
| include: ["pubspec.yaml"] |
| ) |
| } |
| |
| @OutputDirectory |
| File getOutputDirectory() { |
| return intermediateDir |
| } |
| |
| @TaskAction |
| void build() { |
| intermediateDir.mkdirs() |
| project.exec { |
| executable flutterExecutable.absolutePath |
| workingDir pluginDir |
| args "build", "aar" |
| args "--quiet" |
| args "--suppress-analytics" |
| args "--output-dir", "${intermediateDir}" |
| switch (buildMode) { |
| case 'release': |
| args "--release" |
| break |
| case 'debug': |
| args "--debug" |
| break |
| default: |
| assert false |
| } |
| if (verbose) { |
| args "--verbose" |
| } |
| } |
| } |
| } |
| |
| gradle.useLogger(new FlutterEventLogger()) |
| |
| class FlutterEventLogger extends BuildAdapter implements TaskExecutionListener { |
| String mostRecentTask = "" |
| |
| void beforeExecute(Task task) { |
| mostRecentTask = task.name |
| } |
| |
| void afterExecute(Task task, TaskState state) {} |
| |
| void buildFinished(BuildResult result) { |
| if (result.failure != null) { |
| if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.FLUTTER_BUILD_PREFIX)) { |
| result.rethrowFailure() |
| } |
| } |
| } |
| } |