flutter.gradle 25.3 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 org.apache.tools.ant.taskdefs.condition.Os
6 7 8 9 10
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
11
import org.gradle.api.file.CopySpec
12 13
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.Copy
14
import org.gradle.api.tasks.InputFiles
15 16
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
17
import org.gradle.api.tasks.bundling.Jar
18

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

apply plugin: FlutterPlugin

31
class FlutterPlugin implements Plugin<Project> {
32
    private File flutterRoot
33
    private File flutterExecutable
34
    private String localEngine
35
    private String localEngineSrcPath
36 37
    private Properties localProperties

38
    private File flutterJar
39
    private File flutterX86Jar
40 41 42
    private File debugFlutterJar
    private File profileFlutterJar
    private File releaseFlutterJar
43 44
    private File dynamicProfileFlutterJar
    private File dynamicReleaseFlutterJar
45

46 47 48 49 50 51 52
    // 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"

53 54 55
    private Properties readPropertiesIfExist(File propertiesFile) {
        Properties result = new Properties()
        if (propertiesFile.exists()) {
56
            propertiesFile.withReader('UTF-8') { reader -> result.load(reader) }
57 58 59 60
        }
        return result
    }

61 62
    private String resolveProperty(Project project, String name, String defaultValue) {
        if (localProperties == null) {
63
            localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
64
        }
65
        String result
66 67 68 69 70 71 72 73 74 75 76
        if (project.hasProperty(name)) {
            result = project.property(name)
        }
        if (result == null) {
            result = localProperties.getProperty(name)
        }
        if (result == null) {
            result = defaultValue
        }
        return result
    }
77 78 79

    @Override
    void apply(Project project) {
80
        // Add custom build types
81 82 83
        project.android.buildTypes {
            profile {
                initWith debug
84 85 86
                if (it.hasProperty('matchingFallbacks')) {
                    matchingFallbacks = ['debug', 'release']
                }
87
            }
88 89 90 91 92 93 94 95 96 97 98 99
            dynamicProfile {
                initWith debug
                if (it.hasProperty('matchingFallbacks')) {
                    matchingFallbacks = ['debug', 'release']
                }
            }
            dynamicRelease {
                initWith debug
                if (it.hasProperty('matchingFallbacks')) {
                    matchingFallbacks = ['debug', 'release']
                }
            }
100
        }
101

102
        String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
103
        if (flutterRootPath == null) {
104
            throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
105
        }
106 107
        flutterRoot = project.file(flutterRootPath)
        if (!flutterRoot.isDirectory()) {
108 109 110
            throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
        }

111 112 113
        String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
        flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();

114 115 116 117 118 119
        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')
            }
120
            flutterJar = Paths.get(engineOut.absolutePath, "flutter.jar").toFile()
121
            if (!flutterJar.isFile()) {
122
                throw new GradleException('Local engine build does not contain flutter.jar')
123
            }
124 125 126 127

            localEngine = engineOut.name
            localEngineSrcPath = engineOut.parentFile.parent

128
            project.dependencies {
129 130
                if (project.getConfigurations().findByName("api")) {
                    api project.files(flutterJar)
131 132 133
                } else {
                    compile project.files(flutterJar)
                }
134
            }
135 136
        } else {
            Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
137 138 139 140 141 142 143 144
            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()
145 146
            dynamicProfileFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-profile").resolve("flutter.jar").toFile()
            dynamicReleaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-release").resolve("flutter.jar").toFile()
147
            if (!debugFlutterJar.isFile()) {
148
                project.exec {
149
                    executable flutterExecutable.absolutePath
150
                    args "--suppress-analytics"
151 152
                    args "precache"
                }
153 154
                if (!debugFlutterJar.isFile()) {
                    throw new GradleException("Unable to find flutter.jar in SDK: ${debugFlutterJar}")
155 156
                }
            }
157 158

            // Add x86/x86_64 native library. Debug mode only, for now.
159
            flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar")
160
            Task flutterX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar", Jar) {
161 162
                destinationDir flutterX86Jar.parentFile
                archiveName flutterX86Jar.name
163
                from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") {
164 165
                    into "lib/x86"
                }
166
                from("${flutterRoot}/bin/cache/artifacts/engine/android-x64/libflutter.so") {
167 168 169
                    into "lib/x86_64"
                }
            }
170
            // Add flutter.jar dependencies to all <buildType>Api configurations, including custom ones
171
            // added after applying the Flutter plugin.
172 173
            project.android.buildTypes.each { addFlutterJarApiDependency(project, it, flutterX86JarTask) }
            project.android.buildTypes.whenObjectAdded { addFlutterJarApiDependency(project, it, flutterX86JarTask) }
174 175
        }

