Set up plumbing for getting relevant paths to the right places without too much global state.

`dart bin/sky_tools.dart --debug --sky-src-path=/path/to/sky/src/ install` now works.
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
new file mode 100644
index 0000000..37b7e5e
--- /dev/null
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -0,0 +1,113 @@
+// 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.
+
+library sky_tools.application_package;
+
+import 'dart:io';
+
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as path;
+
+final Logger _logging = new Logger('sky_tools.application_package');
+
+abstract class ApplicationPackage {
+  /// Path to the directory the apk or bundle lives in.
+  String appDir;
+
+  /// Path to the actual apk or bundle.
+  String get appPath => path.join(appDir, appFileName);
+
+  /// Package ID from the Android Manifest or equivalent.
+  String appPackageID;
+
+  /// File name of the apk or bundle.
+  String appFileName;
+
+  ApplicationPackage(this.appDir, this.appPackageID, this.appFileName);
+}
+
+class AndroidApk extends ApplicationPackage {
+  static const String _apkName = 'SkyShell.apk';
+  static const String _androidPackage = 'org.domokit.sky.shell';
+
+  AndroidApk(String appDir,
+      [String appPackageID = _androidPackage, String appFileName = _apkName])
+      : super(path.join(appDir, 'apks'), appPackageID, appFileName);
+}
+
+enum BuildType { prebuilt, release, debug, }
+
+enum BuildPlatform { android, iOS, iOSSimulator, mac, linux, }
+
+class ApplicationPackageFactory {
+  static final Map<BuildPlatform, Map<BuildType, String>> _buildPaths =
+      _initBuildPaths();
+
+  /// Path to your Sky src directory, if you are building Sky locally.
+  /// Required if you are requesting release or debug BuildTypes.
+  static String _srcPath = null;
+  static String get srcPath => _srcPath;
+  static void set srcPath(String newPath) {
+    _srcPath = path.normalize(newPath);
+  }
+
+  /// Default BuildType chosen if no BuildType is specified.
+  static BuildType defaultBuildType = BuildType.prebuilt;
+
+  /// Default BuildPlatforms chosen if no BuildPlatforms are specified.
+  static List<BuildPlatform> defaultBuildPlatforms = [BuildPlatform.android];
+
+  static Map<BuildPlatform, ApplicationPackage> getAvailableApplicationPackages(
+      {BuildType requestedType, List<BuildPlatform> requestedPlatforms}) {
+    if (requestedType == null) {
+      requestedType = defaultBuildType;
+    }
+    if (requestedPlatforms == null) {
+      requestedPlatforms = defaultBuildPlatforms;
+    }
+
+    Map<BuildPlatform, ApplicationPackage> packages = {};
+    for (BuildPlatform platform in requestedPlatforms) {
+      String buildPath = _getBuildPath(requestedType, platform);
+      switch (platform) {
+        case BuildPlatform.android:
+          packages[platform] = new AndroidApk(buildPath);
+          break;
+        default:
+          // TODO(iansf): Add other platforms
+          assert(false);
+      }
+    }
+    return packages;
+  }
+
+  static Map<BuildPlatform, Map<BuildType, String>> _initBuildPaths() {
+    Map<BuildPlatform, Map<BuildType, String>> buildPaths = {};
+    for (BuildPlatform platform in BuildPlatform.values) {
+      buildPaths[platform] = {};
+    }
+    return buildPaths;
+  }
+
+  static String _getBuildPath(BuildType type, BuildPlatform platform) {
+    String path = _buildPaths[platform][type];
+    // You must set paths before getting them
+    assert(path != null);
+    return path;
+  }
+
+  static void setBuildPath(
+      BuildType type, BuildPlatform platform, String buildPath) {
+    // You must set srcPath before attempting to set a BuildPath for
+    // non prebuilt ApplicationPackages.
+    assert(type != BuildType.prebuilt || srcPath != null);
+    if (type != BuildType.prebuilt) {
+      buildPath = path.join(srcPath, buildPath);
+    }
+    if (!FileSystemEntity.isDirectorySync(buildPath)) {
+      _logging.warning('$buildPath is not a valid directory');
+    }
+    _buildPaths[platform][type] = path.normalize(buildPath);
+  }
+}
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 5547555..ffe3c56 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -10,6 +10,7 @@
 import 'package:path/path.dart' as path;
 
 import 'process_wrapper.dart';
+import 'application_package.dart';
 
 final Logger _logging = new Logger('sky_tools.device');
 
@@ -40,13 +41,13 @@
   _Device._(this.id);
 
   /// Install an app package on the current device
-  bool installApp(String appPath, String appPackageID, String appFileName);
+  bool installApp(ApplicationPackage app);
 
   /// Check if the device is currently connected
   bool isConnected();
 
   /// Check if the current version of the given app is already installed
-  bool isAppInstalled(String appPath, String appPackageID, String appFileName);
+  bool isAppInstalled(ApplicationPackage app);
 }
 
 class AndroidDevice extends _Device {
@@ -175,68 +176,58 @@
     return false;
   }
 
