Add a --no-http flag to start command

This flag builds a local FLX file and pushes that to the device instead of
using an HTTP server.
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 09a2061..f705622 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -66,16 +66,15 @@
 
   ApplicationPackageStore({ this.android, this.iOS, this.iOSSimulator });
 
-  ApplicationPackage getPackageForPlatform(BuildPlatform platform) {
+  ApplicationPackage getPackageForPlatform(TargetPlatform platform) {
     switch (platform) {
-      case BuildPlatform.android:
+      case TargetPlatform.android:
         return android;
-      case BuildPlatform.iOS:
+      case TargetPlatform.iOS:
         return iOS;
-      case BuildPlatform.iOSSimulator:
+      case TargetPlatform.iOSSimulator:
         return iOSSimulator;
-      case BuildPlatform.mac:
-      case BuildPlatform.linux:
+      case TargetPlatform.linux:
         return null;
     }
   }
@@ -86,31 +85,32 @@
     IOSApp iOSSimulator;
 
     for (BuildConfiguration config in configs) {
-      switch (config.platform) {
-        case BuildPlatform.android:
+      switch (config.targetPlatform) {
+        case TargetPlatform.android:
           assert(android == null);
-          String localPath = config.type == BuildType.prebuilt ?
-            await ArtifactStore.getPath(Artifact.flutterShell) :
-            path.join(config.buildDir, 'apks', AndroidApk._defaultName);
-          android = new AndroidApk(localPath: localPath);
+          if (config.type != BuildType.prebuilt) {
+            String localPath = path.join(config.buildDir, 'apks', AndroidApk._defaultName);
+            android = new AndroidApk(localPath: localPath);
+          } else {
+            Artifact artifact = ArtifactStore.getArtifact(
+              type: ArtifactType.shell, targetPlatform: TargetPlatform.android);
+            android = new AndroidApk(localPath: await ArtifactStore.getPath(artifact));
+          }
           break;
 
-        case BuildPlatform.iOS:
+        case TargetPlatform.iOS:
           assert(iOS == null);
           assert(config.type != BuildType.prebuilt);
           iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
           break;
 
-        case BuildPlatform.iOSSimulator:
+        case TargetPlatform.iOSSimulator:
           assert(iOSSimulator == null);
           assert(config.type != BuildType.prebuilt);
           iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
           break;
 
-        case BuildPlatform.mac:
-        case BuildPlatform.linux:
-          // TODO(abarth): Support mac and linux targets.
-          assert(false);
+        case TargetPlatform.linux:
           break;
       }
     }
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 9bd6a06..5e39e47 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -10,15 +10,147 @@
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 
+import 'build_configuration.dart';
+
 final Logger _logging = new Logger('sky_tools.artifacts');
 
-enum Artifact {
-  flutterCompiler,
-  flutterShell,
-  skyViewerMojo,
+const String _kShellCategory = 'shell';
+const String _kViewerCategory = 'viewer';
+
+String _getNameForHostPlatform(HostPlatform platform) {
+  switch (platform) {
+    case HostPlatform.linux:
+      return 'linux-x64';
+    case HostPlatform.mac:
+      return 'darwin-x64';
+  }
+}
+
+String _getNameForTargetPlatform(TargetPlatform platform) {
+  switch (platform) {
+    case TargetPlatform.android:
+      return 'android-arm';
+    case TargetPlatform.iOS:
+      return 'ios-arm';
+    case TargetPlatform.iOSSimulator:
+      return 'ios-x64';
+    case TargetPlatform.linux:
+      return 'linux-x64';
+  }
+}
+
+// Keep in sync with https://github.com/flutter/engine/blob/master/sky/tools/big_red_button.py#L50
+String _getCloudStorageBaseUrl({String category, String platform, String revision}) {
+  if (platform == 'darwin-x64') {
+    // In the fullness of time, we'll have a consistent URL pattern for all of
+    // our artifacts, but, for the time being, darwin artifacts are stored in a
+    // different cloud storage bucket.
+    return 'https://storage.googleapis.com/mojo_infra/flutter/${platform}/${revision}/';
+  }
+  return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${revision}/';
+}
+
+enum ArtifactType {
+  snapshot,
+  shell,
+  viewer,
+}
+
+class Artifact {
+  const Artifact._({
+    this.name,
+    this.fileName,
+    this.category,
+    this.type,
+    this.hostPlatform,
+    this.targetPlatform
+  });
+
+  final String name;
+  final String fileName;
+  final String category; // TODO(abarth): Remove categories.
+  final ArtifactType type;
+  final HostPlatform hostPlatform;
+  final TargetPlatform targetPlatform;
+
+  String get platform {
+    if (targetPlatform != null)
+      return _getNameForTargetPlatform(targetPlatform);
+    if (hostPlatform != null)
+      return _getNameForHostPlatform(hostPlatform);
+    assert(false);
+    return null;
+  }
+
+  String getUrl(String revision) {
+    return _getCloudStorageBaseUrl(category: category, platform: platform, revision: revision) + fileName;
+  }
+
+  // Whether the artifact needs to be marked as executable on disk.
+  bool get executable => type == ArtifactType.snapshot;
 }
 
 class ArtifactStore {
+  static const List<Artifact> knownArtifacts = const <Artifact>[
+    const Artifact._(
+      name: 'Sky Shell',
+      fileName: 'SkyShell.apk',
+      category: _kShellCategory,
+      type: ArtifactType.shell,
+      targetPlatform: TargetPlatform.android
+    ),
+    const Artifact._(
+      name: 'Sky Snapshot',
+      fileName: 'sky_snapshot',
+      category: _kShellCategory,
+      type: ArtifactType.snapshot,
+      hostPlatform: HostPlatform.linux
+    ),
+    const Artifact._(
+      name: 'Sky Snapshot',
+      fileName: 'sky_snapshot',
+      category: _kShellCategory,
+      type: ArtifactType.snapshot,
+      hostPlatform: HostPlatform.mac
+    ),
+    const Artifact._(
+      name: 'Sky Viewer',
+      fileName: 'sky_viewer.mojo',
+      category: _kViewerCategory,
+      type: ArtifactType.viewer,
+      targetPlatform: TargetPlatform.android
+    ),
+    const Artifact._(
+      name: 'Sky Viewer',
+      fileName: 'sky_viewer.mojo',
+      category: _kViewerCategory,
+      type: ArtifactType.viewer,
+      targetPlatform: TargetPlatform.linux
+    ),
+  ];
+
+  static Artifact getArtifact({
+    ArtifactType type,
+    HostPlatform hostPlatform,
+    TargetPlatform targetPlatform
+  }) {
+    for (Artifact artifact in ArtifactStore.knownArtifacts) {
+      if (type != null &&
+          type != artifact.type)
+        continue;
+      if (hostPlatform != null &&
+          artifact.hostPlatform != null &&
+          hostPlatform != artifact.hostPlatform)
+        continue;
+      if (targetPlatform != null &&
+          artifact.targetPlatform != null &&
+          targetPlatform != artifact.targetPlatform)
+        continue;
+      return artifact;
+    }
+    return null;
+  }
+
   static String packageRoot;
   static String _engineRevision;
 
@@ -31,102 +163,68 @@
     return _engineRevision;
   }
 
-  // Keep in sync with https://github.com/flutter/engine/blob/master/sky/tools/big_red_button.py#L50
-  static String googleStorageUrl(String category, String platform) {
-    return 'https://storage.googleapis.com/mojo/sky/${category}/${platform}/${engineRevision}/';
+  static String getCloudStorageBaseUrl(String category, String platform) {
+    return _getCloudStorageBaseUrl(category: category, platform: platform, revision: engineRevision);
   }
 
   static Future _downloadFile(String url, File file) async {
-    print('Downloading $url to ${file.path}.');
+    _logging.info('Downloading $url to ${file.path}.');
     HttpClient httpClient = new HttpClient();
     HttpClientRequest request = await httpClient.getUrl(Uri.parse(url));
     HttpClientResponse response = await request.close();
-    _logging.fine('Received response');
-    if (response.statusCode != 200) throw new Exception(response.reasonPhrase);
+    _logging.fine('Received response statusCode=${response.statusCode}');
+    if (response.statusCode != 200)
+      throw new Exception(response.reasonPhrase);
     IOSink sink = file.openWrite();
     await sink.addStream(response);
     await sink.close();
     _logging.fine('Wrote file');
   }
 
-  static Future<Directory> _cacheDir() async {
+  static Directory _getBaseCacheDir() {
     Directory cacheDir = new Directory(path.join(packageRoot, 'sky_tools', 'cache'));
-    if (!await cacheDir.exists()) {
-      await cacheDir.create(recursive: true);
-    }
+    if (!cacheDir.existsSync())
+      cacheDir.createSync(recursive: true);
     return cacheDir;
   }
 
-  static Future<Directory> _engineSpecificCacheDir() async {
-    Directory cacheDir = await _cacheDir();
+  static Directory _getCacheDirForArtifact(Artifact artifact) {
+    Directory baseDir = _getBaseCacheDir();
     // For now, all downloaded artifacts are release mode host binaries so use
     // a path that mirrors a local release build.
     // TODO(jamesr): Add support for more configurations.
     String config = 'Release';
-    Directory engineSpecificDir = new Directory(path.join(cacheDir.path, 'sky_engine', engineRevision, config));
-
-    if (!await engineSpecificDir.exists()) {
-      await engineSpecificDir.create(recursive: true);
-    }
-    return engineSpecificDir;
-  }
-
-  // Whether the artifact needs to be marked as executable on disk.
-  static bool _needsToBeExecutable(Artifact artifact) {
-    return artifact == Artifact.flutterCompiler;
+    Directory artifactSpecificDir = new Directory(path.join(
+        baseDir.path, 'sky_engine', engineRevision, config, artifact.platform));
+    if (!artifactSpecificDir.existsSync())
+      artifactSpecificDir.createSync(recursive: true);
+    return artifactSpecificDir;
   }
 
   static Future<String> getPath(Artifact artifact) async {
-    Directory cacheDir = await _engineSpecificCacheDir();
-
-    String category;
-    String platform;
-    String name;
-
-    switch (artifact) {
-      case Artifact.flutterCompiler:
-        category = 'shell';
-        name = 'sky_snapshot';
-        break;
-      case Artifact.flutterShell:
-        category = 'shell';
-        platform = 'android-arm';
-        name = 'SkyShell.apk';
-        break;
-      case Artifact.skyViewerMojo:
-        category = 'viewer';
-        name = 'sky_viewer.mojo';
-        break;
-    }
-
-    File cachedFile = new File(path.join(cacheDir.path, name));
-    if (!await cachedFile.exists()) {
-      _logging.info('Downloading ${name} from the cloud, one moment please...');
-      if (platform == null) {
-        if (!Platform.isLinux)
-          throw new Exception('Platform unsupported.');
-        platform = 'linux-x64';
-      }
-      String url = googleStorageUrl(category, platform) + name;
-      await _downloadFile(url, cachedFile);
-      if (_needsToBeExecutable(artifact)) {
-        ProcessResult result = await Process.run('chmod', ['u+x', cachedFile.path]);
-        if (result.exitCode != 0) throw new Exception(result.stderr);
+    Directory cacheDir = _getCacheDirForArtifact(artifact);
+    File cachedFile = new File(path.join(cacheDir.path, artifact.fileName));
+    if (!cachedFile.existsSync()) {
+      print('Downloading ${artifact.name} from the cloud, one moment please...');
+      await _downloadFile(artifact.getUrl(engineRevision), cachedFile);
+      if (artifact.executable) {
+        // TODO(abarth): We should factor this out into a separate function that
+        // can have a platform-specific implementation.
+        ProcessResult result = Process.runSync('chmod', ['u+x', cachedFile.path]);
+        if (result.exitCode != 0)
+          throw new Exception(result.stderr);
       }
     }
     return cachedFile.path;
   }
 
-  static Future clear() async {
-    Directory cacheDir = await _cacheDir();
+  static void clear() {
+    Directory cacheDir = _getBaseCacheDir();
     _logging.fine('Clearing cache directory ${cacheDir.path}');
-    await cacheDir.delete(recursive: true);
+    cacheDir.deleteSync(recursive: true);
   }
 
-  static Future populate() async {
-    for (Artifact artifact in Artifact.values) {
-      _logging.fine('Populating cache with $artifact');
-      await getPath(artifact);
-    }
+  static Future populate() {
+    return Future.wait(knownArtifacts.map((artifact) => getPath(artifact)));
   }
 }
diff --git a/packages/flutter_tools/lib/src/build_configuration.dart b/packages/flutter_tools/lib/src/build_configuration.dart
index 0db0299e..5fc0356 100644
--- a/packages/flutter_tools/lib/src/build_configuration.dart
+++ b/packages/flutter_tools/lib/src/build_configuration.dart
@@ -2,29 +2,48 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:io';
+
+import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 
+final Logger _logging = new Logger('sky_tools.build_configuration');
+
 enum BuildType {
   prebuilt,
   release,
   debug,
 }
 
-enum BuildPlatform {
-  android,
-  iOS,
-  iOSSimulator,
+enum HostPlatform {
   mac,
   linux,
 }
 
+enum TargetPlatform {
+  android,
+  iOS,
+  iOSSimulator,
+  linux,
+}
+
+HostPlatform getCurrentHostPlatform() {
+  if (Platform.isMacOS)
+    return HostPlatform.mac;
+  if (Platform.isLinux)
+    return HostPlatform.linux;
+  _logging.warning('Unsupported host platform, defaulting to Linux');
+  return HostPlatform.linux;
+}
+
 class BuildConfiguration {
-  BuildConfiguration.prebuilt({ this.platform })
+  BuildConfiguration.prebuilt({ this.hostPlatform, this.targetPlatform })
     : type = BuildType.prebuilt, buildDir = null;
 
   BuildConfiguration.local({
     this.type,
-    this.platform,
+    this.hostPlatform,
+    this.targetPlatform,
     String enginePath,
     String buildPath
   }) : buildDir = path.normalize(path.join(enginePath, buildPath)) {
@@ -32,6 +51,7 @@
   }
 
   final BuildType type;
-  final BuildPlatform platform;
+  final HostPlatform hostPlatform;
+  final TargetPlatform targetPlatform;
   final String buildDir;
 }
diff --git a/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart b/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart
index b12c257..8f0b389 100644
--- a/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart
@@ -27,8 +27,10 @@
         negatable: false,
         help: 'Very noisy logging, including the output of all '
             'shell commands executed.');
+    argParser.addOption('package-root',
+        help: 'Path to your packages directory.', defaultsTo: 'packages');
 
-    argParser.addSeparator('Global build selection options:');
+    argParser.addSeparator('Local build selection options:');
     argParser.addFlag('debug',
         negatable: false,
         help:
@@ -48,42 +50,40 @@
             'Automatically detect your engine src directory from an overridden Flutter package.'
             'Useful if you are building Flutter locally and are using a dependency_override for'
             'the Flutter package that points to your engine src directory.');
-    argParser.addOption('engine-src-path',
+    argParser.addOption('engine-src-path', hide: true,
         help:
             'Path to your engine src directory, if you are building Flutter locally. '
             'Ignored if neither debug nor release is set. Not normally required.');
-    argParser.addOption('android-debug-build-path',
+    argParser.addOption('android-debug-build-path', hide: true,
         help:
             'Path to your Android Debug out directory, if you are building Flutter locally. '
             'This path is relative to engine-src-path. Not normally required.',
         defaultsTo: 'out/android_Debug/');
-    argParser.addOption('android-release-build-path',
+    argParser.addOption('android-release-build-path', hide: true,
         help:
             'Path to your Android Release out directory, if you are building Flutter locally. '
             'This path is relative to engine-src-path. Not normally required.',
         defaultsTo: 'out/android_Release/');
-    argParser.addOption('ios-debug-build-path',
+    argParser.addOption('ios-debug-build-path', hide: true,
         help:
             'Path to your iOS Debug out directory, if you are building Flutter locally. '
             'This path is relative to engine-src-path. Not normally required.',
         defaultsTo: 'out/ios_Debug/');
-    argParser.addOption('ios-release-build-path',
+    argParser.addOption('ios-release-build-path', hide: true,
         help:
             'Path to your iOS Release out directory, if you are building Flutter locally. '
             'This path is relative to engine-src-path. Not normally required.',
         defaultsTo: 'out/ios_Release/');
-    argParser.addOption('ios-sim-debug-build-path',
+    argParser.addOption('ios-sim-debug-build-path', hide: true,
         help:
             'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
             'This path is relative to engine-src-path. Not normally required.',
         defaultsTo: 'out/ios_sim_Debug/');
-    argParser.addOption('ios-sim-release-build-path',
+    argParser.addOption('ios-sim-release-build-path', hide: true,
         help:
             'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
             'This path is relative to engine-src-path. Not normally required.',
         defaultsTo: 'out/ios_sim_Release/');
-    argParser.addOption('package-root',
-        help: 'Path to your packages directory.', defaultsTo: 'packages');
   }
 
   List<BuildConfiguration> get buildConfigurations {
@@ -124,6 +124,7 @@
     String enginePath = globalResults['engine-src-path'];
     bool isDebug = globalResults['debug'];
     bool isRelease = globalResults['release'];
+    HostPlatform hostPlatform = getCurrentHostPlatform();
 
     if (enginePath == null && globalResults['local-build']) {
       Directory flutterDir = new Directory(path.join(globalResults['package-root'], 'flutter'));
@@ -141,7 +142,8 @@
     List<BuildConfiguration> configs = <BuildConfiguration>[];
 
     if (enginePath == null) {
-      configs.add(new BuildConfiguration.prebuilt(platform: BuildPlatform.android));
+      configs.add(new BuildConfiguration.prebuilt(
+          hostPlatform: hostPlatform, targetPlatform: TargetPlatform.android));
     } else {
       if (!FileSystemEntity.isDirectorySync(enginePath))
         _logging.warning('$enginePath is not a valid directory');
@@ -152,7 +154,8 @@
       if (isDebug) {
         configs.add(new BuildConfiguration.local(
           type: BuildType.debug,
-          platform: BuildPlatform.android,
+          hostPlatform: hostPlatform,
+          targetPlatform: TargetPlatform.android,
           enginePath: enginePath,
           buildPath: globalResults['android-debug-build-path']
         ));
@@ -160,14 +163,16 @@
         if (Platform.isMacOS) {
           configs.add(new BuildConfiguration.local(
             type: BuildType.debug,
-            platform: BuildPlatform.iOS,
+            hostPlatform: hostPlatform,
+            targetPlatform: TargetPlatform.iOS,
             enginePath: enginePath,
             buildPath: globalResults['ios-debug-build-path']
           ));
 
           configs.add(new BuildConfiguration.local(
             type: BuildType.debug,
-            platform: BuildPlatform.iOSSimulator,
+            hostPlatform: hostPlatform,
+            targetPlatform: TargetPlatform.iOSSimulator,
             enginePath: enginePath,
             buildPath: globalResults['ios-sim-debug-build-path']
           ));
@@ -177,7 +182,8 @@
       if (isRelease) {
         configs.add(new BuildConfiguration.local(
           type: BuildType.release,
-          platform: BuildPlatform.android,
+          hostPlatform: hostPlatform,
+          targetPlatform: TargetPlatform.android,
           enginePath: enginePath,
           buildPath: globalResults['android-release-build-path']
         ));
@@ -185,14 +191,16 @@
         if (Platform.isMacOS) {
           configs.add(new BuildConfiguration.local(
             type: BuildType.release,
-            platform: BuildPlatform.iOS,
+            hostPlatform: hostPlatform,
+            targetPlatform: TargetPlatform.iOS,
             enginePath: enginePath,
             buildPath: globalResults['ios-release-build-path']
           ));
 
           configs.add(new BuildConfiguration.local(
             type: BuildType.release,
-            platform: BuildPlatform.iOSSimulator,
+            hostPlatform: hostPlatform,
+            targetPlatform: TargetPlatform.iOSSimulator,
             enginePath: enginePath,
             buildPath: globalResults['ios-sim-release-build-path']
           ));
diff --git a/packages/flutter_tools/lib/src/commands/run_mojo.dart b/packages/flutter_tools/lib/src/commands/run_mojo.dart
index bbab6ac..5853204 100644
--- a/packages/flutter_tools/lib/src/commands/run_mojo.dart
+++ b/packages/flutter_tools/lib/src/commands/run_mojo.dart
@@ -9,6 +9,7 @@
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 
+import '../build_configuration.dart';
 import '../artifacts.dart';
 import '../process.dart';
 
@@ -40,7 +41,7 @@
   }
 
   Future<int> _runAndroid(String devtoolsPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) {
-    String skyViewerUrl = ArtifactStore.googleStorageUrl('viewer', 'android-arm');
+    String skyViewerUrl = ArtifactStore.getCloudStorageBaseUrl('viewer', 'android-arm');
     String command = _makePathAbsolute(devtoolsPath);
     String appName = path.basename(appPath);
     String appDir = path.dirname(appPath);
@@ -65,7 +66,8 @@
   }
 
   Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async {
-    String viewerPath = _makePathAbsolute(await ArtifactStore.getPath(Artifact.skyViewerMojo));
+    Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.viewer, targetPlatform: TargetPlatform.linux);
+    String viewerPath = _makePathAbsolute(await ArtifactStore.getPath(artifact));
     String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release';
     String mojoShellPath = _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell'));
     List<String> cmd = [
diff --git a/packages/flutter_tools/lib/src/commands/start.dart b/packages/flutter_tools/lib/src/commands/start.dart
index 33f9f46..2fb4701 100644
--- a/packages/flutter_tools/lib/src/commands/start.dart
+++ b/packages/flutter_tools/lib/src/commands/start.dart
@@ -3,17 +3,21 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 
 import '../application_package.dart';
 import '../device.dart';
+import 'build.dart';
 import 'flutter_command.dart';
 import 'install.dart';
 import 'stop.dart';
 
 final Logger _logging = new Logger('sky_tools.start');
+const String _localBundlePath = 'app.flx';
+const bool _kUseServer = true;
 
 class StartCommand extends FlutterCommand {
   final String name = 'start';
@@ -31,13 +35,20 @@
         defaultsTo: '.',
         abbr: 't',
         help: 'Target app path or filename to start.');
+    argParser.addFlag('http',
+        negatable: true,
+        defaultsTo: true,
+        help: 'Use a local HTTP server to serve your app to your device.');
     argParser.addFlag('boot',
         help: 'Boot the iOS Simulator if it isn\'t already running.');
   }
 
   @override
   Future<int> runInProject() async {
-    await downloadApplicationPackagesAndConnectToDevices();
+    await Future.wait([
+      downloadToolchain(),
+      downloadApplicationPackagesAndConnectToDevices(),
+    ]);
 
     bool poke = argResults['poke'];
     if (!poke) {
@@ -59,8 +70,19 @@
         continue;
       if (device is AndroidDevice) {
         String target = path.absolute(argResults['target']);
-        if (await device.startServer(target, poke, argResults['checked'], package))
-          startedSomething = true;
+        if (argResults['http']) {
+          if (await device.startServer(target, poke, argResults['checked'], package))
+            startedSomething = true;
+        } else {
+          String mainPath = target;
+          if (FileSystemEntity.isDirectorySync(target))
+            mainPath = path.join(target, 'lib', 'main.dart');
+          BuildCommand builder = new BuildCommand();
+          builder.inheritFromParent(this);
+          await builder.build(outputPath: _localBundlePath, mainPath: mainPath);
+          if (device.startBundle(package, _localBundlePath, poke, argResults['checked']))
+            startedSomething = true;
+        }
       } else {
         if (await device.startApp(package))
           startedSomething = true;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 75c994e..fa13654 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -66,7 +66,7 @@
   /// Check if the current version of the given app is already installed
   bool isAppInstalled(ApplicationPackage app);
 
-  BuildPlatform get platform;
+  TargetPlatform get platform;
 
   Future<int> logs({bool clear: false});
 
@@ -271,7 +271,7 @@
   }
 
   @override
-  BuildPlatform get platform => BuildPlatform.iOS;
+  TargetPlatform get platform => TargetPlatform.iOS;
 
   /// Note that clear is not supported on iOS at this time.
   Future<int> logs({bool clear: false}) async {
@@ -487,7 +487,7 @@
   }
 
   @override
-  BuildPlatform get platform => BuildPlatform.iOSSimulator;
+  TargetPlatform get platform => TargetPlatform.iOSSimulator;
 
   Future<int> logs({bool clear: false}) async {
     if (!isConnected()) {
@@ -695,6 +695,10 @@
     return '${_getDeviceDataPath(app)}/${app.name}.sha1';
   }
 
+  String _getDeviceBundlePath(ApplicationPackage app) {
+    return '${_getDeviceDataPath(app)}/dev.flx';
+  }
+
   String _getDeviceApkSha1(ApplicationPackage app) {
     return runCheckedSync([adbPath, 'shell', 'cat', _getDeviceSha1Path(app)]);
   }
@@ -750,12 +754,45 @@
     return true;
   }
 
+  void _forwardObservatoryPort() {
+    // Set up port forwarding for observatory.
+    String observatoryPortString = 'tcp:$_observatoryPort';
+    runCheckedSync(
+        [adbPath, 'forward', observatoryPortString, observatoryPortString]);
+  }
+
+  bool startBundle(AndroidApk apk, String bundlePath, bool poke, bool checked) {
+    if (!FileSystemEntity.isFileSync(bundlePath)) {
+      _logging.severe('Cannot find $bundlePath');
+      return false;
+    }
+
+    if (!poke)
+      _forwardObservatoryPort();
+
+    String deviceTmpPath = '/data/local/tmp/dev.flx';
+    String deviceBundlePath = _getDeviceBundlePath(apk);
+    runCheckedSync([adbPath, 'push', bundlePath, deviceTmpPath]);
+    runCheckedSync([adbPath, 'shell', 'mv', deviceTmpPath, deviceBundlePath]);
+    List<String> cmd = [
+      adbPath,
+      'shell', 'am', 'start',
+      '-a', 'android.intent.action.RUN',
+      '-d', deviceBundlePath,
+    ];
+    if (checked)
+      cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
+    cmd.add(apk.launchActivity);
+    runCheckedSync(cmd);
+    return true;
+  }
+
   Future<bool> startServer(
       String target, bool poke, bool checked, AndroidApk apk) async {
     String serverRoot = '';
     String mainDart = '';
     String missingMessage = '';
-    if (await FileSystemEntity.isDirectory(target)) {
+    if (FileSystemEntity.isDirectorySync(target)) {
       serverRoot = target;
       mainDart = path.join(serverRoot, 'lib', 'main.dart');
       missingMessage = 'Missing lib/main.dart in project: $serverRoot';
@@ -765,16 +802,13 @@
       missingMessage = '$mainDart does not exist.';
     }
 
-    if (!await FileSystemEntity.isFile(mainDart)) {
+    if (!FileSystemEntity.isFileSync(mainDart)) {
       _logging.severe(missingMessage);
       return false;
     }
 
     if (!poke) {
-      // Set up port forwarding for observatory.
-      String observatoryPortString = 'tcp:$_observatoryPort';
-      runCheckedSync(
-          [adbPath, 'forward', observatoryPortString, observatoryPortString]);
+      _forwardObservatoryPort();
 
       // Actually start the server.
       Process server = await Process.start(
@@ -794,28 +828,20 @@
 
     String relativeDartMain = _convertToURL(path.relative(mainDart, from: serverRoot));
     String url = 'http://localhost:$_serverPort/$relativeDartMain';
-    if (poke) {
+    if (poke)
       url += '?rand=${new Random().nextDouble()}';
-    }
 
     // Actually launch the app on Android.
     List<String> cmd = [
       adbPath,
-      'shell',
-      'am',
-      'start',
-      '-a',
-      'android.intent.action.VIEW',
-      '-d',
-      url,
+      'shell', 'am', 'start',
+      '-a', 'android.intent.action.VIEW',
+      '-d', url,
     ];
-    if (checked) {
+    if (checked)
       cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
-    }
     cmd.add(apk.launchActivity);
-
     runCheckedSync(cmd);
-
     return true;
   }
 
@@ -880,7 +906,7 @@
   }
 
   @override
-  BuildPlatform get platform => BuildPlatform.android;
+  TargetPlatform get platform => TargetPlatform.android;
 
   void clearLogs() {
     runSync([adbPath, 'logcat', '-c']);
@@ -985,24 +1011,20 @@
     IOSSimulator iOSSimulator;
 
     for (BuildConfiguration config in configs) {
-      switch (config.platform) {
-        case BuildPlatform.android:
+      switch (config.targetPlatform) {
+        case TargetPlatform.android:
           assert(android == null);
           android = new AndroidDevice();
           break;
-        case BuildPlatform.iOS:
+        case TargetPlatform.iOS:
           assert(iOS == null);
           iOS = new IOSDevice();
           break;
-        case BuildPlatform.iOSSimulator:
+        case TargetPlatform.iOSSimulator:
           assert(iOSSimulator == null);
           iOSSimulator = new IOSSimulator();
           break;
-
-        case BuildPlatform.mac:
-        case BuildPlatform.linux:
-          // TODO(abarth): Support mac and linux targets.
-          assert(false);
+        case TargetPlatform.linux:
           break;
       }
     }
diff --git a/packages/flutter_tools/lib/src/toolchain.dart b/packages/flutter_tools/lib/src/toolchain.dart
index 655e6b6..34301ec 100644
--- a/packages/flutter_tools/lib/src/toolchain.dart
+++ b/packages/flutter_tools/lib/src/toolchain.dart
@@ -11,16 +11,16 @@
 import 'process.dart';
 
 class Compiler {
-  Compiler(this._compilerPath);
+  Compiler(this._path);
 
-  String _compilerPath;
+  String _path;
 
   Future<int> compile({
     String mainPath,
     String snapshotPath
   }) {
     return runCommandAndStreamOutput([
-      _compilerPath,
+      _path,
       mainPath,
       '--package-root=${ArtifactStore.packageRoot}',
       '--snapshot=$snapshotPath'
@@ -28,18 +28,22 @@
   }
 }
 
+Future<String> _getCompilerPath(BuildConfiguration config) async {
+  if (config.type != BuildType.prebuilt)
+    return path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
+  Artifact artifact = ArtifactStore.getArtifact(
+    type: ArtifactType.snapshot, hostPlatform: config.hostPlatform);
+  return await ArtifactStore.getPath(artifact);
+}
+
 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');
-
+    // TODO(abarth): Shouldn't we consider all the configs?
+    String compilerPath = await _getCompilerPath(configs.first);
     return new Toolchain(compiler: new Compiler(compilerPath));
   }
 }
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index b971bd1..94466d4 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -26,21 +26,21 @@
 }
 
 class MockAndroidDevice extends Mock implements AndroidDevice {
-  BuildPlatform get platform => BuildPlatform.android;
+  TargetPlatform get platform => TargetPlatform.android;
 
   @override
   dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
 }
 
 class MockIOSDevice extends Mock implements IOSDevice {
-  BuildPlatform get platform => BuildPlatform.iOS;
+  TargetPlatform get platform => TargetPlatform.iOS;
 
   @override
   dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
 }
 
 class MockIOSSimulator extends Mock implements IOSSimulator {
-  BuildPlatform get platform => BuildPlatform.iOSSimulator;
+  TargetPlatform get platform => TargetPlatform.iOSSimulator;
 
   @override
   dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);