flutter.gradle 31.8 KB
Newer Older
1 2
import java.nio.file.Path
import java.nio.file.Paths
3 4

import com.android.builder.model.AndroidProject
5
import com.android.build.OutputFile
6
import org.apache.tools.ant.taskdefs.condition.Os
7 8 9 10 11
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
12
import org.gradle.api.file.CopySpec
13 14
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Copy
15
import org.gradle.api.tasks.InputFiles
16 17
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
18
import org.gradle.api.tasks.bundling.Jar
19

20
buildscript {
21
    repositories {
22
        google()
23
        jcenter()
24 25
    }
    dependencies {
26
        classpath 'com.android.tools.build:gradle:3.2.1'
27
    }
28 29
}

Dan Field's avatar
Dan Field committed
30 31 32 33 34 35 36
android {
    compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
}

37 38
apply plugin: FlutterPlugin

39
class FlutterPlugin implements Plugin<Project> {
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
    // 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"

83
    private Map baseJar = [:]
84
    private File flutterRoot
85
    private File flutterExecutable
86
    private String localEngine
87
    private String localEngineSrcPath
88
    private Properties localProperties
89
    private File flutterJar
90

91 92
    @Override
    void apply(Project project) {
93 94 95
        project.extensions.create("flutter", FlutterExtension)
        project.afterEvaluate this.&addFlutterTask

96 97 98
        // 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.
99
        if (splitPerAbi(project)) {
100 101 102 103 104 105 106 107 108 109 110 111 112
            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
                    }
                }
            }
        }
113
        getTargetPlatforms(project).each { targetArch ->
114
            String abiValue = PLATFORM_ARCH_MAP[targetArch]
115 116
            project.android {
                packagingOptions {
117 118 119
                    // Prevent the ELF library from getting corrupted.
                    doNotStrip "*/${abiValue}/libapp.so"
                }
120
                if (splitPerAbi(project)) {
121 122 123 124 125
                    splits {
                        abi {
                            include abiValue
                        }
                    }
126 127 128 129
                }
            }
        }

130
        // Add custom build types
131 132 133
        project.android.buildTypes {
            profile {
                initWith debug
134 135 136
                if (it.hasProperty('matchingFallbacks')) {
                    matchingFallbacks = ['debug', 'release']
                }
137 138
            }
        }
139

140
        String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
141
        if (flutterRootPath == null) {
142
            throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
143
        }
144 145
        flutterRoot = project.file(flutterRootPath)
        if (!flutterRoot.isDirectory()) {
146 147 148
            throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
        }

149 150 151
        String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
        flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();

152
        if (useLocalEngine(project)) {
153 154 155 156 157
            String engineOutPath = project.property('localEngineOut')
            File engineOut = project.file(engineOutPath)
            if (!engineOut.isDirectory()) {
                throw new GradleException('localEngineOut must point to a local engine build')
            }
158
            Path baseEnginePath = Paths.get(engineOut.absolutePath)
159
            flutterJar = baseEnginePath.resolve("flutter.jar").toFile()
160
            if (!flutterJar.isFile()) {
161
                throw new GradleException("Local engine jar not found: $flutterJar")
162
            }
163 164
            localEngine = engineOut.name
            localEngineSrcPath = engineOut.parentFile.parent
165 166
            // The local engine is built for one of the build type.
            // However, we use the same engine for each of the build types.
167
            project.android.buildTypes.each {
168
                addApiDependencies(project, it.name, project.files {
169
                    flutterJar
170 171
                })
            }
172
        } else {
173 174
            String basePlatformArch = getBasePlatform(project)
            // This is meant to include the compiled classes only, however it will include `libflutter.so` as well.
175 176
            Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
            File debugJar = baseEnginePath.resolve("${basePlatformArch}").resolve("flutter.jar").toFile()
177
            baseJar["debug"] = debugJar
178 179 180 181 182 183 184 185 186
            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}")
                }
