Commit 12f75817 authored by Adam Barth's avatar Adam Barth

Refactor the build command so that it can be used internally

Instead of calling through `pub` to invoke build, this patch refactors the
build command so that it can be called directly.
parent d9af9399
...@@ -6,10 +6,10 @@ import 'dart:async'; ...@@ -6,10 +6,10 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:args/command_runner.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import '../artifacts.dart'; import '../toolchain.dart';
import 'flutter_command.dart';
const String _kSnapshotKey = 'snapshot_blob.bin'; const String _kSnapshotKey = 'snapshot_blob.bin';
const List<String> _kDensities = const ['drawable-xxhdpi']; const List<String> _kDensities = const ['drawable-xxhdpi'];
...@@ -96,74 +96,80 @@ Future<ArchiveFile> _createFile(String key, String assetBase) async { ...@@ -96,74 +96,80 @@ Future<ArchiveFile> _createFile(String key, String assetBase) async {
return new ArchiveFile.noCompress(key, content.length, content); return new ArchiveFile.noCompress(key, content.length, content);
} }
Future _compileSnapshot({
String compilerPath,
String mainPath,
String packageRoot,
String snapshotPath
}) async {
if (compilerPath == null) {
compilerPath = await ArtifactStore.getPath(Artifact.flutterCompiler);
}
ProcessResult result = await Process.run(compilerPath, [
mainPath,
'--package-root=$packageRoot',
'--snapshot=$snapshotPath'
]);
if (result.exitCode != 0) {
print(result.stdout);
print(result.stderr);
exit(result.exitCode);
}
}
Future<ArchiveFile> _createSnapshotFile(String snapshotPath) async { Future<ArchiveFile> _createSnapshotFile(String snapshotPath) async {
File file = new File(snapshotPath); File file = new File(snapshotPath);
List<int> content = await file.readAsBytes(); List<int> content = await file.readAsBytes();
return new ArchiveFile(_kSnapshotKey, content.length, content); return new ArchiveFile(_kSnapshotKey, content.length, content);
} }
class BuildCommand extends Command { const String _kDefaultAssetBase = 'packages/material_design_icons/icons';
final name = 'build'; const String _kDefaultMainPath = 'lib/main.dart';
final description = 'Create a Flutter app.'; const String _kDefaultOutputPath = 'app.flx';
const String _kDefaultSnapshotPath = 'snapshot_blob.bin';
class BuildCommand extends FlutterCommand {
final String name = 'build';
final String description = 'Create a Flutter app.';
BuildCommand() { BuildCommand() {
argParser.addOption('asset-base', defaultsTo: 'packages/material_design_icons/icons'); argParser.addOption('asset-base', defaultsTo: _kDefaultAssetBase);
argParser.addOption('compiler'); argParser.addOption('compiler');
argParser.addOption('main', defaultsTo: 'lib/main.dart'); argParser.addOption('main', defaultsTo: _kDefaultMainPath);
argParser.addOption('manifest'); argParser.addOption('manifest');
argParser.addOption('output-file', abbr: 'o', defaultsTo: 'app.flx'); argParser.addOption('output-file', abbr: 'o', defaultsTo: _kDefaultOutputPath);
argParser.addOption('snapshot', defaultsTo: 'snapshot_blob.bin'); argParser.addOption('snapshot', defaultsTo: _kDefaultSnapshotPath);
} }
@override @override
Future<int> run() async { Future<int> run() async {
String manifestPath = argResults['manifest']; String compilerPath = argResults['compiler'];
if (compilerPath == null)
await downloadToolchain();
else
toolchain = new Toolchain(compiler: new Compiler(compilerPath));
return await build(
assetBase: argResults['asset-base'],
mainPath: argResults['main'],
manifestPath: argResults['manifest'],
outputPath: argResults['output-file'],
snapshotPath: argResults['snapshot']
);
}
Future<int> build({
String assetBase: _kDefaultAssetBase,
String mainPath: _kDefaultMainPath,
String manifestPath,
String outputPath: _kDefaultOutputPath,
String snapshotPath: _kDefaultSnapshotPath
}) async {
Map manifestDescriptor = await _loadManifest(manifestPath); Map manifestDescriptor = await _loadManifest(manifestPath);
Iterable<_Asset> assets = _parseAssets(manifestDescriptor, manifestPath); Iterable<_Asset> assets = _parseAssets(manifestDescriptor, manifestPath);
Iterable<_MaterialAsset> materialAssets = _parseMaterialAssets(manifestDescriptor); Iterable<_MaterialAsset> materialAssets = _parseMaterialAssets(manifestDescriptor);
Archive archive = new Archive(); Archive archive = new Archive();
String snapshotPath = argResults['snapshot']; int result = await toolchain.compiler.compile(mainPath: mainPath, snapshotPath: snapshotPath);
await _compileSnapshot( if (result != 0)
compilerPath: argResults['compiler'], return result;
mainPath: argResults['main'],
packageRoot: globalResults['package-root'],
snapshotPath: snapshotPath);
archive.addFile(await _createSnapshotFile(snapshotPath)); archive.addFile(await _createSnapshotFile(snapshotPath));
for (_Asset asset in assets) for (_Asset asset in assets)
archive.addFile(await _createFile(asset.key, asset.base)); archive.addFile(await _createFile(asset.key, asset.base));
for (_MaterialAsset asset in materialAssets) { for (_MaterialAsset asset in materialAssets) {
ArchiveFile file = await _createFile(asset.key, argResults['asset-base']); ArchiveFile file = await _createFile(asset.key, assetBase);
if (file != null) if (file != null)
archive.addFile(file); archive.addFile(file);
} }
File outputFile = new File(argResults['output-file']); File outputFile = new File(outputPath);
await outputFile.writeAsString('#!mojo mojo:sky_viewer\n'); await outputFile.writeAsString('#!mojo mojo:sky_viewer\n');
await outputFile.writeAsBytes(new ZipEncoder().encode(archive), mode: FileMode.APPEND); await outputFile.writeAsBytes(new ZipEncoder().encode(archive), mode: FileMode.APPEND, flush: true);
return 0; return 0;
} }
} }
...@@ -8,6 +8,7 @@ import 'package:args/command_runner.dart'; ...@@ -8,6 +8,7 @@ import 'package:args/command_runner.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import '../toolchain.dart';
import 'flutter_command_runner.dart'; import 'flutter_command_runner.dart';
abstract class FlutterCommand extends Command { abstract class FlutterCommand extends Command {
...@@ -18,6 +19,11 @@ abstract class FlutterCommand extends Command { ...@@ -18,6 +19,11 @@ abstract class FlutterCommand extends Command {
applicationPackages = await ApplicationPackageStore.forConfigs(runner.buildConfigurations); applicationPackages = await ApplicationPackageStore.forConfigs(runner.buildConfigurations);
} }
Future downloadToolchain() async {
if (toolchain == null)
toolchain = await Toolchain.forConfigs(runner.buildConfigurations);
}
void connectToDevices() { void connectToDevices() {
if (devices == null) if (devices == null)
devices = new DeviceStore.forConfigs(runner.buildConfigurations); devices = new DeviceStore.forConfigs(runner.buildConfigurations);
...@@ -30,9 +36,11 @@ abstract class FlutterCommand extends Command { ...@@ -30,9 +36,11 @@ abstract class FlutterCommand extends Command {
void inheritFromParent(FlutterCommand other) { void inheritFromParent(FlutterCommand other) {
applicationPackages = other.applicationPackages; applicationPackages = other.applicationPackages;
toolchain = other.toolchain;
devices = other.devices; devices = other.devices;
} }
ApplicationPackageStore applicationPackages; ApplicationPackageStore applicationPackages;
Toolchain toolchain;
DeviceStore devices; DeviceStore devices;
} }
...@@ -7,10 +7,11 @@ import 'dart:io'; ...@@ -7,10 +7,11 @@ import 'dart:io';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'flutter_command.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../device.dart'; import '../device.dart';
import '../process.dart'; import '../process.dart';
import 'build.dart';
import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.listen'); final Logger _logging = new Logger('sky_tools.listen');
...@@ -33,9 +34,13 @@ class ListenCommand extends FlutterCommand { ...@@ -33,9 +34,13 @@ class ListenCommand extends FlutterCommand {
help: 'Target app path or filename to start.'); help: 'Target app path or filename to start.');
} }
static const String _localFlutterBundle = 'app.flx';
static const String _remoteFlutterBundle = 'Documents/app.flx';
@override @override
Future<int> run() async { Future<int> run() async {
await downloadApplicationPackagesAndConnectToDevices(); await downloadApplicationPackagesAndConnectToDevices();
await downloadToolchain();
if (argResults.rest.length > 0) { if (argResults.rest.length > 0) {
watchCommand = _initWatchCommand(argResults.rest); watchCommand = _initWatchCommand(argResults.rest);
...@@ -46,27 +51,9 @@ class ListenCommand extends FlutterCommand { ...@@ -46,27 +51,9 @@ class ListenCommand extends FlutterCommand {
while (true) { while (true) {
_logging.info('Updating running Flutter apps...'); _logging.info('Updating running Flutter apps...');
// TODO(iansf): refactor build command so that this doesn't have BuildCommand builder = new BuildCommand();
// to call out like this. builder.inheritFromParent(this);
List<String> command = ['pub', 'run', 'sky_tools', 'build',]; builder.build(outputPath: _localFlutterBundle);
try {
// In testing, sky-src-path isn't added to the options, and
// the ArgParser module throws an exception, so we have to
// catch and ignore the error in order to test.
if (globalResults.wasParsed('sky-src-path')) {
command.addAll([
// TODO(iansf): Don't rely on sky-src-path for the snapshotter.
'--compiler',
'${globalResults['sky-src-path']}'
'/out/ios_sim_Debug/clang_x64/sky_snapshot'
]);
}
} catch (e) {}
runSync(command);
String localFlutterBundle = 'app.flx';
String remoteFlutterBundle = 'Documents/app.flx';
for (Device device in devices.all) { for (Device device in devices.all) {
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform); ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
...@@ -76,11 +63,11 @@ class ListenCommand extends FlutterCommand { ...@@ -76,11 +63,11 @@ class ListenCommand extends FlutterCommand {
await devices.android.startServer( await devices.android.startServer(
argResults['target'], true, argResults['checked'], package); argResults['target'], true, argResults['checked'], package);
} else if (device is IOSDevice) { } else if (device is IOSDevice) {
device.pushFile(package, localFlutterBundle, remoteFlutterBundle); device.pushFile(package, _localFlutterBundle, _remoteFlutterBundle);
} else if (device is IOSSimulator) { } else if (device is IOSSimulator) {
// TODO(abarth): Move pushFile up to Device once Android supports // TODO(abarth): Move pushFile up to Device once Android supports
// pushing new bundles. // pushing new bundles.
device.pushFile(package, localFlutterBundle, remoteFlutterBundle); device.pushFile(package, _localFlutterBundle, _remoteFlutterBundle);
} else { } else {
assert(false); assert(false);
} }
......
// Copyright 2015 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 'dart:async';
import 'package:path/path.dart' as path;
import 'artifacts.dart';
import 'build_configuration.dart';
import 'process.dart';
class Compiler {
Compiler(this._compilerPath);
String _compilerPath;
Future<int> compile({
String mainPath,
String snapshotPath
}) {
return runCommandAndStreamOutput([
_compilerPath,
mainPath,
'--package-root=${ArtifactStore.packageRoot}',
'--snapshot=$snapshotPath'
]);
}
}
class Toolchain {
Toolchain({ this.compiler });
final Compiler compiler;
static Future<Toolchain> forConfigs(List<BuildConfiguration> configs) async {
// TODO(abarth): Add a notion of "host platform" to the build configs.
BuildConfiguration config = configs.first;
String compilerPath = config.type == BuildType.prebuilt ?
await ArtifactStore.getPath(Artifact.flutterCompiler) :
path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
return new Toolchain(compiler: new Compiler(compilerPath));
}
}
...@@ -7,6 +7,7 @@ import 'package:sky_tools/src/application_package.dart'; ...@@ -7,6 +7,7 @@ import 'package:sky_tools/src/application_package.dart';
import 'package:sky_tools/src/build_configuration.dart'; import 'package:sky_tools/src/build_configuration.dart';
import 'package:sky_tools/src/commands/flutter_command.dart'; import 'package:sky_tools/src/commands/flutter_command.dart';
import 'package:sky_tools/src/device.dart'; import 'package:sky_tools/src/device.dart';
import 'package:sky_tools/src/toolchain.dart';
class MockApplicationPackageStore extends ApplicationPackageStore { class MockApplicationPackageStore extends ApplicationPackageStore {
MockApplicationPackageStore() : super( MockApplicationPackageStore() : super(
...@@ -15,6 +16,15 @@ class MockApplicationPackageStore extends ApplicationPackageStore { ...@@ -15,6 +16,15 @@ class MockApplicationPackageStore extends ApplicationPackageStore {
iOSSimulator: new IOSApp(localPath: '/mock/path/to/iOSSimulator/SkyShell.app')); iOSSimulator: new IOSApp(localPath: '/mock/path/to/iOSSimulator/SkyShell.app'));
} }
class MockCompiler extends Mock implements Compiler {
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
class MockToolchain extends Toolchain {
MockToolchain() : super(compiler: new MockCompiler());
}
class MockAndroidDevice extends Mock implements AndroidDevice { class MockAndroidDevice extends Mock implements AndroidDevice {
BuildPlatform get platform => BuildPlatform.android; BuildPlatform get platform => BuildPlatform.android;
...@@ -46,5 +56,6 @@ class MockDeviceStore extends DeviceStore { ...@@ -46,5 +56,6 @@ class MockDeviceStore extends DeviceStore {
void applyMocksToCommand(FlutterCommand command) { void applyMocksToCommand(FlutterCommand command) {
command command
..applicationPackages = new MockApplicationPackageStore() ..applicationPackages = new MockApplicationPackageStore()
..toolchain = new MockToolchain()
..devices = new MockDeviceStore(); ..devices = new MockDeviceStore();
} }
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