176 177
        project.extensions.create("flutter", FlutterExtension)
        project.afterEvaluate this.&addFlutterTask
178

179
        File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
180 181 182 183 184 185
        Properties plugins = readPropertiesIfExist(pluginsFile)

        plugins.each { name, _ ->
            def pluginProject = project.rootProject.findProject(":$name")
            if (pluginProject != null) {
                project.dependencies {
186 187 188 189 190
                    if (project.getConfigurations().findByName("implementation")) {
                        implementation pluginProject
                    } else {
                        compile pluginProject
                    }
191
                }
192 193 194 195 196 197 198
		pluginProject.afterEvaluate {
                    pluginProject.android.buildTypes {
                        profile {
                            initWith debug
                        }
                    }
		}
199
                pluginProject.afterEvaluate this.&addFlutterJarCompileOnlyDependency
200 201 202 203 204 205
            } else {
                project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
            }
        }
    }

206
    private void addFlutterJarCompileOnlyDependency(Project project) {
207 208 209
        if (project.state.failure) {
            return
        }
210 211
        project.dependencies {
            if (flutterJar != null) {
212 213 214 215 216
                if (project.getConfigurations().findByName("compileOnly")) {
                    compileOnly project.files(flutterJar)
                } else {
                    provided project.files(flutterJar)
                }
217
            } else {
218 219
                if (project.getConfigurations().findByName("debugCompileOnly")) {
                    debugCompileOnly project.files(debugFlutterJar)
220
                    profileCompileOnly project.files(profileFlutterJar)
221 222 223
                    releaseCompileOnly project.files(releaseFlutterJar)
                } else {
                    debugProvided project.files(debugFlutterJar)
224
                    profileProvided project.files(profileFlutterJar)
225 226
                    releaseProvided project.files(releaseFlutterJar)
                }
227 228
            }
        }
229 230
    }

231
    /**
232
     * Adds suitable flutter.jar api dependencies to the specified buildType.
233 234 235
     *
     * Note: The BuildType DSL type is not public, and is therefore omitted from the signature.
     */
236
    private void addFlutterJarApiDependency(Project project, buildType, Task flutterX86JarTask) {
237
        project.dependencies {
238
            String configuration;
239 240
            if (project.getConfigurations().findByName("api")) {
                configuration = buildType.name + "Api";
241 242 243 244
            } else {
                configuration = buildType.name + "Compile";
            }
            add(configuration, project.files {
245 246
                String buildMode = buildModeFor(buildType)
                if (buildMode == "debug") {
247
                    [flutterX86JarTask, debugFlutterJar]
248 249
                } else if (buildMode == "profile") {
                    profileFlutterJar
250 251 252 253
                } else if (buildMode == "dynamicProfile") {
                    dynamicProfileFlutterJar
                } else if (buildMode == "dynamicRelease") {
                    dynamicReleaseFlutterJar
254 255 256 257 258 259 260 261 262 263 264 265
                } 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.
     *
266
     * @return "debug", "profile", "dynamicProfile", "dynamicRelease", or "release" (fall-back).
267 268 269 270
     */
    private static String buildModeFor(buildType) {
        if (buildType.name == "profile") {
            return "profile"
271 272 273 274
        } else if (buildType.name == "dynamicProfile") {
            return "dynamicProfile"
        } else if (buildType.name == "dynamicRelease") {
            return "dynamicRelease"
275 276 277 278 279 280
        } else if (buildType.debuggable) {
            return "debug"
        }
        return "release"
    }

281
    private void addFlutterTask(Project project) {
282 283 284
        if (project.state.failure) {
            return
        }
285 286 287 288
        if (project.flutter.source == null) {
            throw new GradleException("Must provide Flutter source directory")
        }

289
        String target = project.flutter.target
290 291 292
        if (target == null) {
            target = 'lib/main.dart'
        }
293 294 295
        if (project.hasProperty('target')) {
            target = project.property('target')
        }
296

297 298 299 300
        Boolean verboseValue = null
        if (project.hasProperty('verbose')) {
            verboseValue = project.property('verbose').toBoolean()
        }
301 302 303 304 305 306 307 308
        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')
        }
309 310
        Boolean trackWidgetCreationValue = false
        if (project.hasProperty('track-widget-creation')) {
311
            trackWidgetCreationValue = project.property('track-widget-creation').toBoolean()
312
        }
313 314 315
        String compilationTraceFilePathValue = null
        if (project.hasProperty('precompile')) {
            compilationTraceFilePathValue = project.property('precompile')
316
        }
317 318 319 320
        Boolean buildHotUpdateValue = false
        if (project.hasProperty('hotupdate')) {
            buildHotUpdateValue = project.property('hotupdate').toBoolean()
        }
321 322 323 324 325 326 327 328
        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')
        }
329 330 331
        Boolean buildSharedLibraryValue = false
        if (project.hasProperty('build-shared-library')) {
            buildSharedLibraryValue = project.property('build-shared-library').toBoolean()
332
        }
333 334 335 336
        String targetPlatformValue = null
        if (project.hasProperty('target-platform')) {
            targetPlatformValue = project.property('target-platform')
        }
337

338
        def addFlutterDeps = { variant ->
339
            String flutterBuildMode = buildModeFor(variant.buildType)
340
            if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar')) {
341 342 343 344
                Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
                if (task) {
                    task.dependsOn project.flutterBuildX86Jar
                }
345 346 347 348
                task = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin")
                if (task) {
                    task.dependsOn project.flutterBuildX86Jar
                }
349 350
            }

351
            FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) {
352
                flutterRoot this.flutterRoot
353
                flutterExecutable this.flutterExecutable
354
                buildMode flutterBuildMode
355
                localEngine this.localEngine
356
                localEngineSrcPath this.localEngineSrcPath
357
                targetPath target
358
                verbose verboseValue
359 360
                fileSystemRoots fileSystemRootsValue
                fileSystemScheme fileSystemSchemeValue
361
                trackWidgetCreation trackWidgetCreationValue
362
                compilationTraceFilePath compilationTraceFilePathValue
363
                buildHotUpdate buildHotUpdateValue
364
                buildSharedLibrary buildSharedLibraryValue
365
                targetPlatform targetPlatformValue
366 367
                sourceDir project.file(project.flutter.source)
                intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
368 369
                extraFrontEndOptions extraFrontEndOptionsValue
                extraGenSnapshotOptions extraGenSnapshotOptionsValue
370 371
            }

372 373 374
            // 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")
375
            Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
376
                dependsOn flutterTask
377 378 379
                dependsOn packageAssets ? packageAssets : variant.mergeAssets
                dependsOn cleanPackageAssets ? cleanPackageAssets : "clean${variant.mergeAssets.name.capitalize()}"
                into packageAssets ? packageAssets.outputDir : variant.mergeAssets.outputDir
380
                with flutterTask.assets
381
            }
382 383 384 385 386 387 388 389 390
            if (packageAssets) {
                // Only include configurations that exist in parent project.
                Task mergeAssets = project.tasks.findByPath(":app:merge${variant.name.capitalize()}Assets")
                if (mergeAssets) {
                    mergeAssets.dependsOn(copyFlutterAssetsTask)
                }
            } else {
                variant.outputs[0].processResources.dependsOn(copyFlutterAssetsTask)
            }
391
        }
