Re-organize flutter_tools code to better support proper layering. (#12957)

* executable.dart#main() depends on runner.dart#run()
* Refactor code such that non-commands don't depend on commands.

No code was actually changed in this PR - code was merely moved from
point A to point B.
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 5dca420..5e89040 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -4,23 +4,7 @@
 
 import 'dart:async';
 
-import 'package:args/command_runner.dart';
-import 'package:intl/intl_standalone.dart' as intl;
-import 'package:meta/meta.dart';
-import 'package:process/process.dart';
-
-import 'src/artifacts.dart';
-import 'src/base/common.dart';
-import 'src/base/config.dart';
-import 'src/base/context.dart';
-import 'src/base/file_system.dart';
-import 'src/base/io.dart';
-import 'src/base/logger.dart';
-import 'src/base/platform.dart';
-import 'src/base/process.dart';
-import 'src/base/terminal.dart';
-import 'src/base/utils.dart';
-import 'src/cache.dart';
+import 'runner.dart' as runner;
 import 'src/commands/analyze.dart';
 import 'src/commands/build.dart';
 import 'src/commands/channel.dart';
@@ -44,17 +28,7 @@
 import 'src/commands/trace.dart';
 import 'src/commands/update_packages.dart';
 import 'src/commands/upgrade.dart';
-import 'src/crash_reporting.dart';
-import 'src/devfs.dart';
-import 'src/device.dart';
-import 'src/doctor.dart';
-import 'src/globals.dart';
-import 'src/ios/simulators.dart';
-import 'src/run_hot.dart';
 import 'src/runner/flutter_command.dart';
-import 'src/runner/flutter_command_runner.dart';
-import 'src/usage.dart';
-import 'src/version.dart';
 
 /// Main entry point for commands.
 ///
@@ -65,7 +39,7 @@
       (args.isNotEmpty && args.first == 'help') || (args.length == 1 && verbose);
   final bool verboseHelp = help && verbose;
 
-  await run(args, <FlutterCommand>[
+  await runner.run(args, <FlutterCommand>[
     new AnalyzeCommand(verboseHelp: verboseHelp),
     new BuildCommand(verboseHelp: verboseHelp),
     new ChannelCommand(),
@@ -91,240 +65,3 @@
     new UpgradeCommand(),
   ], verbose: verbose, verboseHelp: verboseHelp);
 }
-
-Future<int> run(List<String> args, List<FlutterCommand> subCommands, {
-  bool verbose: false,
-  bool verboseHelp: false,
-  bool reportCrashes,
-  String flutterVersion,
-}) async {
-  reportCrashes ??= !isRunningOnBot;
-
-  if (verboseHelp) {
-    // Remove the verbose option; for help, users don't need to see verbose logs.
-    args = new List<String>.from(args);
-    args.removeWhere((String option) => option == '-v' || option == '--verbose');
-  }
-
-  final FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp);
-  subCommands.forEach(runner.addCommand);
-
-  // Construct a context.
-  final AppContext _executableContext = new AppContext();
-
-  // Make the context current.
-  return await _executableContext.runInZone(() async {
-    // Initialize the context with some defaults.
-    // NOTE: Similar lists also exist in `bin/fuchsia_builder.dart` and
-    // `test/src/context.dart`. If you update this list of defaults, look
-    // in those locations as well to see if you need a similar update there.
-
-    // Seed these context entries first since others depend on them
-    context.putIfAbsent(Stdio, () => const Stdio());
-    context.putIfAbsent(Platform, () => const LocalPlatform());
-    context.putIfAbsent(FileSystem, () => const LocalFileSystem());
-    context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
-    context.putIfAbsent(AnsiTerminal, () => new AnsiTerminal());
-    context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
-    context.putIfAbsent(Config, () => new Config());
-
-    // Order-independent context entries
-    context.putIfAbsent(DeviceManager, () => new DeviceManager());
-    context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
-    context.putIfAbsent(Doctor, () => new Doctor());
-    context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
-    context.putIfAbsent(Cache, () => new Cache());
-    context.putIfAbsent(Artifacts, () => new CachedArtifacts());
-    context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
-    context.putIfAbsent(SimControl, () => new SimControl());
-
-    // Initialize the system locale.
-    await intl.findSystemLocale();
-
-    try {
-      await runner.run(args);
-      await _exit(0);
-    } catch (error, stackTrace) {
-      String getVersion() => flutterVersion ?? FlutterVersion.getVersionString();
-      return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion);
-    }
-    return 0;
-  });
-}
-
-/// Writes the [string] to one of the standard output streams.
-@visibleForTesting
-typedef void WriteCallback([String string]);
-
-/// Writes a line to STDERR.
-///
-/// Overwrite this in tests to avoid spurious test output.
-@visibleForTesting
-WriteCallback writelnStderr = stderr.writeln;
-
-Future<int> _handleToolError(
-    dynamic error,
-    StackTrace stackTrace,
-    bool verbose,
-    List<String> args,
-    bool reportCrashes,
-    String getFlutterVersion(),
-) async {
-  if (error is UsageException) {
-    writelnStderr(error.message);
-    writelnStderr();
-    writelnStderr(
-        "Run 'flutter -h' (or 'flutter <command> -h') for available "
-            'flutter commands and options.'
-    );
-    // Argument error exit code.
-    return _exit(64);
-  } else if (error is ToolExit) {
-    if (error.message != null)
-      writelnStderr(error.message);
-    if (verbose) {
-      writelnStderr();
-      writelnStderr(stackTrace.toString());
-      writelnStderr();
-    }
-    return _exit(error.exitCode ?? 1);
-  } else if (error is ProcessExit) {
-    // We've caught an exit code.
-    if (error.immediate) {
-      exit(error.exitCode);
-      return error.exitCode;
-    } else {
-      return _exit(error.exitCode);
-    }
-  } else {
-    // We've crashed; emit a log report.
-    writelnStderr();
-
-    if (!reportCrashes) {
-      // Print the stack trace on the bots - don't write a crash report.
-      writelnStderr('$error');
-      writelnStderr(stackTrace.toString());
-      return _exit(1);
-    } else {
-      flutterUsage.sendException(error, stackTrace);
-
-      if (error is String)
-        writelnStderr('Oops; flutter has exited unexpectedly: "$error".');
-      else
-        writelnStderr('Oops; flutter has exited unexpectedly.');
-
-      await CrashReportSender.instance.sendReport(
-        error: error,
-        stackTrace: stackTrace,
-        getFlutterVersion: getFlutterVersion,
-      );
-      try {
-        final File file = await _createLocalCrashReport(args, error, stackTrace);
-        writelnStderr(
-            'Crash report written to ${file.path};\n'
-                'please let us know at https://github.com/flutter/flutter/issues.',
-        );
-        return _exit(1);
-      } catch (error) {
-        writelnStderr(
-            'Unable to generate crash report due to secondary error: $error\n'
-                'please let us know at https://github.com/flutter/flutter/issues.',
-        );
-        // Any exception throw here (including one thrown by `_exit()`) will
-        // get caught by our zone's `onError` handler. In order to avoid an
-        // infinite error loop, we throw an error that is recognized above
-        // and will trigger an immediate exit.
-        throw new ProcessExit(1, immediate: true);
-      }
-    }
-  }
-}
-
-/// File system used by the crash reporting logic.
-///
-/// We do not want to use the file system stored in the context because it may
-/// be recording. Additionally, in the case of a crash we do not trust the
-/// integrity of the [AppContext].
-@visibleForTesting
-FileSystem crashFileSystem = const LocalFileSystem();
-
-/// Saves the crash report to a local file.
-Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace) async {
-  File crashFile = getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');
-
-  final StringBuffer buffer = new StringBuffer();
-
-  buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
-
-  buffer.writeln('## command\n');
-  buffer.writeln('flutter ${args.join(' ')}\n');
-
-  buffer.writeln('## exception\n');
-  buffer.writeln('${error.runtimeType}: $error\n');
-  buffer.writeln('```\n$stackTrace```\n');
-
-  buffer.writeln('## flutter doctor\n');
-  buffer.writeln('```\n${await _doctorText()}```');
-
-  try {
-    await crashFile.writeAsString(buffer.toString());
-  } on FileSystemException catch (_) {
-    // Fallback to the system temporary directory.
-    crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
-    try {
-      await crashFile.writeAsString(buffer.toString());
-    } on FileSystemException catch (e) {
-      printError('Could not write crash report to disk: $e');
-      printError(buffer.toString());
-    }
-  }
-
-  return crashFile;
-}
-
-Future<String> _doctorText() async {
-  try {
-    final BufferLogger logger = new BufferLogger();
-    final AppContext appContext = new AppContext();
-
-    appContext.setVariable(Logger, logger);
-
-    await appContext.runInZone(() => doctor.diagnose());
-
-    return logger.statusText;
-  } catch (error, trace) {
-    return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
-  }
-}
-
-Future<int> _exit(int code) async {
-  if (flutterUsage.isFirstRun)
-    flutterUsage.printWelcome();
-
-  // Send any last analytics calls that are in progress without overly delaying
-  // the tool's exit (we wait a maximum of 250ms).
-  if (flutterUsage.enabled) {
-    final Stopwatch stopwatch = new Stopwatch()..start();
-    await flutterUsage.ensureAnalyticsSent();
-    printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
-  }
-
-  // Run shutdown hooks before flushing logs
-  await runShutdownHooks();
-
-  final Completer<Null> completer = new Completer<Null>();
-
-  // Give the task / timer queue one cycle through before we hard exit.
-  Timer.run(() {
-    try {
-      printTrace('exiting with code $code');
-      exit(code);
-      completer.complete();
-    } catch (error, stackTrace) {
-      completer.completeError(error, stackTrace);
-    }
-  });
-
-  await completer.future;
-  return code;
-}
diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart
new file mode 100644
index 0000000..9479bea
--- /dev/null
+++ b/packages/flutter_tools/lib/runner.dart
@@ -0,0 +1,274 @@
+// 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:args/command_runner.dart';
+import 'package:intl/intl_standalone.dart' as intl;
+import 'package:meta/meta.dart';
+import 'package:process/process.dart';
+
+import 'src/artifacts.dart';
+import 'src/base/common.dart';
+import 'src/base/config.dart';
+import 'src/base/context.dart';
+import 'src/base/file_system.dart';
+import 'src/base/io.dart';
+import 'src/base/logger.dart';
+import 'src/base/platform.dart';
+import 'src/base/process.dart';
+import 'src/base/terminal.dart';
+import 'src/base/utils.dart';
+import 'src/cache.dart';
+import 'src/crash_reporting.dart';
+import 'src/devfs.dart';
+import 'src/device.dart';
+import 'src/doctor.dart';
+import 'src/globals.dart';
+import 'src/ios/simulators.dart';
+import 'src/run_hot.dart';
+import 'src/runner/flutter_command.dart';
+import 'src/runner/flutter_command_runner.dart';
+import 'src/usage.dart';
+import 'src/version.dart';
+
+/// Runs the Flutter tool with support for the specified list of [commands].
+Future<int> run(
+  List<String> args,
+  List<FlutterCommand> commands, {
+  bool verbose: false,
+  bool verboseHelp: false,
+  bool reportCrashes,
+  String flutterVersion,
+}) async {
+  reportCrashes ??= !isRunningOnBot;
+
+  if (verboseHelp) {
+    // Remove the verbose option; for help, users don't need to see verbose logs.
+    args = new List<String>.from(args);
+    args.removeWhere((String option) => option == '-v' || option == '--verbose');
+  }
+
+  final FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp);
+  commands.forEach(runner.addCommand);
+
+  // Construct a context.
+  final AppContext _executableContext = new AppContext();
+
+  // Make the context current.
+  return await _executableContext.runInZone(() async {
+    // Initialize the context with some defaults.
+    // NOTE: Similar lists also exist in `bin/fuchsia_builder.dart` and
+    // `test/src/context.dart`. If you update this list of defaults, look
+    // in those locations as well to see if you need a similar update there.
+
+    // Seed these context entries first since others depend on them
+    context.putIfAbsent(Stdio, () => const Stdio());
+    context.putIfAbsent(Platform, () => const LocalPlatform());
+    context.putIfAbsent(FileSystem, () => const LocalFileSystem());
+    context.putIfAbsent(ProcessManager, () => const LocalProcessManager());
+    context.putIfAbsent(AnsiTerminal, () => new AnsiTerminal());
+    context.putIfAbsent(Logger, () => platform.isWindows ? new WindowsStdoutLogger() : new StdoutLogger());
+    context.putIfAbsent(Config, () => new Config());
+
+    // Order-independent context entries
+    context.putIfAbsent(DeviceManager, () => new DeviceManager());
+    context.putIfAbsent(DevFSConfig, () => new DevFSConfig());
+    context.putIfAbsent(Doctor, () => new Doctor());
+    context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
+    context.putIfAbsent(Cache, () => new Cache());
+    context.putIfAbsent(Artifacts, () => new CachedArtifacts());
+    context.putIfAbsent(IOSSimulatorUtils, () => new IOSSimulatorUtils());
+    context.putIfAbsent(SimControl, () => new SimControl());
+
+    // Initialize the system locale.
+    await intl.findSystemLocale();
+
+    try {
+      await runner.run(args);
+      await _exit(0);
+    } catch (error, stackTrace) {
+      String getVersion() => flutterVersion ?? FlutterVersion.getVersionString();
+      return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion);
+    }
+    return 0;
+  });
+}
+
+/// Writes the [string] to one of the standard output streams.
+@visibleForTesting
+typedef void WriteCallback([String string]);
+
+/// Writes a line to STDERR.
+///
+/// Overwrite this in tests to avoid spurious test output.
+@visibleForTesting
+WriteCallback writelnStderr = stderr.writeln;
+
+Future<int> _handleToolError(
+    dynamic error,
+    StackTrace stackTrace,
+    bool verbose,
+    List<String> args,
+    bool reportCrashes,
+    String getFlutterVersion(),
+    ) async {
+  if (error is UsageException) {
+    writelnStderr(error.message);
+    writelnStderr();
+    writelnStderr(
+        "Run 'flutter -h' (or 'flutter <command> -h') for available "
+            'flutter commands and options.'
+    );
+    // Argument error exit code.
+    return _exit(64);
+  } else if (error is ToolExit) {
+    if (error.message != null)
+      writelnStderr(error.message);
+    if (verbose) {
+      writelnStderr();
+      writelnStderr(stackTrace.toString());
+      writelnStderr();
+    }
+    return _exit(error.exitCode ?? 1);
+  } else if (error is ProcessExit) {
+    // We've caught an exit code.
+    if (error.immediate) {
+      exit(error.exitCode);
+      return error.exitCode;
+    } else {
+      return _exit(error.exitCode);
+    }
+  } else {
+    // We've crashed; emit a log report.
+    writelnStderr();
+
+    if (!reportCrashes) {
+      // Print the stack trace on the bots - don't write a crash report.
+      writelnStderr('$error');
+      writelnStderr(stackTrace.toString());
+      return _exit(1);
+    } else {
+      flutterUsage.sendException(error, stackTrace);
+
+      if (error is String)
+        writelnStderr('Oops; flutter has exited unexpectedly: "$error".');
+      else
+        writelnStderr('Oops; flutter has exited unexpectedly.');
+
+      await CrashReportSender.instance.sendReport(
+        error: error,
+        stackTrace: stackTrace,
+        getFlutterVersion: getFlutterVersion,
+      );
+      try {
+        final File file = await _createLocalCrashReport(args, error, stackTrace);
+        writelnStderr(
+          'Crash report written to ${file.path};\n'
+              'please let us know at https://github.com/flutter/flutter/issues.',
+        );
+        return _exit(1);
+      } catch (error) {
+        writelnStderr(
+          'Unable to generate crash report due to secondary error: $error\n'
+              'please let us know at https://github.com/flutter/flutter/issues.',
+        );
+        // Any exception throw here (including one thrown by `_exit()`) will
+        // get caught by our zone's `onError` handler. In order to avoid an
+        // infinite error loop, we throw an error that is recognized above
+        // and will trigger an immediate exit.
+        throw new ProcessExit(1, immediate: true);
+      }
+    }
+  }
+}
+
+/// File system used by the crash reporting logic.
+///
+/// We do not want to use the file system stored in the context because it may
+/// be recording. Additionally, in the case of a crash we do not trust the
+/// integrity of the [AppContext].
+@visibleForTesting
+FileSystem crashFileSystem = const LocalFileSystem();
+
+/// Saves the crash report to a local file.
+Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace) async {
+  File crashFile = getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');
+
+  final StringBuffer buffer = new StringBuffer();
+
+  buffer.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
+
+  buffer.writeln('## command\n');
+  buffer.writeln('flutter ${args.join(' ')}\n');
+
+  buffer.writeln('## exception\n');
+  buffer.writeln('${error.runtimeType}: $error\n');
+  buffer.writeln('```\n$stackTrace```\n');
+
+  buffer.writeln('## flutter doctor\n');
+  buffer.writeln('```\n${await _doctorText()}```');
+
+  try {
+    await crashFile.writeAsString(buffer.toString());
+  } on FileSystemException catch (_) {
+    // Fallback to the system temporary directory.
+    crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
+    try {
+      await crashFile.writeAsString(buffer.toString());
+    } on FileSystemException catch (e) {
+      printError('Could not write crash report to disk: $e');
+      printError(buffer.toString());
+    }
+  }
+
+  return crashFile;
+}
+
+Future<String> _doctorText() async {
+  try {
+    final BufferLogger logger = new BufferLogger();
+    final AppContext appContext = new AppContext();
+
+    appContext.setVariable(Logger, logger);
+
+    await appContext.runInZone(() => doctor.diagnose());
+
+    return logger.statusText;
+  } catch (error, trace) {
+    return 'encountered exception: $error\n\n${trace.toString().trim()}\n';
+  }
+}
+
+Future<int> _exit(int code) async {
+  if (flutterUsage.isFirstRun)
+    flutterUsage.printWelcome();
+
+  // Send any last analytics calls that are in progress without overly delaying
+  // the tool's exit (we wait a maximum of 250ms).
+  if (flutterUsage.enabled) {
+    final Stopwatch stopwatch = new Stopwatch()..start();
+    await flutterUsage.ensureAnalyticsSent();
+    printTrace('ensureAnalyticsSent: ${stopwatch.elapsedMilliseconds}ms');
+  }
+
+  // Run shutdown hooks before flushing logs
+  await runShutdownHooks();
+
+  final Completer<Null> completer = new Completer<Null>();
+
+  // Give the task / timer queue one cycle through before we hard exit.
+  Timer.run(() {
+    try {
+      printTrace('exiting with code $code');
+      exit(code);
+      completer.complete();
+    } catch (error, stackTrace) {
+      completer.completeError(error, stackTrace);
+    }
+  });
+
+  await completer.future;
+  return code;
+}
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index fde165e..49872b3 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -7,6 +7,7 @@
 
 import '../android/android_sdk.dart';
 import '../android/android_workflow.dart';
