| 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://maven.google.com' |
| } |
| } |
| dependencies { |
| classpath 'com.android.tools.build:gradle:3.0.1' |
| } |
| } |
| |
| 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 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(project.rootProject.file("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 a 'profile' build type |
| 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 (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("implementation")) { |
| implementation 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() |
| 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") |
| project.tasks.create("flutterBuildX86Jar", 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>Implementation configurations, including custom ones |
| // added after applying the Flutter plugin. |
| project.android.buildTypes.each { addFlutterJarImplementationDependency(project, it) } |
| project.android.buildTypes.whenObjectAdded { addFlutterJarImplementationDependency(project, it) } |
| } |
| |
| project.extensions.create("flutter", FlutterExtension) |
| project.afterEvaluate this.&addFlutterTask |
| |
| File pluginsFile = new File(project.rootProject.projectDir.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 implementation dependencies to the specified buildType. |
| * |
| * Note: The BuildType DSL type is not public, and is therefore omitted from the signature. |
| */ |
| private void addFlutterJarImplementationDependency(Project project, buildType) { |
| project.dependencies { |
| String configuration; |
| if (project.getConfigurations().findByName("implementation")) { |
| configuration = buildType.name + "Implementation"; |
| } else { |
| configuration = buildType.name + "Compile"; |
| } |
| add(configuration, project.files { |
| String buildMode = buildModeFor(buildType) |
| if (buildMode == "debug") { |
| [flutterX86Jar, debugFlutterJar] |
| } else if (buildMode == "profile") { |
| profileFlutterJar |
| } 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", or "release" (fall-back). |
| */ |
| private static String buildModeFor(buildType) { |
| if (buildType.name == "profile") { |
| return "profile" |
| } 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 previewDart2Value = false |
| if (project.hasProperty('preview-dart-2')) { |
| previewDart2Value = project.property('preview-dart-2') |
| } |
| |
| 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 preferSharedLibraryValue = false |
| if (project.hasProperty('prefer-shared-library')) { |
| preferSharedLibraryValue = project.property('prefer-shared-library') |
| } |
| String targetPlatformValue = null |
| if (project.hasProperty('target-platform')) { |
| targetPlatformValue = project.property('target-platform') |
| } |
| |
| project.android.applicationVariants.all { variant -> |
| String flutterBuildMode = buildModeFor(variant.buildType) |
| if (flutterBuildMode == 'debug' && project.tasks.findByName('flutterBuildX86Jar')) { |
| 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 |
| } |
| } |
| |
| GenerateDependencies dependenciesTask = project.tasks.create(name: "flutterDependencies${variant.name.capitalize()}", type: GenerateDependencies) { |
| flutterRoot this.flutterRoot |
| flutterExecutable this.flutterExecutable |
| buildMode flutterBuildMode |
| localEngine this.localEngine |
| localEngineSrcPath this.localEngineSrcPath |
| targetPath target |
| previewDart2 previewDart2Value |
| preferSharedLibrary preferSharedLibraryValue |
| targetPlatform targetPlatformValue |
| sourceDir project.file(project.flutter.source) |
| intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}") |
| } |
| |
| FlutterTask flutterTask = project.tasks.create(name: "flutterBuild${variant.name.capitalize()}", type: FlutterTask) { |
| dependsOn dependenciesTask |
| flutterRoot this.flutterRoot |
| flutterExecutable this.flutterExecutable |
| buildMode flutterBuildMode |
| localEngine this.localEngine |
| localEngineSrcPath this.localEngineSrcPath |
| targetPath target |
| previewDart2 previewDart2Value |
| preferSharedLibrary preferSharedLibraryValue |
| targetPlatform targetPlatformValue |
| sourceDir project.file(project.flutter.source) |
| intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}") |
| extraFrontEndOptions extraFrontEndOptionsValue |
| extraGenSnapshotOptions extraGenSnapshotOptionsValue |
| } |
| |
| Task copyFlxTask = 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(copyFlxTask) |
| } |
| } |
| } |
| |
| 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 previewDart2 |
| @Optional @Input |
| Boolean preferSharedLibrary |
| @Optional @Input |
| String targetPlatform |
| File sourceDir |
| File intermediateDir |
| @Optional @Input |
| String extraFrontEndOptions |
| @Optional @Input |
| String extraGenSnapshotOptions |
| |
| @OutputFile |
| File getDependenciesFile() { |
| if (buildMode != 'debug') { |
| return project.file("${intermediateDir}/snapshot.d") |
| } |
| return project.file("${intermediateDir}/snapshot_blob.bin.d") |
| } |
| |
| void buildFlx() { |
| if (!sourceDir.isDirectory()) { |
| throw new GradleException("Invalid Flutter source directory: ${sourceDir}") |
| } |
| |
| intermediateDir.mkdirs() |
| |
| if (buildMode != "debug") { |
| 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 (previewDart2) { |
| args "--preview-dart-2" |
| } |
| if (extraFrontEndOptions != null) { |
| args "--extra-front-end-options", "${extraFrontEndOptions}" |
| } |
| if (extraGenSnapshotOptions != null) { |
| args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" |
| } |
| if (preferSharedLibrary) { |
| args "--prefer-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", "flx" |
| args "--suppress-analytics" |
| args "--target", targetPath |
| if (previewDart2) { |
| args "--preview-dart-2" |
| } |
| |
| args "--output-file", "${intermediateDir}/app.flx" |
| if (buildMode != "debug") { |
| args "--precompiled" |
| } else { |
| if (!previewDart2) { |
| args "--snapshot", "${intermediateDir}/snapshot_blob.bin" |
| args "--depfile", "${intermediateDir}/snapshot_blob.bin.d" |
| } |
| } |
| args "--working-dir", "${intermediateDir}/flutter_assets" |
| } |
| } |
| } |
| |
| class GenerateDependencies extends BaseFlutterTask { |
| @TaskAction |
| void build() { |
| File dependenciesFile = getDependenciesFile(); |
| if (!dependenciesFile.exists()) { |
| buildFlx() |
| } |
| } |
| } |
| |
| 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') { |
| if (preferSharedLibrary) { |
| 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 null |
| } |
| |
| @InputFiles |
| FileCollection getSourceFiles() { |
| File dependenciesFile = getDependenciesFile() |
| FileCollection sources = readDependencies(dependenciesFile) |
| if (sources != null) { |
| // We have a dependencies file. Add a dependency on gen_snapshot as well, since the |
| // snapshots have to be rebuilt if it changes. |
| FileCollection snapshotter = readDependencies(project.file("${intermediateDir}/gen_snapshot.d")) |
| if (snapshotter != null) { |
| sources = sources.plus(snapshotter) |
| } |
| if (localEngineSrcPath != null) { |
| sources = sources.plus(project.files("$localEngineSrcPath/$localEngine")) |
| } |
| // Finally, add a dependency on pubspec.yaml as well. |
| return sources.plus(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() { |
| buildFlx() |
| } |
| } |