392 393 394 395 396
        if (project.android.hasProperty("applicationVariants")) {
            project.android.applicationVariants.all addFlutterDeps
        } else {
            project.android.libraryVariants.all addFlutterDeps
        }
397 398 399 400 401
    }
}

class FlutterExtension {
    String source
402
    String target
403 404
}

405
abstract class BaseFlutterTask extends DefaultTask {
406
    File flutterRoot
407
    File flutterExecutable
408 409
    String buildMode
    String localEngine
410
    String localEngineSrcPath
411
    @Input
412
    String targetPath
413
    @Optional @Input
414 415
    Boolean verbose
    @Optional @Input
416 417 418
    String[] fileSystemRoots
    @Optional @Input
    String fileSystemScheme
419
    @Input
420 421
    Boolean trackWidgetCreation
    @Optional @Input
422
    String compilationTraceFilePath
423
    @Optional @Input
424 425
    Boolean buildHotUpdate
    @Optional @Input
426
    Boolean buildSharedLibrary
427 428
    @Optional @Input
    String targetPlatform
429
    File sourceDir
430
    File intermediateDir
431 432 433 434
    @Optional @Input
    String extraFrontEndOptions
    @Optional @Input
    String extraGenSnapshotOptions
435

436 437
    @OutputFiles
    FileCollection getDependenciesFiles() {
438
        FileCollection depfiles = project.files()
439 440 441 442 443

        // 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")
444 445 446 447 448

        // 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")
449
        return depfiles
450 451
    }

452
    void buildBundle() {
453 454 455 456 457 458
        if (!sourceDir.isDirectory()) {
            throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
        }

        intermediateDir.mkdirs()

459
        if (buildMode == "profile" || buildMode == "release") {
460 461 462 463 464 465 466 467
            project.exec {
                executable flutterExecutable.absolutePath
                workingDir sourceDir
                if (localEngine != null) {
                    args "--local-engine", localEngine
                    args "--local-engine-src-path", localEngineSrcPath
                }
                args "build", "aot"
468
                args "--suppress-analytics"
469 470 471 472
                args "--quiet"
                args "--target", targetPath
                args "--target-platform", "android-arm"
                args "--output-dir", "${intermediateDir}"
473 474 475
                if (trackWidgetCreation) {
                    args "--track-widget-creation"
                }
476
                if (extraFrontEndOptions != null) {
477
                    args "--extra-front-end-options", "${extraFrontEndOptions}"
478 479
                }
                if (extraGenSnapshotOptions != null) {
480 481
                    args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
                }
482 483
                if (buildSharedLibrary) {
                    args "--build-shared-library"
484
                }
485 486 487
                if (targetPlatform != null) {
                    args "--target-platform", "${targetPlatform}"
                }
488 489 490
                args "--${buildMode}"
            }
        }
491

492 493 494 495 496 497 498
        project.exec {
            executable flutterExecutable.absolutePath
            workingDir sourceDir
            if (localEngine != null) {
                args "--local-engine", localEngine
                args "--local-engine-src-path", localEngineSrcPath
            }
499
            args "build", "bundle"
500
            args "--suppress-analytics"
501
            args "--target", targetPath
502 503 504
            if (verbose) {
                args "--verbose"
            }
505 506 507 508 509 510 511 512
            if (fileSystemRoots != null) {
                for (root in fileSystemRoots) {
                    args "--filesystem-root", root
                }
            }
            if (fileSystemScheme != null) {
                args "--filesystem-scheme", fileSystemScheme
            }
513 514 515
            if (trackWidgetCreation) {
                args "--track-widget-creation"
            }
516 517
            if (compilationTraceFilePath != null) {
                args "--precompile", compilationTraceFilePath
518
            }
519 520 521
            if (buildHotUpdate) {
                args "--hotupdate"
            }
522 523 524
            if (extraFrontEndOptions != null) {
                args "--extra-front-end-options", "${extraFrontEndOptions}"
            }
525 526 527 528 529 530
            if (extraGenSnapshotOptions != null) {
                args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
            }
            if (targetPlatform != null) {
                args "--target-platform", "${targetPlatform}"
            }
531
            if (buildMode == "release" || buildMode == "profile") {
532
                args "--precompiled"
533 534
            } else {
                args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
535
            }
536
            args "--asset-dir", "${intermediateDir}/flutter_assets"
537 538 539 540 541 542 543 544 545 546 547 548
            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"
            }
549 550 551 552 553
        }
    }
}

