[gen_l10n] Create pubspec.yaml in ".dart_tool/flutter_gen" if it does not already exist (#68206)

* Generate pubspec.yaml for synthetic package if it did not exist prior
diff --git a/packages/flutter_tools/lib/src/build_system/targets/localizations.dart b/packages/flutter_tools/lib/src/build_system/targets/localizations.dart
index 156ec73..1a03fec 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/localizations.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/localizations.dart
@@ -119,7 +119,6 @@
       logger: environment.logger,
       fileSystem: environment.fileSystem,
     );
-
     generateLocalizations(
       logger: environment.logger,
       options: options,
diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart
index a961b16..79dc52e 100644
--- a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart
+++ b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart
@@ -13,12 +13,15 @@
 import 'gen_l10n_types.dart';
 import 'localizations_utils.dart';
 
-/// The default path used when the `useSyntheticPackage` setting is set to true
+/// The path for the synthetic package.
+final String defaultSyntheticPackagePath = globals.fs.path.join('.dart_tool', 'flutter_gen');
+
+/// The default path used when the `_useSyntheticPackage` setting is set to true
 /// in [LocalizationsGenerator].
 ///
 /// See [LocalizationsGenerator.initialize] for where and how it is used by the
 /// localizations tool.
-final String defaultSyntheticPackagePath = globals.fs.path.join('.dart_tool', 'flutter_gen', 'gen_l10n');
+final String syntheticL10nPackagePath = globals.fs.path.join(defaultSyntheticPackagePath, 'gen_l10n');
 
 List<String> generateMethodParameters(Message message) {
   assert(message.placeholders.isNotEmpty);
@@ -405,6 +408,7 @@
   Iterable<Message> _allMessages;
   AppResourceBundleCollection _allBundles;
   LocaleInfo _templateArbLocale;
+  bool _useSyntheticPackage = true;
 
   /// The directory that contains the project's arb files, as well as the
   /// header file, if specified.
@@ -538,12 +542,10 @@
     bool useSyntheticPackage = true,
     String projectPathString,
   }) {
+    _useSyntheticPackage = useSyntheticPackage;
     setProjectDir(projectPathString);
     setInputDirectory(inputPathString);
-    setOutputDirectory(
-      outputPathString: outputPathString ?? inputPathString,
-      useSyntheticPackage: useSyntheticPackage,
-    );
+    setOutputDirectory(outputPathString ?? inputPathString);
     setTemplateArbFile(templateArbFileName);
     setBaseOutputFile(outputFileString);
     setPreferredSupportedLocales(preferredSupportedLocale);
@@ -614,15 +616,14 @@
 
   /// Sets the reference [Directory] for [outputDirectory].
   @visibleForTesting
-  void setOutputDirectory({
+  void setOutputDirectory(
     String outputPathString,
-    bool useSyntheticPackage = true,
-  }) {
-    if (useSyntheticPackage) {
+  ) {
+    if (_useSyntheticPackage) {
       outputDirectory = _fs.directory(
         projectDirectory != null
-          ? _getAbsoluteProjectPath(defaultSyntheticPackagePath)
-          : defaultSyntheticPackagePath
+          ? _getAbsoluteProjectPath(syntheticL10nPackagePath)
+          : syntheticL10nPackagePath
       );
     } else {
       if (outputPathString == null) {
@@ -1004,11 +1005,20 @@
     // First, generate the string contents of all necessary files.
     _generateCode();
 
+    // A pubspec.yaml file is required when using a synthetic package. If it does not
+    // exist, create a blank one.
+    if (_useSyntheticPackage) {
+      final Directory syntheticPackageDirectory = _fs.directory(defaultSyntheticPackagePath);
+      syntheticPackageDirectory.createSync(recursive: true);
+      final File flutterGenPubspec = syntheticPackageDirectory.childFile('pubspec.yaml');
+      if (!flutterGenPubspec.existsSync()) {
+        flutterGenPubspec.writeAsStringSync(emptyPubspecTemplate);
+      }
+    }
+
     // Since all validity checks have passed up to this point,
     // write the contents into the directory.
-    if (!outputDirectory.existsSync()) {
-      outputDirectory.createSync(recursive: true);
-    }
+    outputDirectory.createSync(recursive: true);
 
     // Ensure that the created directory has read/write permissions.
     final FileStat fileStat = outputDirectory.statSync();
diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart
index 1302fbf..14f731e 100644
--- a/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart
+++ b/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+const String emptyPubspecTemplate = '''# Generated by the flutter tool
+name: synthetic_package
+description: The Flutter application's synthetic package.
+''';
+
 const String fileTemplate = '''
 @(header)
 import 'dart:async';
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 5d5bc66..ac8c6ac 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -877,7 +877,6 @@
       processManager: globals.processManager,
       projectDir: globals.fs.currentDirectory,
     );
-    globals.logger.printTrace('Starting incremental build...');
     _lastBuild = await globals.buildSystem.buildIncremental(
       const GenerateLocalizationsTarget(),
       _environment,
diff --git a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart
index 125b458..7002897 100644
--- a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart
+++ b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:file/memory.dart';
+import 'package:yaml/yaml.dart';
 
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/logger.dart';
@@ -19,7 +20,8 @@
 export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf, test; // Defines a 'package:test' shim.
 
 final String defaultL10nPathString = globals.fs.path.join('lib', 'l10n');
-final String syntheticPackagePath = globals.fs.path.join('.dart_tool', 'flutter_gen', 'gen_l10n');
+final String syntheticPackagePath = globals.fs.path.join('.dart_tool', 'flutter_gen');
+final String syntheticL10nPackagePath = globals.fs.path.join(syntheticPackagePath, 'gen_l10n');
 const String defaultTemplateArbFileName = 'app_en.arb';
 const String defaultOutputFileString = 'output-localization-file.dart';
 const String defaultClassNameString = 'AppLocalizations';
@@ -109,10 +111,8 @@
         _standardFlutterDirectoryL10nSetup(fs);
         final LocalizationsGenerator generator = LocalizationsGenerator(fs);
         try {
-          generator.setOutputDirectory(
-            outputPathString: null,
-            useSyntheticPackage: false,
-          );
+          generator.initialize(useSyntheticPackage: false);
+          generator.setOutputDirectory(null);
         } on L10nException catch (e) {
           expect(e.message, contains('cannot be null'));
           return;
@@ -302,7 +302,7 @@
         generator = LocalizationsGenerator(fs);
         try {
           generator.setInputDirectory(defaultL10nPathString);
-          generator.setOutputDirectory();
+          generator.setOutputDirectory(null);
           generator.setTemplateArbFile(defaultTemplateArbFileName);
           generator.setBaseOutputFile(defaultOutputFileString);
         } on L10nException catch (e) {
@@ -433,7 +433,7 @@
       fail('Generating output should not fail: \n${e.message}');
     }
 
-    final Directory outputDirectory = fs.directory(syntheticPackagePath);
+    final Directory outputDirectory = fs.directory(syntheticL10nPackagePath);
     expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue);
     expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue);
     expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue);
@@ -624,7 +624,7 @@
           templateArbFileName: defaultTemplateArbFileName,
           outputFileString: defaultOutputFileString,
           classNameString: defaultClassNameString,
-          inputsAndOutputsListPath: syntheticPackagePath,
+          inputsAndOutputsListPath: syntheticL10nPackagePath,
         )
         ..loadResources()
         ..writeOutputFiles();
@@ -633,7 +633,7 @@
     }
 
     final File inputsAndOutputsList = fs.file(
-      fs.path.join(syntheticPackagePath, 'gen_l10n_inputs_and_outputs.json'),
+      fs.path.join(syntheticL10nPackagePath, 'gen_l10n_inputs_and_outputs.json'),
     );
     expect(inputsAndOutputsList.existsSync(), isTrue);
 
@@ -645,9 +645,9 @@
 
     expect(jsonResult.containsKey('outputs'), isTrue);
     final List<dynamic> outputList = jsonResult['outputs'] as List<dynamic>;
-    expect(outputList, contains(fs.path.absolute(syntheticPackagePath, 'output-localization-file.dart')));
-    expect(outputList, contains(fs.path.absolute(syntheticPackagePath, 'output-localization-file_en.dart')));
-    expect(outputList, contains(fs.path.absolute(syntheticPackagePath, 'output-localization-file_es.dart')));
+    expect(outputList, contains(fs.path.absolute(syntheticL10nPackagePath, 'output-localization-file.dart')));
+    expect(outputList, contains(fs.path.absolute(syntheticL10nPackagePath, 'output-localization-file_en.dart')));
+    expect(outputList, contains(fs.path.absolute(syntheticL10nPackagePath, 'output-localization-file_es.dart')));
   });
 
   test('setting both a headerString and a headerFile should fail', () {
@@ -1095,11 +1095,11 @@
         fail('Generating output files should not fail: $e');
       }
 
-      expect(fs.isFileSync(fs.path.join(syntheticPackagePath, 'output-localization-file_en.dart')), true);
-      expect(fs.isFileSync(fs.path.join(syntheticPackagePath, 'output-localization-file_en_US.dart')), false);
+      expect(fs.isFileSync(fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart')), true);
+      expect(fs.isFileSync(fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en_US.dart')), false);
 
       final String englishLocalizationsFile = fs.file(
-        fs.path.join(syntheticPackagePath, 'output-localization-file_en.dart')
+        fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart')
       ).readAsStringSync();
       expect(englishLocalizationsFile, contains('class AppLocalizationsEnCa extends AppLocalizationsEn'));
       expect(englishLocalizationsFile, contains('class AppLocalizationsEn extends AppLocalizations'));
@@ -1129,7 +1129,7 @@
       }
 
       final String localizationsFile = fs.file(
-        fs.path.join(syntheticPackagePath, defaultOutputFileString),
+        fs.path.join(syntheticL10nPackagePath, defaultOutputFileString),
       ).readAsStringSync();
       expect(localizationsFile, contains(
 '''
@@ -1159,7 +1159,7 @@
       }
 
       final String localizationsFile = fs.file(
-        fs.path.join(syntheticPackagePath, defaultOutputFileString),
+        fs.path.join(syntheticL10nPackagePath, defaultOutputFileString),
       ).readAsStringSync();
       expect(localizationsFile, contains(
 '''
@@ -1586,4 +1586,62 @@
       });
     });
   });
+
+  test('should generate a valid pubspec.yaml file when using synthetic package if it does not already exist', () {
+    _standardFlutterDirectoryL10nSetup(fs);
+    LocalizationsGenerator generator;
+    try {
+      generator = LocalizationsGenerator(fs);
+      generator
+        ..initialize(
+          inputPathString: defaultL10nPathString,
+          templateArbFileName: defaultTemplateArbFileName,
+          outputFileString: defaultOutputFileString,
+          classNameString: defaultClassNameString,
+        )
+        ..loadResources()
+        ..writeOutputFiles();
+    } on L10nException catch (e) {
+      fail('Generating output should not fail: \n${e.message}');
+    }
+
+    final Directory outputDirectory = fs.directory(syntheticPackagePath);
+    final File pubspecFile = outputDirectory.childFile('pubspec.yaml');
+    expect(pubspecFile.existsSync(), isTrue);
+
+    final YamlNode yamlNode = loadYamlNode(pubspecFile.readAsStringSync());
+    expect(yamlNode, isA<YamlMap>());
+
+    final YamlMap yamlMap = yamlNode as YamlMap;
+    final String pubspecName = yamlMap['name'] as String;
+    final String pubspecDescription = yamlMap['description'] as String;
+    expect(pubspecName, 'synthetic_package');
+    expect(pubspecDescription, "The Flutter application's synthetic package.");
+  });
+
+  test('should not overwrite existing pubspec.yaml file when using synthetic package', () {
+    _standardFlutterDirectoryL10nSetup(fs);
+    final File pubspecFile = fs.file(fs.path.join(syntheticPackagePath, 'pubspec.yaml'))
+      ..createSync(recursive: true)
+      ..writeAsStringSync('abcd');
+
+    LocalizationsGenerator generator;
+    try {
+      generator = LocalizationsGenerator(fs);
+      generator
+        ..initialize(
+          inputPathString: defaultL10nPathString,
+          templateArbFileName: defaultTemplateArbFileName,
+          outputFileString: defaultOutputFileString,
+          classNameString: defaultClassNameString,
+        )
+        ..loadResources()
+        ..writeOutputFiles();
+    } on L10nException catch (e) {
+      fail('Generating output should not fail: \n${e.message}');
+    }
+
+    // The original pubspec file should not be overwritten.
+    expect(pubspecFile.readAsStringSync(), 'abcd');
+  });
 }