187
            }
188 189 190 191 192 193 194
            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)
195
                addApiDependencies(project, it.name, project.files {
196 197
                    baseJar[buildMode]
                })
198
            }
199 200
            project.android.buildTypes.whenObjectAdded {
                def buildMode = buildModeFor(it)
201
                addApiDependencies(project, it.name, project.files {
202 203
                    baseJar[buildMode]
                })
204
            }
205
        }
206

207
        File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
208 209 210 211 212 213
        Properties plugins = readPropertiesIfExist(pluginsFile)

        plugins.each { name, _ ->
            def pluginProject = project.rootProject.findProject(":$name")
            if (pluginProject != null) {
                project.dependencies {
214 215 216 217 218
                    if (project.getConfigurations().findByName("implementation")) {
                        implementation pluginProject
                    } else {
                        compile pluginProject
                    }
219
                }
220
                pluginProject.afterEvaluate {
221 222 223 224 225
                    pluginProject.android.buildTypes {
                        profile {
                            initWith debug
                        }
                    }
226 227 228 229 230 231 232 233 234
                    
                    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] ))
                    }
235
                }
236 237 238 239 240 241
            } else {
                project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
            }
        }
    }

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
    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')
    }

    /**
     * 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;
    }

303
    private void addFlutterJarCompileOnlyDependency(Project project, String variantName, FileCollection files) {
304 305 306
        if (project.state.failure) {
            return
        }
307

308
        project.dependencies {
309 310 311
            String configuration;
            if (project.getConfigurations().findByName("compileOnly")) {
                configuration = "${variantName}CompileOnly";
312
            } else {
313
                configuration = "${variantName}Provided";
314
            }
315 316

            add(configuration, files)
317
        }
318 319
    }

320
    private static void addApiDependencies(Project project, String variantName, FileCollection files) {
321
        project.dependencies {
322
            String configuration;
323
            // `compile` dependencies are now `api` dependencies.
324
            if (project.getConfigurations().findByName("api")) {
325
                configuration = "${variantName}Api";
326
            } else {
327
                configuration = "${variantName}Compile";
328
            }
329
            add(configuration, files)
330 331 332 333 334 335 336 337
        }
    }

    /**
     * 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.
     *
338
     * @return "debug", "profile", or "release" (fall-back).
339 340 341 342 343 344 345 346 347 348
     */
    private static String buildModeFor(buildType) {
        if (buildType.name == "profile") {
            return "profile"
        } else if (buildType.debuggable) {
            return "debug"
        }
        return "release"
    }

349 350 351 352 353 354 355 356 357
    private static String getEngineArtifactDirName(buildType, targetArch) {
        if (buildType.name == "profile") {
            return "${targetArch}-profile"
        } else if (buildType.debuggable) {
            return "${targetArch}"
        }
        return "${targetArch}-release"
    }

358
    private void addFlutterTask(Project project) {
359 360 361
        if (project.state.failure) {
            return
        }
362 363 364 365
        if (project.flutter.source == null) {
            throw new GradleException("Must provide Flutter source directory")
        }

366
        String target = project.flutter.target
367 368 369
        if (target == null) {
            target = 'lib/main.dart'
        }
370 371 372
        if (project.hasProperty('target')) {
            target = project.property('target')
        }
373

374 375 376 377
        Boolean verboseValue = null
        if (project.hasProperty('verbose')) {
            verboseValue = project.property('verbose').toBoolean()
        }
378 379 380 381 382 383 384 385
        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')
        }
386 387
        Boolean trackWidgetCreationValue = false
        if (project.hasProperty('track-widget-creation')) {
388
            trackWidgetCreationValue = project.property('track-widget-creation').toBoolean()
389
        }
390
        String compilationTraceFilePathValue = null
391 392
        if (project.hasProperty('compilation-trace-file')) {
            compilationTraceFilePathValue = project.property('compilation-trace-file')
393
        }