+import '../android/apk.dart';
 import '../application_package.dart';
 import '../base/common.dart' show throwToolExit;
 import '../base/file_system.dart';
@@ -17,7 +18,6 @@
 import '../base/process_manager.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
-import '../commands/build_apk.dart';
 import '../device.dart';
 import '../globals.dart';
 import '../protocol_discovery.dart';
diff --git a/packages/flutter_tools/lib/src/android/apk.dart b/packages/flutter_tools/lib/src/android/apk.dart
new file mode 100644
index 0000000..1c34cac
--- /dev/null
+++ b/packages/flutter_tools/lib/src/android/apk.dart
@@ -0,0 +1,37 @@
+// 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 '../base/common.dart';
+import '../build_info.dart';
+import '../globals.dart';
+import 'android_sdk.dart';
+import 'gradle.dart';
+
+Future<Null> buildApk({
+  String target,
+  BuildInfo buildInfo: BuildInfo.debug
+}) async {
+  if (!isProjectUsingGradle()) {
+    throwToolExit(
+        'The build process for Android has changed, and the current project configuration\n'
+            'is no longer valid. Please consult\n\n'
+            '  https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
+            'for details on how to upgrade the project.'
+    );
+  }
+
+  // Validate that we can find an android sdk.
+  if (androidSdk == null)
+    throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
+
+  final List<String> validationResult = androidSdk.validateSdkWellFormed();
+  if (validationResult.isNotEmpty) {
+    validationResult.forEach(printError);
+    throwToolExit('Try re-installing or updating your Android SDK.');
+  }
+
+  return buildGradleProject(buildInfo, target);
+}
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 2041908..fc9d97f 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -4,31 +4,9 @@
 
 import 'dart:async';
 
