From 978fada33c1fa6a8b4411f0c33d0981c9f7aa745 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Fri, 8 Nov 2019 12:41:24 -0800 Subject: [PATCH] Refactor flutter.gradle to use assemble directly (#43876) Removes multiple re-entrant calls of bundle and aot and replaces them with a single call to assemble. This restores full caching and will allow follow-up performance improvements when building multiple ABIs --- dev/devicelab/bin/tasks/module_test.dart | 4 +- dev/devicelab/lib/framework/apk_utils.dart | 2 +- packages/flutter_tools/gradle/flutter.gradle | 165 ++++-------------- .../lib/src/build_system/targets/android.dart | 124 +++++++++++++ .../lib/src/build_system/targets/dart.dart | 37 +++- .../lib/src/commands/assemble.dart | 36 +++- .../lib/src/commands/build_aar.dart | 7 + .../lib/src/commands/build_apk.dart | 7 + .../lib/src/commands/build_appbundle.dart | 7 + .../hermetic/assemble_test.dart | 2 +- .../build_system/targets/android_test.dart | 87 +++++++++ .../build_system/targets/dart_test.dart | 9 + packages/flutter_tools/test/src/testbed.dart | 104 +++++++++++ 13 files changed, 447 insertions(+), 144 deletions(-) create mode 100644 packages/flutter_tools/lib/src/build_system/targets/android.dart create mode 100644 packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart diff --git a/dev/devicelab/bin/tasks/module_test.dart b/dev/devicelab/bin/tasks/module_test.dart index bea54242ef8..c4f62fb14b2 100644 --- a/dev/devicelab/bin/tasks/module_test.dart +++ b/dev/devicelab/bin/tasks/module_test.dart @@ -218,11 +218,11 @@ Future main() async { final String analyticsOutput = analyticsOutputFile.readAsStringSync(); if (!analyticsOutput.contains('cd24: android-arm64') || !analyticsOutput.contains('cd25: true') - || !analyticsOutput.contains('viewName: build/bundle')) { + || !analyticsOutput.contains('viewName: assemble')) { return TaskResult.failure( 'Building outer app produced the following analytics: "$analyticsOutput"' 'but not the expected strings: "cd24: android-arm64", "cd25: true" and ' - '"viewName: build/bundle"' + '"viewName: assemble"' ); } diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart index 27d539ab16e..8a99dd37f3d 100644 --- a/dev/devicelab/lib/framework/apk_utils.dart +++ b/dev/devicelab/lib/framework/apk_utils.dart @@ -396,7 +396,7 @@ Future _resultOfGradleTask({String workingDirectory, String task, String validateSnapshotDependency(FlutterProject project, String expectedTarget) { final File snapshotBlob = File( path.join(project.rootPath, 'build', 'app', 'intermediates', - 'flutter', 'debug', 'android-arm', 'snapshot_blob.bin.d')); + 'flutter', 'debug', 'android-arm', 'flutter_build.d')); assert(snapshotBlob.existsSync()); final String contentSnapshot = snapshotBlob.readAsStringSync(); diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 24d4a11214d..418887d5b69 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -520,22 +520,6 @@ class FlutterPlugin implements Plugin { if (project.hasProperty('track-widget-creation')) { trackWidgetCreationValue = project.property('track-widget-creation').toBoolean() } - String compilationTraceFilePathValue = null - if (project.hasProperty('compilation-trace-file')) { - compilationTraceFilePathValue = project.property('compilation-trace-file') - } - Boolean createPatchValue = false - if (project.hasProperty('patch')) { - createPatchValue = project.property('patch').toBoolean() - } - Integer buildNumberValue = null - if (project.hasProperty('build-number')) { - buildNumberValue = project.property('build-number').toInteger() - } - String baselineDirValue = null - if (project.hasProperty('baseline-dir')) { - baselineDirValue = project.property('baseline-dir') - } String extraFrontEndOptionsValue = null if (project.hasProperty('extra-front-end-options')) { extraFrontEndOptionsValue = project.property('extra-front-end-options') @@ -574,10 +558,6 @@ class FlutterPlugin implements Plugin { fileSystemRoots fileSystemRootsValue fileSystemScheme fileSystemSchemeValue trackWidgetCreation trackWidgetCreationValue - compilationTraceFilePath compilationTraceFilePathValue - createPatch createPatchValue - buildNumber buildNumberValue - baselineDir baselineDirValue targetPlatform targetArch sourceDir project.file(project.flutter.source) intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${targetArch}") @@ -698,7 +678,7 @@ abstract class BaseFlutterTask extends DefaultTask { String localEngineSrcPath @Input String targetPath - @Optional @Input + @Optional Boolean verbose @Optional @Input String[] fileSystemRoots @@ -707,14 +687,6 @@ abstract class BaseFlutterTask extends DefaultTask { @Input Boolean trackWidgetCreation @Optional @Input - String compilationTraceFilePath - @Optional @Input - Boolean createPatch - @Optional @Input - Integer buildNumber - @Optional @Input - String baselineDir - @Optional @Input String targetPlatform @Input String abi @@ -729,15 +701,8 @@ abstract class BaseFlutterTask extends DefaultTask { 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") + // Includes all sources used in the flutter compilation. + depfiles += project.files("${intermediateDir}/flutter_build.d") return depfiles } @@ -748,88 +713,38 @@ abstract class BaseFlutterTask extends DefaultTask { intermediateDir.mkdirs() - if (buildMode == "profile" || buildMode == "release") { - project.exec { - executable flutterExecutable.absolutePath - workingDir sourceDir - if (localEngine != null) { - args "--local-engine", localEngine - args "--local-engine-src-path", localEngineSrcPath - } - args "build", "aot" - args "--suppress-analytics" - args "--quiet" - args "--target", targetPath - args "--output-dir", "${intermediateDir}" - args "--target-platform", "${targetPlatform}" - if (extraFrontEndOptions != null) { - args "--extra-front-end-options", "${extraFrontEndOptions}" - } - if (extraGenSnapshotOptions != null) { - args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" - } - args "--${buildMode}" - } - } - project.exec { executable flutterExecutable.absolutePath workingDir sourceDir - if (localEngine != null) { args "--local-engine", localEngine args "--local-engine-src-path", localEngineSrcPath } - args "build", "bundle" - args "--target", targetPath - args "--target-platform", "${targetPlatform}" if (verbose) { args "--verbose" + } else { + args "--quiet" } - if (fileSystemRoots != null) { - for (root in fileSystemRoots) { - args "--filesystem-root", root - } - } - if (fileSystemScheme != null) { - args "--filesystem-scheme", fileSystemScheme - } - if (trackWidgetCreation) { - args "--track-widget-creation" - } - if (compilationTraceFilePath != null) { - args "--compilation-trace-file", compilationTraceFilePath - } - if (createPatch) { - args "--patch" - args "--build-number", project.android.defaultConfig.versionCode - if (buildNumber != null) { - assert buildNumber == project.android.defaultConfig.versionCode - } - } - if (baselineDir != null) { - args "--baseline-dir", baselineDir - } + args "assemble" + args "--depfile", "${intermediateDir}/flutter_build.d" + args "--output", "${intermediateDir}" + args "-dTargetFile=${targetPath}" + args "-dTargetPlatform=${targetPlatform}" + args "-dBuildMode=${buildMode}" if (extraFrontEndOptions != null) { - args "--extra-front-end-options", "${extraFrontEndOptions}" + args "-dExtraFrontEndOptions=${extraFrontEndOptions}" } if (extraGenSnapshotOptions != null) { - args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" - } - if (buildMode == "release" || buildMode == "profile") { - args "--precompiled" - } else { - args "--depfile", "${intermediateDir}/snapshot_blob.bin.d" - } - args "--asset-dir", "${intermediateDir}/flutter_assets" - if (buildMode == "debug") { - args "--debug" + args "-dExtraGenSnapshotOptions=${extraGenSnapshotOptions}" } if (buildMode == "profile") { - args "--profile" + args "profile_android_application" } if (buildMode == "release") { - args "--release" + args "release_android_application" + } + if (buildMode == "debug") { + args "debug_android_application" } } } @@ -859,21 +774,17 @@ class FlutterTask extends BaseFlutterTask { } FileCollection readDependencies(File dependenciesFile) { - if (dependenciesFile.exists()) { - try { - // Dependencies file has Makefile syntax: - // : - String depText = dependenciesFile.text - // So we split list of files by non-escaped(by backslash) space, - def matcher = depText.split(': ')[1] =~ /(\\ |[^\s])+/ - // then we replace all escaped spaces with regular spaces - def depList = matcher.collect{it[0].replaceAll("\\\\ ", " ")} - return project.files(depList) - } catch (Exception e) { - logger.error("Error reading dependency file ${dependenciesFile}: ${e}") - } - } - return project.files() + if (dependenciesFile.exists()) { + // Dependencies file has Makefile syntax: + // : + String depText = dependenciesFile.text + // So we split list of files by non-escaped(by backslash) space, + def matcher = depText.split(': ')[1] =~ /(\\ |[^\s])+/ + // then we replace all escaped spaces with regular spaces + def depList = matcher.collect{it[0].replaceAll("\\\\ ", " ")} + return project.files(depList) + } + return project.files(); } @InputFiles @@ -882,23 +793,7 @@ class FlutterTask extends BaseFlutterTask { 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'] - ) + return sources + project.files('pubspec.yaml') } @TaskAction diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart new file mode 100644 index 00000000000..98336d4034f --- /dev/null +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -0,0 +1,124 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../../artifacts.dart'; +import '../../base/file_system.dart'; +import '../../build_info.dart'; +import '../../globals.dart'; +import '../build_system.dart'; +import '../depfile.dart'; +import '../exceptions.dart'; +import 'assets.dart'; +import 'dart.dart'; + +/// Prepares the asset bundle in the format expected by flutter.gradle. +/// +/// The vm_snapshot_data, isolate_snapshot_data, and kernel_blob.bin are +/// expected to be in the root output directory. +/// +/// All assets and manifests are included from flutter_assets/**. +abstract class AndroidAssetBundle extends Target { + const AndroidAssetBundle(); + + @override + List get inputs => const [ + Source.pattern('{BUILD_DIR}/app.dill'), + Source.depfile('flutter_assets.d'), + ]; + + @override + List get outputs => const [ + Source.depfile('flutter_assets.d'), + ]; + + @override + Future build(Environment environment) async { + if (environment.defines[kBuildMode] == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); + final Directory outputDirectory = environment.outputDir + .childDirectory('flutter_assets') + ..createSync(recursive: true); + + // Only copy the prebuilt runtimes and kernel blob in debug mode. + if (buildMode == BuildMode.debug) { + final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug); + final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug); + environment.buildDir.childFile('app.dill') + .copySync(outputDirectory.childFile('kernel_blob.bin').path); + fs.file(vmSnapshotData) + .copySync(outputDirectory.childFile('vm_snapshot_data').path); + fs.file(isolateSnapshotData) + .copySync(outputDirectory.childFile('isolate_snapshot_data').path); + } + final Depfile assetDepfile = await copyAssets(environment, outputDirectory); + assetDepfile.writeToFile(environment.buildDir.childFile('flutter_assets.d')); + } + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; +} + +/// An implementation of [AndroidAssetBundle] that includes dependencies on vm +/// and isolate data. +class DebugAndroidApplication extends AndroidAssetBundle { + const DebugAndroidApplication(); + + @override + String get name => 'debug_android_application'; + + @override + List get inputs => [ + ...super.inputs, + const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug), + const Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug), + ]; + + @override + List get outputs => [ + ...super.outputs, + const Source.pattern('{OUTPUT_DIR}/vm_snapshot_data'), + const Source.pattern('{OUTPUT_DIR}/isolate_snapshot_data'), + const Source.pattern('{OUTPUT_DIR}/kernel_blob.bin'), + ]; +} + +/// An implementation of [AndroidAssetBundle] that only includes assets. +class AotAndroidAssetBundle extends AndroidAssetBundle { + const AotAndroidAssetBundle(); + + @override + String get name => 'aot_android_asset_bundle'; +} + +/// Build a profile android application's Dart artifacts. +class ProfileAndroidApplication extends CopyFlutterAotBundle { + const ProfileAndroidApplication(); + + @override + String get name => 'profile_android_application'; + + @override + List get dependencies => const [ + AotElfProfile(), + AotAndroidAssetBundle(), + ]; +} + +/// Build a release android application's Dart artifacts. +class ReleaseAndroidApplication extends CopyFlutterAotBundle { + const ReleaseAndroidApplication(); + + @override + String get name => 'release_android_application'; + + @override + List get dependencies => const [ + AotElfRelease(), + AotAndroidAssetBundle(), + ]; +} diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart index 4a049722372..8f00e825fbe 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart @@ -29,6 +29,27 @@ const String kBitcodeFlag = 'EnableBitcode'; /// Whether to enable or disable track widget creation. const String kTrackWidgetCreation = 'TrackWidgetCreation'; +/// Additional configuration passed to the dart front end. +/// +/// This is expected to be a comma separated list of strings. +const String kExtraFrontEndOptions = 'ExtraFrontEndOptions'; + +/// Additional configuration passed to gen_snapshot. +/// +/// This is expected to be a comma separated list of strings. +const String kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions'; + +/// Alternative scheme for file URIs. +/// +/// May be used along with [kFileSystemRoots] to support a multiroot +/// filesystem. +const String kFileSystemScheme = 'FileSystemScheme'; + +/// Additional filesystem roots. +/// +/// If provided, must be used along with [kFileSystemScheme]. +const String kFileSystemRoots = 'FileSystemRoots'; + /// The define to control what iOS architectures are built for. /// /// This is expected to be a comma-separated list of architectures. If not @@ -156,6 +177,13 @@ class KernelSnapshot extends Target { final bool trackWidgetCreation = environment.defines[kTrackWidgetCreation] != 'false'; final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); + // This configuration is all optional. + final List extraFrontEndOptions = [ + ...?environment.defines[kExtraFrontEndOptions]?.split(',') + ]; + final List fileSystemRoots = environment.defines[kFileSystemRoots]?.split(','); + final String fileSystemScheme = environment.defines[kFileSystemScheme]; + TargetModel targetModel = TargetModel.flutter; if (targetPlatform == TargetPlatform.fuchsia_x64 || targetPlatform == TargetPlatform.fuchsia_arm64) { @@ -177,6 +205,9 @@ class KernelSnapshot extends Target { linkPlatformKernelIn: buildMode.isPrecompiled, mainPath: targetFileAbsolute, depFilePath: environment.buildDir.childFile('kernel_snapshot.d').path, + extraFrontEndOptions: extraFrontEndOptions, + fileSystemRoots: fileSystemRoots, + fileSystemScheme: fileSystemScheme, ); if (output == null || output.errorCount != 0) { throw Exception('Errors during snapshot creation: $output'); @@ -301,11 +332,12 @@ abstract class CopyFlutterAotBundle extends Target { } } +// This is a one-off rule for implementing build aot in terms of assemble. class ProfileCopyFlutterAotBundle extends CopyFlutterAotBundle { const ProfileCopyFlutterAotBundle(); @override - String get name => 'profile_copy_aot_flutter_bundle'; + String get name => 'profile_android_flutter_bundle'; @override List get dependencies => const [ @@ -313,11 +345,12 @@ class ProfileCopyFlutterAotBundle extends CopyFlutterAotBundle { ]; } +// This is a one-off rule for implementing build aot in terms of assemble. class ReleaseCopyFlutterAotBundle extends CopyFlutterAotBundle { const ReleaseCopyFlutterAotBundle(); @override - String get name => 'release_copy_aot_flutter_bundle'; + String get name => 'release_android_flutter_bundle'; @override List get dependencies => const [ diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index ee1e72d8612..ff92afca76e 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -7,6 +7,8 @@ import 'package:meta/meta.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../build_system/build_system.dart'; +import '../build_system/depfile.dart'; +import '../build_system/targets/android.dart'; import '../build_system/targets/assets.dart'; import '../build_system/targets/dart.dart'; import '../build_system/targets/ios.dart'; @@ -16,6 +18,7 @@ import '../build_system/targets/web.dart'; import '../build_system/targets/windows.dart'; import '../globals.dart'; import '../project.dart'; +import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; /// All currently implemented targets. @@ -33,6 +36,13 @@ const List _kDefaultTargets = [ ReleaseMacOSBundleFlutterAssets(), DebugBundleLinuxAssets(), WebReleaseBundle(), + DebugAndroidApplication(), + ProfileAndroidApplication(), + ReleaseAndroidApplication(), + // These are one-off rules for bundle and aot compat + ReleaseCopyFlutterAotBundle(), + ProfileCopyFlutterAotBundle(), + CopyFlutterBundle(), ]; /// Assemble provides a low level API to interact with the flutter tool build @@ -44,6 +54,9 @@ class AssembleCommand extends FlutterCommand { abbr: 'd', help: 'Allows passing configuration to a target with --define=target=key=value.', ); + argParser.addOption('depfile', help: 'A file path where a depfile will be written. ' + 'This contains all build inputs and outputs in a make style syntax' + ); argParser.addOption('build-inputs', help: 'A file path where a newline ' 'separated file containing all inputs used will be written after a build.' ' This file is not included as a build input or output. This file is not' @@ -68,6 +81,19 @@ class AssembleCommand extends FlutterCommand { @override String get name => 'assemble'; + @override + Future> get usageValues async { + final FlutterProject futterProject = FlutterProject.current(); + if (futterProject == null) { + return const {}; + } + final Environment localEnvironment = environment; + return { + CustomDimensions.commandBuildBundleTargetPlatform: localEnvironment.defines['TargetPlatform'], + CustomDimensions.commandBuildBundleIsModule: '${futterProject.isModule}', + }; + } + /// The target we are building. Target get target { if (argResults.rest.isEmpty) { @@ -125,18 +151,22 @@ class AssembleCommand extends FlutterCommand { )); if (!result.success) { for (MapEntry data in result.exceptions.entries) { - printError('Target ${data.key} failed: ${data.value.exception}'); - printError('${data.value.exception}'); + printError('Target ${data.key} failed: ${data.value.exception}', stackTrace: data.value.stackTrace); } throwToolExit('build failed.'); } - printStatus('build succeeded.'); + printTrace('build succeeded.'); if (argResults.wasParsed('build-inputs')) { writeListIfChanged(result.inputFiles, argResults['build-inputs']); } if (argResults.wasParsed('build-outputs')) { writeListIfChanged(result.outputFiles, argResults['build-outputs']); } + if (argResults.wasParsed('depfile')) { + final File depfileFile = fs.file(argResults['depfile']); + final Depfile depfile = Depfile(result.inputFiles, result.outputFiles); + depfile.writeToFile(fs.file(depfileFile)); + } return null; } } diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart index 3a3043d0a57..73bbb17b8f3 100644 --- a/packages/flutter_tools/lib/src/commands/build_aar.dart +++ b/packages/flutter_tools/lib/src/commands/build_aar.dart @@ -7,6 +7,7 @@ import 'dart:async'; import '../android/android_builder.dart'; import '../base/os.dart'; import '../build_info.dart'; +import '../cache.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; @@ -33,6 +34,12 @@ class BuildAarCommand extends BuildSubCommand { @override final String name = 'aar'; + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.universal, + }; + @override Future> get usageValues async { final Map usage = {}; diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index dab9188cbe2..8b000e601e5 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -7,6 +7,7 @@ import 'dart:async'; import '../android/android_builder.dart'; import '../base/terminal.dart'; import '../build_info.dart'; +import '../cache.dart'; import '../globals.dart'; import '../project.dart'; import '../reporting/reporting.dart'; @@ -41,6 +42,12 @@ class BuildApkCommand extends BuildSubCommand { @override final String name = 'apk'; + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.universal, + }; + @override final String description = 'Build an Android APK file from your app.\n\n' 'This command can build debug and release versions of your application. \'debug\' builds support ' diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index 874f94e6d44..802c1808dc6 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -6,6 +6,7 @@ import 'dart:async'; import '../android/android_builder.dart'; import '../build_info.dart'; +import '../cache.dart'; import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; @@ -34,6 +35,12 @@ class BuildAppBundleCommand extends BuildSubCommand { @override final String name = 'appbundle'; + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.universal, + }; + @override final String description = 'Build an Android App Bundle file from your app.\n\n' diff --git a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart index 0e013aa04b1..5d6e0c141f4 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart @@ -39,7 +39,7 @@ void main() { await commandRunner.run(['assemble', '-o Output', 'debug_macos_bundle_flutter_assets']); final BufferLogger bufferLogger = logger; - expect(bufferLogger.statusText.trim(), 'build succeeded.'); + expect(bufferLogger.traceText, contains('build succeeded.')); })); test('Throws ToolExit if not provided with output', () => testbed.run(() async { diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart new file mode 100644 index 00000000000..b67dd8ca644 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/build_system/targets/android_test.dart @@ -0,0 +1,87 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/build_system/targets/android.dart'; +import 'package:flutter_tools/src/build_system/targets/dart.dart'; +import 'package:flutter_tools/src/cache.dart'; + +import '../../../src/common.dart'; +import '../../../src/testbed.dart'; + +void main() { + final Testbed testbed = Testbed(overrides: { + Cache: () => FakeCache(), + }); + + testbed.test('debug bundle contains expected resources', () async { + final Environment environment = Environment( + outputDir: fs.directory('out')..createSync(), + projectDir: fs.currentDirectory, + buildDir: fs.currentDirectory, + defines: { + kBuildMode: 'debug', + } + ); + environment.buildDir.createSync(recursive: true); + + // create pre-requisites. + environment.buildDir.childFile('app.dill') + ..writeAsStringSync('abcd'); + final Directory hostDirectory = fs.currentDirectory + .childDirectory(getNameForHostPlatform(getCurrentHostPlatform())) + ..createSync(recursive: true); + hostDirectory.childFile('vm_isolate_snapshot.bin').createSync(); + hostDirectory.childFile('isolate_snapshot.bin').createSync(); + + + await const DebugAndroidApplication().build(environment); + + expect(fs.file(fs.path.join('out', 'flutter_assets', 'isolate_snapshot_data')).existsSync(), true); + expect(fs.file(fs.path.join('out', 'flutter_assets', 'vm_snapshot_data')).existsSync(), true); + expect(fs.file(fs.path.join('out', 'flutter_assets', 'kernel_blob.bin')).existsSync(), true); + }); + + testbed.test('profile bundle contains expected resources', () async { + final Environment environment = Environment( + outputDir: fs.directory('out')..createSync(), + projectDir: fs.currentDirectory, + buildDir: fs.currentDirectory, + defines: { + kBuildMode: 'profile', + } + ); + environment.buildDir.createSync(recursive: true); + + // create pre-requisites. + environment.buildDir.childFile('app.so') + ..writeAsStringSync('abcd'); + + await const ProfileAndroidApplication().build(environment); + + expect(fs.file(fs.path.join('out', 'app.so')).existsSync(), true); + }); + + testbed.test('release bundle contains expected resources', () async { + final Environment environment = Environment( + outputDir: fs.directory('out')..createSync(), + projectDir: fs.currentDirectory, + buildDir: fs.currentDirectory, + defines: { + kBuildMode: 'release', + } + ); + environment.buildDir.createSync(recursive: true); + + // create pre-requisites. + environment.buildDir.childFile('app.so') + ..writeAsStringSync('abcd'); + + await const ReleaseAndroidApplication().build(environment); + + expect(fs.file(fs.path.join('out', 'app.so')).existsSync(), true); + }); +} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart index 076b6369829..5e136fb0834 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/dart_test.dart @@ -159,6 +159,9 @@ flutter_tools:lib/'''); depFilePath: anyNamed('depFilePath'), packagesPath: anyNamed('packagesPath'), mainPath: anyNamed('mainPath'), + extraFrontEndOptions: anyNamed('extraFrontEndOptions'), + fileSystemRoots: anyNamed('fileSystemRoots'), + fileSystemScheme: anyNamed('fileSystemScheme'), linkPlatformKernelIn: anyNamed('linkPlatformKernelIn'), )).thenAnswer((Invocation _) async { return const CompilerOutput('example', 0, []); @@ -184,6 +187,9 @@ flutter_tools:lib/'''); depFilePath: anyNamed('depFilePath'), packagesPath: anyNamed('packagesPath'), mainPath: anyNamed('mainPath'), + extraFrontEndOptions: anyNamed('extraFrontEndOptions'), + fileSystemRoots: anyNamed('fileSystemRoots'), + fileSystemScheme: anyNamed('fileSystemScheme'), linkPlatformKernelIn: false, )).thenAnswer((Invocation _) async { return const CompilerOutput('example', 0, []); @@ -211,6 +217,9 @@ flutter_tools:lib/'''); depFilePath: anyNamed('depFilePath'), packagesPath: anyNamed('packagesPath'), mainPath: anyNamed('mainPath'), + extraFrontEndOptions: anyNamed('extraFrontEndOptions'), + fileSystemRoots: anyNamed('fileSystemRoots'), + fileSystemScheme: anyNamed('fileSystemScheme'), linkPlatformKernelIn: false, )).thenAnswer((Invocation _) async { return const CompilerOutput('example', 0, []); diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart index 98126a8fa76..9d684b54302 100644 --- a/packages/flutter_tools/test/src/testbed.dart +++ b/packages/flutter_tools/test/src/testbed.dart @@ -22,8 +22,10 @@ import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/version.dart'; +import 'package:meta/meta.dart'; import 'package:process/process.dart'; +import 'common.dart' as tester; import 'context.dart'; import 'fake_process_manager.dart'; import 'throwing_pub.dart'; @@ -86,6 +88,16 @@ class Testbed { final FutureOr Function() _setup; final Map _overrides; + /// Runs the `test` within a tool zone. + /// + /// Unlike [run], this sets up a test group on its own. + @isTest + void test(String name, FutureOr Function() test, {Map overrides}) { + tester.test(name, () { + return run(test, overrides: overrides); + }); + } + /// Runs `test` within a tool zone. /// /// `overrides` may be used to provide new context values for the single test @@ -801,3 +813,95 @@ class DelegateLogger implements Logger { @override bool get supportsColor => delegate.supportsColor; } + +/// An implementation of the Cache which does not download or require locking. +class FakeCache implements Cache { + @override + bool includeAllPlatforms; + + @override + bool useUnsignedMacBinaries; + + @override + Future areRemoteArtifactsAvailable({String engineVersion, bool includeAllPlatforms = true}) async { + return true; + } + + @override + String get dartSdkVersion => null; + + @override + MapEntry get dyLdLibEntry => null; + + @override + String get engineRevision => null; + + @override + Directory getArtifactDirectory(String name) { + return fs.currentDirectory; + } + + @override + Directory getCacheArtifacts() { + return fs.currentDirectory; + } + + @override + Directory getCacheDir(String name) { + return fs.currentDirectory; + } + + @override + Directory getDownloadDir() { + return fs.currentDirectory; + } + + @override + Directory getRoot() { + return fs.currentDirectory; + } + + @override + File getStampFileFor(String artifactName) { + throw UnsupportedError('Not supported in the fake Cache'); + } + + @override + String getStampFor(String artifactName) { + throw UnsupportedError('Not supported in the fake Cache'); + } + + @override + Future getThirdPartyFile(String urlStr, String serviceName) { + throw UnsupportedError('Not supported in the fake Cache'); + } + + @override + String getVersionFor(String artifactName) { + throw UnsupportedError('Not supported in the fake Cache'); + } + + @override + Directory getWebSdkDirectory() { + return fs.currentDirectory; + } + + @override + bool isOlderThanToolsStamp(FileSystemEntity entity) { + return false; + } + + @override + bool isUpToDate() { + return true; + } + + @override + void setStampFor(String artifactName, String version) { + throw UnsupportedError('Not supported in the fake Cache'); + } + + @override + Future updateAll(Set requiredArtifacts) async { + } +}