Unverified Commit 978fada3 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

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
parent c1d029b8
......@@ -218,11 +218,11 @@ Future<void> 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"'
);
}
......
......@@ -396,7 +396,7 @@ Future<ProcessResult> _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();
......
// 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<Source> get inputs => const <Source>[
Source.pattern('{BUILD_DIR}/app.dill'),
Source.depfile('flutter_assets.d'),
];
@override
List<Source> get outputs => const <Source>[
Source.depfile('flutter_assets.d'),
];
@override
Future<void> 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<Target> get dependencies => const <Target>[
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<Source> get inputs => <Source>[
...super.inputs,
const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
const Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
];
@override
List<Source> get outputs => <Source>[
...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<Target> get dependencies => const <Target>[
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<Target> get dependencies => const <Target>[
AotElfRelease(),
AotAndroidAssetBundle(),
];
}
......@@ -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<String> extraFrontEndOptions = <String>[
...?environment.defines[kExtraFrontEndOptions]?.split(',')
];
final List<String> 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<Target> get dependencies => const <Target>[
......@@ -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<Target> get dependencies => const <Target>[
......
......@@ -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<Target> _kDefaultTargets = <Target>[
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<Map<CustomDimensions, String>> get usageValues async {
final FlutterProject futterProject = FlutterProject.current();
if (futterProject == null) {
return const <CustomDimensions, String>{};
}
final Environment localEnvironment = environment;
return <CustomDimensions, String>{
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<String, ExceptionMeasurement> 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;
}
}
......
......@@ -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<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.androidGenSnapshot,
DevelopmentArtifact.universal,
};
@override
Future<Map<CustomDimensions, String>> get usageValues async {
final Map<CustomDimensions, String> usage = <CustomDimensions, String>{};
......
......@@ -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<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
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 '
......
......@@ -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<Set<DevelopmentArtifact>> get requiredArtifacts async => <DevelopmentArtifact>{
DevelopmentArtifact.androidGenSnapshot,
DevelopmentArtifact.universal,
};
@override
final String description =
'Build an Android App Bundle file from your app.\n\n'
......
......@@ -39,7 +39,7 @@ void main() {
await commandRunner.run(<String>['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 {
......
// 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: <Type, Generator>{
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: <String, String>{
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: <String, String>{
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: <String, String>{
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);
});
}
......@@ -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, <Uri>[]);
......@@ -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, <Uri>[]);
......@@ -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, <Uri>[]);
......
......@@ -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<void> Function() _setup;
final Map<Type, Generator> _overrides;
/// Runs the `test` within a tool zone.
///
/// Unlike [run], this sets up a test group on its own.
@isTest
void test<T>(String name, FutureOr<T> Function() test, {Map<Type, Generator> 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<bool> areRemoteArtifactsAvailable({String engineVersion, bool includeAllPlatforms = true}) async {
return true;
}
@override
String get dartSdkVersion => null;
@override
MapEntry<String, String> 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<String> 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<void> updateAll(Set<DevelopmentArtifact> requiredArtifacts) async {
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment