Remove ExitWithException (#2755)

diff --git a/lib/src/command.dart b/lib/src/command.dart
index a6ecae2..37f1335 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -132,6 +132,24 @@
       ? '<subcommand> [arguments...]'
       : (takesArguments ? '[arguments...]' : '');
 
+  /// If not `null` this overrides the default exit-code [exit_codes.SUCCESS]
+  /// when exiting successfully.
+  ///
+  /// This should only be modified by [overrideExitCode].
+  int _exitCodeOverride;
+
+  /// Override the exit code that would normally be used when exiting
+  /// successfully. Intended to be used by subcommands like `run` that wishes
+  /// to control the top-level exitcode.
+  ///
+  /// This may only be called once.
+  @nonVirtual
+  @protected
+  void overrideExitCode(int exitCode) {
+    assert(_exitCodeOverride == null, 'overrideExitCode was called twice!');
+    _exitCodeOverride = exitCode;
+  }
+
   @override
   @nonVirtual
   FutureOr<int> run() async {
@@ -145,9 +163,10 @@
     try {
       await captureErrors(runProtected,
           captureStackChains: _pubTopLevel.captureStackChains);
+      if (_exitCodeOverride != null) {
+        return _exitCodeOverride;
+      }
       return exit_codes.SUCCESS;
-    } on ExitWithException catch (e) {
-      return e.exitCode;
     } catch (error, chain) {
       log.exception(error, chain);
 
diff --git a/lib/src/command/cache_repair.dart b/lib/src/command/cache_repair.dart
index 314d736..41bae2b 100644
--- a/lib/src/command/cache_repair.dart
+++ b/lib/src/command/cache_repair.dart
@@ -5,7 +5,6 @@
 import 'dart:async';
 
 import '../command.dart';
-import '../exceptions.dart';
 import '../exit_codes.dart' as exit_codes;
 import '../log.dart' as log;
 import '../source/cached.dart';
@@ -84,7 +83,7 @@
     }
 
     if (failures.isNotEmpty || globalRepairResults.last.isNotEmpty) {
-      throw ExitWithException(exit_codes.UNAVAILABLE);
+      overrideExitCode(exit_codes.UNAVAILABLE);
     }
   }
 }
diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index 165997a..b8f7f1b 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -7,7 +7,6 @@
 import 'package:path/path.dart' as p;
 
 import '../command.dart';
-import '../exceptions.dart';
 import '../executable.dart';
 import '../log.dart' as log;
 import '../utils.dart';
@@ -78,6 +77,6 @@
           () => globalEntrypoint.precompileExecutable(executable)),
       alwaysUseSubprocess: alwaysUseSubprocess,
     );
-    throw ExitWithException(exitCode);
+    overrideExitCode(exitCode);
   }
 }
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index f33a529..ab7cd6c 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -8,7 +8,6 @@
 
 import '../ascii_tree.dart' as tree;
 import '../command.dart';
-import '../exceptions.dart';
 import '../exit_codes.dart' as exit_codes;
 import '../http.dart';
 import '../io.dart';
@@ -152,7 +151,8 @@
     var isValid =
         await _validate(packageBytesFuture.then((bytes) => bytes.length));
     if (!isValid) {
-      throw ExitWithException(exit_codes.DATA);
+      overrideExitCode(exit_codes.DATA);
+      return;
     } else if (dryRun) {
       return;
     } else {
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index 567a761..7491d61 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -7,7 +7,6 @@
 import 'package:path/path.dart' as p;
 
 import '../command.dart';
-import '../exceptions.dart';
 import '../executable.dart';
 import '../log.dart' as log;
 import '../utils.dart';
@@ -99,7 +98,7 @@
       vmArgs: vmArgs,
       alwaysUseSubprocess: alwaysUseSubprocess,
     );
-    throw ExitWithException(exitCode);
+    overrideExitCode(exitCode);
   }
 
   /// Implement a mode for use in `dartdev run`.
@@ -139,7 +138,7 @@
 
     final vmArgs = vmArgsFromArgResults(argResults);
 
-    throw ExitWithException(
+    overrideExitCode(
       await runExecutable(
         entrypoint,
         Executable(package, 'bin/$command.dart'),
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index f80419c..6fd09d2 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -6,7 +6,6 @@
 import 'dart:io';
 
 import '../command.dart';
-import '../exceptions.dart';
 import '../exit_codes.dart' as exit_codes;
 import '../http.dart';
 import '../log.dart' as log;
@@ -52,7 +51,8 @@
     if (argResults.rest.isEmpty) {
       log.error('No uploader command given.');
       printUsage();
-      throw ExitWithException(exit_codes.USAGE);
+      overrideExitCode(exit_codes.USAGE);
+      return;
     }
 
     var rest = argResults.rest.toList();
@@ -62,11 +62,13 @@
     if (!['add', 'remove'].contains(command)) {
       log.error('Unknown uploader command "$command".');
       printUsage();
-      throw ExitWithException(exit_codes.USAGE);
+      overrideExitCode(exit_codes.USAGE);
+      return;
     } else if (rest.isEmpty) {
       log.error('No uploader given for "pub uploader $command".');
       printUsage();
-      throw ExitWithException(exit_codes.USAGE);
+      overrideExitCode(exit_codes.USAGE);
+      return;
     }
 
     final package = argResults['package'] ?? entrypoint.root.name;
diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart
index 8e13b27..4e49504 100644
--- a/lib/src/exceptions.dart
+++ b/lib/src/exceptions.dart
@@ -108,9 +108,3 @@
       error is YamlException ||
       error is UsageException;
 }
-
-/// Used to signal a specific error code from a [PubCommand].
-class ExitWithException implements Exception {
-  final int exitCode;
-  ExitWithException(this.exitCode);
-}