-import 'package:meta/meta.dart' show required;
-
-import '../android/android_sdk.dart';
-import '../android/gradle.dart';
-import '../base/common.dart';
-import '../build_info.dart';
-import '../globals.dart';
+import '../android/apk.dart';
 import 'build.dart';
 
-export '../android/android_device.dart' show AndroidDevice;
-
-class ApkKeystoreInfo {
-  ApkKeystoreInfo({
-    @required this.keystore,
-    this.password,
-    this.keyAlias,
-    @required this.keyPassword,
-  }) : assert(keystore != null);
-
-  final String keystore;
-  final String password;
-  final String keyAlias;
-  final String keyPassword;
-}
-
 class BuildApkCommand extends BuildSubCommand {
   BuildApkCommand() {
     usesTargetOption();
@@ -53,29 +31,3 @@
     await buildApk(buildInfo: getBuildInfo(), target: targetFile);
   }
 }
-
-Future<Null> buildApk({
-  String target,
-  BuildInfo buildInfo: BuildInfo.debug
-}) async {
-  if (!isProjectUsingGradle()) {
-    throwToolExit(
-        'The build process for Android has changed, and the current project configuration\n'
-        'is no longer valid. Please consult\n\n'
-        '  https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
-        'for details on how to upgrade the project.'
-    );
-  }
-
-  // Validate that we can find an android sdk.
-  if (androidSdk == null)
-    throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
-
-  final List<String> validationResult = androidSdk.validateSdkWellFormed();
-  if (validationResult.isNotEmpty) {
-    validationResult.forEach(printError);
-    throwToolExit('Try re-installing or updating your Android SDK.');
-  }
-
-  return buildGradleProject(buildInfo, target);
-}
diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart
index 33e88ed..045200d 100644
--- a/packages/flutter_tools/lib/src/commands/trace.dart
+++ b/packages/flutter_tools/lib/src/commands/trace.dart
@@ -8,16 +8,10 @@
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/utils.dart';
-import '../build_info.dart';
 import '../cache.dart';
 import '../globals.dart';
 import '../runner/flutter_command.dart';