class FlutterTask extends BaseFlutterTask {
554
    @OutputDirectory
555 556 557
    File getOutputDirectory() {
        return intermediateDir
    }
558

559 560
    CopySpec getAssets() {
        return project.copySpec {
561
            from "${intermediateDir}"
562

563
            include "flutter_assets/**" // the working dir and its files
564

565
            if (buildMode == 'release' || buildMode == 'profile') {
566
                if (buildSharedLibrary) {
567
                    include "app.so"
568 569 570 571 572 573
                } else {
                    include "vm_snapshot_data"
                    include "vm_snapshot_instr"
                    include "isolate_snapshot_data"
                    include "isolate_snapshot_instr"
                }
574
            }
575
        }
576 577
    }

578 579 580 581
    FileCollection readDependencies(File dependenciesFile) {
        if (dependenciesFile.exists()) {
            try {
                // Dependencies file has Makefile syntax:
582
                //   <target> <files>: <source> <files> <separated> <by> <non-escaped space>
583
                String depText = dependenciesFile.text
584 585 586 587 588
                // 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)
589 590 591 592
            } catch (Exception e) {
                logger.error("Error reading dependency file ${dependenciesFile}: ${e}")
            }
        }
593
        return project.files()
594 595
    }

596 597
    @InputFiles
    FileCollection getSourceFiles() {
598 599 600 601 602
        FileCollection sources = project.files()
        for (File depfile in getDependenciesFiles()) {
          sources += readDependencies(depfile)
        }
        if (!sources.isEmpty()) {
603 604
            // We have a dependencies file. Add a dependency on gen_snapshot as well, since the
            // snapshots have to be rebuilt if it changes.
605
            sources += readDependencies(project.file("${intermediateDir}/gen_snapshot.d"))
606
            sources += readDependencies(project.file("${intermediateDir}/frontend_server.d"))
607
            if (localEngineSrcPath != null) {
608
                sources += project.files("$localEngineSrcPath/$localEngine")
609
            }
610
            // Finally, add a dependency on pubspec.yaml as well.
611
            return sources + project.files('pubspec.yaml')
612 613 614 615 616 617 618
        }
        // No dependencies file (or problems parsing it). Fall back to source files.
        return project.fileTree(
                dir: sourceDir,
                exclude: ['android', 'ios'],
                include: ['**/*.dart', 'pubspec.yaml']
        )
619 620
    }

621 622
    @TaskAction
    void build() {
623
        buildBundle()
624 625
    }
}
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645

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()
            }
        }
    }
}