Re-Re-land Implement flutter build bundle with assemble (#41230)
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
index d50bab8..f3b4572 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
@@ -139,7 +139,7 @@
await runProjectTest((FlutterProject project) async {
section('gradlew assembleDebug');
await project.runGradleTask('assembleDebug');
- final String errorMessage = validateSnapshotDependency(project, 'build/app.dill');
+ final String errorMessage = validateSnapshotDependency(project, 'kernel_blob.bin');
if (errorMessage != null) {
throw TaskResult.failure(errorMessage);
}
diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart
index 4695573..e22b265 100644
--- a/dev/devicelab/lib/framework/apk_utils.dart
+++ b/dev/devicelab/lib/framework/apk_utils.dart
@@ -382,25 +382,29 @@
class _Dependencies {
_Dependencies(String depfilePath) {
- final RegExp _separatorExpr = RegExp(r'([^\\]) ');
- final RegExp _escapeExpr = RegExp(r'\\(.)');
-
// Depfile format:
// outfile1 outfile2 : file1.dart file2.dart file3.dart file\ 4.dart
final String contents = File(depfilePath).readAsStringSync();
- final List<String> colonSeparated = contents.split(': ');
- target = colonSeparated[0].trim();
- dependencies = colonSeparated[1]
- // Put every file on right-hand side on the separate line
+ final List<String> colonSeparated = contents.split(':');
+ targets = _processList(colonSeparated[0].trim());
+ dependencies = _processList(colonSeparated[1].trim());
+ }
+
+ final RegExp _separatorExpr = RegExp(r'([^\\]) ');
+ final RegExp _escapeExpr = RegExp(r'\\(.)');
+
+ Set<String> _processList(String rawText) {
+ return rawText
+ // Put every file on right-hand side on the separate line
.replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
.split('\n')
- // Expand escape sequences, so that '\ ', for example,ß becomes ' '
+ // Expand escape sequences, so that '\ ', for example,ß becomes ' '
.map<String>((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
.where((String path) => path.isNotEmpty)
.toSet();
}
- String target;
+ Set<String> targets;
Set<String> dependencies;
}
@@ -409,6 +413,6 @@
final _Dependencies deps = _Dependencies(
path.join(project.rootPath, 'build', 'app', 'intermediates',
'flutter', 'debug', 'android-arm', 'snapshot_blob.bin.d'));
- return deps.target == expectedTarget ? null :
- 'Dependency file should have $expectedTarget as target. Instead has ${deps.target}';
+ return deps.targets.any((String target) => target.contains(expectedTarget)) ? null :
+ 'Dependency file should have $expectedTarget as target. Instead has ${deps.targets}';
}
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 157410e..8c9591d 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -92,6 +92,9 @@
case Artifact.frontendServerSnapshotForEngineDartSdk:
return 'frontend_server.dart.snapshot';
case Artifact.engineDartBinary:
+ if (platform == TargetPlatform.windows_x64) {
+ return 'dart.exe';
+ }
return 'dart';
case Artifact.dart2jsSnapshot:
return 'dart2js.dart.snapshot';
@@ -265,7 +268,7 @@
case Artifact.engineDartSdkPath:
return dartSdkPath;
case Artifact.engineDartBinary:
- return fs.path.join(dartSdkPath, 'bin', _artifactToFileName(artifact));
+ return fs.path.join(dartSdkPath, 'bin', _artifactToFileName(artifact, platform));
case Artifact.platformKernelDill:
return fs.path.join(_getFlutterPatchedSdkPath(mode), _artifactToFileName(artifact));
case Artifact.platformLibrariesJson:
diff --git a/packages/flutter_tools/lib/src/build_system/build_system.dart b/packages/flutter_tools/lib/src/build_system/build_system.dart
index bcc0d1b..21937b8 100644
--- a/packages/flutter_tools/lib/src/build_system/build_system.dart
+++ b/packages/flutter_tools/lib/src/build_system/build_system.dart
@@ -10,6 +10,7 @@
import 'package:meta/meta.dart';
import 'package:pool/pool.dart';
+import '../base/context.dart';
import '../base/file_system.dart';
import '../base/platform.dart';
import '../cache.dart';
@@ -21,6 +22,9 @@
export 'source.dart';
+/// The [BuildSystem] instance.
+BuildSystem get buildSystem => context.get<BuildSystem>();
+
/// Configuration for the build system itself.
class BuildSystemConfig {
/// Create a new [BuildSystemConfig].
@@ -497,14 +501,14 @@
}
if (canSkip) {
skipped = true;
- printStatus('Skipping target: ${node.target.name}');
+ printTrace('Skipping target: ${node.target.name}');
for (File output in node.outputs) {
outputFiles[output.path] = output;
}
} else {
- printStatus('${node.target.name}: Starting due to ${node.invalidatedReasons}');
+ printTrace('${node.target.name}: Starting due to ${node.invalidatedReasons}');
await node.target.build(environment);
- printStatus('${node.target.name}: Complete');
+ printTrace('${node.target.name}: Complete');
// Update hashes for output files.
await fileCache.hashFiles(node.outputs);
@@ -515,7 +519,10 @@
// Delete outputs from previous stages that are no longer a part of the build.
for (String previousOutput in node.previousOutputs) {
if (!outputFiles.containsKey(previousOutput)) {
- fs.file(previousOutput).deleteSync();
+ final File previousFile = fs.file(previousOutput);
+ if (previousFile.existsSync()) {
+ previousFile.deleteSync();
+ }
}
}
}
diff --git a/packages/flutter_tools/lib/src/build_system/file_hash_store.dart b/packages/flutter_tools/lib/src/build_system/file_hash_store.dart
index 0df2a3e..5673026 100644
--- a/packages/flutter_tools/lib/src/build_system/file_hash_store.dart
+++ b/packages/flutter_tools/lib/src/build_system/file_hash_store.dart
@@ -103,7 +103,7 @@
FileStorage fileStorage;
try {
fileStorage = FileStorage.fromBuffer(data);
- } on FormatException {
+ } catch (err) {
printTrace('Filestorage format changed');
_cacheFile.deleteSync();
return;
@@ -123,7 +123,7 @@
printTrace('Persisting file store');
final File file = _cacheFile;
if (!file.existsSync()) {
- file.createSync();
+ file.createSync(recursive: true);
}
final List<FileHash> fileHashes = <FileHash>[];
for (MapEntry<String, String> entry in currentHashes.entries) {
diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
index 3e19118..7eb1cdf 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart
@@ -48,6 +48,43 @@
}
}
+/// A specific asset behavior for building bundles.
+class AssetOutputBehavior extends SourceBehavior {
+ const AssetOutputBehavior();
+
+ @override
+ List<File> inputs(Environment environment) {
+ final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+ assetBundle.build(
+ manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
+ packagesPath: environment.projectDir.childFile('.packages').path,
+ );
+ // Filter the file type to remove the files that are generated by this
+ // command as inputs.
+ final List<File> results = <File>[];
+ final Iterable<DevFSFileContent> files = assetBundle.entries.values.whereType<DevFSFileContent>();
+ for (DevFSFileContent devFsContent in files) {
+ results.add(fs.file(devFsContent.file.path));
+ }
+ return results;
+ }
+
+ @override
+ List<File> outputs(Environment environment) {
+ final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+ assetBundle.build(
+ manifestPath: environment.projectDir.childFile('pubspec.yaml').path,
+ packagesPath: environment.projectDir.childFile('.packages').path,
+ );
+ final List<File> results = <File>[];
+ for (String key in assetBundle.entries.keys) {
+ final File file = fs.file(fs.path.join(environment.outputDir.path, key));
+ results.add(file);
+ }
+ return results;
+ }
+}
+
/// Copy the assets defined in the flutter manifest into a build directory.
class CopyAssets extends Target {
const CopyAssets();
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 f9b450a..249da6f 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -2,17 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:pool/pool.dart';
+
import '../../artifacts.dart';
+import '../../asset.dart';
import '../../base/build.dart';
import '../../base/file_system.dart';
import '../../base/platform.dart';
import '../../build_info.dart';
import '../../compile.dart';
import '../../dart/package_map.dart';
+import '../../devfs.dart';
import '../../globals.dart';
import '../../project.dart';
import '../build_system.dart';
import '../exceptions.dart';
+import 'assets.dart';
/// The define to pass a [BuildMode].
const String kBuildMode= 'BuildMode';
@@ -55,6 +60,111 @@
return dartFiles;
}
+/// Copies the prebuilt flutter bundle.
+// This is a one-off rule for implementing build bundle in terms of assemble.
+class CopyFlutterBundle extends Target {
+ const CopyFlutterBundle();
+
+ @override
+ String get name => 'copy_flutter_bundle';
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
+ Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
+ Source.pattern('{BUILD_DIR}/app.dill'),
+ Source.behavior(AssetOutputBehavior())
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{OUTPUT_DIR}/vm_snapshot_data'),
+ Source.pattern('{OUTPUT_DIR}/isolate_snapshot_data'),
+ Source.pattern('{OUTPUT_DIR}/kernel_blob.bin'),
+ Source.pattern('{OUTPUT_DIR}/AssetManifest.json'),
+ Source.pattern('{OUTPUT_DIR}/FontManifest.json'),
+ Source.pattern('{OUTPUT_DIR}/LICENSE'),
+ Source.behavior(AssetOutputBehavior())
+ ];
+
+ @override
+ Future<void> build(Environment environment) async {
+ if (environment.defines[kBuildMode] == null) {
+ throw MissingDefineException(kBuildMode, 'copy_flutter_bundle');
+ }
+ final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
+
+ // We're not smart enough to only remove assets that are removed. If
+ // anything changes blow away the whole directory.
+ if (environment.outputDir.existsSync()) {
+ environment.outputDir.deleteSync(recursive: true);
+ }
+ environment.outputDir.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(environment.outputDir.childFile('kernel_blob.bin').path);
+ fs.file(vmSnapshotData)
+ .copySync(environment.outputDir.childFile('vm_snapshot_data').path);
+ fs.file(isolateSnapshotData)
+ .copySync(environment.outputDir.childFile('isolate_snapshot_data').path);
+ }
+
+ final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
+ await assetBundle.build();
+ final Pool pool = Pool(64);
+ await Future.wait<void>(
+ assetBundle.entries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
+ final PoolResource resource = await pool.request();
+ try {
+ final File file = fs.file(fs.path.join(environment.outputDir.path, entry.key));
+ file.parent.createSync(recursive: true);
+ final DevFSContent content = entry.value;
+ if (content is DevFSFileContent && content.file is File) {
+ await (content.file as File).copy(file.path);
+ } else {
+ await file.writeAsBytes(await entry.value.contentsAsBytes());
+ }
+ } finally {
+ resource.release();
+ }
+ }));
+ }
+
+ @override
+ List<Target> get dependencies => const <Target>[
+ KernelSnapshot(),
+ ];
+}
+
+/// Copies the prebuilt flutter bundle for release mode.
+class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
+ const ReleaseCopyFlutterBundle();
+
+ @override
+ String get name => 'release_flutter_bundle';
+
+ @override
+ List<Source> get inputs => const <Source>[
+ Source.behavior(AssetOutputBehavior())
+ ];
+
+ @override
+ List<Source> get outputs => const <Source>[
+ Source.pattern('{OUTPUT_DIR}/AssetManifest.json'),
+ Source.pattern('{OUTPUT_DIR}/FontManifest.json'),
+ Source.pattern('{OUTPUT_DIR}/LICENSE'),
+ Source.behavior(AssetOutputBehavior())
+ ];
+
+ @override
+ List<Target> get dependencies => const <Target>[];
+}
+
+
/// Generate a snapshot of the dart code used in the program.
class KernelSnapshot extends Target {
const KernelSnapshot();
@@ -91,8 +201,7 @@
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final String targetFile = environment.defines[kTargetFile] ?? fs.path.join('lib', 'main.dart');
final String packagesPath = environment.projectDir.childFile('.packages').path;
- final PackageUriMapper packageUriMapper = PackageUriMapper(targetFile,
- packagesPath, null, null);
+ final String targetFileAbsolute = fs.file(targetFile).absolute.path;
final CompilerOutput output = await compiler.compile(
sdkRoot: artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
@@ -101,10 +210,9 @@
targetModel: TargetModel.flutter,
targetProductVm: buildMode == BuildMode.release,
outputFilePath: environment.buildDir.childFile('app.dill').path,
- depFilePath: null,
packagesPath: packagesPath,
linkPlatformKernelIn: buildMode == BuildMode.release,
- mainPath: packageUriMapper.map(targetFile)?.toString() ?? targetFile,
+ mainPath: targetFileAbsolute,
);
if (output.errorCount != 0) {
throw Exception('Errors during snapshot creation: $output');
diff --git a/packages/flutter_tools/lib/src/bundle.dart b/packages/flutter_tools/lib/src/bundle.dart
index cd5abb8..5130420 100644
--- a/packages/flutter_tools/lib/src/bundle.dart
+++ b/packages/flutter_tools/lib/src/bundle.dart
@@ -11,7 +11,10 @@
import 'asset.dart';
import 'base/common.dart';
import 'base/file_system.dart';
+import 'base/platform.dart';
import 'build_info.dart';
+import 'build_system/build_system.dart';
+import 'build_system/targets/dart.dart';
import 'compile.dart';
import 'dart/package_map.dart';
import 'devfs.dart';
@@ -69,6 +72,7 @@
List<String> extraGenSnapshotOptions = const <String>[],
List<String> fileSystemRoots,
String fileSystemScheme,
+ bool shouldBuildWithAssemble = false,
}) async {
mainPath ??= defaultMainPath;
depfilePath ??= defaultDepfilePath;
@@ -77,6 +81,18 @@
applicationKernelFilePath ??= getDefaultApplicationKernelPath(trackWidgetCreation: trackWidgetCreation);
final FlutterProject flutterProject = FlutterProject.current();
+ if (shouldBuildWithAssemble) {
+ await buildWithAssemble(
+ buildMode: buildMode ?? BuildMode.debug,
+ targetPlatform: platform,
+ mainPath: mainPath,
+ flutterProject: flutterProject,
+ outputDir: assetDirPath,
+ depfilePath: depfilePath,
+ );
+ return;
+ }
+
DevFSContent kernelContent;
if (!precompiledSnapshot) {
if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) {
@@ -124,6 +140,66 @@
}
}
+/// Build an application bundle using flutter assemble.
+///
+/// This is a temporary shim to migrate the build implementations.
+Future<void> buildWithAssemble({
+ @required FlutterProject flutterProject,
+ @required BuildMode buildMode,
+ @required TargetPlatform targetPlatform,
+ @required String mainPath,
+ @required String outputDir,
+ @required String depfilePath,
+}) async {
+ final Environment environment = Environment(
+ projectDir: flutterProject.directory,
+ outputDir: fs.directory(outputDir),
+ buildDir: flutterProject.dartTool.childDirectory('flutter_build'),
+ defines: <String, String>{
+ kTargetFile: mainPath,
+ kBuildMode: getNameForBuildMode(buildMode),
+ kTargetPlatform: getNameForTargetPlatform(targetPlatform),
+ }
+ );
+ final Target target = buildMode == BuildMode.debug
+ ? const CopyFlutterBundle()
+ : const ReleaseCopyFlutterBundle();
+ final BuildResult result = await buildSystem.build(target, environment);
+
+ if (!result.success) {
+ for (ExceptionMeasurement measurement in result.exceptions.values) {
+ printError(measurement.exception.toString());
+ printError(measurement.stackTrace.toString());
+ }
+ throwToolExit('Failed to build bundle.');
+ }
+
+ // Output depfile format:
+ final StringBuffer buffer = StringBuffer();
+ buffer.write('flutter_bundle');
+ _writeFilesToBuffer(result.outputFiles, buffer);
+ buffer.write(': ');
+ _writeFilesToBuffer(result.inputFiles, buffer);
+
+ final File depfile = fs.file(depfilePath);
+ if (!depfile.parent.existsSync()) {
+ depfile.parent.createSync(recursive: true);
+ }
+ depfile.writeAsStringSync(buffer.toString());
+}
+
+void _writeFilesToBuffer(List<File> files, StringBuffer buffer) {
+ for (File outputFile in files) {
+ if (platform.isWindows) {
+ // Paths in a depfile have to be escaped on windows.
+ final String escapedPath = outputFile.path.replaceAll(r'\', r'\\');
+ buffer.write(' $escapedPath');
+ } else {
+ buffer.write(' ${outputFile.path}');
+ }
+ }
+}
+
Future<AssetBundle> buildAssets({
String manifestPath,
String assetDirPath,
diff --git a/packages/flutter_tools/lib/src/codegen.dart b/packages/flutter_tools/lib/src/codegen.dart
index 8f25e5f..3a39a48 100644
--- a/packages/flutter_tools/lib/src/codegen.dart
+++ b/packages/flutter_tools/lib/src/codegen.dart
@@ -84,8 +84,6 @@
/// Only a subset of the arguments provided to the [KernelCompiler] are
/// supported here. Using the build pipeline implies a fixed multiroot
/// filesystem and requires a pubspec.
-///
-/// This is only safe to use if [experimentalBuildEnabled] is true.
class CodeGeneratingKernelCompiler implements KernelCompiler {
const CodeGeneratingKernelCompiler();
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 48b85d8..8fdb708 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -5,7 +5,6 @@
import 'package:meta/meta.dart';
import '../base/common.dart';
-import '../base/context.dart';
import '../base/file_system.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/assets.dart';
@@ -18,9 +17,6 @@
import '../project.dart';
import '../runner/flutter_command.dart';
-/// The [BuildSystem] instance.
-BuildSystem get buildSystem => context.get<BuildSystem>();
-
/// All currently implemented targets.
const List<Target> _kDefaultTargets = <Target>[
UnpackLinux(),
diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart
index 49eb02b..e06df7c 100644
--- a/packages/flutter_tools/lib/src/commands/build_bundle.dart
+++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart
@@ -131,6 +131,7 @@
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
fileSystemScheme: argResults['filesystem-scheme'],
fileSystemRoots: argResults['filesystem-root'],
+ shouldBuildWithAssemble: true,
);
return null;
}
diff --git a/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart b/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart
index 71b829d..a14bc8b 100644
--- a/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/build_system_test.dart
@@ -235,6 +235,31 @@
expect(environment.buildDir.childFile('foo.out').existsSync(), false);
}));
+ test('Does not crash when filesytem and cache are out of sync', () => testbed.run(() async {
+ final TestTarget testTarget = TestTarget((Environment environment) async {
+ environment.buildDir.childFile('foo.out').createSync();
+ })
+ ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')]
+ ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/foo.out')];
+ fs.file('foo.dart').createSync();
+
+ await buildSystem.build(testTarget, environment);
+
+ expect(environment.buildDir.childFile('foo.out').existsSync(), true);
+ environment.buildDir.childFile('foo.out').deleteSync();
+
+ final TestTarget testTarget2 = TestTarget((Environment environment) async {
+ environment.buildDir.childFile('bar.out').createSync();
+ })
+ ..inputs = const <Source>[Source.pattern('{PROJECT_DIR}/foo.dart')]
+ ..outputs = const <Source>[Source.pattern('{BUILD_DIR}/bar.out')];
+
+ await buildSystem.build(testTarget2, environment);
+
+ expect(environment.buildDir.childFile('bar.out').existsSync(), true);
+ expect(environment.buildDir.childFile('foo.out').existsSync(), false);
+ }));
+
test('reruns build if stamp is corrupted', () => testbed.run(() async {
final TestTarget testTarget = TestTarget((Environment envionment) async {
environment.buildDir.childFile('foo.out').createSync();
diff --git a/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart b/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart
index 7b08bdb..051e55f 100644
--- a/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/filecache_test.dart
@@ -68,4 +68,17 @@
expect(fileStorage.files.single.hash, currentHash);
expect(fileStorage.files.single.path, file.resolveSymbolicLinksSync());
}));
+
+ test('handles persisting with a missing build directory', () => testbed.run(() {
+ final File file = fs.file('foo.dart')
+ ..createSync()
+ ..writeAsStringSync('hello');
+ final FileHashStore fileCache = FileHashStore(environment);
+ fileCache.initialize();
+ environment.buildDir.deleteSync(recursive: true);
+
+ fileCache.hashFiles(<File>[file]);
+ // Does not throw.
+ fileCache.persist();
+ }));
}
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 8f39b9a..b628eba 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
@@ -81,6 +81,7 @@
fs.path.join('bin', 'cache', 'pkg', 'sky_engine', 'sdk_ext',
'vmservice_io.dart'),
fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart'),
+ fs.path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart.exe'),
fs.path.join(engineArtifacts, getNameForHostPlatform(hostPlatform),
'frontend_server.dart.snapshot'),
fs.path.join(engineArtifacts, 'android-arm-profile',
diff --git a/packages/flutter_tools/test/general.shard/bundle_shim_test.dart b/packages/flutter_tools/test/general.shard/bundle_shim_test.dart
new file mode 100644
index 0000000..86bbc4d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/bundle_shim_test.dart
@@ -0,0 +1,67 @@
+// 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/common.dart';
+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/bundle.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:mockito/mockito.dart';
+
+import '../src/common.dart';
+import '../src/testbed.dart';
+
+// Tests for the temporary flutter assemble/bundle shim.
+void main() {
+ Testbed testbed;
+
+ setUp(() {
+ testbed = Testbed(overrides: <Type, Generator>{
+ BuildSystem: () => MockBuildSystem(),
+ });
+ });
+
+ test('Copies assets to expected directory after building', () => testbed.run(() async {
+ when(buildSystem.build(any, any)).thenAnswer((Invocation invocation) async {
+ final Environment environment = invocation.positionalArguments[1];
+ environment.outputDir.childFile('kernel_blob.bin').createSync(recursive: true);
+ environment.outputDir.childFile('isolate_snapshot_data').createSync();
+ environment.outputDir.childFile('vm_snapshot_data').createSync();
+ environment.outputDir.childFile('LICENSE').createSync(recursive: true);
+ return BuildResult(success: true);
+ });
+ await buildWithAssemble(
+ buildMode: BuildMode.debug,
+ flutterProject: FlutterProject.current(),
+ mainPath: fs.path.join('lib', 'main.dart'),
+ outputDir: 'example',
+ targetPlatform: TargetPlatform.ios,
+ depfilePath: 'example.d',
+ );
+ expect(fs.file(fs.path.join('example', 'kernel_blob.bin')).existsSync(), true);
+ expect(fs.file(fs.path.join('example', 'LICENSE')).existsSync(), true);
+ expect(fs.file(fs.path.join('example.d')).existsSync(), true);
+ }));
+
+ test('Handles build system failure', () => testbed.run(() {
+ when(buildSystem.build(any, any)).thenAnswer((Invocation _) async {
+ return BuildResult(
+ success: false,
+ exceptions: <String, ExceptionMeasurement>{},
+ );
+ });
+
+ expect(() => buildWithAssemble(
+ buildMode: BuildMode.debug,
+ flutterProject: FlutterProject.current(),
+ mainPath: 'lib/main.dart',
+ outputDir: 'example',
+ targetPlatform: TargetPlatform.linux_x64,
+ depfilePath: 'example.d',
+ ), throwsA(isInstanceOf<ToolExit>()));
+ }));
+}
+
+class MockBuildSystem extends Mock implements BuildSystem {}