-import '../vmservice.dart';
-
-// Names of some of the Timeline events we care about.
-const String kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
-const String kFrameworkInitEventName = 'Framework initialization';
-const String kFirstUsefulFrameEventName = 'Widgets completed first useful frame';
+import '../tracing.dart';
 
 class TraceCommand extends FlutterCommand {
   TraceCommand() {
@@ -93,119 +87,3 @@
     printStatus('Trace file saved to ${localFile.path}');
   }
 }
-
-class Tracing {
-  Tracing(this.vmService);
-
-  static Future<Tracing> connect(Uri uri) async {
-    final VMService observatory = await VMService.connect(uri);
-    return new Tracing(observatory);
-  }
-
-  final VMService vmService;
-
-  Future<Null> startTracing() async {
-    await vmService.vm.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
-    await vmService.vm.clearVMTimeline();
-  }
-
-  /// Stops tracing; optionally wait for first frame.
-  Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
-    bool waitForFirstFrame: false
-  }) async {
-    Map<String, dynamic> timeline;
-
-    if (!waitForFirstFrame) {
-      // Stop tracing immediately and get the timeline
-      await vmService.vm.setVMTimelineFlags(<String>[]);
-      timeline = await vmService.vm.getVMTimeline();
-    } else {
-      final Completer<Null> whenFirstFrameRendered = new Completer<Null>();
-
-      vmService.onTimelineEvent.listen((ServiceEvent timelineEvent) {
-        final List<Map<String, dynamic>> events = timelineEvent.timelineEvents;
-        for (Map<String, dynamic> event in events) {
-          if (event['name'] == kFirstUsefulFrameEventName)
-            whenFirstFrameRendered.complete();
-        }
-      });
-
-      await whenFirstFrameRendered.future.timeout(
-        const Duration(seconds: 10),
-        onTimeout: () {
-          printError(
-            'Timed out waiting for the first frame event. Either the '
-            'application failed to start, or the event was missed because '
-            '"flutter run" took too long to subscribe to timeline events.'
-          );
-          return null;
-        }
-      );
-
-      timeline = await vmService.vm.getVMTimeline();
-
-      await vmService.vm.setVMTimelineFlags(<String>[]);
-    }
-
-    return timeline;
-  }
-}
-
-/// Download the startup trace information from the given observatory client and
-/// store it to build/start_up_info.json.
-Future<Null> downloadStartupTrace(VMService observatory) async {
-  final String traceInfoFilePath = fs.path.join(getBuildDirectory(), 'start_up_info.json');
-  final File traceInfoFile = fs.file(traceInfoFilePath);
-
-  // Delete old startup data, if any.
-  if (await traceInfoFile.exists())
-    await traceInfoFile.delete();
-
-  // Create "build" directory, if missing.
-  if (!(await traceInfoFile.parent.exists()))
-    await traceInfoFile.parent.create();
-
-  final Tracing tracing = new Tracing(observatory);
-
-  final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
-    waitForFirstFrame: true
-  );
-
-  int extractInstantEventTimestamp(String eventName) {
-    final List<Map<String, dynamic>> events = timeline['traceEvents'];
-    final Map<String, dynamic> event = events.firstWhere(
-      (Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null
-    );
-    return event == null ? null : event['ts'];
-  }
-
-  final int engineEnterTimestampMicros = extractInstantEventTimestamp(kFlutterEngineMainEnterEventName);
-  final int frameworkInitTimestampMicros = extractInstantEventTimestamp(kFrameworkInitEventName);
-  final int firstFrameTimestampMicros = extractInstantEventTimestamp(kFirstUsefulFrameEventName);
-
-  if (engineEnterTimestampMicros == null) {
-    printTrace('Engine start event is missing in the timeline: $timeline');
-    throw 'Engine start event is missing in the timeline. Cannot compute startup time.';
-  }
-
-  if (firstFrameTimestampMicros == null) {
-    printTrace('First frame event is missing in the timeline: $timeline');
-    throw 'First frame event is missing in the timeline. Cannot compute startup time.';
-  }
-
-  final int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
-  final Map<String, dynamic> traceInfo = <String, dynamic>{
-    'engineEnterTimestampMicros': engineEnterTimestampMicros,
-    'timeToFirstFrameMicros': timeToFirstFrameMicros,
-  };
-
-  if (frameworkInitTimestampMicros != null) {
-    traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
-    traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
-  }
-
-  traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
-
-  printStatus('Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.');
-  printStatus('Saved startup trace info in ${traceInfoFile.path}.');
-}
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 76d8bd6..713d61c 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -7,10 +7,10 @@
 import 'package:meta/meta.dart';
 
 import 'base/file_system.dart';
