Refactor flutter command exit code - part 1 (#6803)

* convert flutter commands to use ToolExit for non-zero exit code
* add convenience method throwToolExit
* print ToolExit message iff not null
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index ed7f6c0..b0dded2 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -104,7 +104,8 @@
       // Argument error exit code.
       _exit(64);
     } else if (error is ToolExit) {
-      stderr.writeln(error.message);
+      if (error.message != null)
+        stderr.writeln(error.message);
       if (verbose) {
         stderr.writeln();
         stderr.writeln(chain.terse.toString());
diff --git a/packages/flutter_tools/lib/src/base/common.dart b/packages/flutter_tools/lib/src/base/common.dart
index 4ed265b..f4f7c5b 100644
--- a/packages/flutter_tools/lib/src/base/common.dart
+++ b/packages/flutter_tools/lib/src/base/common.dart
@@ -23,6 +23,14 @@
 }
 String _homeDirPath;
 
+/// Throw a specialized exception for expected situations
+/// where the tool should exit with a clear message to the user
+/// and no stack trace unless the --verbose option is specified.
+/// For example: network errors
+void throwToolExit(String message, { int exitCode }) {
+  throw new ToolExit(message, exitCode: exitCode);
+}
+
 /// Specialized exception for expected situations
 /// where the tool should exit with a clear message to the user
 /// and no stack trace unless the --verbose option is specified.
diff --git a/packages/flutter_tools/lib/src/base/net.dart b/packages/flutter_tools/lib/src/base/net.dart
index 60dc35c..7734bde 100644
--- a/packages/flutter_tools/lib/src/base/net.dart
+++ b/packages/flutter_tools/lib/src/base/net.dart
@@ -20,7 +20,7 @@
 
   printTrace('Received response statusCode=${response.statusCode}');
   if (response.statusCode != 200) {
-    throw new ToolExit(
+    throwToolExit(
       'Download failed: $url\n'
           '  because (${response.statusCode}) ${response.reasonPhrase}',
       exitCode: kNetworkProblemExitCode,
diff --git a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart
index 0e4cb4c..dcd73cb 100644
--- a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart
@@ -9,6 +9,7 @@
 import 'package:args/args.dart';
 import 'package:path/path.dart' as path;
 
+import '../base/common.dart';
 import '../base/logger.dart';
 import '../base/utils.dart';
 import '../cache.dart';
@@ -53,7 +54,10 @@
     await server.start();
     final int exitCode = await server.onExit;
 
-    printStatus('Analysis server exited with code $exitCode.');
+    String message = 'Analysis server exited with code $exitCode.';
+    if (exitCode != 0)
+      throwToolExit(message, exitCode: exitCode);
+    printStatus(message);
     return 0;
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart
index 006ecd9..b7b8b61 100644
--- a/packages/flutter_tools/lib/src/commands/analyze_once.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart
@@ -10,6 +10,7 @@
 import 'package:path/path.dart' as path;
 import 'package:yaml/yaml.dart' as yaml;
 
+import '../base/common.dart';
 import '../cache.dart';
 import '../dart/analysis.dart';
 import '../globals.dart';
@@ -123,12 +124,18 @@
 
     // prepare a union of all the .packages files
     if (dependencies.hasConflicts) {
-      printError(dependencies.generateConflictReport());
-      printError('Make sure you have run "pub upgrade" in all the directories mentioned above.');
-      if (dependencies.hasConflictsAffectingFlutterRepo)
-        printError('For packages in the flutter repository, try using "flutter update-packages --upgrade" to do all of them at once.');
-      printError('If this does not help, to track down the conflict you can use "pub deps --style=list" and "pub upgrade --verbosity=solver" in the affected directories.');
-      return 1;
+      StringBuffer message = new StringBuffer();
+      message.writeln(dependencies.generateConflictReport());
+      message.writeln('Make sure you have run "pub upgrade" in all the directories mentioned above.');
+      if (dependencies.hasConflictsAffectingFlutterRepo) {
+        message.writeln(
+            'For packages in the flutter repository, try using '
+            '"flutter update-packages --upgrade" to do all of them at once.');
+      }
+      message.write(
+          'If this does not help, to track down the conflict you can use '
+          '"pub deps --style=list" and "pub upgrade --verbosity=solver" in the affected directories.');
+      throwToolExit(message.toString());
     }
     Map<String, String> packages = dependencies.asPackageMap();
 
@@ -185,11 +192,11 @@
       writeBenchmark(stopwatch, errorCount, membersMissingDocumentation);
 
     if (errorCount > 0) {
+      // we consider any level of error to be an error exit (we don't report different levels)
       if (membersMissingDocumentation > 0 && flutterRepo)
-        printError('[lint] $membersMissingDocumentation public ${ membersMissingDocumentation == 1 ? "member lacks" : "members lack" } documentation (ran in ${elapsed}s)');
+        throwToolExit('[lint] $membersMissingDocumentation public ${ membersMissingDocumentation == 1 ? "member lacks" : "members lack" } documentation (ran in ${elapsed}s)');
       else
-        print('(Ran in ${elapsed}s)');
-      return 1; // we consider any level of error to be an error exit (we don't report different levels)
+        throwToolExit('(Ran in ${elapsed}s)');
     }
     if (argResults['congratulate']) {
       if (membersMissingDocumentation > 0 && flutterRepo) {
diff --git a/packages/flutter_tools/lib/src/commands/channel.dart b/packages/flutter_tools/lib/src/commands/channel.dart
index bc564d5..236c2a6 100644
--- a/packages/flutter_tools/lib/src/commands/channel.dart
+++ b/packages/flutter_tools/lib/src/commands/channel.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../base/common.dart';
 import '../base/process.dart';
 import '../cache.dart';
 import '../globals.dart';
@@ -20,16 +21,14 @@
   String get invocation => '${runner.executableName} $name [<channel-name>]';
 
   @override
-  Future<int> runCommand() async {
+  Future<int> runCommand() {
     switch (argResults.rest.length) {
       case 0:
-        return await _listChannels();
+        return _listChannels();
       case 1:
-        return await _switchChannel(argResults.rest[0]);
+        return _switchChannel(argResults.rest[0]);
       default:
-        printStatus('Too many arguments.');
-        printStatus(usage);
-        return 2;
+        throw new ToolExit('Too many arguments.\n$usage');
     }
   }
 
@@ -39,7 +38,7 @@
         workingDirectory: Cache.flutterRoot);
 
     printStatus('Flutter channels:');
-    return runCommandAndStreamOutput(
+    int result = await runCommandAndStreamOutput(
       <String>['git', 'branch', '-r'],
       workingDirectory: Cache.flutterRoot,
       mapFunction: (String line) {
@@ -51,13 +50,19 @@
         return '  $branchName';
       },
     );
+    if (result != 0)
+      throwToolExit('List channels failed: $result', exitCode: result);
+    return 0;
   }
 
-  Future<int> _switchChannel(String branchName) {
+  Future<int> _switchChannel(String branchName) async {
     printStatus('Switching to flutter channel named $branchName');
-    return runCommandAndStreamOutput(
+    int result = await runCommandAndStreamOutput(
       <String>['git', 'checkout', branchName],
       workingDirectory: Cache.flutterRoot,
     );
+    if (result != 0)
+      throwToolExit('Switch channel failed: $result', exitCode: result);
+    return 0;
   }
 }
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 781bb62..47c8d43 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -8,6 +8,7 @@
 import 'package:path/path.dart' as path;
 
 import '../android/android.dart' as android;
+import '../base/common.dart';
 import '../base/utils.dart';
 import '../cache.dart';
 import '../dart/pub.dart';
@@ -46,28 +47,23 @@
 
   @override
   Future<int> runCommand() async {
-    if (argResults.rest.isEmpty) {
-      printError('No option specified for the output directory.');
-      printError(usage);
-      return 2;
-    }
+    if (argResults.rest.isEmpty)
+      throwToolExit('No option specified for the output directory.\n$usage', exitCode: 2);
 
     if (argResults.rest.length > 1) {
-      printError('Multiple output directories specified.');
+      String message = 'Multiple output directories specified.';
       for (String arg in argResults.rest) {
         if (arg.startsWith('-')) {
-          printError('Try moving $arg to be immediately following $name');
+          message += '\nTry moving $arg to be immediately following $name';
           break;
         }
       }
-      return 2;
+      throwToolExit(message, exitCode: 2);
     }
 
-    if (Cache.flutterRoot == null) {
-      printError('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment\n'
-        'variable was specified. Unable to find package:flutter.');
-      return 2;
-    }
+    if (Cache.flutterRoot == null)
+      throwToolExit('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment\n'
+        'variable was specified. Unable to find package:flutter.', exitCode: 2);
 
     await Cache.instance.updateAll();
 
@@ -75,16 +71,12 @@
 
     String flutterPackagesDirectory = path.join(flutterRoot, 'packages');
     String flutterPackagePath = path.join(flutterPackagesDirectory, 'flutter');
-    if (!FileSystemEntity.isFileSync(path.join(flutterPackagePath, 'pubspec.yaml'))) {
-      printError('Unable to find package:flutter in $flutterPackagePath');
-      return 2;
-    }
+    if (!FileSystemEntity.isFileSync(path.join(flutterPackagePath, 'pubspec.yaml')))
+      throwToolExit('Unable to find package:flutter in $flutterPackagePath', exitCode: 2);
 
     String flutterDriverPackagePath = path.join(flutterRoot, 'packages', 'flutter_driver');
-    if (!FileSystemEntity.isFileSync(path.join(flutterDriverPackagePath, 'pubspec.yaml'))) {
-      printError('Unable to find package:flutter_driver in $flutterDriverPackagePath');
-      return 2;
-    }
+    if (!FileSystemEntity.isFileSync(path.join(flutterDriverPackagePath, 'pubspec.yaml')))
+      throwToolExit('Unable to find package:flutter_driver in $flutterDriverPackagePath', exitCode: 2);
 
     Directory projectDir = new Directory(argResults.rest.first);
     String dirPath = path.normalize(projectDir.absolute.path);
@@ -92,16 +84,12 @@
     String projectName = _normalizeProjectName(path.basename(dirPath));
 
     String error =_validateProjectDir(dirPath, flutterRoot: flutterRoot);
-    if (error != null) {
-      printError(error);
-      return 1;
-    }
+    if (error != null)
+      throwToolExit(error);
 
     error = _validateProjectName(projectName);
-    if (error != null) {
-      printError(error);
-      return 1;
-    }
+    if (error != null)
+      throwToolExit(error);
 
     int generatedCount = _renderTemplates(
       projectName,
@@ -114,11 +102,8 @@
 
     printStatus('');
 
-    if (argResults['pub']) {
-      int code = await pubGet(directory: dirPath);
-      if (code != 0)
-        return code;
-    }
+    if (argResults['pub'])
+      await pubGet(directory: dirPath);
 
     printStatus('');
 
@@ -149,7 +134,6 @@
         "directory in order to launch your app.");
       printStatus("Your main program file is: $relativePath/lib/main.dart");
     }
-
     return 0;
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 3c5e66d..f55f6bb 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -7,6 +7,7 @@
 import 'dart:io';
 
 import '../android/android_device.dart';
+import '../base/common.dart';
 import '../base/context.dart';
 import '../base/logger.dart';
 import '../base/utils.dart';
@@ -51,12 +52,15 @@
 
     Cache.releaseLockEarly();
 
-    return appContext.runInZone(() {
+    return appContext.runInZone(() async {
       Daemon daemon = new Daemon(
           stdinCommandStream, stdoutCommandResponse,
           daemonCommand: this, notifyingLogger: notifyingLogger);
 
-      return daemon.onExit;
+      int code = await daemon.onExit;
+      if (code != null)
+        throwToolExit(null, exitCode: code);
+      return 0;
     }, onError: _handleError);
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/devices.dart b/packages/flutter_tools/lib/src/commands/devices.dart
index f8670e1..afe91a8 100644
--- a/packages/flutter_tools/lib/src/commands/devices.dart
+++ b/packages/flutter_tools/lib/src/commands/devices.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../base/common.dart';
 import '../base/utils.dart';
 import '../device.dart';
 import '../globals.dart';
@@ -19,9 +20,10 @@
   @override
   Future<int> runCommand() async {
     if (!doctor.canListAnything) {
-      printError("Unable to locate a development device; please run 'flutter doctor' for "
-        "information about installing additional components.");
-      return 1;
+      throwToolExit(
+        "Unable to locate a development device; please run 'flutter doctor' for "
+        "information about installing additional components.",
+        exitCode: 1);
     }
 
     List<Device> devices = await deviceManager.getAllConnectedDevices();
@@ -35,7 +37,6 @@
       printStatus('${devices.length} connected ${pluralize('device', devices.length)}:\n');
       Device.printDevices(devices);
     }
-
     return 0;
   }
 }