394 395 396 397 398 399 400 401 402 403 404
        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')
405
        }
406 407 408 409 410 411 412 413
        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')
        }
414

415
        def targetPlatforms = getTargetPlatforms(project)
416
        def addFlutterDeps = { variant ->
417
            if (splitPerAbi(project)) {
418 419 420 421 422 423 424 425 426 427 428 429
                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
                    }
                }
            }
430

431 432
            String flutterBuildMode = buildModeFor(variant.buildType)
            if (flutterBuildMode == 'debug' && project.tasks.findByName("${FLUTTER_BUILD_PREFIX}X86Jar")) {
433 434 435 436
                Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
                if (task) {
                    task.dependsOn project.flutterBuildX86Jar
                }
437 438 439 440
                task = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin")
                if (task) {
                    task.dependsOn project.flutterBuildX86Jar
                }
441 442
            }

443
            def flutterTasks = []
444 445 446 447
            targetPlatforms.each { targetArch ->
                String abiValue = PLATFORM_ARCH_MAP[targetArch]
                String taskName = "compile${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}${targetArch.replace('android-', '').capitalize()}"
                FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
                    flutterRoot this.flutterRoot
                    flutterExecutable this.flutterExecutable
                    buildMode flutterBuildMode
                    localEngine this.localEngine
                    localEngineSrcPath this.localEngineSrcPath
                    abi abiValue
                    targetPath target
                    verbose verboseValue
                    fileSystemRoots fileSystemRootsValue
                    fileSystemScheme fileSystemSchemeValue
                    trackWidgetCreation trackWidgetCreationValue
                    compilationTraceFilePath compilationTraceFilePathValue
                    createPatch createPatchValue
                    buildNumber buildNumberValue
                    baselineDir baselineDirValue
463
                    targetPlatform targetArch
464
                    sourceDir project.file(project.flutter.source)
465
                    intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${targetArch}")
466 467 468 469 470
                    extraFrontEndOptions extraFrontEndOptionsValue
                    extraGenSnapshotOptions extraGenSnapshotOptionsValue
                }
                flutterTasks.add(compileTask)
            }
471
            def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
472 473 474 475 476 477
            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')
            }
478
            Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
479 480
                destinationDir libJar.parentFile
                archiveName libJar.name
