Align `flutter pub get/upgrade/add/remove/downgrade` (#117896)

* Align `flutter pub get/upgrade/add/remove/downgrade`

* Add final . to command description

* Remove trailing whitespace

* Don't print message that command is being run

* Update expectations

* Use relative path

* Remove duplicated line

* Improve function dartdoc
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart
index 14503a0..b65e317 100644
--- a/packages/flutter_tools/lib/src/commands/packages.dart
+++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -20,10 +20,10 @@
 
 class PackagesCommand extends FlutterCommand {
   PackagesCommand() {
-    addSubcommand(PackagesGetCommand('get', false));
-    addSubcommand(PackagesInteractiveGetCommand('upgrade', "Upgrade the current package's dependencies to latest versions."));
-    addSubcommand(PackagesInteractiveGetCommand('add', 'Add a dependency to pubspec.yaml.'));
-    addSubcommand(PackagesInteractiveGetCommand('remove', 'Removes a dependency from the current package.'));
+    addSubcommand(PackagesGetCommand('get', "Get the current package's dependencies.", PubContext.pubGet));
+    addSubcommand(PackagesGetCommand('upgrade', "Upgrade the current package's dependencies to latest versions.", PubContext.pubUpgrade));
+    addSubcommand(PackagesGetCommand('add', 'Add a dependency to pubspec.yaml.', PubContext.pubAdd));
+    addSubcommand(PackagesGetCommand('remove', 'Removes a dependency from the current package.', PubContext.pubRemove));
     addSubcommand(PackagesTestCommand());
     addSubcommand(PackagesForwardCommand('publish', 'Publish the current package to pub.dartlang.org.', requiresPubspec: true));
     addSubcommand(PackagesForwardCommand('downgrade', 'Downgrade packages in a Flutter project.', requiresPubspec: true));
@@ -56,136 +56,6 @@
   Future<FlutterCommandResult> runCommand() async => FlutterCommandResult.fail();
 }
 
-class PackagesGetCommand extends FlutterCommand {
-  PackagesGetCommand(this.name, this.upgrade) {
-    argParser.addFlag('offline',
-      negatable: false,
-      help: 'Use cached packages instead of accessing the network.',
-    );
-  }
-
-  @override
-  final String name;
-
-  final bool upgrade;
-
-  @override
-  String get description {
-    return '${ upgrade ? "Upgrade" : "Get" } packages in a Flutter project.';
-  }
-
-  @override
-  String get invocation {
-    return '${runner!.executableName} pub $name [<target directory>]';
-  }
-
-  /// The pub packages usage values are incorrect since these are calculated/sent
-  /// before pub get completes. This needs to be performed after dependency resolution.
-  @override
-  Future<CustomDimensions> get usageValues async {
-    final ArgResults argumentResults = argResults!;
-    final String? workingDirectory = argumentResults.rest.length == 1 ? argumentResults.rest[0] : null;
-    final String? target = findProjectRoot(globals.fs, workingDirectory);
-    if (target == null) {
-      return const CustomDimensions();
-    }
-
-    int numberPlugins;
-
-    final FlutterProject rootProject = FlutterProject.fromDirectory(globals.fs.directory(target));
-    // Do not send plugin analytics if pub has not run before.
-    final bool hasPlugins = rootProject.flutterPluginsDependenciesFile.existsSync()
-      && rootProject.packageConfigFile.existsSync();
-    if (hasPlugins) {
-      // Do not fail pub get if package config files are invalid before pub has
-      // had a chance to run.
-      final List<Plugin> plugins = await findPlugins(rootProject, throwOnError: false);
-      numberPlugins = plugins.length;
-    } else {
-      numberPlugins = 0;
-    }
-
-    return CustomDimensions(
-      commandPackagesNumberPlugins: numberPlugins,
-      commandPackagesProjectModule: rootProject.isModule,
-      commandPackagesAndroidEmbeddingVersion: rootProject.android.getEmbeddingVersion().toString().split('.').last,
-    );
-  }
-
-  Future<void> _runPubGet(String directory, FlutterProject flutterProject) async {
-    if (flutterProject.manifest.generateSyntheticPackage) {
-      final Environment environment = Environment(
-        artifacts: globals.artifacts!,
-        logger: globals.logger,
-        cacheDir: globals.cache.getRoot(),
-        engineVersion: globals.flutterVersion.engineRevision,
-        fileSystem: globals.fs,
-        flutterRootDir: globals.fs.directory(Cache.flutterRoot),
-        outputDir: globals.fs.directory(getBuildDirectory()),
-        processManager: globals.processManager,
-        platform: globals.platform,
-        usage: globals.flutterUsage,
-        projectDir: flutterProject.directory,
-        generateDartPluginRegistry: true,
-      );
-
-      await generateLocalizationsSyntheticPackage(
-        environment: environment,
-        buildSystem: globals.buildSystem,
-      );
-    }
-
-    final Stopwatch pubGetTimer = Stopwatch()..start();
-    try {
-      await pub.get(
-        context: PubContext.pubGet,
-        project: flutterProject,
-        upgrade: upgrade,
-        shouldSkipThirdPartyGenerator: false,
-        offline: boolArgDeprecated('offline'),
-      );
-      pubGetTimer.stop();
-      globals.flutterUsage.sendTiming('pub', 'get', pubGetTimer.elapsed, label: 'success');
-    // Not limiting to catching Exception because the exception is rethrown.
-    } catch (_) { // ignore: avoid_catches_without_on_clauses
-      pubGetTimer.stop();
-      globals.flutterUsage.sendTiming('pub', 'get', pubGetTimer.elapsed, label: 'failure');
-      rethrow;
-    }
-  }
-
-  @override
-  Future<FlutterCommandResult> runCommand() async {
-    final ArgResults argumentResults = argResults!;
-    if (argumentResults.rest.length > 1) {
-      throwToolExit('Too many arguments.\n$usage');
-    }
-
-    final String? workingDirectory = argumentResults.rest.length == 1 ? argumentResults.rest[0] : null;
-    final String? target = findProjectRoot(globals.fs, workingDirectory);
-    if (target == null) {
-      throwToolExit(
-        'Expected to find project root in '
-        '${ workingDirectory ?? "current working directory" }.'
-      );
-    }
-    final FlutterProject rootProject = FlutterProject.fromDirectory(globals.fs.directory(target));
-
-    // This will also resolve dependencies for the example folder,
-    await _runPubGet(target, rootProject);
-
-    // We need to regenerate the platform specific tooling for both the project
-    // itself and example (if present).
-    await rootProject.regeneratePlatformSpecificTooling();
-    if (rootProject.hasExampleApp && rootProject.example.pubspecFile.existsSync()) {
-      final FlutterProject exampleProject = rootProject.example;
-      await exampleProject.regeneratePlatformSpecificTooling();
-    }
-
-    return FlutterCommandResult.success();
-  }
-}
-
 class PackagesTestCommand extends FlutterCommand {
   PackagesTestCommand() {
     requiresPubspecYaml();
@@ -223,6 +93,8 @@
     }
   }
 
+  PubContext context = PubContext.pubForward;
+
   @override
   ArgParser argParser = ArgParser.allowAnything();
 
@@ -247,7 +119,11 @@
   Future<FlutterCommandResult> runCommand() async {
     final List<String> subArgs = argResults!.rest.toList()
       ..removeWhere((String arg) => arg == '--');
-    await pub.interactively(<String>[_commandName, ...subArgs], stdio: globals.stdio);
+    await pub.interactively(
+      <String>[ _commandName, ...subArgs],
+      context: context,
+      command: _commandName,
+    );
     return FlutterCommandResult.success();
   }
 }
@@ -270,21 +146,31 @@
     return '${runner!.executableName} packages pub [<arguments...>]';
   }
 
