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 156ec73db0b596..1a03fec6ac9162 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 @@ class GenerateLocalizationsTarget extends Target { 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 a961b161dfd222..79dc52e55bc940 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_templates.dart'; 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 generateMethodParameters(Message message) { assert(message.placeholders.isNotEmpty); @@ -405,6 +408,7 @@ class LocalizationsGenerator { Iterable _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 @@ class LocalizationsGenerator { 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 @@ class LocalizationsGenerator { /// 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 @@ class LocalizationsGenerator { // 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 1302fbf8b2017a..14f731ed5fe848 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 5d5bc66423ef01..ac8c6ac639ff15 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -877,7 +877,6 @@ abstract class ResidentRunner { 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 125b45852c3696..70028973b111da 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:convert'; 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 @@ import 'package:test_api/test_api.dart' hide TypeMatcher, isInstanceOf; // ignor 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 @@ void main() { _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 @@ void main() { 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 @@ void main() { 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 @@ void main() { templateArbFileName: defaultTemplateArbFileName, outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, - inputsAndOutputsListPath: syntheticPackagePath, + inputsAndOutputsListPath: syntheticL10nPackagePath, ) ..loadResources() ..writeOutputFiles(); @@ -633,7 +633,7 @@ void main() { } 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 @@ void main() { expect(jsonResult.containsKey('outputs'), isTrue); final List outputList = jsonResult['outputs'] as List; - 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 @@ void main() { 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 @@ void main() { } final String localizationsFile = fs.file( - fs.path.join(syntheticPackagePath, defaultOutputFileString), + fs.path.join(syntheticL10nPackagePath, defaultOutputFileString), ).readAsStringSync(); expect(localizationsFile, contains( ''' @@ -1159,7 +1159,7 @@ import 'output-localization-file_zh.dart'; } final String localizationsFile = fs.file( - fs.path.join(syntheticPackagePath, defaultOutputFileString), + fs.path.join(syntheticL10nPackagePath, defaultOutputFileString), ).readAsStringSync(); expect(localizationsFile, contains( ''' @@ -1586,4 +1586,62 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); }); }); + + 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()); + + 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'); + }); }