-  String _getDeviceSha1Path(String appPackageID, String appFileName) {
-    return '/sdcard/$appPackageID/$appFileName.sha1';
+  String _getDeviceSha1Path(ApplicationPackage app) {
+    return '/sdcard/${app.appPackageID}/${app.appFileName}.sha1';
   }
 
-  String _getDeviceApkSha1(String appPackageID, String appFileName) {
-    return runCheckedSync([
-      adbPath,
-      'shell',
-      'cat',
-      _getDeviceSha1Path(appPackageID, appFileName)
-    ]);
+  String _getDeviceApkSha1(ApplicationPackage app) {
+    return runCheckedSync([adbPath, 'shell', 'cat', _getDeviceSha1Path(app)]);
   }
 
-  String _getSourceSha1(String apkPath) {
+  String _getSourceSha1(ApplicationPackage app) {
     String sha1 =
-        runCheckedSync(['shasum', '-a', '1', '-p', apkPath]).split(' ')[0];
+        runCheckedSync(['shasum', '-a', '1', '-p', app.appPath]).split(' ')[0];
     return sha1;
   }
 
   @override
-  bool isAppInstalled(String appPath, String appPackageID, String appFileName) {
+  bool isAppInstalled(ApplicationPackage app) {
     if (!isConnected()) {
       return false;
     }
-    if (runCheckedSync([adbPath, 'shell', 'pm', 'path', appPackageID]) == '') {
+    if (runCheckedSync([adbPath, 'shell', 'pm', 'path', app.appPackageID]) ==
+        '') {
       _logging.info(
-          'TODO(iansf): move this log to the caller. $appFileName is not on the device. Installing now...');
+          'TODO(iansf): move this log to the caller. ${app.appFileName} is not on the device. Installing now...');
       return false;
     }
-    if (_getDeviceApkSha1(appPackageID, appFileName) !=
-        _getSourceSha1(appPath)) {
+    if (_getDeviceApkSha1(app) != _getSourceSha1(app)) {
       _logging.info(
-          'TODO(iansf): move this log to the caller. $appFileName is out of date. Installing now...');
+          'TODO(iansf): move this log to the caller. ${app.appFileName} is out of date. Installing now...');
       return false;
     }
     return true;
   }
 
   @override
-  bool installApp(String appPath, String appPackageID, String appFileName) {
+  bool installApp(ApplicationPackage app) {
     if (!isConnected()) {
       _logging.info('Android device not connected. Not installing.');
       return false;
     }
-    if (!FileSystemEntity.isFileSync(appPath)) {
-      _logging.severe('"$appPath" does not exist.');
+    if (!FileSystemEntity.isFileSync(app.appPath)) {
+      _logging.severe('"${app.appPath}" does not exist.');
       return false;
     }
 
-    runCheckedSync([adbPath, 'install', '-r', appPath]);
+    runCheckedSync([adbPath, 'install', '-r', app.appPath]);
 
     Directory tempDir = Directory.systemTemp;
     String sha1Path = path.join(
-        tempDir.path, appPath.replaceAll(path.separator, '_'), '.sha1');
+        tempDir.path, (app.appPath + '.sha1').replaceAll(path.separator, '_'));
     File sha1TempFile = new File(sha1Path);
-    sha1TempFile.writeAsStringSync(_getSourceSha1(appPath), flush: true);
-    runCheckedSync([
-      adbPath,
-      'push',
-      sha1Path,
-      _getDeviceSha1Path(appPackageID, appFileName)
-    ]);
+    sha1TempFile.writeAsStringSync(_getSourceSha1(app), flush: true);
+    runCheckedSync([adbPath, 'push', sha1Path, _getDeviceSha1Path(app)]);
     sha1TempFile.deleteSync();
     return true;
   }
diff --git a/packages/flutter_tools/lib/src/install.dart b/packages/flutter_tools/lib/src/install.dart
index d0b30d6..56583da 100644
--- a/packages/flutter_tools/lib/src/install.dart
+++ b/packages/flutter_tools/lib/src/install.dart
@@ -8,6 +8,7 @@
 
 import 'package:args/args.dart';
 
+import 'application_package.dart';
 import 'common.dart';
 import 'device.dart';
 
@@ -33,11 +34,14 @@
 
     bool installedSomewhere = false;
 
+    Map<BuildPlatform, ApplicationPackage> packages =
+        ApplicationPackageFactory.getAvailableApplicationPackages();
     if (android == null) {
       android = new AndroidDevice();
     }
-    if (android.isConnected()) {
-      installedSomewhere = installedSomewhere || android.installApp('', '', '');
+    ApplicationPackage androidApp = packages[BuildPlatform.android];
+    if (androidApp != null && android.isConnected()) {
+      installedSomewhere = installedSomewhere || android.installApp(androidApp);
     }
 
     if (installedSomewhere) {