+  static final PubContext _context = PubContext.pubPassThrough;
+
   @override
   Future<FlutterCommandResult> runCommand() async {
-    await pub.interactively(argResults!.rest, stdio: globals.stdio);
+    await pub.interactively(
+      command: 'pub',
+      argResults!.rest,
+      context: _context,
+    );
     return FlutterCommandResult.success();
   }
 }
 
-class PackagesInteractiveGetCommand extends FlutterCommand {
-  PackagesInteractiveGetCommand(this._commandName, this._description);
+/// Represents the pub sub-commands that makes package-resolutions.
+class PackagesGetCommand extends FlutterCommand {
+  PackagesGetCommand(this._commandName, this._description, this._context);
 
   @override
   ArgParser argParser = ArgParser.allowAnything();
 
   final String _commandName;
   final String _description;
+  final PubContext _context;
+
+  FlutterProject? _rootProject;
 
   @override
   String get name => _commandName;
@@ -300,29 +186,89 @@
     return '${runner!.executableName} pub $_commandName [<arguments...>]';
   }
 
+  /// An [ArgParser] that accepts all options and flags that the
+  ///
+  /// `pub get`
+  /// `pub upgrade`
+  /// `pub downgrade`
+  /// `pub add`
+  /// `pub remove`
+  ///
+  /// commands accept.
+  ArgParser get _permissiveArgParser {
+    final ArgParser argParser = ArgParser();
+    argParser.addOption('directory', abbr: 'C');
+    argParser.addFlag('offline');
+    argParser.addFlag('dry-run', abbr: 'n');
+    argParser.addFlag('help', abbr: 'h');
+    argParser.addFlag('enforce-lockfile');
+    argParser.addFlag('precompile');
+    argParser.addFlag('major-versions');
+    argParser.addFlag('null-safety');
+    argParser.addFlag('example', defaultsTo: true);
+    argParser.addOption('sdk');
+    argParser.addOption('path');
+    argParser.addOption('hosted-url');
+    argParser.addOption('git-url');
+    argParser.addOption('git-ref');
+    argParser.addOption('git-path');
+    argParser.addFlag('dev');
+    return argParser;
+  }
+
   @override
   Future<FlutterCommandResult> runCommand() async {
     List<String> rest = argResults!.rest;
-    final bool isHelp = rest.contains('-h') || rest.contains('--help');
-    String? target;
-    if (rest.length == 1 && (rest.single.contains('/') || rest.single.contains(r'\'))) {
-      // For historical reasons, if there is one argument to the command and it contains
-      // a multiple-component path (i.e. contains a slash) then we use that to determine
-      // to which project we're applying the command.
-      target = findProjectRoot(globals.fs, rest.single);
-      rest = <String>[];
-    } else {
-      target = findProjectRoot(globals.fs);
+    bool isHelp = false;
+    bool example = true;
+    bool exampleWasParsed = false;
+    String? directoryOption;
+    bool dryRun = false;
+    try {
+      final ArgResults results = _permissiveArgParser.parse(rest);
+      isHelp = results['help'] as bool;
+      directoryOption = results['directory'] as String?;
+      example = results['example'] as bool;
+      exampleWasParsed = results.wasParsed('example');
+      dryRun = results['dry-run'] as bool;
+    } on ArgParserException {
+      // Let pub give the error message.
     }
+    String? target;
+    FlutterProject? rootProject;
 
-    FlutterProject? flutterProject;
     if (!isHelp) {
-      if (target == null) {
-        throwToolExit('Expected to find project root in current working directory.');
-      }
-      flutterProject = FlutterProject.fromDirectory(globals.fs.directory(target));
+      if (directoryOption == null && rest.length == 1 &&
+          ((rest.single.contains('/') || rest.single.contains(r'\')) ||
+            name == 'get')) {
+        // For historical reasons, if there is one argument to the command and it contains
+        // a multiple-component path (i.e. contains a slash) then we use that to determine
+        // to which project we're applying the command.
+        target = findProjectRoot(globals.fs, rest.single);
 
-      if (flutterProject.manifest.generateSyntheticPackage) {
+        globals.printWarning('''
+  Using a naked argument for directory is deprecated and will stop working in a future Flutter release.
+
+  Use --directory instead.''');
+        if (target == null) {
+          throwToolExit('Expected to find project root in ${rest.single}.');
+        }
+        rest = <String>[];
+      } else {
+        target = findProjectRoot(globals.fs, directoryOption);
+        if (target == null) {
+          if (directoryOption == null) {
+            throwToolExit('Expected to find project root in current working directory.');
+          } else {
+            throwToolExit('Expected to find project root in $directoryOption.');
+          }
+        }
+      }
+
+      rootProject = FlutterProject.fromDirectory(globals.fs.directory(target));
+      _rootProject = rootProject;
+
+      if (rootProject.manifest.generateSyntheticPackage) {
         final Environment environment = Environment(
           artifacts: globals.artifacts!,
           logger: globals.logger,
@@ -334,7 +280,7 @@
           processManager: globals.processManager,
           platform: globals.platform,
           usage: globals.flutterUsage,
-          projectDir: flutterProject.directory,
+          projectDir: rootProject.directory,
           generateDartPluginRegistry: true,
         );
 
@@ -344,17 +290,70 @@
         );
       }
     }
+    final String? relativeTarget = target == null ? null : globals.fs.path.relative(target);
 
     final List<String> subArgs = rest.toList()..removeWhere((String arg) => arg == '--');
-    await pub.interactively(
-      <String>[name, ...subArgs],
-      directory: target,
-      stdio: globals.stdio,
-      touchesPackageConfig: !isHelp,
-      generateSyntheticPackage: flutterProject?.manifest.generateSyntheticPackage ?? false,
-    );
+    final Stopwatch timer = Stopwatch()..start();
+    try {
+      await pub.interactively(
+        <String>[
+          name,
+          ...subArgs,
+          // `dart pub get` and friends defaults to `--no-example`.
+          if(!exampleWasParsed && target != null) '--example',
+          if(directoryOption == null && relativeTarget != null) ...<String>['--directory', relativeTarget],
+        ],
+        project: rootProject,
+        context: _context,
+        command: name,
+        touchesPackageConfig: !(isHelp || dryRun),
+      );
+      globals.flutterUsage.sendTiming('pub', 'get', timer.elapsed, label: 'success');
+    // Not limiting to catching Exception because the exception is rethrown.
+    } catch (_) { // ignore: avoid_catches_without_on_clauses
+      globals.flutterUsage.sendTiming('pub', 'get', timer.elapsed, label: 'failure');
+      rethrow;
+    }
 
-    await flutterProject?.regeneratePlatformSpecificTooling();
+    if (rootProject != null) {
+      // We need to regenerate the platform specific tooling for both the project
+      // itself and example(if present).
+      await rootProject.regeneratePlatformSpecificTooling();
+      if (example && rootProject.hasExampleApp && rootProject.example.pubspecFile.existsSync()) {
+        final FlutterProject exampleProject = rootProject.example;
+        await exampleProject.regeneratePlatformSpecificTooling();
+      }
+    }
+
     return FlutterCommandResult.success();
   }
+
+  /// The pub packages usage values are incorrect since these are calculated/sent
+  /// before pub get completes. This needs to be performed after dependency resolution.
+  @override
+  Future<CustomDimensions> get usageValues async {
+    final FlutterProject? rootProject = _rootProject;
+    if (rootProject == null) {
+      return const CustomDimensions();
+    }
+
+    int numberPlugins;
+    // Do not send plugin analytics if pub has not run before.
+    final bool hasPlugins = rootProject.flutterPluginsDependenciesFile.existsSync()
+      && rootProject.packageConfigFile.existsSync();
+    if (hasPlugins) {
+      // Do not fail pub get if package config files are invalid before pub has
+      // had a chance to run.
+      final List<Plugin> plugins = await findPlugins(rootProject, throwOnError: false);
+      numberPlugins = plugins.length;
+    } else {
+      numberPlugins = 0;
+    }
+
+    return CustomDimensions(
+      commandPackagesNumberPlugins: numberPlugins,
+      commandPackagesProjectModule: rootProject.isModule,
+      commandPackagesAndroidEmbeddingVersion: rootProject.android.getEmbeddingVersion().toString().split('.').last,
+    );
+  }
 }
diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart
index fdb97d6..94c07ea 100644
--- a/packages/flutter_tools/lib/src/dart/pub.dart
+++ b/packages/flutter_tools/lib/src/dart/pub.dart
@@ -120,7 +120,10 @@
   static final PubContext interactive = PubContext._(<String>['interactive']);
   static final PubContext pubGet = PubContext._(<String>['get']);
   static final PubContext pubUpgrade = PubContext._(<String>['upgrade']);
+  static final PubContext pubAdd = PubContext._(<String>['add']);
+  static final PubContext pubRemove = PubContext._(<String>['remove']);
   static final PubContext pubForward = PubContext._(<String>['forward']);
+  static final PubContext pubPassThrough = PubContext._(<String>['passthrough']);
   static final PubContext runTest = PubContext._(<String>['run_test']);
   static final PubContext flutterTests = PubContext._(<String>['flutter_tests']);
   static final PubContext updatePackages = PubContext._(<String>['update_packages']);
@@ -161,7 +164,7 @@
     required Stdio stdio,
   }) = _DefaultPub.test;
 
-  /// Runs `pub get` or `pub upgrade` for [project].
+  /// Runs `pub get` for [project].
   ///
   /// [context] provides extra information to package server requests to
   /// understand usage.
@@ -173,7 +176,6 @@
   Future<void> get({
     required PubContext context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     String? flutterRootOverride,
@@ -203,14 +205,23 @@
 
   /// Runs pub in 'interactive' mode.
   ///
-  /// directly piping the stdin stream of this process to that of pub, and the
-  /// stdout/stderr stream of pub to the corresponding streams of this process.
+  /// This will run the pub process with StdioInherited (unless [_stdio] is set
+  /// for testing).
+  ///
+  /// The pub process will be run in current working directory, so `--directory`
+  /// should be passed appropriately in [arguments]. This ensures output from
+  /// pub will refer to relative paths correctly.
+  ///
+  /// [touchesPackageConfig] should be true if this is a command expexted to
+  /// create a new `.dart_tool/package_config.json` file.
   Future<void> interactively(
     List<String> arguments, {
-    String? directory,
-    required io.Stdio stdio,
+    FlutterProject? project,
+    required PubContext context,
+    required String command,
     bool touchesPackageConfig = false,
     bool generateSyntheticPackage = false,
+    bool printProgress = true,
   });
 }
 
@@ -268,7 +279,6 @@
   Future<void> get({
     required PubContext context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     bool generateSyntheticPackage = false,
@@ -280,8 +290,6 @@
   }) async {
     final String directory = project.directory.path;
     final File packageConfigFile = project.packageConfigFile;
-    final Directory generatedDirectory = _fileSystem.directory(
-      _fileSystem.path.join(directory, '.dart_tool', 'flutter_gen'));
     final File lastVersion = _fileSystem.file(
       _fileSystem.path.join(directory, '.dart_tool', 'version'));
     final File currentVersion = _fileSystem.file(
@@ -352,25 +360,7 @@
       flutterRootOverride: flutterRootOverride,
       printProgress: printProgress
     );
-
-    if (!packageConfigFile.existsSync()) {
-      throwToolExit('$directory: pub did not create .dart_tools/package_config.json file.');
-    }
-    lastVersion.writeAsStringSync(currentVersion.readAsStringSync());
-    await _updatePackageConfig(
-      packageConfigFile,
-      generatedDirectory,
-      project.manifest.generateSyntheticPackage,
-    );
-    if (project.hasExampleApp && project.example.pubspecFile.existsSync()) {
-      final Directory exampleGeneratedDirectory = _fileSystem.directory(
-        _fileSystem.path.join(project.example.directory.path, '.dart_tool', 'flutter_gen'));
-      await _updatePackageConfig(
-        project.example.packageConfigFile,
-        exampleGeneratedDirectory,
-        project.example.manifest.generateSyntheticPackage,
-      );
-    }
+    await _updateVersionAndPackageConfig(project);
   }
 
   /// Runs pub with [arguments] and [ProcessStartMode.inheritStdio] mode.
@@ -392,9 +382,6 @@
     String? flutterRootOverride,
   }) async {
     int exitCode;
-    if (printProgress) {
-      _logger.printStatus('Running "flutter pub $command" in ${_fileSystem.path.basename(directory)}...');
-    }
 
     final List<String> pubCommand = _pubCommand(arguments);
     final Map<String, String> pubEnvironment = await _createPubEnvironment(context, flutterRootOverride);
@@ -567,64 +554,22 @@
   @override
   Future<void> interactively(
     List<String> arguments, {
-    String? directory,
-    required io.Stdio stdio,
+    FlutterProject? project,
+    required PubContext context,
+    required String command,
     bool touchesPackageConfig = false,
     bool generateSyntheticPackage = false,
+    bool printProgress = true,
   }) async {
-    // Fully resolved pub or pub.bat is calculated based on current platform.
-    final io.Process process = await _processUtils.start(
-      _pubCommand(<String>[
-          if (_logger.supportsColor) '--color',
-          ...arguments,
-      ]),
-      workingDirectory: directory,
-      environment: await _createPubEnvironment(PubContext.interactive),
+    await _runWithStdioInherited(
+      arguments,
+      command: command,
+      directory: _fileSystem.currentDirectory.path,
+      context: context,
+      printProgress: printProgress,
     );
-
-    // Pipe the Flutter tool stdin to the pub stdin.
-    unawaited(process.stdin.addStream(stdio.stdin)
-      // If pub exits unexpectedly with an error, that will be reported below
-      // by the tool exit after the exit code check.
-      .catchError((dynamic err, StackTrace stack) {
-        _logger.printTrace('Echoing stdin to the pub subprocess failed:');
-        _logger.printTrace('$err\n$stack');
-      }
-    ));
-
-    // Pipe the pub stdout and stderr to the tool stdout and stderr.
-    try {
-      await Future.wait<dynamic>(<Future<dynamic>>[
-        stdio.addStdoutStream(process.stdout),
-        stdio.addStderrStream(process.stderr),
-      ]);
-    } on Exception catch (err, stack) {
-      _logger.printTrace('Echoing stdout or stderr from the pub subprocess failed:');
-      _logger.printTrace('$err\n$stack');
-    }
-
-    // Wait for pub to exit.
-    final int code = await process.exitCode;
-    if (code != 0) {
-      throwToolExit('pub finished with exit code $code', exitCode: code);
-    }
-
-    if (touchesPackageConfig) {
-      final String targetDirectory = directory ?? _fileSystem.currentDirectory.path;
-      final File packageConfigFile = _fileSystem.file(
-        _fileSystem.path.join(targetDirectory, '.dart_tool', 'package_config.json'));
-      final Directory generatedDirectory = _fileSystem.directory(
-        _fileSystem.path.join(targetDirectory, '.dart_tool', 'flutter_gen'));
-      final File lastVersion = _fileSystem.file(
-        _fileSystem.path.join(targetDirectory, '.dart_tool', 'version'));
-      final File currentVersion = _fileSystem.file(
-        _fileSystem.path.join(Cache.flutterRoot!, 'version'));
-        lastVersion.writeAsStringSync(currentVersion.readAsStringSync());
-      await _updatePackageConfig(
-        packageConfigFile,
-        generatedDirectory,
-        generateSyntheticPackage,
-      );
+    if (touchesPackageConfig && project != null) {
+      await _updateVersionAndPackageConfig(project);
     }
   }
 
@@ -766,23 +711,46 @@
     return environment;
   }
 
-  /// Update the package configuration file.
+  /// Updates the .dart_tool/version file to be equal to current Flutter
+  /// version.
   ///
-  /// Creates a corresponding `package_config_subset` file that is used by the build
-  /// system to avoid rebuilds caused by an updated pub timestamp.
+  /// Calls [_updatePackageConfig] for [project] and [project.example] (if it
+  /// exists).
   ///
-  /// if [generateSyntheticPackage] is true then insert flutter_gen synthetic
-  /// package into the package configuration. This is used by the l10n localization
-  /// tooling to insert a new reference into the package_config file, allowing the import
-  /// of a package URI that is not specified in the pubspec.yaml
+  /// This should be called after pub invocations that are expected to update
+  /// the packageConfig.
+  Future<void> _updateVersionAndPackageConfig(FlutterProject project) async {
+    if (!project.packageConfigFile.existsSync()) {
+      throwToolExit('${project.directory}: pub did not create .dart_tools/package_config.json file.');
+    }
+    final File lastVersion = _fileSystem.file(
+      _fileSystem.path.join(project.directory.path, '.dart_tool', 'version'),
+    );
+    final File currentVersion = _fileSystem.file(
+      _fileSystem.path.join(Cache.flutterRoot!, 'version'));
+    lastVersion.writeAsStringSync(currentVersion.readAsStringSync());
+
+    await _updatePackageConfig(project);
+    if (project.hasExampleApp && project.example.pubspecFile.existsSync()) {
+      await _updatePackageConfig(project.example);
+    }
+  }
+
+  /// Update the package configuration file in [project].
+  ///
+  /// Creates a corresponding `package_config_subset` file that is used by the
+  /// build system to avoid rebuilds caused by an updated pub timestamp.
+  ///
+  /// if `project.generateSyntheticPackage` is `true` then insert flutter_gen
+  /// synthetic package into the package configuration. This is used by the l10n
+  /// localization tooling to insert a new reference into the package_config
+  /// file, allowing the import of a package URI that is not specified in the
+  /// pubspec.yaml
   ///
   /// For more information, see:
   ///   * [generateLocalizations], `in lib/src/localizations/gen_l10n.dart`
-  Future<void> _updatePackageConfig(
-    File packageConfigFile,
-    Directory generatedDirectory,
-    bool generateSyntheticPackage,
-  ) async {
+  Future<void> _updatePackageConfig(FlutterProject project) async {
+    final File packageConfigFile = project.packageConfigFile;
     final PackageConfig packageConfig = await loadPackageConfigWithLogging(packageConfigFile, logger: _logger);
 
     packageConfigFile.parent
@@ -792,7 +760,7 @@
         _fileSystem,
       ));
 
-    if (!generateSyntheticPackage) {
+    if (!project.manifest.generateSyntheticPackage) {
       return;
     }
     if (packageConfig.packages.any((Package package) => package.name == 'flutter_gen')) {
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
index 9c419c3..4a617ff 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart
@@ -29,7 +29,6 @@
   Future<void> get({
     PubContext? context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     bool generateSyntheticPackage = false,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
index 6e10ff5..a744513 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart
@@ -480,7 +480,6 @@
   Future<void> get({
     PubContext? context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     bool generateSyntheticPackage = false,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/pub_get_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/pub_get_test.dart
index e8b9db0..4ab2901 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/pub_get_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/pub_get_test.dart
@@ -35,7 +35,7 @@
     fileSystem.currentDirectory.childFile('.flutter-plugins').createSync();
     fileSystem.currentDirectory.childFile('.flutter-plugins-dependencies').createSync();
 
-    final PackagesGetCommand command = PackagesGetCommand('get', false);
+    final PackagesGetCommand command = PackagesGetCommand('get', '', PubContext.pubGet);
     final CommandRunner<void> commandRunner = createTestCommandRunner(command);
 
     await commandRunner.run(<String>['get']);
@@ -60,7 +60,7 @@
       ..createSync(recursive: true)
       ..writeAsBytesSync(<int>[0]);
 
-    final PackagesGetCommand command = PackagesGetCommand('get', false);
+    final PackagesGetCommand command = PackagesGetCommand('get', '', PubContext.pubGet);
     final CommandRunner<void> commandRunner = createTestCommandRunner(command);
 
     await commandRunner.run(<String>['get']);
@@ -81,7 +81,7 @@
     final Directory targetDirectory = fileSystem.currentDirectory.childDirectory('target');
     targetDirectory.childFile('pubspec.yaml').createSync();
 
-    final PackagesGetCommand command = PackagesGetCommand('get', false);
+    final PackagesGetCommand command = PackagesGetCommand('get', '', PubContext.pubGet);
     final CommandRunner<void> commandRunner = createTestCommandRunner(command);
 
     await commandRunner.run(<String>['get', targetDirectory.path]);
@@ -98,7 +98,7 @@
     fileSystem.currentDirectory.childFile('pubspec.yaml').createSync();
     fileSystem.currentDirectory.childDirectory('example').createSync(recursive: true);
 
-    final PackagesGetCommand command = PackagesGetCommand('get', false);
+    final PackagesGetCommand command = PackagesGetCommand('get', '', PubContext.pubGet);
     final CommandRunner<void> commandRunner = createTestCommandRunner(command);
 
     await commandRunner.run(<String>['get']);
@@ -115,7 +115,7 @@
   });
 
   testUsingContext('pub get throws error on missing directory', () async {
-    final PackagesGetCommand command = PackagesGetCommand('get', false);
+    final PackagesGetCommand command = PackagesGetCommand('get', '', PubContext.pubGet);
     final CommandRunner<void> commandRunner = createTestCommandRunner(command);
 
     try {
@@ -159,7 +159,7 @@
       '''
     );
 
-    final PackagesGetCommand command = PackagesGetCommand('get', false);
+    final PackagesGetCommand command = PackagesGetCommand('get', '', PubContext.pubGet);
     final CommandRunner<void> commandRunner = createTestCommandRunner(command);
 
     await commandRunner.run(<String>['get']);
@@ -183,23 +183,21 @@
   final FileSystem fileSystem;
 
   @override
-  Future<void> get({
+  Future<void> interactively(
+    List<String> arguments, {
+    FlutterProject? project,
     required PubContext context,
-    required FlutterProject project,
-    bool skipIfAbsent = false,
-    bool upgrade = false,
-    bool offline = false,
+    required String command,
+    bool touchesPackageConfig = false,
     bool generateSyntheticPackage = false,
-    bool generateSyntheticPackageForExample = false,
-    String? flutterRootOverride,
-    bool checkUpToDate = false,
-    bool shouldSkipThirdPartyGenerator = true,
     bool printProgress = true,
   }) async {
-    fileSystem.directory(project.directory)
-      .childDirectory('.dart_tool')
-      .childFile('package_config.json')
-      ..createSync(recursive: true)
-      ..writeAsStringSync('{"configVersion":2,"packages":[]}');
+    if (project != null) {
+      fileSystem.directory(project.directory)
+        .childDirectory('.dart_tool')
+        .childFile('package_config.json')
+        ..createSync(recursive: true)
+        ..writeAsStringSync('{"configVersion":2,"packages":[]}');
+      }
   }
 }
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart
index 1456bac..56b9fa0 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart
@@ -275,7 +275,6 @@
   Future<void> get({
     required PubContext context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     bool generateSyntheticPackage = false,
diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
index c96b147..4bd4f81 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
@@ -72,6 +72,7 @@
         'packages',
         verb,
         ...?args,
+        '--directory',
         projectPath,
       ]);
       return command;
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index 9ae8672..de4229c 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -1208,7 +1208,6 @@
   Future<void> get({
     PubContext? context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     bool generateSyntheticPackage = false,
diff --git a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart
index d3a8bc5..173a1a7 100644
--- a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart
+++ b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart
@@ -557,7 +557,7 @@
       ),
       throwsA(isA<ToolExit>().having((ToolExit error) => error.message, 'message', toolExitMessage)),
     );
-    expect(logger.statusText, 'Running "flutter pub get" in /...\n');
+    expect(logger.statusText, isEmpty);
     expect(
       mockStdio.stdout.writes.map(utf8.decode),
       <String>[
@@ -634,9 +634,7 @@
         ),
       ),
     );
-    expect(logger.statusText,
-      'Running "flutter pub get" in /...\n'
-    );
+    expect(logger.statusText, isEmpty);
     expect(logger.errorText, isEmpty);
     expect(processManager, hasNoRemainingExpectations);
   });
@@ -1074,7 +1072,6 @@
       context: PubContext.flutterTests,
     ); // pub sets date of .packages to 2002
 
-    expect(logger.statusText, 'Running "flutter pub get" in /...\n');
     expect(logger.errorText, isEmpty);
     expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it
     logger.clear();
@@ -1089,7 +1086,7 @@
       context: PubContext.flutterTests,
     ); // pub does nothing
 
-    expect(logger.statusText, 'Running "flutter pub get" in /...\n');
+    expect(logger.statusText, isEmpty);
     expect(logger.errorText, isEmpty);
     expect(fileSystem.file('pubspec.yaml').lastModifiedSync(), DateTime(2001)); // because nothing should touch it
     logger.clear();
diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
index 765773e..4f5d774 100644
--- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
+++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart
@@ -951,7 +951,6 @@
   Future<void> get({
     required PubContext context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     String? flutterRootOverride,
diff --git a/packages/flutter_tools/test/src/fake_process_manager.dart b/packages/flutter_tools/test/src/fake_process_manager.dart
index fb87570..e0fc510 100644
--- a/packages/flutter_tools/test/src/fake_process_manager.dart
+++ b/packages/flutter_tools/test/src/fake_process_manager.dart
@@ -110,15 +110,8 @@
     Map<String, String>? environment,
     Encoding? encoding,
   ) {
-    expect(command.length, this.command.length);
-    for(int i = 0; i < command.length; i++) {
-      final Pattern expected = this.command[i];
-      if (expected is String) {
-        expect(command[i], expected);
-      } else {
-        expect(command[i], matches(this.command[i]));
-      }
-    }
+    final List<dynamic> matchers = this.command.map((Pattern x) => x is String ? x : matches(x)).toList();
+    expect(command, matchers);
     if (this.workingDirectory != null) {
       expect(this.workingDirectory, workingDirectory);
     }
diff --git a/packages/flutter_tools/test/src/throwing_pub.dart b/packages/flutter_tools/test/src/throwing_pub.dart
index c00a7f1..a96d133 100644
--- a/packages/flutter_tools/test/src/throwing_pub.dart
+++ b/packages/flutter_tools/test/src/throwing_pub.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/dart/pub.dart';
 import 'package:flutter_tools/src/project.dart';
 
@@ -21,7 +20,6 @@
   Future<void> get({
     PubContext? context,
     required FlutterProject project,
-    bool skipIfAbsent = false,
     bool upgrade = false,
     bool offline = false,
     bool checkLastModified = true,
@@ -39,10 +37,12 @@
   @override
   Future<void> interactively(
     List<String> arguments, {
-    String? directory,
-    required Stdio stdio,
+    FlutterProject? project,
+    required PubContext context,
+    required String command,
     bool touchesPackageConfig = false,
     bool generateSyntheticPackage = false,
+    bool printProgress = true,
   }) {
     throw UnsupportedError('Attempted to invoke pub during test.');
   }