Unverified Commit fd22fc3e authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by GitHub

[gen_l10n] Synthetic package generation by default (#62395)

* synthetic packages by default in gen_l10n tool

* Refactor default path for synthetic package

* Remove unused import

* Code cleanup

* Further improvements to help text

* Refactor synthetic package path

* Remove newlines

* Test cleanup

* clean up logic in inputs and outputs list function

* Update l10n.yaml usage

* only add option if value is non-null

* Update stocks app as proof of concept for synthetic package usage

* Address nits

* print pubspec contents

* add print statements

* Do not allow null value for useSyntheticPackage

* +

* +

* +

* +

* Cleanup

* Add test

* Fix text

* Dont parse pubspec directly

* Test using context

* WIP: generate synthetic packages on pub get -- needs tests

* Allow null value

* Update null handling

* Refactor to properly handle null case

* Fix yamlMap condition

* Fix yaml node for real

* WIP: struggling to write tests

* WIP - take absolute path as an option

* Add tests

* Use environment project directory for synthetic package generation pathway

* Fix typo

* Improve help text

* Update defaults

* Remove unauthorized path import

* Fix pathing issues at synthetic package generation

* Fix typo in test

* Use path.join so projectDir matches up based on OS

* Fix Windows pathing in test

* Remove unnecessary replaceApp code for projectDir.path

* Use globals.fs.currentDirectory.path in resident_runner_test.dart

* Fix merge conflict

* Add test to ensure that synthetic package is generated on pub get

* Fix resident_runner_test.dart tests

* Fix tests

* Use package:file instead of dart:io

* WIP - exploration

* Remove synthetic package use from stocks example

* Update integration test to not use synthetic packages

* Remove trailing whitespace

* flutter pub get runs synth package generation

* Remove more print statements

* Add license header

* WIP - minimally working pub.get

* Use own MockBuildSystem

* Modify test and implementation to be a little cleaner

* Fix flutter pub get invocation

* Use synthetic packages in stocks app

* Revert "Use synthetic packages in stocks app"

This reverts commit 45bf24903c3d4a2a5fd5481d7d6bc36c4a348703.

* Add environment and buildSystem params to flutter test

* Address code review feedback

* +

* Isolate codegen into its own API

* Fix imports

* Slight refactor

* Add one more test for no l10n.yaml file

* Remove unneeded mock class and import in pub_get_test.dart

* More code review feedback

* Remove unnecessary imports

* Remove `return await`s that I missed

* use arrow functions instead
parent c935a448
...@@ -2,16 +2,17 @@ ...@@ -2,16 +2,17 @@
## `arb-dir` sets the input directory. The output directory will match ## `arb-dir` sets the input directory. The output directory will match
## the input directory if the output directory is not set. ## the input directory if the output directory is not set.
arb-dir: lib/i18n arb-dir: lib/i18n
## `template-arb-file` describes the template arb file that the tool ## `header-file` is the file that contains a custom
## will use to check and validate the remaining arb files when ## header for each of the generated files.
## generating Flutter's localization files. header-file: header.txt
template-arb-file: stocks_en.arb
## `output-localization-file` is the name of the generated file.
output-localization-file: stock_strings.dart
## `output-class` is the name of the localizations class your ## `output-class` is the name of the localizations class your
## Flutter application will use. The file will need to be ## Flutter application will use. The file will need to be
## imported throughout your application. ## imported throughout your application.
output-class: StockStrings output-class: StockStrings
## `header-file` is the file that contains a custom ## `output-localization-file` is the name of the generated file.
## header for each of the generated files. output-localization-file: stock_strings.dart
header-file: header.txt ## `template-arb-file` describes the template arb file that the tool
## will use to check and validate the remaining arb files when
## generating Flutter's localization files.
synthetic-package: false
template-arb-file: stocks_en.arb
...@@ -27,10 +27,15 @@ void main(List<String> arguments) { ...@@ -27,10 +27,15 @@ void main(List<String> arguments) {
); );
parser.addOption( parser.addOption(
'output-dir', 'output-dir',
help: 'The directory where the generated localization classes will be written. ' help: 'The directory where the generated localization classes will be written '
'if the synthetic-package flag is set to false.'
'\n\n'
'If output-dir is specified and the synthetic-package flag is enabled, '
'this option will be ignored by the tool.'
'\n\n'
'The app must import the file specified in the \'output-localization-file\' ' 'The app must import the file specified in the \'output-localization-file\' '
'option from this directory. If unspecified, this defaults to the same ' 'option from this directory. If unspecified, this defaults to the same '
'directory as the input directory specified in \'arb-dir\'.' 'directory as the input directory specified in \'arb-dir\'.',
); );
parser.addOption( parser.addOption(
'template-arb-file', 'template-arb-file',
...@@ -120,6 +125,20 @@ void main(List<String> arguments) { ...@@ -120,6 +125,20 @@ void main(List<String> arguments) {
'\n\n' '\n\n'
'When null, the JSON file will not be generated.' 'When null, the JSON file will not be generated.'
); );
parser.addFlag(
'synthetic-package',
defaultsTo: true,
help: 'Determines whether or not the generated output files will be '
'generated as a synthetic package or at a specified directory in '
'the Flutter project.'
'\n\n'
'This flag is set to true by default.'
'\n\n'
'When synthetic-package is set to false, it will generate the '
'localizations files in the directory specified by arb-dir by default. '
'\n\n'
'If output-dir is specified, files will be generated there.',
);
parser.addOption( parser.addOption(
'project-dir', 'project-dir',
valueHelp: 'absolute/path/to/flutter/project', valueHelp: 'absolute/path/to/flutter/project',
...@@ -148,6 +167,7 @@ void main(List<String> arguments) { ...@@ -148,6 +167,7 @@ void main(List<String> arguments) {
final String headerFile = results['header-file'] as String; final String headerFile = results['header-file'] as String;
final bool useDeferredLoading = results['use-deferred-loading'] as bool; final bool useDeferredLoading = results['use-deferred-loading'] as bool;
final String inputsAndOutputsListPath = results['gen-inputs-and-outputs-list'] as String; final String inputsAndOutputsListPath = results['gen-inputs-and-outputs-list'] as String;
final bool useSyntheticPackage = results['synthetic-package'] as bool;
final String projectPathString = results['project-dir'] as String; final String projectPathString = results['project-dir'] as String;
const local.LocalFileSystem fs = local.LocalFileSystem(); const local.LocalFileSystem fs = local.LocalFileSystem();
...@@ -166,6 +186,7 @@ void main(List<String> arguments) { ...@@ -166,6 +186,7 @@ void main(List<String> arguments) {
headerFile: headerFile, headerFile: headerFile,
useDeferredLoading: useDeferredLoading, useDeferredLoading: useDeferredLoading,
inputsAndOutputsListPath: inputsAndOutputsListPath, inputsAndOutputsListPath: inputsAndOutputsListPath,
useSyntheticPackage: useSyntheticPackage,
projectPathString: projectPathString, projectPathString: projectPathString,
) )
..loadResources() ..loadResources()
......
...@@ -13,6 +13,13 @@ import 'gen_l10n_templates.dart'; ...@@ -13,6 +13,13 @@ import 'gen_l10n_templates.dart';
import 'gen_l10n_types.dart'; import 'gen_l10n_types.dart';
import 'localizations_utils.dart'; import 'localizations_utils.dart';
/// 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 = path.join('.dart_tool', 'flutter_gen', 'gen_l10n');
List<String> generateMethodParameters(Message message) { List<String> generateMethodParameters(Message message) {
assert(message.placeholders.isNotEmpty); assert(message.placeholders.isNotEmpty);
final Placeholder countPlaceholder = message.isPlural ? message.getCountPlaceholder() : null; final Placeholder countPlaceholder = message.isPlural ? message.getCountPlaceholder() : null;
...@@ -523,11 +530,15 @@ class LocalizationsGenerator { ...@@ -523,11 +530,15 @@ class LocalizationsGenerator {
String headerFile, String headerFile,
bool useDeferredLoading = false, bool useDeferredLoading = false,
String inputsAndOutputsListPath, String inputsAndOutputsListPath,
bool useSyntheticPackage = true,
String projectPathString, String projectPathString,
}) { }) {
setProjectDir(projectPathString); setProjectDir(projectPathString);
setInputDirectory(inputPathString); setInputDirectory(inputPathString);
setOutputDirectory(outputPathString ?? inputPathString); setOutputDirectory(
outputPathString: outputPathString ?? inputPathString,
useSyntheticPackage: useSyntheticPackage,
);
setTemplateArbFile(templateArbFileName); setTemplateArbFile(templateArbFileName);
setBaseOutputFile(outputFileString); setBaseOutputFile(outputFileString);
setPreferredSupportedLocales(preferredSupportedLocaleString); setPreferredSupportedLocales(preferredSupportedLocaleString);
...@@ -595,14 +606,29 @@ class LocalizationsGenerator { ...@@ -595,14 +606,29 @@ class LocalizationsGenerator {
/// Sets the reference [Directory] for [outputDirectory]. /// Sets the reference [Directory] for [outputDirectory].
@visibleForTesting @visibleForTesting
void setOutputDirectory(String outputPathString) { void setOutputDirectory({
if (outputPathString == null) String outputPathString,
throw L10nException('outputPathString argument cannot be null'); bool useSyntheticPackage = true,
outputDirectory = _fs.directory( }) {
projectDirectory != null if (useSyntheticPackage) {
? _getAbsoluteProjectPath(outputPathString) outputDirectory = _fs.directory(
: outputPathString projectDirectory != null
); ? _getAbsoluteProjectPath(defaultSyntheticPackagePath)
: defaultSyntheticPackagePath
);
} else {
if (outputPathString == null)
throw L10nException(
'outputPathString argument cannot be null if not using '
'synthetic package option.'
);
outputDirectory = _fs.directory(
projectDirectory != null
? _getAbsoluteProjectPath(outputPathString)
: outputPathString
);
}
} }
/// Sets the reference [File] for [templateArbFile]. /// Sets the reference [File] for [templateArbFile].
...@@ -716,6 +742,7 @@ class LocalizationsGenerator { ...@@ -716,6 +742,7 @@ class LocalizationsGenerator {
_inputsAndOutputsListFile = _fs.file( _inputsAndOutputsListFile = _fs.file(
path.join(inputsAndOutputsListPath, 'gen_l10n_inputs_and_outputs.json'), path.join(inputsAndOutputsListPath, 'gen_l10n_inputs_and_outputs.json'),
); );
_inputFileList = <String>[]; _inputFileList = <String>[];
_outputFileList = <String>[]; _outputFileList = <String>[];
} }
......
...@@ -12,6 +12,7 @@ import '../../base/io.dart'; ...@@ -12,6 +12,7 @@ import '../../base/io.dart';
import '../../base/logger.dart'; import '../../base/logger.dart';
import '../../convert.dart'; import '../../convert.dart';
import '../../globals.dart' as globals; import '../../globals.dart' as globals;
import '../../project.dart';
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
...@@ -37,6 +38,21 @@ Future<void> generateLocalizations({ ...@@ -37,6 +38,21 @@ Future<void> generateLocalizations({
'gen_l10n.dart', 'gen_l10n.dart',
); );
// If generating a synthetic package, generate a warning if
// flutter: generate is not set.
final FlutterProject flutterProject = FlutterProject.fromDirectory(projectDir);
if (options.useSyntheticPackage && !flutterProject.manifest.generateSyntheticPackage) {
logger.printError(
'Attempted to generate localizations code without having '
'the flutter: generate flag turned on.'
'\n'
'Check pubspec.yaml and ensure that flutter: generate: true has '
'been added and rebuild the project. Otherwise, the localizations '
'source code will not be importable.'
);
throw Exception();
}
final ProcessResult result = await processManager.run(<String>[ final ProcessResult result = await processManager.run(<String>[
dartBinaryPath, dartBinaryPath,
'--disable-dart-dev', '--disable-dart-dev',
...@@ -61,6 +77,8 @@ Future<void> generateLocalizations({ ...@@ -61,6 +77,8 @@ Future<void> generateLocalizations({
'--use-deferred-loading', '--use-deferred-loading',
if (options.preferredSupportedLocales != null) if (options.preferredSupportedLocales != null)
'--preferred-supported-locales=${options.preferredSupportedLocales}', '--preferred-supported-locales=${options.preferredSupportedLocales}',
if (!options.useSyntheticPackage)
'--no-synthetic-package'
]); ]);
if (result.exitCode != 0) { if (result.exitCode != 0) {
logger.printError(result.stdout + result.stderr as String); logger.printError(result.stdout + result.stderr as String);
...@@ -157,7 +175,8 @@ class LocalizationOptions { ...@@ -157,7 +175,8 @@ class LocalizationOptions {
this.preferredSupportedLocales, this.preferredSupportedLocales,
this.headerFile, this.headerFile,
this.deferredLoading, this.deferredLoading,
}); this.useSyntheticPackage = true,
}) : assert(useSyntheticPackage != null);
/// The `--arb-dir` argument. /// The `--arb-dir` argument.
/// ///
...@@ -201,6 +220,12 @@ class LocalizationOptions { ...@@ -201,6 +220,12 @@ class LocalizationOptions {
/// Whether to generate the Dart localization file with locales imported /// Whether to generate the Dart localization file with locales imported
/// as deferred. /// as deferred.
final bool deferredLoading; final bool deferredLoading;
/// The `--synthetic-package` argument.
///
/// Whether to generate the Dart localization files in a synthetic package
/// or in a custom directory.
final bool useSyntheticPackage;
} }
/// Parse the localizations configuration options from [file]. /// Parse the localizations configuration options from [file].
...@@ -232,6 +257,7 @@ LocalizationOptions parseLocalizationsOptions({ ...@@ -232,6 +257,7 @@ LocalizationOptions parseLocalizationsOptions({
preferredSupportedLocales: _tryReadString(yamlMap, 'preferred-supported-locales', logger), preferredSupportedLocales: _tryReadString(yamlMap, 'preferred-supported-locales', logger),
headerFile: _tryReadUri(yamlMap, 'header-file', logger), headerFile: _tryReadUri(yamlMap, 'header-file', logger),
deferredLoading: _tryReadBool(yamlMap, 'use-deferred-loading', logger), deferredLoading: _tryReadBool(yamlMap, 'use-deferred-loading', logger),
useSyntheticPackage: _tryReadBool(yamlMap, 'synthetic-package', logger) ?? true,
); );
} }
......
...@@ -6,6 +6,10 @@ import 'dart:async'; ...@@ -6,6 +6,10 @@ import 'dart:async';
import '../base/common.dart'; import '../base/common.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../build_info.dart';
import '../build_system/build_system.dart';
import '../cache.dart';
import '../dart/generate_synthetic_packages.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
...@@ -89,9 +93,29 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -89,9 +93,29 @@ class PackagesGetCommand extends FlutterCommand {
} }
Future<void> _runPubGet(String directory, FlutterProject flutterProject) async { 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,
projectDir: flutterProject.directory,
);
await generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: globals.buildSystem,
);
}
final Stopwatch pubGetTimer = Stopwatch()..start(); final Stopwatch pubGetTimer = Stopwatch()..start();
try { try {
await pub.get(context: PubContext.pubGet, await pub.get(
context: PubContext.pubGet,
directory: directory, directory: directory,
upgrade: upgrade , upgrade: upgrade ,
offline: boolArg('offline'), offline: boolArg('offline'),
......
...@@ -9,8 +9,10 @@ import '../asset.dart'; ...@@ -9,8 +9,10 @@ import '../asset.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../build_system/build_system.dart';
import '../bundle.dart'; import '../bundle.dart';
import '../cache.dart'; import '../cache.dart';
import '../dart/generate_synthetic_packages.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
import '../devfs.dart'; import '../devfs.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
...@@ -164,6 +166,25 @@ class TestCommand extends FlutterCommand { ...@@ -164,6 +166,25 @@ class TestCommand extends FlutterCommand {
} }
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
if (shouldRunPub) { if (shouldRunPub) {
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,
projectDir: flutterProject.directory,
);
await generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: globals.buildSystem,
);
}
await pub.get( await pub.get(
context: PubContext.getVerifyContext(name), context: PubContext.getVerifyContext(name),
skipPubspecYamlCheck: true, skipPubspecYamlCheck: true,
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/localizations.dart';
Future<void> generateLocalizationsSyntheticPackage({
@required Environment environment,
@required BuildSystem buildSystem,
}) async {
assert(environment != null);
assert(buildSystem != null);
final FileSystem fileSystem = environment.fileSystem;
final File l10nYamlFile = fileSystem.file(
fileSystem.path.join(environment.projectDir.path, 'l10n.yaml'));
// If pubspec.yaml has generate:true and if l10n.yaml exists in the
// root project directory, check to see if a synthetic package should
// be generated for gen_l10n.
if (!l10nYamlFile.existsSync()) {
return;
}
final YamlNode yamlNode = loadYamlNode(l10nYamlFile.readAsStringSync());
if (yamlNode.value != null && yamlNode is! YamlMap) {
throwToolExit(
'Expected ${l10nYamlFile.path} to contain a map, instead was $yamlNode'
);
}
BuildResult result;
// If an l10n.yaml file exists but is empty, attempt to build synthetic
// package with default settings.
if (yamlNode.value == null) {
result = await buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
);
} else {
final YamlMap yamlMap = yamlNode as YamlMap;
final Object value = yamlMap['synthetic-package'];
if (value is! bool && value != null) {
throwToolExit(
'Expected "synthetic-package" to have a bool value, '
'instead was "$value"'
);
}
// Generate gen_l10n synthetic package only if synthetic-package: true or
// synthetic-package is null.
final bool isSyntheticL10nPackage = value as bool ?? true;
if (!isSyntheticL10nPackage) {
return;
}
}
result = await buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
);
if (result == null || result.hasException) {
throwToolExit('Generating synthetic localizations package has failed.');
}
}
...@@ -181,7 +181,8 @@ class _DefaultPub implements Pub { ...@@ -181,7 +181,8 @@ class _DefaultPub implements Pub {
_fileSystem.path.join(directory, 'pubspec.yaml')); _fileSystem.path.join(directory, 'pubspec.yaml'));
final File packageConfigFile = _fileSystem.file( final File packageConfigFile = _fileSystem.file(
_fileSystem.path.join(directory, '.dart_tool', 'package_config.json')); _fileSystem.path.join(directory, '.dart_tool', 'package_config.json'));
final Directory generatedDirectory = _fileSystem.directory(_fileSystem.path.join(directory, '.dart_tool', 'flutter_gen')); final Directory generatedDirectory = _fileSystem.directory(
_fileSystem.path.join(directory, '.dart_tool', 'flutter_gen'));
if (!skipPubspecYamlCheck && !pubSpecYaml.existsSync()) { if (!skipPubspecYamlCheck && !pubSpecYaml.existsSync()) {
if (!skipIfAbsent) { if (!skipIfAbsent) {
......
...@@ -84,11 +84,11 @@ class FlutterProject { ...@@ -84,11 +84,11 @@ class FlutterProject {
/// Returns a [FlutterProject] view of the current directory or a ToolExit error, /// Returns a [FlutterProject] view of the current directory or a ToolExit error,
/// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
static FlutterProject current() => fromDirectory(globals.fs.currentDirectory); static FlutterProject current() => globals.projectFactory.fromDirectory(globals.fs.currentDirectory);
/// Returns a [FlutterProject] view of the given directory or a ToolExit error, /// Returns a [FlutterProject] view of the given directory or a ToolExit error,
/// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
static FlutterProject fromPath(String path) => fromDirectory(globals.fs.directory(path)); static FlutterProject fromPath(String path) => globals.projectFactory.fromDirectory(globals.fs.directory(path));
/// The location of this project. /// The location of this project.
final Directory directory; final Directory directory;
......
...@@ -18,9 +18,11 @@ import '../base/terminal.dart'; ...@@ -18,9 +18,11 @@ import '../base/terminal.dart';
import '../base/user_messages.dart'; import '../base/user_messages.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/icon_tree_shaker.dart' show kIconTreeShakerEnabledDefault; import '../build_system/targets/icon_tree_shaker.dart' show kIconTreeShakerEnabledDefault;
import '../bundle.dart' as bundle; import '../bundle.dart' as bundle;
import '../cache.dart'; import '../cache.dart';
import '../dart/generate_synthetic_packages.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
import '../device.dart'; import '../device.dart';
...@@ -886,6 +888,23 @@ abstract class FlutterCommand extends Command<void> { ...@@ -886,6 +888,23 @@ abstract class FlutterCommand extends Command<void> {
if (shouldRunPub) { if (shouldRunPub) {
final FlutterProject project = FlutterProject.current(); final FlutterProject project = FlutterProject.current();
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,
projectDir: project.directory,
);
await generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: globals.buildSystem,
);
await pub.get( await pub.get(
context: PubContext.getVerifyContext(name), context: PubContext.getVerifyContext(name),
generateSyntheticPackage: project.manifest.generateSyntheticPackage, generateSyntheticPackage: project.manifest.generateSyntheticPackage,
......
...@@ -14,7 +14,7 @@ import '../../../src/context.dart'; ...@@ -14,7 +14,7 @@ import '../../../src/context.dart';
void main() { void main() {
// Verifies that values are correctly passed through the localizations // Verifies that values are correctly passed through the localizations
// target, but does not validate them beyond the serialized data type. // target, but does not validate them beyond the serialized data type.
testWithoutContext('generateLocalizations forwards arguments correctly', () async { testUsingContext('generateLocalizations forwards arguments correctly', () async {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final Logger logger = BufferLogger.test(); final Logger logger = BufferLogger.test();
final String projectDir = fileSystem.path.join('path', 'to', 'flutter_project'); final String projectDir = fileSystem.path.join('path', 'to', 'flutter_project');
...@@ -34,7 +34,8 @@ void main() { ...@@ -34,7 +34,8 @@ void main() {
'--header-file=header', '--header-file=header',
'--header=HEADER', '--header=HEADER',
'--use-deferred-loading', '--use-deferred-loading',
'--preferred-supported-locales=en_US' '--preferred-supported-locales=en_US',
'--no-synthetic-package',
], ],
), ),
]); ]);
...@@ -57,6 +58,7 @@ void main() { ...@@ -57,6 +58,7 @@ void main() {
preferredSupportedLocales: 'en_US', preferredSupportedLocales: 'en_US',
templateArbFile: Uri.file('example.arb'), templateArbFile: Uri.file('example.arb'),
untranslatedMessagesFile: Uri.file('untranslated'), untranslatedMessagesFile: Uri.file('untranslated'),
useSyntheticPackage: false,
); );
await generateLocalizations( await generateLocalizations(
options: options, options: options,
...@@ -72,6 +74,54 @@ void main() { ...@@ -72,6 +74,54 @@ void main() {
expect(processManager.hasRemainingExpectations, false); expect(processManager.hasRemainingExpectations, false);
}); });
testUsingContext('generateLocalizations throws exception on missing flutter: generate: true flag', () async {
final FileSystem fileSystem = MemoryFileSystem.test();
final BufferLogger logger = BufferLogger.test();
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[]);
final Directory arbDirectory = fileSystem.directory('arb')
..createSync();
arbDirectory.childFile('foo.arb').createSync();
arbDirectory.childFile('bar.arb').createSync();
// Missing flutter: generate: true should throw exception.
fileSystem.file('pubspec.yaml').writeAsStringSync('''
flutter:
uses-material-design: true
''');
final LocalizationOptions options = LocalizationOptions(
header: 'HEADER',
headerFile: Uri.file('header'),
arbDirectory: Uri.file('arb'),
deferredLoading: true,
outputClass: 'Foo',
outputLocalizationsFile: Uri.file('bar'),
preferredSupportedLocales: 'en_US',
templateArbFile: Uri.file('example.arb'),
untranslatedMessagesFile: Uri.file('untranslated'),
// Set synthetic package to true.
useSyntheticPackage: true,
);
expect(
() => generateLocalizations(
options: options,
logger: logger,
fileSystem: fileSystem,
processManager: processManager,
projectDir: fileSystem.currentDirectory,
dartBinaryPath: 'dart',
flutterRoot: '',
dependenciesDir: fileSystem.currentDirectory,
),
throwsA(isA<Exception>()),
);
expect(
logger.errorText,
contains('Attempted to generate localizations code without having the flutter: generate flag turned on.'),
);
});
testWithoutContext('generateLocalizations is skipped if l10n.yaml does not exist.', () async { testWithoutContext('generateLocalizations is skipped if l10n.yaml does not exist.', () async {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final Environment environment = Environment.test( final Environment environment = Environment.test(
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
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/dart/generate_synthetic_packages.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/localizations.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
void main() {
testWithoutContext('calls buildSystem.build with blank l10n.yaml file', () {
// Project directory setup for gen_l10n logic
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
// Add generate:true to pubspec.yaml.
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
final String content = pubspecFile.readAsStringSync().replaceFirst(
'\nflutter:\n',
'\nflutter:\n generate: true\n',
);
pubspecFile.writeAsStringSync(content);
// Create an l10n.yaml file
fileSystem.file('l10n.yaml').createSync();
final FakeProcessManager mockProcessManager = FakeProcessManager.any();
final BufferLogger mockBufferLogger = BufferLogger.test();
final Artifacts mockArtifacts = Artifacts.test();
final Environment environment = Environment.test(
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: mockBufferLogger,
artifacts: mockArtifacts,
processManager: mockProcessManager,
);
final BuildSystem buildSystem = MockBuildSystem();
expect(
() => generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: buildSystem,
),
throwsToolExit(message: 'Generating synthetic localizations package has failed.'),
);
// [BuildSystem] should have called build with [GenerateLocalizationsTarget].
verify(buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
)).called(1);
});
testWithoutContext('calls buildSystem.build with l10n.yaml synthetic-package: true', () {
// Project directory setup for gen_l10n logic
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
// Add generate:true to pubspec.yaml.
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
final String content = pubspecFile.readAsStringSync().replaceFirst(
'\nflutter:\n',
'\nflutter:\n generate: true\n',
);
pubspecFile.writeAsStringSync(content);
// Create an l10n.yaml file
fileSystem.file('l10n.yaml').writeAsStringSync('synthetic-package: true');
final FakeProcessManager mockProcessManager = FakeProcessManager.any();
final BufferLogger mockBufferLogger = BufferLogger.test();
final Artifacts mockArtifacts = Artifacts.test();
final Environment environment = Environment.test(
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: mockBufferLogger,
artifacts: mockArtifacts,
processManager: mockProcessManager,
);
final BuildSystem buildSystem = MockBuildSystem();
expect(
() => generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: buildSystem,
),
throwsToolExit(message: 'Generating synthetic localizations package has failed.'),
);
// [BuildSystem] should have called build with [GenerateLocalizationsTarget].
verify(buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
)).called(1);
});
testWithoutContext('calls buildSystem.build with l10n.yaml synthetic-package: null', () {
// Project directory setup for gen_l10n logic
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
// Add generate:true to pubspec.yaml.
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
final String content = pubspecFile.readAsStringSync().replaceFirst(
'\nflutter:\n',
'\nflutter:\n generate: true\n',
);
pubspecFile.writeAsStringSync(content);
// Create an l10n.yaml file
fileSystem.file('l10n.yaml').writeAsStringSync('synthetic-package: null');
final FakeProcessManager mockProcessManager = FakeProcessManager.any();
final BufferLogger mockBufferLogger = BufferLogger.test();
final Artifacts mockArtifacts = Artifacts.test();
final Environment environment = Environment.test(
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: mockBufferLogger,
artifacts: mockArtifacts,
processManager: mockProcessManager,
);
final BuildSystem buildSystem = MockBuildSystem();
expect(
() => generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: buildSystem,
),
throwsToolExit(message: 'Generating synthetic localizations package has failed.'),
);
// [BuildSystem] should have called build with [GenerateLocalizationsTarget].
verify(buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
)).called(1);
});
testWithoutContext('does not call buildSystem.build when l10n.yaml is not present', () async {
// Project directory setup for gen_l10n logic
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
// Add generate:true to pubspec.yaml.
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
final String content = pubspecFile.readAsStringSync().replaceFirst(
'\nflutter:\n',
'\nflutter:\n generate: true\n',
);
pubspecFile.writeAsStringSync(content);
final FakeProcessManager mockProcessManager = FakeProcessManager.any();
final BufferLogger mockBufferLogger = BufferLogger.test();
final Artifacts mockArtifacts = Artifacts.test();
final Environment environment = Environment.test(
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: mockBufferLogger,
artifacts: mockArtifacts,
processManager: mockProcessManager,
);
final BuildSystem buildSystem = MockBuildSystem();
await generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: buildSystem,
);
// [BuildSystem] should not be called with [GenerateLocalizationsTarget].
verifyNever(buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
));
});
testWithoutContext('does not call buildSystem.build with incorrect l10n.yaml format', () {
// Project directory setup for gen_l10n logic
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
// Add generate:true to pubspec.yaml.
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
final String content = pubspecFile.readAsStringSync().replaceFirst(
'\nflutter:\n',
'\nflutter:\n generate: true\n',
);
pubspecFile.writeAsStringSync(content);
// Create an l10n.yaml file
fileSystem.file('l10n.yaml').writeAsStringSync('helloWorld');
final FakeProcessManager mockProcessManager = FakeProcessManager.any();
final BufferLogger mockBufferLogger = BufferLogger.test();
final Artifacts mockArtifacts = Artifacts.test();
final Environment environment = Environment.test(
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: mockBufferLogger,
artifacts: mockArtifacts,
processManager: mockProcessManager,
);
final BuildSystem buildSystem = MockBuildSystem();
expect(
() => generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: buildSystem,
),
throwsToolExit(message: 'to contain a map, instead was helloWorld'),
);
// [BuildSystem] should not be called with [GenerateLocalizationsTarget].
verifyNever(buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
));
});
testWithoutContext('does not call buildSystem.build with non-bool "synthetic-package" value', () {
// Project directory setup for gen_l10n logic
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
// Add generate:true to pubspec.yaml.
final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync();
final String content = pubspecFile.readAsStringSync().replaceFirst(
'\nflutter:\n',
'\nflutter:\n generate: true\n',
);
pubspecFile.writeAsStringSync(content);
// Create an l10n.yaml file
fileSystem.file('l10n.yaml').writeAsStringSync('synthetic-package: nonBoolValue');
final FakeProcessManager mockProcessManager = FakeProcessManager.any();
final BufferLogger mockBufferLogger = BufferLogger.test();
final Artifacts mockArtifacts = Artifacts.test();
final Environment environment = Environment.test(
fileSystem.currentDirectory,
fileSystem: fileSystem,
logger: mockBufferLogger,
artifacts: mockArtifacts,
processManager: mockProcessManager,
);
final BuildSystem buildSystem = MockBuildSystem();
expect(
() => generateLocalizationsSyntheticPackage(
environment: environment,
buildSystem: buildSystem,
),
throwsToolExit(message: 'to have a bool value, instead was "nonBoolValue"'),
);
// [BuildSystem] should not be called with [GenerateLocalizationsTarget].
verifyNever(buildSystem.build(
const GenerateLocalizationsTarget(),
environment,
));
});
}
class MockBuildSystem extends Mock implements BuildSystem {}
...@@ -1201,6 +1201,7 @@ void main() { ...@@ -1201,6 +1201,7 @@ void main() {
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb')) globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
.createSync(recursive: true); .createSync(recursive: true);
globals.fs.file('l10n.yaml').createSync(); globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n');
await residentRunner.runSourceGenerators(); await residentRunner.runSourceGenerators();
...@@ -1227,6 +1228,7 @@ void main() { ...@@ -1227,6 +1228,7 @@ void main() {
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb')) globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
.createSync(recursive: true); .createSync(recursive: true);
globals.fs.file('l10n.yaml').createSync(); globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n');
await residentRunner.runSourceGenerators(); await residentRunner.runSourceGenerators();
......
...@@ -16,6 +16,7 @@ class GenL10nProject extends Project { ...@@ -16,6 +16,7 @@ class GenL10nProject extends Project {
@override @override
Future<void> setUpIn(Directory dir, { Future<void> setUpIn(Directory dir, {
bool useDeferredLoading = false, bool useDeferredLoading = false,
bool useSyntheticPackage = false,
}) { }) {
this.dir = dir; this.dir = dir;
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en.arb'), appEn); writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en.arb'), appEn);
...@@ -27,7 +28,10 @@ class GenL10nProject extends Project { ...@@ -27,7 +28,10 @@ class GenL10nProject extends Project {
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hant.arb'), appZhHant); writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hant.arb'), appZhHant);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hans.arb'), appZhHans); writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hans.arb'), appZhHans);
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hant_TW.arb'), appZhHantTw); writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_zh_Hant_TW.arb'), appZhHantTw);
writeFile(globals.fs.path.join(dir.path, 'l10n.yaml'), l10nYaml(useDeferredLoading: useDeferredLoading)); writeFile(globals.fs.path.join(dir.path, 'l10n.yaml'), l10nYaml(
useDeferredLoading: useDeferredLoading,
useSyntheticPackage: useSyntheticPackage,
));
return super.setUpIn(dir); return super.setUpIn(dir);
} }
...@@ -568,12 +572,18 @@ void main() { ...@@ -568,12 +572,18 @@ void main() {
String l10nYaml({ String l10nYaml({
@required bool useDeferredLoading, @required bool useDeferredLoading,
@required bool useSyntheticPackage,
}) { }) {
String l10nYamlString = '';
if (useDeferredLoading) { if (useDeferredLoading) {
return r''' l10nYamlString += 'use-deferred-loading: true\n';
use-deferred-loading: false }
''';
if (!useSyntheticPackage) {
l10nYamlString += 'synthetic-package: false\n';
} }
return '';
return l10nYamlString;
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment