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 1692ca1946b..c982699a6c6 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/localizations.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/localizations.dart @@ -58,6 +58,7 @@ class GenerateLocalizationsTarget extends Target { final LocalizationOptions options = parseLocalizationsOptionsFromYAML( file: configFile, logger: environment.logger, + fileSystem: environment.fileSystem, defaultArbDir: defaultArbDir, defaultSyntheticPackage: !featureFlags.isExplicitPackageDependenciesEnabled, ); diff --git a/packages/flutter_tools/lib/src/commands/generate_localizations.dart b/packages/flutter_tools/lib/src/commands/generate_localizations.dart index f71f1a85aa3..53b5c43e0dc 100644 --- a/packages/flutter_tools/lib/src/commands/generate_localizations.dart +++ b/packages/flutter_tools/lib/src/commands/generate_localizations.dart @@ -260,6 +260,7 @@ class GenerateLocalizationsCommand extends FlutterCommand { options = parseLocalizationsOptionsFromYAML( file: _fileSystem.file('l10n.yaml'), logger: _logger, + fileSystem: _fileSystem, defaultArbDir: defaultArbDir, defaultSyntheticPackage: !featureFlags.isExplicitPackageDependenciesEnabled, ); diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart index 5b42e006646..2313ee3739e 100644 --- a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart +++ b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart @@ -1000,6 +1000,14 @@ class LocalizationsGenerator { return true; } + void _addToFileList(List fileList, String path) { + fileList.add(_fs.path.normalize(path)); + } + + void _addAllToFileList(List fileList, Iterable paths) { + fileList.addAll(paths.map(_fs.path.normalize)); + } + // Load _allMessages from templateArbFile and _allBundles from all of the ARB // files in inputDirectory. Also initialized: supportedLocales. void loadResources() { @@ -1030,7 +1038,8 @@ class LocalizationsGenerator { .toList(); hadErrors = _allMessages.any((Message message) => message.hadErrors); if (inputsAndOutputsListFile != null) { - _inputFileList.addAll( + _addAllToFileList( + _inputFileList, _allBundles.bundles.map((AppResourceBundle bundle) { return bundle.file.absolute.path; }), @@ -1492,7 +1501,7 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " // Generate the required files for localizations. _languageFileMap.forEach((File file, String contents) { file.writeAsStringSync(useCRLF ? contents.replaceAll('\n', '\r\n') : contents); - _outputFileList.add(file.absolute.path); + _addToFileList(_outputFileList, file.absolute.path); }); baseOutputFile.writeAsStringSync( @@ -1527,7 +1536,7 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " ); } final File? inputsAndOutputsListFileLocal = inputsAndOutputsListFile; - _outputFileList.add(baseOutputFile.absolute.path); + _addToFileList(_outputFileList, baseOutputFile.absolute.path); if (inputsAndOutputsListFileLocal != null) { // Generate a JSON file containing the inputs and outputs of the gen_l10n script. if (!inputsAndOutputsListFileLocal.existsSync()) { @@ -1548,7 +1557,7 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " void _generateUntranslatedMessagesFile(Logger logger, File untranslatedMessagesFile) { if (_unimplementedMessages.isEmpty) { untranslatedMessagesFile.writeAsStringSync('{}'); - _outputFileList.add(untranslatedMessagesFile.absolute.path); + _addToFileList(_outputFileList, untranslatedMessagesFile.absolute.path); return; } @@ -1576,6 +1585,6 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " resultingFile += '}\n'; untranslatedMessagesFile.writeAsStringSync(resultingFile); - _outputFileList.add(untranslatedMessagesFile.absolute.path); + _addToFileList(_outputFileList, untranslatedMessagesFile.absolute.path); } } diff --git a/packages/flutter_tools/lib/src/localizations/localizations_utils.dart b/packages/flutter_tools/lib/src/localizations/localizations_utils.dart index 8eb3941dd71..dfb291c3d70 100644 --- a/packages/flutter_tools/lib/src/localizations/localizations_utils.dart +++ b/packages/flutter_tools/lib/src/localizations/localizations_utils.dart @@ -479,6 +479,7 @@ class LocalizationOptions { LocalizationOptions parseLocalizationsOptionsFromYAML({ required File file, required Logger logger, + required FileSystem fileSystem, required String defaultArbDir, required bool defaultSyntheticPackage, }) { @@ -497,14 +498,24 @@ LocalizationOptions parseLocalizationsOptionsFromYAML({ throw Exception(); } return LocalizationOptions( - arbDir: _tryReadUri(yamlNode, 'arb-dir', logger)?.path ?? defaultArbDir, - outputDir: _tryReadUri(yamlNode, 'output-dir', logger)?.path, - templateArbFile: _tryReadUri(yamlNode, 'template-arb-file', logger)?.path, - outputLocalizationFile: _tryReadUri(yamlNode, 'output-localization-file', logger)?.path, - untranslatedMessagesFile: _tryReadUri(yamlNode, 'untranslated-messages-file', logger)?.path, + arbDir: _tryReadFilePath(yamlNode, 'arb-dir', logger, fileSystem) ?? defaultArbDir, + outputDir: _tryReadFilePath(yamlNode, 'output-dir', logger, fileSystem), + templateArbFile: _tryReadFilePath(yamlNode, 'template-arb-file', logger, fileSystem), + outputLocalizationFile: _tryReadFilePath( + yamlNode, + 'output-localization-file', + logger, + fileSystem, + ), + untranslatedMessagesFile: _tryReadFilePath( + yamlNode, + 'untranslated-messages-file', + logger, + fileSystem, + ), outputClass: _tryReadString(yamlNode, 'output-class', logger), header: _tryReadString(yamlNode, 'header', logger), - headerFile: _tryReadUri(yamlNode, 'header-file', logger)?.path, + headerFile: _tryReadFilePath(yamlNode, 'header-file', logger, fileSystem), useDeferredLoading: _tryReadBool(yamlNode, 'use-deferred-loading', logger), preferredSupportedLocales: _tryReadStringList(yamlNode, 'preferred-supported-locales', logger), syntheticPackage: @@ -596,8 +607,8 @@ List? _tryReadStringList(YamlMap yamlMap, String key, Logger logger) { throw Exception(); } -// Try to read a valid `Uri` or null from `yamlMap`, otherwise throw. -Uri? _tryReadUri(YamlMap yamlMap, String key, Logger logger) { +// Try to read a valid file `Uri` or null from `yamlMap` to file path, otherwise throw. +String? _tryReadFilePath(YamlMap yamlMap, String key, Logger logger, FileSystem fileSystem) { final String? value = _tryReadString(yamlMap, key, logger); if (value == null) { return null; @@ -606,5 +617,5 @@ Uri? _tryReadUri(YamlMap yamlMap, String key, Logger logger) { if (uri == null) { logger.printError('"$value" must be a relative file URI'); } - return uri; + return uri != null ? fileSystem.path.normalize(uri.path) : null; } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart index 8e94189d114..c961c7eec5b 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/build_system/depfile.dart' show Depfile; import 'package:flutter_tools/src/build_system/targets/localizations.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/generate_localizations.dart'; @@ -516,6 +517,42 @@ format: true }, ); + testUsingContext('generates normalized input & output file paths', () async { + final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb')) + ..createSync(recursive: true); + arbFile.writeAsStringSync(''' +{ + "helloWorld": "Hello, World!" +}'''); + final File configFile = fileSystem.file('l10n.yaml')..createSync(); + // Writing both forward and backward slashes to test both cases. + configFile.writeAsStringSync(r''' +arb-dir: lib/l10n +output-dir: lib\l10n +format: false +'''); + final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync(); + pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec); + + processManager.addCommand(const FakeCommand(command: [])); + final Environment environment = Environment.test( + fileSystem.currentDirectory, + artifacts: artifacts, + processManager: processManager, + fileSystem: fileSystem, + logger: BufferLogger.test(), + ); + const Target buildTarget = GenerateLocalizationsTarget(); + await buildTarget.build(environment); + + final File dependencyFile = environment.buildDir.childFile(buildTarget.depfiles.single); + final Depfile depfile = environment.depFileService.parse(dependencyFile); + + final String oppositeSeparator = fileSystem.path.separator == '/' ? r'\' : '/'; + expect(depfile.inputs, everyElement(isNot(contains(oppositeSeparator)))); + expect(depfile.outputs, everyElement(isNot(contains(oppositeSeparator)))); + }); + testUsingContext( 'nullable-getter defaults to true', () async { diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/localizations_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/localizations_test.dart index e489a36e60a..80e30ff06cd 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/localizations_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/localizations_test.dart @@ -51,6 +51,7 @@ nullable-getter: false final LocalizationOptions options = parseLocalizationsOptionsFromYAML( file: configFile, logger: BufferLogger.test(), + fileSystem: fileSystem, defaultArbDir: fileSystem.path.join('lib', 'l10n'), defaultSyntheticPackage: true, ); @@ -90,6 +91,7 @@ nullable-getter: false final LocalizationOptions options = parseLocalizationsOptionsFromYAML( file: configFile, logger: BufferLogger.test(), + fileSystem: fileSystem, defaultArbDir: fileSystem.path.join('lib', 'l10n'), defaultSyntheticPackage: true, ); @@ -118,6 +120,7 @@ nullable-getter: false final LocalizationOptions options = parseLocalizationsOptionsFromYAML( file: configFile, logger: BufferLogger.test(), + fileSystem: fileSystem, defaultArbDir: fileSystem.path.join('lib', 'l10n'), defaultSyntheticPackage: false, ); @@ -136,6 +139,7 @@ preferred-supported-locales: ['en_US', 'de'] final LocalizationOptions options = parseLocalizationsOptionsFromYAML( file: configFile, logger: BufferLogger.test(), + fileSystem: fileSystem, defaultArbDir: fileSystem.path.join('lib', 'l10n'), defaultSyntheticPackage: true, ); @@ -156,6 +160,7 @@ use-deferred-loading: string () => parseLocalizationsOptionsFromYAML( file: configFile, logger: BufferLogger.test(), + fileSystem: fileSystem, defaultArbDir: fileSystem.path.join('lib', 'l10n'), defaultSyntheticPackage: true, ), @@ -174,6 +179,7 @@ template-arb-file: {name}_en.arb () => parseLocalizationsOptionsFromYAML( file: configFile, logger: BufferLogger.test(), + fileSystem: fileSystem, defaultArbDir: fileSystem.path.join('lib', 'l10n'), defaultSyntheticPackage: true, ), 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 210ab7480db..46f56175c6d 100644 --- a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart +++ b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart @@ -7,6 +7,7 @@ import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/convert.dart'; +import 'package:flutter_tools/src/globals.dart' as globals show platform; import 'package:flutter_tools/src/localizations/gen_l10n.dart'; import 'package:flutter_tools/src/localizations/gen_l10n_types.dart'; import 'package:flutter_tools/src/localizations/localizations_utils.dart'; @@ -89,8 +90,10 @@ void main() { bool relaxSyntax = false, bool useNamedParameters = false, void Function(Directory)? setup, + FileSystem? fileSystem, }) { - final Directory l10nDirectory = fs.directory(defaultL10nPath)..createSync(recursive: true); + final Directory l10nDirectory = (fileSystem ?? fs).directory(defaultL10nPath) + ..createSync(recursive: true); for (final String locale in localeToArbFile.keys) { l10nDirectory.childFile('app_$locale.arb').writeAsStringSync(localeToArbFile[locale]!); } @@ -98,7 +101,7 @@ void main() { setup(l10nDirectory); } return LocalizationsGenerator( - fileSystem: fs, + fileSystem: fileSystem ?? fs, inputPathString: l10nDirectory.path, outputPathString: outputPathString ?? l10nDirectory.path, templateArbFileName: defaultTemplateArbFileName, @@ -613,6 +616,29 @@ void main() { ); }); + testUsingContext('generates normalized input & output file paths', () { + final FileSystem fs = MemoryFileSystem.test( + style: globals.platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, + ); + setupLocalizations( + {'en': singleMessageArbFileString, 'es': singleEsMessageArbFileString}, + fileSystem: fs, + inputsAndOutputsListPath: defaultL10nPath, + ); + final File inputsAndOutputsList = fs.file( + fs.path.join(defaultL10nPath, 'gen_l10n_inputs_and_outputs.json'), + ); + expect(inputsAndOutputsList.existsSync(), isTrue); + + final Map jsonResult = + json.decode(inputsAndOutputsList.readAsStringSync()) as Map; + final String oppositeSeparator = globals.platform.isWindows ? '/' : r'\'; + final List inputList = jsonResult['inputs'] as List; + expect(inputList, everyElement(isNot(contains(oppositeSeparator)))); + final List outputList = jsonResult['outputs'] as List; + expect(outputList, everyElement(isNot(contains(oppositeSeparator)))); + }); + testWithoutContext('setting both a headerString and a headerFile should fail', () { expect( () {