-import 'commands/trace.dart';
 import 'device.dart';
 import 'globals.dart';
 import 'resident_runner.dart';
+import 'tracing.dart';
 
 class ColdRunner extends ResidentRunner {
   ColdRunner(
diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart
new file mode 100644
index 0000000..e2e258e
--- /dev/null
+++ b/packages/flutter_tools/lib/src/tracing.dart
@@ -0,0 +1,132 @@
+// 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 'base/file_system.dart';
+import 'base/utils.dart';
+import 'build_info.dart';
+import 'globals.dart';
+import 'vmservice.dart';
+
+// Names of some of the Timeline events we care about.
+const String _kFlutterEngineMainEnterEventName = 'FlutterEngineMainEnter';
+const String _kFrameworkInitEventName = 'Framework initialization';
+const String _kFirstUsefulFrameEventName = 'Widgets completed first useful frame';
+
+class Tracing {
+  Tracing(this.vmService);
+
+  static Future<Tracing> connect(Uri uri) async {
+    final VMService observatory = await VMService.connect(uri);
+    return new Tracing(observatory);
+  }
+
+  final VMService vmService;
+
+  Future<Null> startTracing() async {
+    await vmService.vm.setVMTimelineFlags(<String>['Compiler', 'Dart', 'Embedder', 'GC']);
+    await vmService.vm.clearVMTimeline();
+  }
+
+  /// Stops tracing; optionally wait for first frame.
+  Future<Map<String, dynamic>> stopTracingAndDownloadTimeline({
+    bool waitForFirstFrame: false
+  }) async {
+    Map<String, dynamic> timeline;
+
+    if (!waitForFirstFrame) {
+      // Stop tracing immediately and get the timeline
+      await vmService.vm.setVMTimelineFlags(<String>[]);
+      timeline = await vmService.vm.getVMTimeline();
+    } else {
+      final Completer<Null> whenFirstFrameRendered = new Completer<Null>();
+
+      vmService.onTimelineEvent.listen((ServiceEvent timelineEvent) {
+        final List<Map<String, dynamic>> events = timelineEvent.timelineEvents;
+        for (Map<String, dynamic> event in events) {
+          if (event['name'] == _kFirstUsefulFrameEventName)
+            whenFirstFrameRendered.complete();
+        }
+      });
+
+      await whenFirstFrameRendered.future.timeout(
+          const Duration(seconds: 10),
+          onTimeout: () {
+            printError(
+                'Timed out waiting for the first frame event. Either the '
+                    'application failed to start, or the event was missed because '
+                    '"flutter run" took too long to subscribe to timeline events.'
+            );
+            return null;
+          }
+      );
+
+      timeline = await vmService.vm.getVMTimeline();
+
+      await vmService.vm.setVMTimelineFlags(<String>[]);
+    }
+
+    return timeline;
+  }
+}
+
+/// Download the startup trace information from the given observatory client and
+/// store it to build/start_up_info.json.
+Future<Null> downloadStartupTrace(VMService observatory) async {
+  final String traceInfoFilePath = fs.path.join(getBuildDirectory(), 'start_up_info.json');
+  final File traceInfoFile = fs.file(traceInfoFilePath);
+
+  // Delete old startup data, if any.
+  if (await traceInfoFile.exists())
+    await traceInfoFile.delete();
+
+  // Create "build" directory, if missing.
+  if (!(await traceInfoFile.parent.exists()))
+    await traceInfoFile.parent.create();
+
+  final Tracing tracing = new Tracing(observatory);
+
+  final Map<String, dynamic> timeline = await tracing.stopTracingAndDownloadTimeline(
+      waitForFirstFrame: true
+  );
+
+  int extractInstantEventTimestamp(String eventName) {
+    final List<Map<String, dynamic>> events = timeline['traceEvents'];
+    final Map<String, dynamic> event = events.firstWhere(
+            (Map<String, dynamic> event) => event['name'] == eventName, orElse: () => null
+    );
+    return event == null ? null : event['ts'];
+  }
+
+  final int engineEnterTimestampMicros = extractInstantEventTimestamp(_kFlutterEngineMainEnterEventName);
+  final int frameworkInitTimestampMicros = extractInstantEventTimestamp(_kFrameworkInitEventName);
+  final int firstFrameTimestampMicros = extractInstantEventTimestamp(_kFirstUsefulFrameEventName);
+
+  if (engineEnterTimestampMicros == null) {
+    printTrace('Engine start event is missing in the timeline: $timeline');
+    throw 'Engine start event is missing in the timeline. Cannot compute startup time.';
+  }
+
+  if (firstFrameTimestampMicros == null) {
+    printTrace('First frame event is missing in the timeline: $timeline');
+    throw 'First frame event is missing in the timeline. Cannot compute startup time.';
+  }
+
+  final int timeToFirstFrameMicros = firstFrameTimestampMicros - engineEnterTimestampMicros;
+  final Map<String, dynamic> traceInfo = <String, dynamic>{
+    'engineEnterTimestampMicros': engineEnterTimestampMicros,
+    'timeToFirstFrameMicros': timeToFirstFrameMicros,
+  };
+
+  if (frameworkInitTimestampMicros != null) {
+    traceInfo['timeToFrameworkInitMicros'] = frameworkInitTimestampMicros - engineEnterTimestampMicros;
+    traceInfo['timeAfterFrameworkInitMicros'] = firstFrameTimestampMicros - frameworkInitTimestampMicros;
+  }
+
+  traceInfoFile.writeAsStringSync(toPrettyJson(traceInfo));
+
+  printStatus('Time to first frame: ${timeToFirstFrameMicros ~/ 1000}ms.');
+  printStatus('Saved startup trace info in ${traceInfoFile.path}.');
+}
diff --git a/packages/flutter_tools/test/crash_reporting_test.dart b/packages/flutter_tools/test/crash_reporting_test.dart
index c7aa531..2416d25 100644
--- a/packages/flutter_tools/test/crash_reporting_test.dart
+++ b/packages/flutter_tools/test/crash_reporting_test.dart
@@ -13,7 +13,7 @@
 import 'package:http/testing.dart';
 import 'package:test/test.dart';
 
-import 'package:flutter_tools/executable.dart' as tools;
+import 'package:flutter_tools/runner.dart' as tools;
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/base/logger.dart';
diff --git a/packages/flutter_tools/test/replay/common.dart b/packages/flutter_tools/test/replay/common.dart
index b9159e3..2467652 100644
--- a/packages/flutter_tools/test/replay/common.dart
+++ b/packages/flutter_tools/test/replay/common.dart
@@ -5,7 +5,7 @@
 import 'dart:async';
 
 import 'package:file/local.dart';
-import 'package:flutter_tools/executable.dart' as tools;
+import 'package:flutter_tools/runner.dart' as tools;
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/io.dart' as io;