481
                libFlutterPlatforms.each { targetArch ->
482 483 484
                    // 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`.
485 486 487
                    if (getBasePlatform(project) == targetArch) {
                        return
                    }
488 489 490 491
                    // Don't include `libflutter.so` for other architectures when a local engine is specified.
                    if (useLocalEngine(project)) {
                        return
                    }
492 493 494 495 496
                    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/**'
497 498 499
                    }
                }
                dependsOn flutterTasks
500 501
                // Add the ELF library.
                flutterTasks.each { flutterTask ->
502
                    from(flutterTask.intermediateDir) {
503 504 505
                        include '*.so'
                        rename { String filename ->
                            return "lib/${flutterTask.abi}/lib${filename}"
506 507 508
                        }
                    }
                }
509
            }
510
            // Include the snapshots and libflutter.so in `lib/`.
511
            addApiDependencies(project, variant.name, project.files {
512 513 514
                packFlutterSnapshotsAndLibsTask
            })

515 516 517
            // 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")
518
            Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
519
                dependsOn flutterTasks
520 521 522 523 524
                if (packageAssets && cleanPackageAssets) {
                    dependsOn packageAssets
                    dependsOn cleanPackageAssets
                    into packageAssets.outputDir
                } else {
525 526
                    dependsOn variant.mergeAssets
                    dependsOn "clean${variant.mergeAssets.name.capitalize()}"
527
                    variant.mergeAssets.mustRunAfter("clean${variant.mergeAssets.name.capitalize()}")
528
                    into variant.mergeAssets.outputDir
529
                }
530
                flutterTasks.each { flutterTask ->
531 532
                    with flutterTask.assets
                }
533
            }
534
            if (packageAssets) {
535 536 537 538 539 540 541 542
                String mainModuleName = "app"
                try {
                    String tmpModuleName = project.rootProject.ext.mainModuleName
                    if (tmpModuleName != null && !tmpModuleName.empty) {
                        mainModuleName = tmpModuleName
                    }
                } catch (Exception e) {
                }
543
                // Only include configurations that exist in parent project.
544
                Task mergeAssets = project.tasks.findByPath(":${mainModuleName}:merge${variant.name.capitalize()}Assets")
545 546 547 548
                if (mergeAssets) {
                    mergeAssets.dependsOn(copyFlutterAssetsTask)
                }
            } else {
549 550
                def processResources = variant.outputs.first().processResources
                processResources.dependsOn(copyFlutterAssetsTask)
551
            }
552
        }
553

554 555 556 557 558
        if (project.android.hasProperty("applicationVariants")) {
            project.android.applicationVariants.all addFlutterDeps
        } else {
            project.android.libraryVariants.all addFlutterDeps
        }
559 560 561 562 563
    }
}

class FlutterExtension {
    String source
564
    String target
565 566
}

567
abstract class BaseFlutterTask extends DefaultTask {
568
    File flutterRoot
569
    File flutterExecutable
570 571
    String buildMode
    String localEngine
572
    String localEngineSrcPath
573
    @Input
574
    String targetPath
575
    @Optional @Input
576 577
    Boolean verbose
    @Optional @Input
578 579 580
    String[] fileSystemRoots
    @Optional @Input
    String fileSystemScheme
581
    @Input
582 583
    Boolean trackWidgetCreation
    @Optional @Input
584
    String compilationTraceFilePath
585
    @Optional @Input
586 587 588 589 590
    Boolean createPatch
    @Optional @Input
    Integer buildNumber
    @Optional @Input
    String baselineDir
591
    @Optional @Input
592
    String targetPlatform
593 594
    @Input
    String abi
595
    File sourceDir
596
    File intermediateDir
597 598 599 600
    @Optional @Input
    String extraFrontEndOptions
    @Optional @Input
    String extraGenSnapshotOptions
601

602 603
    @OutputFiles
    FileCollection getDependenciesFiles() {
604
        FileCollection depfiles = project.files()
605 606 607 608 609

        // 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")
610 611 612 613 614

        // 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")
615
        return depfiles
616 617
    }

618
    void buildBundle() {
619 620 621 622 623 624
        if (!sourceDir.isDirectory()) {
            throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
        }

        intermediateDir.mkdirs()

625
        if (buildMode == "profile" || buildMode == "release") {
626 627 628 629 630 631 632 633
            project.exec {
                executable flutterExecutable.absolutePath
                workingDir sourceDir
                if (localEngine != null) {
                    args "--local-engine", localEngine
                    args "--local-engine-src-path", localEngineSrcPath
                }
                args "build", "aot"
634
                args "--suppress-analytics"
635 636 637
                args "--quiet"
                args "--target", targetPath
                args "--output-dir", "${intermediateDir}"
638
                args "--target-platform", "${targetPlatform}"
639 640 641
                if (trackWidgetCreation) {
                    args "--track-widget-creation"
                }
642
                if (extraFrontEndOptions != null) {
643
                    args "--extra-front-end-options", "${extraFrontEndOptions}"
644 645
                }
                if (extraGenSnapshotOptions != null) {
646 647
                    args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
                }
648 649 650
                args "--${buildMode}"
            }
        }
651

652 653 654 655 656 657 658
        project.exec {
            executable flutterExecutable.absolutePath
            workingDir sourceDir
            if (localEngine != null) {
                args "--local-engine", localEngine
                args "--local-engine-src-path", localEngineSrcPath
            }
659
            args "build", "bundle"
660
            args "--target", targetPath
661
            args "--target-platform", "${targetPlatform}"
662 663 664
            if (verbose) {
                args "--verbose"
            }
665 666 667 668 669 670 671 672
            if (fileSystemRoots != null) {
                for (root in fileSystemRoots) {
                    args "--filesystem-root", root
                }
            }
            if (fileSystemScheme != null) {
                args "--filesystem-scheme", fileSystemScheme
            }
673 674 675
            if (trackWidgetCreation) {
                args "--track-widget-creation"
            }
676
            if (compilationTraceFilePath != null) {
677
                args "--compilation-trace-file", compilationTraceFilePath
678
            }
679 680 681 682 683 684 685 686 687
            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
688
            }
689 690 691
            if (extraFrontEndOptions != null) {
                args "--extra-front-end-options", "${extraFrontEndOptions}"
            }
692 693 694
            if (extraGenSnapshotOptions != null) {
                args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
            }
695
            if (buildMode == "release" || buildMode == "profile") {
696
                args "--precompiled"
697 698
            } else {
                args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
699
            }
700
            args "--asset-dir", "${intermediateDir}/flutter_assets"
701 702 703
            if (buildMode == "debug") {
                args "--debug"
            }
704
            if (buildMode == "profile") {
705 706
                args "--profile"
            }
707
            if (buildMode == "release") {
708 709
                args "--release"
            }
710 711 712 713 714
        }
    }
}

class FlutterTask extends BaseFlutterTask {
715
    @OutputDirectory
716 717 718
    File getOutputDirectory() {
        return intermediateDir
    }
719

720 721
    CopySpec getAssets() {
        return project.copySpec {
722 723
            from "${intermediateDir}"
            include "flutter_assets/**" // the working dir and its files
724 725 726 727 728 729
        }
    }

    CopySpec getSnapshots() {
        return project.copySpec {
            from "${intermediateDir}"
730

731
            if (buildMode == 'release' || buildMode == 'profile') {
732
                include "app.so"
733
            }
734
        }
735 736
    }

737 738 739 740
    FileCollection readDependencies(File dependenciesFile) {
        if (dependenciesFile.exists()) {
            try {
                // Dependencies file has Makefile syntax:
741
                //   <target> <files>: <source> <files> <separated> <by> <non-escaped space>
742
                String depText = dependenciesFile.text
743 744 745 746 747
                // 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)
748 749 750 751
            } catch (Exception e) {
                logger.error("Error reading dependency file ${dependenciesFile}: ${e}")
            }
        }
752
        return project.files()
753 754
    }

755 756
    @InputFiles
    FileCollection getSourceFiles() {
757 758 759 760 761
        FileCollection sources = project.files()
        for (File depfile in getDependenciesFiles()) {
          sources += readDependencies(depfile)
        }
        if (!sources.isEmpty()) {
762 763
            // We have a dependencies file. Add a dependency on gen_snapshot as well, since the
            // snapshots have to be rebuilt if it changes.
764
            sources += readDependencies(project.file("${intermediateDir}/gen_snapshot.d"))
765
            sources += readDependencies(project.file("${intermediateDir}/frontend_server.d"))
766
            if (localEngineSrcPath != null) {
767
                sources += project.files("$localEngineSrcPath/$localEngine")
768
            }
769
            // Finally, add a dependency on pubspec.yaml as well.
770
            return sources + project.files('pubspec.yaml')
771 772 773 774 775 776 777
        }
        // No dependencies file (or problems parsing it). Fall back to source files.
        return project.fileTree(
                dir: sourceDir,
                exclude: ['android', 'ios'],
                include: ['**/*.dart', 'pubspec.yaml']
        )
778 779
    }

780 781
    @TaskAction
    void build() {
782
        buildBundle()
783 784
    }
}
785 786 787 788 789 790 791 792 793 794 795 796 797 798

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) {
799
            if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.FLUTTER_BUILD_PREFIX)) {
800 801 802 803 804
                result.rethrowFailure()
            }
        }
    }
}