| import java.nio.file.Path | |
| import java.nio.file.Paths | |
| import com.android.builder.model.AndroidProject | |
| 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 { | |
| jcenter() | |
| maven { | |
| url 'https://dl.google.com/dl/android/maven2' | |
| } | |
| } | |
| dependencies { | |
| classpath 'com.android.tools.build:gradle:3.1.2' | |
| } | |
| } | |
| apply plugin: FlutterPlugin | |
| class FlutterPlugin implements Plugin<Project> { | |
| private File flutterRoot | |
| private File flutterExecutable | |
| private String localEngine | |
| private String localEngineSrcPath | |
| private Properties localProperties | |
| private File flutterJar | |
| private File flutterX86Jar | |
| private File debugFlutterJar | |
| private File profileFlutterJar | |
| private File releaseFlutterJar | |
| private File dynamicProfileFlutterJar | |
| private File dynamicReleaseFlutterJar | |
| // 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 flutterBuildPrefix = "flutterBuild" | |
| private Properties readPropertiesIfExist(File propertiesFile) { | |
| Properties result = new Properties() | |
| if (propertiesFile.exists()) { | |
| propertiesFile.withReader('UTF-8') { reader -> result.load(reader) } | |
| } | |
| return result | |
| } | |
| 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 | |
| } | |
| @Override | |
| void apply(Project project) { | |
| // Add custom build types | |
| project.android.buildTypes { | |
| profile { | |
| initWith debug | |
| if (it.hasProperty('matchingFallbacks')) { | |
| matchingFallbacks = ['debug', 'release'] | |
| } | |
| } | |
| dynamicProfile { | |
| initWith debug | |
| if (it.hasProperty('matchingFallbacks')) { | |
| matchingFallbacks = ['debug', 'release'] | |
| } | |
| } | |
| dynamicRelease { | |
| 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 (project.hasProperty('localEngineOut')) { | |
| String engineOutPath = project.property('localEngineOut') | |
| File engineOut = project.file(engineOutPath) | |
| if (!engineOut.isDirectory()) { | |
| throw new GradleException('localEngineOut must point to a local engine build') | |
| } | |
| flutterJar = Paths.get(engineOut.absolutePath, "flutter.jar").toFile() | |
| if (!flutterJar.isFile()) { | |
| throw new GradleException('Local engine build does not contain flutter.jar') | |
| } | |
| localEngine = engineOut.name | |
| localEngineSrcPath = engineOut.parentFile.parent | |
| project.dependencies { | |
| if (project.getConfigurations().findByName("api")) { | |
| api project.files(flutterJar) | |
| } else { | |
| compile project.files(flutterJar) | |
| } | |
| } | |
| } else { | |
| Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine") | |
| String targetArch = 'arm' | |
| if (project.hasProperty('target-platform') && | |
| project.property('target-platform') == 'android-arm64') { | |
| targetArch = 'arm64' | |
| } | |
| debugFlutterJar = baseEnginePath.resolve("android-${targetArch}").resolve("flutter.jar").toFile() | |
| profileFlutterJar = baseEnginePath.resolve("android-${targetArch}-profile").resolve("flutter.jar").toFile() | |
| releaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-release").resolve("flutter.jar").toFile() | |
| dynamicProfileFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-profile").resolve("flutter.jar").toFile() | |
| dynamicReleaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-release").resolve("flutter.jar").toFile() | |
| if (!debugFlutterJar.isFile()) { | |
| project.exec { | |
| executable flutterExecutable.absolutePath | |
| args "--suppress-analytics" | |
| args "precache" | |
| } | |
| if (!debugFlutterJar.isFile()) { | |
| throw new GradleException("Unable to find flutter.jar in SDK: ${debugFlutterJar}") | |
| } | |
| } | |
| // Add x86/x86_64 native library. Debug mode only, for now. | |
| flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar") | |
| Task flutterX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar", Jar) { | |
| destinationDir flutterX86Jar.parentFile | |
| archiveName flutterX86Jar.name | |
| from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") { | |
| into "lib/x86" | |
| } | |
| from("${flutterRoot}/bin/cache/artifacts/engine/android-x64/libflutter.so") { | |
| into "lib/x86_64" | |
| } | |
| } | |
| // Add flutter.jar dependencies to all <buildType>Api configurations, including custom ones | |
| // added after applying the Flutter plugin. | |
| project.android.buildTypes.each { addFlutterJarApiDependency(project, it, flutterX86JarTask) } | |
| project.android.buildTypes.whenObjectAdded { addFlutterJarApiDependency(project, it, flutterX86JarTask) } | |
| } | |
| project.extensions.create("flutter", FlutterExtension) | |
| project.afterEvaluate this.&addFlutterTask | |
| File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins') | |
| Properties plugins = readPropertiesIfExist(pluginsFile) | |
| plugins.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 this.&addFlutterJarCompileOnlyDependency | |
| } else { | |
| project.logger.error("Plugin project :$name not found. Please update settings.gradle.") | |
| } | |
| } | |
| } | |
| private void addFlutterJarCompileOnlyDependency(Project project) { | |
| if (project.state.failure) { | |
| return | |
| } | |
| project.dependencies { | |
| if (flutterJar != null) { | |
| if (project.getConfigurations().findByName("compileOnly")) { | |
| compileOnly project.files(flutterJar) | |
| } else { | |
| provided project.files(flutterJar) | |
| } | |
| } else { | |
| if (project.getConfigurations().findByName("debugCompileOnly")) { | |
| debugCompileOnly project.files(debugFlutterJar) | |
| releaseCompileOnly project.files(releaseFlutterJar) | |
| } else { | |
| debugProvided project.files(debugFlutterJar) | |
| releaseProvided project.files(releaseFlutterJar) | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Adds suitable flutter.jar api dependencies to the specified buildType. | |
| * | |
| * Note: The BuildType DSL type is not public, and is therefore omitted from the signature. | |
| */ | |
| private void addFlutterJarApiDependency(Project project, buildType, Task flutterX86JarTask) { | |
| project.dependencies { | |
| String configuration; | |
| if (project.getConfigurations().findByName("api")) { | |
| configuration = buildType.name + "Api"; | |
| } else { | |
| configuration = buildType.name + "Compile"; | |
| } | |
| add(configuration, project.files { | |
| String buildMode = buildModeFor(buildType) | |
| if (buildMode == "debug") { | |
| [flutterX86JarTask, debugFlutterJar] | |
| } else if (buildMode == "profile") { | |
| profileFlutterJar | |
| } else if (buildMode == "dynamicProfile") { | |
| dynamicProfileFlutterJar | |
| } else if (buildMode == "dynamicRelease") { | |
| dynamicReleaseFlutterJar | |
| } else { | |
| releaseFlutterJar | |
| } | |
| }) | |
| } | |
| } | |
| /** | |
| * 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", "dynamicProfile", "dynamicRelease", or "release" (fall-back). | |
| */ | |
| private static String buildModeFor(buildType) { | |
| if (buildType.name == "profile") { | |
| return "profile" | |
| } else if (buildType.name == "dynamicProfile") { | |
| return "dynamicProfile" | |
| } else if (buildType.name == "dynamicRelease") { | |
| return "dynamicRelease" | |
| } else if (buildType.debuggable) { | |
| return "debug" | |
| } | |
| return "release" | |
| } | |
| private void addFlutterTask(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') | |
| } | |
| Boolean verboseValue = null | |
| if (project.hasProperty('verbose')) { | |
| verboseValue = project.property('verbose').toBoolean() | |
| } | |
| 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('precompile')) { | |
| compilationTraceFilePathValue = project.property('precompile') | |
| } | |
| Boolean buildHotUpdateValue = false | |
| if (project.hasProperty('hotupdate')) { | |
| buildHotUpdateValue = project.property('hotupdate').toBoolean() | |
| } | |
| 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') | |
| } | |
| Boolean buildSharedLibraryValue = false | |
| if (project.hasProperty('build-shared-library')) { | |
| buildSharedLibraryValue = project.property('build-shared-library').toBoolean() | |
| } | |
| String targetPlatformValue = null | |
| if (project.hasProperty('target-platform')) { | |
| targetPlatformValue = project.property('target-platform') | |
| } | |
| def addFlutterDeps = { variant -> | |
| String flutterBuildMode = buildModeFor(variant.buildType) | |
| if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}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 | |
| } | |
| } | |
| FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) { | |
| flutterRoot this.flutterRoot | |
| flutterExecutable this.flutterExecutable | |
| buildMode flutterBuildMode | |
| localEngine this.localEngine | |
| localEngineSrcPath this.localEngineSrcPath | |
| targetPath target | |
| verbose verboseValue | |
| fileSystemRoots fileSystemRootsValue | |
| fileSystemScheme fileSystemSchemeValue | |
| trackWidgetCreation trackWidgetCreationValue | |
| compilationTraceFilePath compilationTraceFilePathValue | |
| buildHotUpdate buildHotUpdateValue | |
| buildSharedLibrary buildSharedLibraryValue | |
| targetPlatform targetPlatformValue | |
| sourceDir project.file(project.flutter.source) | |
| intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}") | |
| extraFrontEndOptions extraFrontEndOptionsValue | |
| extraGenSnapshotOptions extraGenSnapshotOptionsValue | |
| } | |
| Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) { | |
| dependsOn flutterTask | |
| dependsOn variant.mergeAssets | |
| dependsOn "clean${variant.mergeAssets.name.capitalize()}" | |
| into variant.mergeAssets.outputDir | |
| with flutterTask.assets | |
| } | |
| variant.outputs[0].processResources.dependsOn(copyFlutterAssetsTask) | |
| } | |
| if (project.android.hasProperty("applicationVariants")) { | |
| project.android.applicationVariants.all addFlutterDeps | |
| } else { | |
| project.android.libraryVariants.all addFlutterDeps | |
| } | |
| } | |
| } | |
| 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 | |
| @Optional @Input | |
| Boolean trackWidgetCreation | |
| @Optional @Input | |
| String compilationTraceFilePath | |
| @Optional @Input | |
| Boolean buildHotUpdate | |
| @Optional @Input | |
| Boolean buildSharedLibrary | |
| @Optional @Input | |
| String targetPlatform | |
| 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 "--target-platform", "android-arm" | |
| args "--output-dir", "${intermediateDir}" | |
| if (trackWidgetCreation) { | |
| args "--track-widget-creation" | |
| } | |
| if (extraFrontEndOptions != null) { | |
| args "--extra-front-end-options", "${extraFrontEndOptions}" | |
| } | |
| if (extraGenSnapshotOptions != null) { | |
| args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" | |
| } | |
| if (buildSharedLibrary) { | |
| args "--build-shared-library" | |
| } | |
| if (targetPlatform != null) { | |
| args "--target-platform", "${targetPlatform}" | |
| } | |
| 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 "--suppress-analytics" | |
| args "--target", targetPath | |
| 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 "--precompile", compilationTraceFilePath | |
| } | |
| if (buildHotUpdate) { | |
| args "--hotupdate" | |
| } | |
| if (extraFrontEndOptions != null) { | |
| args "--extra-front-end-options", "${extraFrontEndOptions}" | |
| } | |
| if (extraGenSnapshotOptions != null) { | |
| args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" | |
| } | |
| if (targetPlatform != null) { | |
| args "--target-platform", "${targetPlatform}" | |
| } | |
| 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" || buildMode == "dynamicProfile") { | |
| args "--profile" | |
| } | |
| if (buildMode == "release" || buildMode == "dynamicRelease") { | |
| args "--release" | |
| } | |
| if (buildMode == "dynamicProfile" || buildMode == "dynamicRelease") { | |
| args "--dynamic" | |
| } | |
| } | |
| } | |
| } | |
| 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 | |
| if (buildMode != 'debug' || compilationTraceFilePath) { | |
| if (buildSharedLibrary) { | |
| include "app.so" | |
| } else { | |
| include "vm_snapshot_data" | |
| include "vm_snapshot_instr" | |
| include "isolate_snapshot_data" | |
| include "isolate_snapshot_instr" | |
| } | |
| } | |
| } | |
| } | |
| FileCollection readDependencies(File dependenciesFile) { | |
| if (dependenciesFile.exists()) { | |
| try { | |
| // Dependencies file has Makefile syntax: | |
| // <target> <files>: <source> <files> <separated> <by> <space> | |
| String depText = dependenciesFile.text | |
| return project.files(depText.split(': ')[1].split()) | |
| } 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() | |
| } | |
| } | |
| 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.flutterBuildPrefix)) { | |
| result.rethrowFailure() | |
| } | |
| } | |
| } | |
| } |