Add a build command to sky_tools

This command will produce an flx package. Currently, this command doesn't work
because we don't have the Flutter compiler downloaded from Google storage yet.
A future patch will make that happen.
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
new file mode 100644
index 0000000..85e9e29
--- /dev/null
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -0,0 +1,21 @@
+// 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 'dart:io';
+
+enum Artifact {
+  FlutterCompiler
+}
+
+class _ArtifactStore {
+  _ArtifactStore._();
+
+  Future<File> getPath(Artifact artifact) async {
+    // TODO(abarth): Download artifacts from cloud storage.
+    return new File('');
+  }
+}
+
+final _ArtifactStore artifactStore = new _ArtifactStore._();
diff --git a/packages/flutter_tools/lib/src/build.dart b/packages/flutter_tools/lib/src/build.dart
new file mode 100644
index 0000000..7e7eac0
--- /dev/null
+++ b/packages/flutter_tools/lib/src/build.dart
@@ -0,0 +1,168 @@
+// 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:io';
+import 'dart:async';
+
+import 'package:archive/archive.dart';
+import 'package:args/args.dart';
+import 'package:yaml/yaml.dart';
+
+import 'common.dart';
+import 'artifacts.dart';
+
+const String kSnapshotKey = 'snapshot_blob.bin';
+const List<String> kDensities = const ['drawable-xxhdpi'];
+const List<String> kThemes = const ['white', 'black'];
+const List<int> kSizes = const [24];
+
+class Asset {
+  final String base;
+  final String key;
+
+  Asset({ this.base, this.key });
+}
+
+Iterable<Asset> parseAssets(Map manifestDescriptor, String manifestPath) sync* {
+  if (manifestDescriptor == null || !manifestDescriptor.containsKey('assets'))
+    return;
+  String basePath = new File(manifestPath).parent.path;
+  for (String asset in manifestDescriptor['assets'])
+    yield new Asset(base: basePath, key: asset);
+}
+
+class MaterialAsset {
+  final String name;
+  final String density;
+  final String theme;
+  final int size;
+
+  MaterialAsset(Map descriptor)
+    : name = descriptor['name'],
+      density = descriptor['density'],
+      theme = descriptor['theme'],
+      size = descriptor['size'];
+
+  String get key {
+    List<String> parts = name.split('/');
+    String category = parts[0];
+    String subtype = parts[1];
+    return '$category/$density/ic_${subtype}_${theme}_${size}dp.png';
+  }
+}
+
+List generateValues(Map assetDescriptor, String key, List defaults) {
+  if (assetDescriptor.containsKey(key))
+    return [assetDescriptor[key]];
+  return defaults;
+}
+
+Iterable<MaterialAsset> generateMaterialAssets(Map assetDescriptor) sync* {
+  Map currentAssetDescriptor = new Map.from(assetDescriptor);
+  for (String density in generateValues(assetDescriptor, 'density', kDensities)) {
+    currentAssetDescriptor['density'] = density;
+    for (String theme in generateValues(assetDescriptor, 'theme', kThemes)) {
+      currentAssetDescriptor['theme'] = theme;
+      for (int size in generateValues(assetDescriptor, 'size', kSizes)) {
+        currentAssetDescriptor['size'] = size;
+        yield new MaterialAsset(currentAssetDescriptor);
+      }
+    }
+  }
+}
+
+Iterable<MaterialAsset> parseMaterialAssets(Map manifestDescriptor) sync* {
+  if (manifestDescriptor == null || !manifestDescriptor.containsKey('material-design-icons'))
+    return;
+  for (Map assetDescriptor in manifestDescriptor['material-design-icons']) {
+    for (MaterialAsset asset in generateMaterialAssets(assetDescriptor)) {
+      yield asset;
+    }
+  }
+}
+
+Future loadManifest(String manifestPath) async {
+  if (manifestPath == null)
+    return null;
+  String manifestDescriptor = await new File(manifestPath).readAsString();
+  return loadYaml(manifestDescriptor);
+}
+
+Future<ArchiveFile> createFile(String key, String assetBase) async {
+  File file = new File('${assetBase}/${key}');
+  if (!await file.exists())
+    return null;
+  List<int> content = await file.readAsBytes();
+  return new ArchiveFile.noCompress(key, content.length, content);
+}
+
+Future compileSnapshot({
+  String mainPath,
+  String packageRoot,
+  String snapshotPath
+}) async {
+  File compiler = await artifactStore.getPath(Artifact.FlutterCompiler);
+  await Process.run(compiler.path, [
+    mainPath,
+    '--package-root=$packageRoot',
+    '--snapshot=$snapshotPath'
+  ]);
+}
+
+Future<ArchiveFile> createSnapshotFile(String snapshotPath) async {
+  File file = new File(snapshotPath);
+  List<int> content = await file.readAsBytes();
+  return new ArchiveFile(kSnapshotKey, content.length, content);
+}
+
+class BuildCommandHandler extends CommandHandler {
+  BuildCommandHandler() : super('build', 'Create an Flutter package.');
+
+  ArgParser get parser {
+    ArgParser parser = new ArgParser();
+    parser.addFlag('help', abbr: 'h', negatable: false);
+    parser.addOption('asset-base', defaultsTo: 'packages/material_design_icons/icons');
+    parser.addOption('main', defaultsTo: 'lib/main.dart');
+    parser.addOption('manifest', defaultsTo: 'flutter.yaml');
+    parser.addOption('output-file', abbr: 'o', defaultsTo: 'app.flx');
+    parser.addOption('package-root', defaultsTo: 'packages');
+    parser.addOption('snapshot', defaultsTo: 'snapshot_blob.bin');
+    return parser;
+  }
+
+  @override
+  Future<int> processArgResults(ArgResults results) async {
+    if (results['help']) {
+      print(parser.usage);
+      return 0;
+    }
+
+    String manifestPath = results['manifest'];
+    Map manifestDescriptor = await loadManifest(manifestPath);
+    Iterable<Asset> assets = parseAssets(manifestDescriptor, manifestPath);
+    Iterable<MaterialAsset> materialAssets = parseMaterialAssets(manifestDescriptor);
+
+    Archive archive = new Archive();
+
+    String snapshotPath = results['snapshot'];
+    await compileSnapshot(
+      mainPath: results['main'],
+      packageRoot: results['package-root'],
+      snapshotPath: snapshotPath);
+    archive.addFile(await createSnapshotFile(snapshotPath));
+
+    for (Asset asset in assets)
+      archive.addFile(await createFile(asset.key, asset.base));
+
+    for (MaterialAsset asset in materialAssets) {
+      ArchiveFile file = await createFile(asset.key, results['asset-base']);
+      if (file != null)
+        archive.addFile(file);
+    }
+
+    File outputFile = new File(results['output-file']);
+    await outputFile.writeAsBytes(new ZipEncoder().encode(archive));
+    return 0;
+  }
+}