Begin migrating tools to NNBD (#3891)

- Updates dependencies to null-safe versions
- Migrates common.dart (which doesn't depend on anything)
- Migrates common_tests.dart and its one dependency, utils.dart
- Adds build_runner for Mockito mock generation
- Adds a new utility methods for getting arguments that handle both the casting and the removal of nullability to address a common problematic pattern while migrating code.
  - Converts all files, not just the migrated ones, to those new helpers.

Migrating common.dart and utils.dart should unblock a command-by-command migration to null safety.

Reverts the separate of podspect lints into a step that doesn't do a Flutter upgrade
(https://github.com/flutter/plugins/pull/3700) because without that step we had a
version of Dart too old to run null-safe tooling.

First step of https://github.com/flutter/flutter/issues/81912
diff --git a/.cirrus.yml b/.cirrus.yml
index e3d6f78..2598bfe 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -194,6 +194,12 @@
   << : *MACOS_TEMPLATE
   << : *FLUTTER_UPGRADE_TEMPLATE
   matrix:
+    ### iOS+macOS tasks ***
+    - name: lint_darwin_plugins
+      script:
+        # TODO(jmagman): Lint macOS podspecs but skip any that fail library validation.
+        - find . -name "*.podspec" | xargs grep -l "osx" | xargs rm
+        - ./script/tool_runner.sh podspecs
     ### iOS tasks ###
     - name: build_all_plugins_ipa
       env:
@@ -251,14 +257,3 @@
         - ./script/tool_runner.sh build-examples --macos --no-ipa
       drive_script:
         - ./script/tool_runner.sh drive-examples --macos
-
-task:
-  # Don't use FLUTTER_UPGRADE_TEMPLATE, Flutter tooling not needed.
-  << : *MACOS_TEMPLATE
-  << : *TOOL_SETUP_TEMPLATE
-  matrix:
-    - name: lint_darwin_plugins
-      script:
-        # TODO(jmagman): Lint macOS podspecs but skip any that fail library validation.
-        - find . -name "*.podspec" | xargs grep -l "osx" | xargs rm
-        - ./script/tool_runner.sh podspecs
diff --git a/script/tool/bin/flutter_plugin_tools.dart b/script/tool/bin/flutter_plugin_tools.dart
index 0f30bee..eea0db5 100644
--- a/script/tool/bin/flutter_plugin_tools.dart
+++ b/script/tool/bin/flutter_plugin_tools.dart
@@ -2,4 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 export 'package:flutter_plugin_tools/src/main.dart';
diff --git a/script/tool/lib/src/analyze_command.dart b/script/tool/lib/src/analyze_command.dart
index 61be5f9..ec22e06 100644
--- a/script/tool/lib/src/analyze_command.dart
+++ b/script/tool/lib/src/analyze_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 
 import 'package:file/file.dart';
@@ -42,8 +44,8 @@
         continue;
       }
 
-      final bool allowed = (argResults[_customAnalysisFlag] as List<String>)
-          .any((String directory) =>
+      final bool allowed = (getStringListArg(_customAnalysisFlag)).any(
+          (String directory) =>
               directory != null &&
               directory.isNotEmpty &&
               p.isWithin(p.join(packagesDir.path, directory), file.path));
diff --git a/script/tool/lib/src/build_examples_command.dart b/script/tool/lib/src/build_examples_command.dart
index 0069c05..f712986 100644
--- a/script/tool/lib/src/build_examples_command.dart
+++ b/script/tool/lib/src/build_examples_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:io' as io;
 
@@ -52,7 +54,7 @@
     ];
     final Map<String, bool> platforms = <String, bool>{
       for (final String platform in platformSwitches)
-        platform: argResults[platform] as bool
+        platform: getBoolArg(platform)
     };
     if (!platforms.values.any((bool enabled) => enabled)) {
       print(
@@ -63,7 +65,7 @@
     final String flutterCommand =
         const LocalPlatform().isWindows ? 'flutter.bat' : 'flutter';
 
-    final String enableExperiment = argResults[kEnableExperiment] as String;
+    final String enableExperiment = getStringArg(kEnableExperiment);
 
     final List<String> failingPackages = <String>[];
     await for (final Directory plugin in getPlugins()) {
diff --git a/script/tool/lib/src/common.dart b/script/tool/lib/src/common.dart
index 1e864fc..e975ec1 100644
--- a/script/tool/lib/src/common.dart
+++ b/script/tool/lib/src/common.dart
@@ -12,14 +12,13 @@
 import 'package:file/file.dart';
 import 'package:git/git.dart';
 import 'package:http/http.dart' as http;
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 
 /// The signature for a print handler for commands that allow overriding the
 /// print destination.
-typedef Print = void Function(Object object);
+typedef Print = void Function(Object? object);
 
 /// Key for windows platform.
 const String kWindows = 'windows';
@@ -50,7 +49,7 @@
 
 /// Returns whether the given directory contains a Flutter package.
 bool isFlutterPackage(FileSystemEntity entity, FileSystem fileSystem) {
-  if (entity == null || entity is! Directory) {
+  if (entity is! Directory) {
     return false;
   }
 
@@ -59,7 +58,7 @@
         fileSystem.file(p.join(entity.path, 'pubspec.yaml'));
     final YamlMap pubspecYaml =
         loadYaml(pubspecFile.readAsStringSync()) as YamlMap;
-    final YamlMap dependencies = pubspecYaml['dependencies'] as YamlMap;
+    final YamlMap? dependencies = pubspecYaml['dependencies'] as YamlMap?;
     if (dependencies == null) {
       return false;
     }
@@ -87,7 +86,7 @@
       platform == kMacos ||
       platform == kWindows ||
       platform == kLinux);
-  if (entity == null || entity is! Directory) {
+  if (entity is! Directory) {
     return false;
   }
 
@@ -96,15 +95,15 @@
         fileSystem.file(p.join(entity.path, 'pubspec.yaml'));
     final YamlMap pubspecYaml =
         loadYaml(pubspecFile.readAsStringSync()) as YamlMap;
-    final YamlMap flutterSection = pubspecYaml['flutter'] as YamlMap;
+    final YamlMap? flutterSection = pubspecYaml['flutter'] as YamlMap?;
     if (flutterSection == null) {
       return false;
     }
-    final YamlMap pluginSection = flutterSection['plugin'] as YamlMap;
+    final YamlMap? pluginSection = flutterSection['plugin'] as YamlMap?;
     if (pluginSection == null) {
       return false;
     }
-    final YamlMap platforms = pluginSection['platforms'] as YamlMap;
+    final YamlMap? platforms = pluginSection['platforms'] as YamlMap?;
     if (platforms == null) {
       // Legacy plugin specs are assumed to support iOS and Android.
       if (!pluginSection.containsKey('platforms')) {
@@ -151,7 +150,7 @@
 }
 
 /// Throws a [ToolExit] with `exitCode` and log the `errorMessage` in red.
-void printErrorAndExit({@required String errorMessage, int exitCode = 1}) {
+void printErrorAndExit({required String errorMessage, int exitCode = 1}) {
   final Colorize redError = Colorize(errorMessage)..red();
   print(redError);
   throw ToolExit(exitCode);
@@ -236,17 +235,17 @@
   /// The git directory to use. By default it uses the parent directory.
   ///
   /// This can be mocked for testing.
-  final GitDir gitDir;
+  final GitDir? gitDir;
 
-  int _shardIndex;
-  int _shardCount;
+  int? _shardIndex;
+  int? _shardCount;
 
   /// The shard of the overall command execution that this instance should run.
   int get shardIndex {
     if (_shardIndex == null) {
       _checkSharding();
     }
-    return _shardIndex;
+    return _shardIndex!;
   }
 
   /// The number of shards this command is divided into.
@@ -254,12 +253,27 @@
     if (_shardCount == null) {
       _checkSharding();
     }
-    return _shardCount;
+    return _shardCount!;
+  }
+
+  /// Convenience accessor for boolean arguments.
+  bool getBoolArg(String key) {
+    return (argResults![key] as bool?) ?? false;
+  }
+
+  /// Convenience accessor for String arguments.
+  String getStringArg(String key) {
+    return (argResults![key] as String?) ?? '';
+  }
+
+  /// Convenience accessor for List<String> arguments.
+  List<String> getStringListArg(String key) {
+    return (argResults![key] as List<String>?) ?? <String>[];
   }
 
   void _checkSharding() {
-    final int shardIndex = int.tryParse(argResults[_shardIndexArg] as String);
-    final int shardCount = int.tryParse(argResults[_shardCountArg] as String);
+    final int? shardIndex = int.tryParse(getStringArg(_shardIndexArg));
+    final int? shardCount = int.tryParse(getStringArg(_shardCountArg));
     if (shardIndex == null) {
       usageException('$_shardIndexArg must be an integer');
     }
@@ -317,12 +331,10 @@
   ///    is a sibling of the packages directory. This is used for a small number
   ///    of packages in the flutter/packages repository.
   Stream<Directory> _getAllPlugins() async* {
-    Set<String> plugins =
-        Set<String>.from(argResults[_pluginsArg] as List<String>);
+    Set<String> plugins = Set<String>.from(getStringListArg(_pluginsArg));
     final Set<String> excludedPlugins =
-        Set<String>.from(argResults[_excludeArg] as List<String>);
-    final bool runOnChangedPackages =
-        argResults[_runOnChangedPackagesArg] as bool;
+        Set<String>.from(getStringListArg(_excludeArg));
+    final bool runOnChangedPackages = getBoolArg(_runOnChangedPackagesArg);
     if (plugins.isEmpty && runOnChangedPackages) {
       plugins = await _getChangedPackages();
     }
@@ -429,9 +441,9 @@
   /// Throws tool exit if [gitDir] nor root directory is a git directory.
   Future<GitVersionFinder> retrieveVersionFinder() async {
     final String rootDir = packagesDir.parent.absolute.path;
-    final String baseSha = argResults[_kBaseSha] as String;
+    final String baseSha = getStringArg(_kBaseSha);
 
-    GitDir baseGitDir = gitDir;
+    GitDir? baseGitDir = gitDir;
     if (baseGitDir == null) {
       if (!await GitDir.isGitDir(rootDir)) {
         printErrorAndExit(
@@ -490,7 +502,7 @@
   Future<int> runAndStream(
     String executable,
     List<String> args, {
-    Directory workingDir,
+    Directory? workingDir,
     bool exitOnError = false,
   }) async {
     print(
@@ -522,7 +534,7 @@
   ///
   /// Returns the [io.ProcessResult] of the [executable].
   Future<io.ProcessResult> run(String executable, List<String> args,
-      {Directory workingDir,
+      {Directory? workingDir,
       bool exitOnError = false,
       bool logOnError = false,
       Encoding stdoutEncoding = io.systemEncoding,
@@ -550,15 +562,15 @@
   /// passing [workingDir].
   ///
   /// Returns the started [io.Process].
-  Future<io.Process> start(String executable, List<String> args,
-      {Directory workingDirectory}) async {
+  Future<io.Process?> start(String executable, List<String> args,
+      {Directory? workingDirectory}) async {
     final io.Process process = await io.Process.start(executable, args,
         workingDirectory: workingDirectory?.path);
     return process;
   }
 
   String _getErrorString(String executable, List<String> args,
-      {Directory workingDir}) {
+      {Directory? workingDir}) {
     final String workdir = workingDir == null ? '' : ' in ${workingDir.path}';
     return 'ERROR: Unable to execute "$executable ${args.join(' ')}"$workdir.';
   }
@@ -569,7 +581,7 @@
   /// Constructor.
   ///
   /// Note: you should manually close the [httpClient] when done using the finder.
-  PubVersionFinder({this.pubHost = defaultPubHost, @required this.httpClient});
+  PubVersionFinder({this.pubHost = defaultPubHost, required this.httpClient});
 
   /// The default pub host to use.
   static const String defaultPubHost = 'https://pub.dev';
@@ -584,8 +596,8 @@
 
   /// Get the package version on pub.
   Future<PubVersionFinderResponse> getPackageVersion(
-      {@required String package}) async {
-    assert(package != null && package.isNotEmpty);
+      {required String package}) async {
+    assert(package.isNotEmpty);
     final Uri pubHostUri = Uri.parse(pubHost);
     final Uri url = pubHostUri.replace(path: '/packages/$package.json');
     final http.Response response = await httpClient.get(url);
@@ -618,8 +630,8 @@
 class PubVersionFinderResponse {
   /// Constructor.
   PubVersionFinderResponse({this.versions, this.result, this.httpResponse}) {
-    if (versions != null && versions.isNotEmpty) {
-      versions.sort((Version a, Version b) {
+    if (versions != null && versions!.isNotEmpty) {
+      versions!.sort((Version a, Version b) {
         // TODO(cyanglaz): Think about how to handle pre-release version with [Version.prioritize].
         // https://github.com/flutter/flutter/issues/82222
         return b.compareTo(a);
@@ -631,13 +643,13 @@
   ///
   /// This is sorted by largest to smallest, so the first element in the list is the largest version.
   /// Might be `null` if the [result] is not [PubVersionFinderResult.success].
-  final List<Version> versions;
+  final List<Version>? versions;
 
   /// The result of the version finder.
-  final PubVersionFinderResult result;
+  final PubVersionFinderResult? result;
 
   /// The response object of the http request.
-  final http.Response httpResponse;
+  final http.Response? httpResponse;
 }
 
 /// An enum representing the result of [PubVersionFinder].
@@ -667,7 +679,7 @@
   final GitDir baseGitDir;
 
   /// The base sha used to get diff.
-  final String baseSha;
+  final String? baseSha;
 
   static bool _isPubspec(String file) {
     return file.trim().endsWith('pubspec.yaml');
@@ -684,8 +696,7 @@
     final io.ProcessResult changedFilesCommand = await baseGitDir
         .runCommand(<String>['diff', '--name-only', baseSha, 'HEAD']);
     print('Determine diff with base sha: $baseSha');
-    final String changedFilesStdout =
-        changedFilesCommand.stdout.toString() ?? '';
+    final String changedFilesStdout = changedFilesCommand.stdout.toString();
     if (changedFilesStdout.isEmpty) {
       return <String>[];
     }
@@ -696,7 +707,8 @@
 
   /// Get the package version specified in the pubspec file in `pubspecPath` and
   /// at the revision of `gitRef` (defaulting to the base if not provided).
-  Future<Version> getPackageVersion(String pubspecPath, {String gitRef}) async {
+  Future<Version?> getPackageVersion(String pubspecPath,
+      {String? gitRef}) async {
     final String ref = gitRef ?? (await _getBaseSha());
 
     io.ProcessResult gitShow;
@@ -707,20 +719,19 @@
       return null;
     }
     final String fileContent = gitShow.stdout as String;
-    final String versionString = loadYaml(fileContent)['version'] as String;
+    final String? versionString = loadYaml(fileContent)['version'] as String?;
     return versionString == null ? null : Version.parse(versionString);
   }
 
   Future<String> _getBaseSha() async {
-    if (baseSha != null && baseSha.isNotEmpty) {
-      return baseSha;
+    if (baseSha != null && baseSha!.isNotEmpty) {
+      return baseSha!;
     }
 
     io.ProcessResult baseShaFromMergeBase = await baseGitDir.runCommand(
         <String>['merge-base', '--fork-point', 'FETCH_HEAD', 'HEAD'],
         throwOnError: false);
-    if (baseShaFromMergeBase == null ||
-        baseShaFromMergeBase.stderr != null ||
+    if (baseShaFromMergeBase.stderr != null ||
         baseShaFromMergeBase.stdout == null) {
       baseShaFromMergeBase = await baseGitDir
           .runCommand(<String>['merge-base', 'FETCH_HEAD', 'HEAD']);
diff --git a/script/tool/lib/src/create_all_plugins_app_command.dart b/script/tool/lib/src/create_all_plugins_app_command.dart
index 4b5b4c6..d825703 100644
--- a/script/tool/lib/src/create_all_plugins_app_command.dart
+++ b/script/tool/lib/src/create_all_plugins_app_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:io' as io;
 
diff --git a/script/tool/lib/src/drive_examples_command.dart b/script/tool/lib/src/drive_examples_command.dart
index 1572b94..76ba820 100644
--- a/script/tool/lib/src/drive_examples_command.dart
+++ b/script/tool/lib/src/drive_examples_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'package:file/file.dart';
 import 'package:path/path.dart' as p;
@@ -53,10 +55,10 @@
   Future<void> run() async {
     final List<String> failingTests = <String>[];
     final List<String> pluginsWithoutTests = <String>[];
-    final bool isLinux = argResults[kLinux] == true;
-    final bool isMacos = argResults[kMacos] == true;
-    final bool isWeb = argResults[kWeb] == true;
-    final bool isWindows = argResults[kWindows] == true;
+    final bool isLinux = getBoolArg(kLinux);
+    final bool isMacos = getBoolArg(kMacos);
+    final bool isWeb = getBoolArg(kWeb);
+    final bool isWindows = getBoolArg(kWindows);
     await for (final Directory plugin in getPlugins()) {
       final String pluginName = plugin.basename;
       if (pluginName.endsWith('_platform_interface') &&
@@ -140,8 +142,7 @@
 
           final List<String> driveArgs = <String>['drive'];
 
-          final String enableExperiment =
-              argResults[kEnableExperiment] as String;
+          final String enableExperiment = getStringArg(kEnableExperiment);
           if (enableExperiment.isNotEmpty) {
             driveArgs.add('--enable-experiment=$enableExperiment');
           }
@@ -222,12 +223,12 @@
 
   Future<bool> _pluginSupportedOnCurrentPlatform(
       FileSystemEntity plugin, FileSystem fileSystem) async {
-    final bool isAndroid = argResults[kAndroid] == true;
-    final bool isIOS = argResults[kIos] == true;
-    final bool isLinux = argResults[kLinux] == true;
-    final bool isMacos = argResults[kMacos] == true;
-    final bool isWeb = argResults[kWeb] == true;
-    final bool isWindows = argResults[kWindows] == true;
+    final bool isAndroid = getBoolArg(kAndroid);
+    final bool isIOS = getBoolArg(kIos);
+    final bool isLinux = getBoolArg(kLinux);
+    final bool isMacos = getBoolArg(kMacos);
+    final bool isWeb = getBoolArg(kWeb);
+    final bool isWindows = getBoolArg(kWindows);
     if (isAndroid) {
       return isAndroidPlugin(plugin, fileSystem);
     }
diff --git a/script/tool/lib/src/firebase_test_lab_command.dart b/script/tool/lib/src/firebase_test_lab_command.dart
index ff7d05b..2d91def 100644
--- a/script/tool/lib/src/firebase_test_lab_command.dart
+++ b/script/tool/lib/src/firebase_test_lab_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:io' as io;
 
@@ -30,7 +32,7 @@
         defaultsTo:
             p.join(io.Platform.environment['HOME'], 'gcloud-service-key.json'));
     argParser.addOption('test-run-id',
-        defaultsTo: Uuid().v4(),
+        defaultsTo: const Uuid().v4(),
         help:
             'Optional string to append to the results path, to avoid conflicts. '
             'Randomly chosen on each invocation if none is provided. '
@@ -78,7 +80,7 @@
       <String>[
         'auth',
         'activate-service-account',
-        '--key-file=${argResults['service-key']}',
+        '--key-file=${getStringArg('service-key')}',
       ],
       exitOnError: true,
       logOnError: true,
@@ -87,7 +89,7 @@
       'config',
       'set',
       'project',
-      argResults['project'] as String,
+      getStringArg('project'),
     ]);
     if (exitCode == 0) {
       _print('\nFirebase project configured.');
@@ -125,7 +127,7 @@
       final Directory androidDirectory =
           fileSystem.directory(p.join(exampleDirectory.path, 'android'));
 
-      final String enableExperiment = argResults[kEnableExperiment] as String;
+      final String enableExperiment = getStringArg(kEnableExperiment);
       final String encodedEnableExperiment =
           Uri.encodeComponent('--enable-experiment=$enableExperiment');
 
@@ -213,7 +215,7 @@
             continue;
           }
           final String buildId = io.Platform.environment['CIRRUS_BUILD_ID'];
-          final String testRunId = argResults['test-run-id'] as String;
+          final String testRunId = getStringArg('test-run-id');
           final String resultsDir =
               'plugins_android_test/$packageName/$buildId/$testRunId/${resultsCounter++}/';
           final List<String> args = <String>[
@@ -229,10 +231,10 @@
             'build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk',
             '--timeout',
             '5m',
-            '--results-bucket=${argResults['results-bucket']}',
+            '--results-bucket=${getStringArg('results-bucket')}',
             '--results-dir=$resultsDir',
           ];
-          for (final String device in argResults['device'] as List<String>) {
+          for (final String device in getStringListArg('device')) {
             args.addAll(<String>['--device', device]);
           }
           exitCode = await processRunner.runAndStream('gcloud', args,
diff --git a/script/tool/lib/src/format_command.dart b/script/tool/lib/src/format_command.dart
index 9c29f0f..b3a8bd0 100644
--- a/script/tool/lib/src/format_command.dart
+++ b/script/tool/lib/src/format_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
@@ -13,8 +15,8 @@
 
 import 'common.dart';
 
-const String _googleFormatterUrl =
-    'https://github.com/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar';
+final Uri _googleFormatterUrl = Uri.https('github.com',
+    '/google/google-java-format/releases/download/google-java-format-1.3/google-java-format-1.3-all-deps.jar');
 
 /// A command to format all package code.
 class FormatCommand extends PluginCommand {
@@ -47,7 +49,7 @@
     await _formatJava(googleFormatterPath);
     await _formatCppAndObjectiveC();
 
-    if (argResults['fail-on-change'] == true) {
+    if (getBoolArg('fail-on-change')) {
       final bool modified = await _didModifyAnything();
       if (modified) {
         throw ToolExit(1);
@@ -105,7 +107,7 @@
     // 'ProcessException: Argument list too long'.
     final Iterable<List<String>> batches = partition(allFiles, 100);
     for (final List<String> batch in batches) {
-      await processRunner.runAndStream(argResults['clang-format'] as String,
+      await processRunner.runAndStream(getStringArg('clang-format'),
           <String>['-i', '--style=Google', ...batch],
           workingDir: packagesDir, exitOnError: true);
     }
diff --git a/script/tool/lib/src/java_test_command.dart b/script/tool/lib/src/java_test_command.dart
index 5df9762..dff83d3 100644
--- a/script/tool/lib/src/java_test_command.dart
+++ b/script/tool/lib/src/java_test_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 
 import 'package:file/file.dart';
diff --git a/script/tool/lib/src/license_check_command.dart b/script/tool/lib/src/license_check_command.dart
index a652864..cc4eb96 100644
--- a/script/tool/lib/src/license_check_command.dart
+++ b/script/tool/lib/src/license_check_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 
 import 'package:file/file.dart';
diff --git a/script/tool/lib/src/lint_podspecs_command.dart b/script/tool/lib/src/lint_podspecs_command.dart
index ebcefd6..0bf4c69 100644
--- a/script/tool/lib/src/lint_podspecs_command.dart
+++ b/script/tool/lib/src/lint_podspecs_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -89,7 +91,7 @@
     final List<File> podspecs = await getFiles().where((File entity) {
       final String filePath = entity.path;
       return p.extension(filePath) == '.podspec' &&
-          !(argResults['skip'] as List<String>)
+          !getStringListArg('skip')
               .contains(p.basenameWithoutExtension(filePath));
     }).toList();
 
@@ -122,7 +124,7 @@
 
   Future<ProcessResult> _runPodLint(String podspecPath,
       {bool libraryLint}) async {
-    final bool allowWarnings = (argResults['ignore-warnings'] as List<String>)
+    final bool allowWarnings = (getStringListArg('ignore-warnings'))
         .contains(p.basenameWithoutExtension(podspecPath));
     final List<String> arguments = <String>[
       'lib',
diff --git a/script/tool/lib/src/list_command.dart b/script/tool/lib/src/list_command.dart
index 49302a9..cac6cfc 100644
--- a/script/tool/lib/src/list_command.dart
+++ b/script/tool/lib/src/list_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 
 import 'package:file/file.dart';
@@ -36,7 +38,7 @@
 
   @override
   Future<void> run() async {
-    switch (argResults[_type] as String) {
+    switch (getStringArg(_type)) {
       case _plugin:
         await for (final Directory package in getPlugins()) {
           print(package.path);
diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart
index 6fba3b3..4b0e857 100644
--- a/script/tool/lib/src/main.dart
+++ b/script/tool/lib/src/main.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:io' as io;
 
 import 'package:args/command_runner.dart';
diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart
index 84503f4..3f90465 100644
--- a/script/tool/lib/src/publish_check_command.dart
+++ b/script/tool/lib/src/publish_check_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
@@ -78,7 +80,7 @@
   Future<void> run() async {
     final ZoneSpecification logSwitchSpecification = ZoneSpecification(
         print: (Zone self, ZoneDelegate parent, Zone zone, String message) {
-      final bool logMachineMessage = argResults[_machineFlag] as bool;
+      final bool logMachineMessage = getBoolArg(_machineFlag);
       if (logMachineMessage && message != _prettyJson(_machineOutput)) {
         _humanMessages.add(message);
       } else {
@@ -123,7 +125,7 @@
           isError: false);
     }
 
-    if (argResults[_machineFlag] as bool) {
+    if (getBoolArg(_machineFlag)) {
       _setStatus(status);
       _machineOutput[_humanMessageKey] = _humanMessages;
       print(_prettyJson(_machineOutput));
@@ -184,7 +186,7 @@
       return true;
     }
 
-    if (!(argResults[_allowPrereleaseFlag] as bool)) {
+    if (!getBoolArg(_allowPrereleaseFlag)) {
       return false;
     }
 
@@ -270,7 +272,7 @@
 
   void _printImportantStatusMessage(String message, {@required bool isError}) {
     final String statusMessage = '${isError ? 'ERROR' : 'SUCCESS'}: $message';
-    if (argResults[_machineFlag] as bool) {
+    if (getBoolArg(_machineFlag)) {
       print(statusMessage);
     } else {
       final Colorize colorizedMessage = Colorize(statusMessage);
diff --git a/script/tool/lib/src/publish_plugin_command.dart b/script/tool/lib/src/publish_plugin_command.dart
index 1c8a2dc..cb72c18 100644
--- a/script/tool/lib/src/publish_plugin_command.dart
+++ b/script/tool/lib/src/publish_plugin_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
@@ -122,9 +124,9 @@
 
   @override
   Future<void> run() async {
-    final String package = argResults[_packageOption] as String;
-    final bool publishAllChanged = argResults[_allChangedFlag] as bool;
-    if (package == null && !publishAllChanged) {
+    final String package = getStringArg(_packageOption);
+    final bool publishAllChanged = getBoolArg(_allChangedFlag);
+    if (package.isEmpty && !publishAllChanged) {
       _print(
           'Must specify a package to publish. See `plugin_tools help publish-plugin`.');
       throw ToolExit(1);
@@ -138,14 +140,14 @@
     final GitDir baseGitDir =
         await GitDir.fromExisting(packagesDir.path, allowSubdirectory: true);
 
-    final bool shouldPushTag = argResults[_pushTagsOption] == true;
-    final String remote = argResults[_remoteOption] as String;
+    final bool shouldPushTag = getBoolArg(_pushTagsOption);
+    final String remote = getStringArg(_remoteOption);
     String remoteUrl;
     if (shouldPushTag) {
       remoteUrl = await _verifyRemote(remote);
     }
     _print('Local repo is ready!');
-    if (argResults[_dryRunFlag] as bool) {
+    if (getBoolArg(_dryRunFlag)) {
       _print('===============  DRY RUN ===============');
     }
 
@@ -244,7 +246,7 @@
     if (!await _publishPlugin(packageDir: packageDir)) {
       return false;
     }
-    if (argResults[_tagReleaseOption] as bool) {
+    if (getBoolArg(_tagReleaseOption)) {
       if (!await _tagRelease(
         packageDir: packageDir,
         remote: remote,
@@ -333,7 +335,7 @@
   }) async {
     final String tag = _getTag(packageDir);
     _print('Tagging release $tag...');
-    if (!(argResults[_dryRunFlag] as bool)) {
+    if (!getBoolArg(_dryRunFlag)) {
       final io.ProcessResult result = await processRunner.run(
         'git',
         <String>['tag', tag],
@@ -416,15 +418,14 @@
   }
 
   Future<bool> _publish(Directory packageDir) async {
-    final List<String> publishFlags =
-        argResults[_pubFlagsOption] as List<String>;
+    final List<String> publishFlags = getStringListArg(_pubFlagsOption);
     _print(
         'Running `pub publish ${publishFlags.join(' ')}` in ${packageDir.absolute.path}...\n');
-    if (argResults[_dryRunFlag] as bool) {
+    if (getBoolArg(_dryRunFlag)) {
       return true;
     }
 
-    if (argResults[_skipConfirmationFlag] as bool) {
+    if (getBoolArg(_skipConfirmationFlag)) {
       publishFlags.add('--force');
     }
     if (publishFlags.contains('--force')) {
@@ -474,7 +475,7 @@
     @required String remoteUrl,
   }) async {
     assert(remote != null && tag != null && remoteUrl != null);
-    if (!(argResults[_skipConfirmationFlag] as bool)) {
+    if (!getBoolArg(_skipConfirmationFlag)) {
       _print('Ready to push $tag to $remoteUrl (y/n)?');
       final String input = _stdin.readLineSync();
       if (input.toLowerCase() != 'y') {
@@ -482,7 +483,7 @@
         return false;
       }
     }
-    if (!(argResults[_dryRunFlag] as bool)) {
+    if (!getBoolArg(_dryRunFlag)) {
       final io.ProcessResult result = await processRunner.run(
         'git',
         <String>['push', remote, tag],
diff --git a/script/tool/lib/src/test_command.dart b/script/tool/lib/src/test_command.dart
index bb4f9c1..c4aeab7 100644
--- a/script/tool/lib/src/test_command.dart
+++ b/script/tool/lib/src/test_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 
 import 'package:file/file.dart';
@@ -44,7 +46,7 @@
 
       print('RUNNING $packageName tests...');
 
-      final String enableExperiment = argResults[kEnableExperiment] as String;
+      final String enableExperiment = getStringArg(kEnableExperiment);
 
       // `flutter test` automatically gets packages.  `pub run test` does not.  :(
       int exitCode = 0;
diff --git a/script/tool/lib/src/version_check_command.dart b/script/tool/lib/src/version_check_command.dart
index 475caf5..a802c4d 100644
--- a/script/tool/lib/src/version_check_command.dart
+++ b/script/tool/lib/src/version_check_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 
 import 'package:file/file.dart';
@@ -135,7 +137,7 @@
                 '"publish_to: none".');
       }
       Version sourceVersion;
-      if (argResults[_againstPubFlag] as bool) {
+      if (getBoolArg(_againstPubFlag)) {
         final String packageName = pubspecFile.parent.basename;
         final PubVersionFinderResponse pubVersionFinderResponse =
             await _pubVersionFinder.getPackageVersion(package: packageName);
@@ -161,7 +163,7 @@
       }
       if (sourceVersion == null) {
         String safeToIgnoreMessage;
-        if (argResults[_againstPubFlag] as bool) {
+        if (getBoolArg(_againstPubFlag)) {
           safeToIgnoreMessage =
               '${indentation}Unable to find package on pub server.';
         } else {
@@ -181,8 +183,7 @@
           getAllowedNextVersions(sourceVersion, headVersion);
 
       if (!allowedNextVersions.containsKey(headVersion)) {
-        final String source =
-            (argResults[_againstPubFlag] as bool) ? 'pub' : 'master';
+        final String source = (getBoolArg(_againstPubFlag)) ? 'pub' : 'master';
         final String error = '${indentation}Incorrectly updated version.\n'
             '${indentation}HEAD: $headVersion, $source: $sourceVersion.\n'
             '${indentation}Allowed versions: $allowedNextVersions';
diff --git a/script/tool/lib/src/xctest_command.dart b/script/tool/lib/src/xctest_command.dart
index 64f8557..335e005 100644
--- a/script/tool/lib/src/xctest_command.dart
+++ b/script/tool/lib/src/xctest_command.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
@@ -48,8 +50,8 @@
 
   @override
   Future<void> run() async {
-    String destination = argResults[_kiOSDestination] as String;
-    if (destination == null) {
+    String destination = getStringArg(_kiOSDestination);
+    if (destination.isEmpty) {
       final String simulatorId = await _findAvailableIphoneSimulator();
       if (simulatorId == null) {
         print(_kFoundNoSimulatorsMessage);
@@ -58,7 +60,7 @@
       destination = 'id=$simulatorId';
     }
 
-    final List<String> skipped = argResults[_kSkip] as List<String>;
+    final List<String> skipped = getStringListArg(_kSkip);
 
     final List<String> failingPackages = <String>[];
     await for (final Directory plugin in getPlugins()) {
diff --git a/script/tool/pubspec.yaml b/script/tool/pubspec.yaml
index 58dfc9f..725c839 100644
--- a/script/tool/pubspec.yaml
+++ b/script/tool/pubspec.yaml
@@ -4,28 +4,29 @@
 version: 0.1.1
 
 dependencies:
-  args: "^1.4.3"
-  path: "^1.6.1"
-  http: "^0.12.1"
-  async: "^2.0.7"
-  yaml: "^2.1.15"
-  quiver: "^2.0.2"
-  pub_semver: ^1.4.2
-  colorize: ^2.0.0
-  git: ^1.0.0
-  platform: ^2.2.0
-  pubspec_parse: "^0.1.4"
-  test: ^1.6.4
-  meta: ^1.1.7
-  file: ^5.0.10
-  uuid: ^2.0.4
-  http_multi_server: ^2.2.0
-  collection: ^1.14.13
+  args: ^2.1.0
+  async: ^2.6.1
+  collection: ^1.15.0
+  colorize: ^3.0.0
+  file: ^6.1.0
+  git: ^2.0.0
+  http: ^0.13.3
+  http_multi_server: ^3.0.1
+  meta: ^1.3.0
+  path: ^1.8.0
+  platform: ^3.0.0
+  pub_semver: ^2.0.0
+  pubspec_parse: ^1.0.0
+  quiver: ^3.0.1
+  test: ^1.17.3
+  uuid: ^3.0.4
+  yaml: ^3.1.0
 
 dev_dependencies:
-  matcher: ^0.12.6
-  mockito: ^4.1.1
-  pedantic: ^1.8.0
+  build_runner: ^2.0.3
+  matcher: ^0.12.10
+  mockito: ^5.0.7
+  pedantic: ^1.11.0
 
 environment:
-  sdk: ">=2.3.0 <3.0.0"
+  sdk: '>=2.12.0 <3.0.0'
diff --git a/script/tool/test/analyze_command_test.dart b/script/tool/test/analyze_command_test.dart
index 11acb59..1e656b4 100644
--- a/script/tool/test/analyze_command_test.dart
+++ b/script/tool/test/analyze_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/analyze_command.dart';
diff --git a/script/tool/test/build_examples_command_test.dart b/script/tool/test/build_examples_command_test.dart
index 40da27d..7b48887 100644
--- a/script/tool/test/build_examples_command_test.dart
+++ b/script/tool/test/build_examples_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/build_examples_command.dart';
diff --git a/script/tool/test/common_test.dart b/script/tool/test/common_test.dart
index d6ac449..477e186 100644
--- a/script/tool/test/common_test.dart
+++ b/script/tool/test/common_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 
@@ -12,21 +13,24 @@
 import 'package:git/git.dart';
 import 'package:http/http.dart' as http;
 import 'package:http/testing.dart';
+import 'package:mockito/annotations.dart';
 import 'package:mockito/mockito.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:test/test.dart';
 
+import 'common_test.mocks.dart';
 import 'util.dart';
 
+@GenerateMocks(<Type>[GitDir])
 void main() {
-  RecordingProcessRunner processRunner;
-  CommandRunner<void> runner;
-  FileSystem fileSystem;
-  Directory packagesDir;
-  Directory thirdPartyPackagesDir;
-  List<String> plugins;
-  List<List<String>> gitDirCommands;
-  String gitDiffResponse;
+  late RecordingProcessRunner processRunner;
+  late CommandRunner<void> runner;
+  late FileSystem fileSystem;
+  late Directory packagesDir;
+  late Directory thirdPartyPackagesDir;
+  late List<String> plugins;
+  late List<List<String>?> gitDirCommands;
+  late String gitDiffResponse;
 
   setUp(() {
     fileSystem = MemoryFileSystem();
@@ -35,14 +39,15 @@
         .childDirectory('third_party')
         .childDirectory('packages');
 
-    gitDirCommands = <List<String>>[];
+    gitDirCommands = <List<String>?>[];
     gitDiffResponse = '';
     final MockGitDir gitDir = MockGitDir();
-    when(gitDir.runCommand(any)).thenAnswer((Invocation invocation) {
-      gitDirCommands.add(invocation.positionalArguments[0] as List<String>);
+    when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
+        .thenAnswer((Invocation invocation) {
+      gitDirCommands.add(invocation.positionalArguments[0] as List<String>?);
       final MockProcessResult mockProcessResult = MockProcessResult();
       if (invocation.positionalArguments[0][0] == 'diff') {
-        when<String>(mockProcessResult.stdout as String)
+        when<String?>(mockProcessResult.stdout as String?)
             .thenReturn(gitDiffResponse);
       }
       return Future<ProcessResult>.value(mockProcessResult);
@@ -255,23 +260,24 @@
   });
 
   group('$GitVersionFinder', () {
-    List<List<String>> gitDirCommands;
-    String gitDiffResponse;
-    String mergeBaseResponse;
-    MockGitDir gitDir;
+    late List<List<String>?> gitDirCommands;
+    late String gitDiffResponse;
+    String? mergeBaseResponse;
+    late MockGitDir gitDir;
 
     setUp(() {
-      gitDirCommands = <List<String>>[];
+      gitDirCommands = <List<String>?>[];
       gitDiffResponse = '';
       gitDir = MockGitDir();
-      when(gitDir.runCommand(any)).thenAnswer((Invocation invocation) {
-        gitDirCommands.add(invocation.positionalArguments[0] as List<String>);
+      when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
+          .thenAnswer((Invocation invocation) {
+        gitDirCommands.add(invocation.positionalArguments[0] as List<String>?);
         final MockProcessResult mockProcessResult = MockProcessResult();
         if (invocation.positionalArguments[0][0] == 'diff') {
-          when<String>(mockProcessResult.stdout as String)
+          when<String?>(mockProcessResult.stdout as String?)
               .thenReturn(gitDiffResponse);
         } else if (invocation.positionalArguments[0][0] == 'merge-base') {
-          when<String>(mockProcessResult.stdout as String)
+          when<String?>(mockProcessResult.stdout as String?)
               .thenReturn(mergeBaseResponse);
         }
         return Future<ProcessResult>.value(mockProcessResult);
@@ -320,10 +326,11 @@
 file1/pubspec.yaml
 file2/file2.cc
 ''';
+
       final GitVersionFinder finder = GitVersionFinder(gitDir, null);
       await finder.getChangedFiles();
       verify(gitDir.runCommand(
-          <String>['diff', '--name-only', mergeBaseResponse, 'HEAD']));
+          <String>['diff', '--name-only', mergeBaseResponse!, 'HEAD']));
     });
 
     test('use correct base sha if specified', () async {
@@ -350,8 +357,8 @@
 
       expect(response.versions, isNull);
       expect(response.result, PubVersionFinderResult.noPackageFound);
-      expect(response.httpResponse.statusCode, 404);
-      expect(response.httpResponse.body, '');
+      expect(response.httpResponse!.statusCode, 404);
+      expect(response.httpResponse!.body, '');
     });
 
     test('HTTP error when getting versions from pub', () async {
@@ -364,8 +371,8 @@
 
       expect(response.versions, isNull);
       expect(response.result, PubVersionFinderResult.fail);
-      expect(response.httpResponse.statusCode, 400);
-      expect(response.httpResponse.body, '');
+      expect(response.httpResponse!.statusCode, 400);
+      expect(response.httpResponse!.body, '');
     });
 
     test('Get a correct list of versions when http response is OK.', () async {
@@ -408,8 +415,8 @@
         Version.parse('0.0.1'),
       ]);
       expect(response.result, PubVersionFinderResult.success);
-      expect(response.httpResponse.statusCode, 200);
-      expect(response.httpResponse.body, json.encode(httpResponse));
+      expect(response.httpResponse!.statusCode, 200);
+      expect(response.httpResponse!.body, json.encode(httpResponse));
     });
   });
 }
@@ -420,7 +427,7 @@
     Directory packagesDir,
     FileSystem fileSystem, {
     ProcessRunner processRunner = const ProcessRunner(),
-    GitDir gitDir,
+    GitDir? gitDir,
   }) : super(packagesDir, fileSystem,
             processRunner: processRunner, gitDir: gitDir);
 
@@ -440,6 +447,4 @@
   }
 }
 
-class MockGitDir extends Mock implements GitDir {}
-
 class MockProcessResult extends Mock implements ProcessResult {}
diff --git a/script/tool/test/common_test.mocks.dart b/script/tool/test/common_test.mocks.dart
new file mode 100644
index 0000000..b7f7807
--- /dev/null
+++ b/script/tool/test/common_test.mocks.dart
@@ -0,0 +1,143 @@
+// Mocks generated by Mockito 5.0.7 from annotations
+// in flutter_plugin_tools/test/common_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i6;
+import 'dart:io' as _i4;
+
+import 'package:git/src/branch_reference.dart' as _i3;
+import 'package:git/src/commit.dart' as _i2;
+import 'package:git/src/commit_reference.dart' as _i8;
+import 'package:git/src/git_dir.dart' as _i5;
+import 'package:git/src/tag.dart' as _i7;
+import 'package:git/src/tree_entry.dart' as _i9;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+
+// ignore_for_file: prefer_const_constructors
+
+// ignore_for_file: avoid_redundant_argument_values
+
+class _FakeCommit extends _i1.Fake implements _i2.Commit {}
+
+class _FakeBranchReference extends _i1.Fake implements _i3.BranchReference {}
+
+class _FakeProcessResult extends _i1.Fake implements _i4.ProcessResult {}
+
+/// A class which mocks [GitDir].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockGitDir extends _i1.Mock implements _i5.GitDir {
+  MockGitDir() {
+    _i1.throwOnMissingStub(this);
+  }
+
+  @override
+  String get path =>
+      (super.noSuchMethod(Invocation.getter(#path), returnValue: '') as String);
+  @override
+  _i6.Future<int> commitCount([String? branchName = r'HEAD']) =>
+      (super.noSuchMethod(Invocation.method(#commitCount, [branchName]),
+          returnValue: Future<int>.value(0)) as _i6.Future<int>);
+  @override
+  _i6.Future<_i2.Commit> commitFromRevision(String? revision) =>
+      (super.noSuchMethod(Invocation.method(#commitFromRevision, [revision]),
+              returnValue: Future<_i2.Commit>.value(_FakeCommit()))
+          as _i6.Future<_i2.Commit>);
+  @override
+  _i6.Future<Map<String, _i2.Commit>> commits([String? branchName = r'HEAD']) =>
+      (super.noSuchMethod(Invocation.method(#commits, [branchName]),
+              returnValue:
+                  Future<Map<String, _i2.Commit>>.value(<String, _i2.Commit>{}))
+          as _i6.Future<Map<String, _i2.Commit>>);
+  @override
+  _i6.Future<_i3.BranchReference?> branchReference(String? branchName) =>
+      (super.noSuchMethod(Invocation.method(#branchReference, [branchName]),
+              returnValue:
+                  Future<_i3.BranchReference?>.value(_FakeBranchReference()))
+          as _i6.Future<_i3.BranchReference?>);
+  @override
+  _i6.Future<List<_i3.BranchReference>> branches() => (super.noSuchMethod(
+          Invocation.method(#branches, []),
+          returnValue:
+              Future<List<_i3.BranchReference>>.value(<_i3.BranchReference>[]))
+      as _i6.Future<List<_i3.BranchReference>>);
+  @override
+  _i6.Stream<_i7.Tag> tags() =>
+      (super.noSuchMethod(Invocation.method(#tags, []),
+          returnValue: Stream<_i7.Tag>.empty()) as _i6.Stream<_i7.Tag>);
+  @override
+  _i6.Future<List<_i8.CommitReference>> showRef(
+          {bool? heads = false, bool? tags = false}) =>
+      (super.noSuchMethod(
+              Invocation.method(#showRef, [], {#heads: heads, #tags: tags}),
+              returnValue: Future<List<_i8.CommitReference>>.value(
+                  <_i8.CommitReference>[]))
+          as _i6.Future<List<_i8.CommitReference>>);
+  @override
+  _i6.Future<_i3.BranchReference> currentBranch() =>
+      (super.noSuchMethod(Invocation.method(#currentBranch, []),
+              returnValue:
+                  Future<_i3.BranchReference>.value(_FakeBranchReference()))
+          as _i6.Future<_i3.BranchReference>);
+  @override
+  _i6.Future<List<_i9.TreeEntry>> lsTree(String? treeish,
+          {bool? subTreesOnly = false, String? path}) =>
+      (super.noSuchMethod(
+              Invocation.method(#lsTree, [treeish],
+                  {#subTreesOnly: subTreesOnly, #path: path}),
+              returnValue: Future<List<_i9.TreeEntry>>.value(<_i9.TreeEntry>[]))
+          as _i6.Future<List<_i9.TreeEntry>>);
+  @override
+  _i6.Future<String?> createOrUpdateBranch(
+          String? branchName, String? treeSha, String? commitMessage) =>
+      (super.noSuchMethod(
+          Invocation.method(
+              #createOrUpdateBranch, [branchName, treeSha, commitMessage]),
+          returnValue: Future<String?>.value('')) as _i6.Future<String?>);
+  @override
+  _i6.Future<String> commitTree(String? treeSha, String? commitMessage,
+          {List<String>? parentCommitShas}) =>
+      (super.noSuchMethod(
+          Invocation.method(#commitTree, [treeSha, commitMessage],
+              {#parentCommitShas: parentCommitShas}),
+          returnValue: Future<String>.value('')) as _i6.Future<String>);
+  @override
+  _i6.Future<Map<String, String>> writeObjects(List<String>? paths) =>
+      (super.noSuchMethod(Invocation.method(#writeObjects, [paths]),
+              returnValue:
+                  Future<Map<String, String>>.value(<String, String>{}))
+          as _i6.Future<Map<String, String>>);
+  @override
+  _i6.Future<_i4.ProcessResult> runCommand(Iterable<String>? args,
+          {bool? throwOnError = true}) =>
+      (super.noSuchMethod(
+          Invocation.method(#runCommand, [args], {#throwOnError: throwOnError}),
+          returnValue:
+              Future<_i4.ProcessResult>.value(_FakeProcessResult())) as _i6
+          .Future<_i4.ProcessResult>);
+  @override
+  _i6.Future<bool> isWorkingTreeClean() =>
+      (super.noSuchMethod(Invocation.method(#isWorkingTreeClean, []),
+          returnValue: Future<bool>.value(false)) as _i6.Future<bool>);
+  @override
+  _i6.Future<_i2.Commit?> updateBranch(
+          String? branchName,
+          _i6.Future<dynamic> Function(_i4.Directory)? populater,
+          String? commitMessage) =>
+      (super.noSuchMethod(
+              Invocation.method(
+                  #updateBranch, [branchName, populater, commitMessage]),
+              returnValue: Future<_i2.Commit?>.value(_FakeCommit()))
+          as _i6.Future<_i2.Commit?>);
+  @override
+  _i6.Future<_i2.Commit?> updateBranchWithDirectoryContents(String? branchName,
+          String? sourceDirectoryPath, String? commitMessage) =>
+      (super.noSuchMethod(
+              Invocation.method(#updateBranchWithDirectoryContents,
+                  [branchName, sourceDirectoryPath, commitMessage]),
+              returnValue: Future<_i2.Commit?>.value(_FakeCommit()))
+          as _i6.Future<_i2.Commit?>);
+}
diff --git a/script/tool/test/create_all_plugins_app_command_test.dart b/script/tool/test/create_all_plugins_app_command_test.dart
index fedc046..caf4218 100644
--- a/script/tool/test/create_all_plugins_app_command_test.dart
+++ b/script/tool/test/create_all_plugins_app_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:file/local.dart';
diff --git a/script/tool/test/drive_examples_command_test.dart b/script/tool/test/drive_examples_command_test.dart
index 5ba8d8a..7905bb2 100644
--- a/script/tool/test/drive_examples_command_test.dart
+++ b/script/tool/test/drive_examples_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/common.dart';
diff --git a/script/tool/test/firebase_test_lab_test.dart b/script/tool/test/firebase_test_lab_test.dart
index d606869..b4e5b5b 100644
--- a/script/tool/test/firebase_test_lab_test.dart
+++ b/script/tool/test/firebase_test_lab_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:io';
 
 import 'package:args/command_runner.dart';
diff --git a/script/tool/test/java_test_command_test.dart b/script/tool/test/java_test_command_test.dart
index ba01691..cfa8d44 100644
--- a/script/tool/test/java_test_command_test.dart
+++ b/script/tool/test/java_test_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/java_test_command.dart';
diff --git a/script/tool/test/license_check_command_test.dart b/script/tool/test/license_check_command_test.dart
index 23de258..a0c74fc 100644
--- a/script/tool/test/license_check_command_test.dart
+++ b/script/tool/test/license_check_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
diff --git a/script/tool/test/lint_podspecs_command_test.dart b/script/tool/test/lint_podspecs_command_test.dart
index a1fa1f7..13bb1cc 100644
--- a/script/tool/test/lint_podspecs_command_test.dart
+++ b/script/tool/test/lint_podspecs_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/lint_podspecs_command.dart';
diff --git a/script/tool/test/list_command_test.dart b/script/tool/test/list_command_test.dart
index e7fcfe7..a767671 100644
--- a/script/tool/test/list_command_test.dart
+++ b/script/tool/test/list_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/list_command.dart';
diff --git a/script/tool/test/mocks.dart b/script/tool/test/mocks.dart
index ad1f357..a83ca75 100644
--- a/script/tool/test/mocks.dart
+++ b/script/tool/test/mocks.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:io' as io;
 
diff --git a/script/tool/test/publish_check_command_test.dart b/script/tool/test/publish_check_command_test.dart
index eca7caf..8c34b33 100644
--- a/script/tool/test/publish_check_command_test.dart
+++ b/script/tool/test/publish_check_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:collection';
 import 'dart:convert';
 import 'dart:io' as io;
diff --git a/script/tool/test/publish_plugin_command_test.dart b/script/tool/test/publish_plugin_command_test.dart
index b622e05..357e72e 100644
--- a/script/tool/test/publish_plugin_command_test.dart
+++ b/script/tool/test/publish_plugin_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
diff --git a/script/tool/test/test_command_test.dart b/script/tool/test/test_command_test.dart
index 1dd0c15..5502491 100644
--- a/script/tool/test/test_command_test.dart
+++ b/script/tool/test/test_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:flutter_plugin_tools/src/test_command.dart';
diff --git a/script/tool/test/util.dart b/script/tool/test/util.dart
index d708dc7..7d4278f 100644
--- a/script/tool/test/util.dart
+++ b/script/tool/test/util.dart
@@ -21,13 +21,13 @@
     style: const LocalPlatform().isWindows
         ? FileSystemStyle.windows
         : FileSystemStyle.posix);
-Directory mockPackagesDir;
+late Directory mockPackagesDir;
 
 /// Creates a mock packages directory in the mock file system.
 ///
 /// If [parentDir] is set the mock packages dir will be creates as a child of
 /// it. If not [mockFileSystem] will be used instead.
-void initializeFakePackages({Directory parentDir}) {
+void initializeFakePackages({Directory? parentDir}) {
   mockPackagesDir =
       (parentDir ?? mockFileSystem.currentDirectory).childDirectory('packages');
   mockPackagesDir.createSync();
@@ -51,7 +51,7 @@
   bool includeVersion = false,
   String version = '0.0.1',
   String parentDirectoryName = '',
-  Directory packagesDirectory,
+  Directory? packagesDirectory,
 }) {
   assert(!(withSingleExample && withExamples.isNotEmpty),
       'cannot pass withSingleExample and withExamples simultaneously');
@@ -211,7 +211,8 @@
 /// what was printed.
 /// A custom [errorHandler] can be used to handle the runner error as desired without throwing.
 Future<List<String>> runCapturingPrint(
-    CommandRunner<void> runner, List<String> args, {_ErrorHandler errorHandler}) async {
+    CommandRunner<void> runner, List<String> args,
+    {_ErrorHandler? errorHandler}) async {
   final List<String> prints = <String>[];
   final ZoneSpecification spec = ZoneSpecification(
     print: (_, __, ___, String message) {
@@ -220,8 +221,8 @@
   );
   try {
     await Zone.current
-      .fork(specification: spec)
-      .run<Future<void>>(() => runner.run(args));
+        .fork(specification: spec)
+        .run<Future<void>>(() => runner.run(args));
   } on Error catch (e) {
     if (errorHandler == null) {
       rethrow;
@@ -234,25 +235,25 @@
 
 /// A mock [ProcessRunner] which records process calls.
 class RecordingProcessRunner extends ProcessRunner {
-  io.Process processToReturn;
+  io.Process? processToReturn;
   final List<ProcessCall> recordedCalls = <ProcessCall>[];
 
   /// Populate for [io.ProcessResult] to use a String [stdout] instead of a [List] of [int].
-  String resultStdout;
+  String? resultStdout;
 
   /// Populate for [io.ProcessResult] to use a String [stderr] instead of a [List] of [int].
-  String resultStderr;
+  String? resultStderr;
 
   @override
   Future<int> runAndStream(
     String executable,
     List<String> args, {
-    Directory workingDir,
+    Directory? workingDir,
     bool exitOnError = false,
   }) async {
     recordedCalls.add(ProcessCall(executable, args, workingDir?.path));
     return Future<int>.value(
-        processToReturn == null ? 0 : await processToReturn.exitCode);
+        processToReturn == null ? 0 : await processToReturn!.exitCode);
   }
 
   /// Returns [io.ProcessResult] created from [processToReturn], [resultStdout], and [resultStderr].
@@ -260,28 +261,26 @@
   Future<io.ProcessResult> run(
     String executable,
     List<String> args, {
-    Directory workingDir,
+    Directory? workingDir,
     bool exitOnError = false,
     bool logOnError = false,
     Encoding stdoutEncoding = io.systemEncoding,
     Encoding stderrEncoding = io.systemEncoding,
   }) async {
     recordedCalls.add(ProcessCall(executable, args, workingDir?.path));
-    io.ProcessResult result;
+    io.ProcessResult? result;
 
-    if (processToReturn != null) {
-      result = io.ProcessResult(
-          processToReturn.pid,
-          await processToReturn.exitCode,
-          resultStdout ?? processToReturn.stdout,
-          resultStderr ?? processToReturn.stderr);
+    final io.Process? process = processToReturn;
+    if (process != null) {
+      result = io.ProcessResult(process.pid, await process.exitCode,
+          resultStdout ?? process.stdout, resultStderr ?? process.stderr);
     }
     return Future<io.ProcessResult>.value(result);
   }
 
   @override
   Future<io.Process> start(String executable, List<String> args,
-      {Directory workingDirectory}) async {
+      {Directory? workingDirectory}) async {
     recordedCalls.add(ProcessCall(executable, args, workingDirectory?.path));
     return Future<io.Process>.value(processToReturn);
   }
@@ -299,7 +298,7 @@
   final List<String> args;
 
   /// The working directory this process was called from.
-  final String workingDir;
+  final String? workingDir;
 
   @override
   bool operator ==(dynamic other) {
@@ -311,10 +310,7 @@
 
   @override
   int get hashCode =>
-      executable?.hashCode ??
-      0 ^ args?.hashCode ??
-      0 ^ workingDir?.hashCode ??
-      0;
+      (executable.hashCode) ^ (args.hashCode) ^ (workingDir?.hashCode ?? 0);
 
   @override
   String toString() {
diff --git a/script/tool/test/version_check_test.dart b/script/tool/test/version_check_test.dart
index d67103f..cef2ab1 100644
--- a/script/tool/test/version_check_test.dart
+++ b/script/tool/test/version_check_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
@@ -65,7 +67,8 @@
       gitDiffResponse = '';
       gitShowResponses = <String, String>{};
       gitDir = MockGitDir();
-      when(gitDir.runCommand(any)).thenAnswer((Invocation invocation) {
+      when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
+          .thenAnswer((Invocation invocation) {
         gitDirCommands.add(invocation.positionalArguments[0] as List<String>);
         final MockProcessResult mockProcessResult = MockProcessResult();
         if (invocation.positionalArguments[0][0] == 'diff') {
diff --git a/script/tool/test/xctest_command_test.dart b/script/tool/test/xctest_command_test.dart
index 1707dc8..53e82ac 100644
--- a/script/tool/test/xctest_command_test.dart
+++ b/script/tool/test/xctest_command_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// @dart=2.9
+
 import 'dart:convert';
 
 import 'package:args/command_runner.dart';