[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 45bf24903c.

* 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
This commit is contained in:
Shi-Hao Hong 2020-08-31 13:19:41 +08:00 committed by GitHub
parent c935a44869
commit fd22fc3e35
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 705 additions and 149 deletions

View file

@ -2,16 +2,17 @@
## `arb-dir` sets the input directory. The output directory will match
## the input directory if the output directory is not set.
arb-dir: lib/i18n
## `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.
template-arb-file: stocks_en.arb
## `output-localization-file` is the name of the generated file.
output-localization-file: stock_strings.dart
## `header-file` is the file that contains a custom
## header for each of the generated files.
header-file: header.txt
## `output-class` is the name of the localizations class your
## Flutter application will use. The file will need to be
## imported throughout your application.
output-class: StockStrings
## `header-file` is the file that contains a custom
## header for each of the generated files.
header-file: header.txt
## `output-localization-file` is the name of the generated file.
output-localization-file: stock_strings.dart
## `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

View file

@ -27,10 +27,15 @@ void main(List<String> arguments) {
);
parser.addOption(
'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\' '
'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(
'template-arb-file',
@ -120,6 +125,20 @@ void main(List<String> arguments) {
'\n\n'
'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(
'project-dir',
valueHelp: 'absolute/path/to/flutter/project',
@ -148,6 +167,7 @@ void main(List<String> arguments) {
final String headerFile = results['header-file'] as String;
final bool useDeferredLoading = results['use-deferred-loading'] as bool;
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;
const local.LocalFileSystem fs = local.LocalFileSystem();
@ -166,6 +186,7 @@ void main(List<String> arguments) {
headerFile: headerFile,
useDeferredLoading: useDeferredLoading,
inputsAndOutputsListPath: inputsAndOutputsListPath,
useSyntheticPackage: useSyntheticPackage,
projectPathString: projectPathString,
)
..loadResources()

View file

@ -13,6 +13,13 @@ 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
/// 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) {
assert(message.placeholders.isNotEmpty);
final Placeholder countPlaceholder = message.isPlural ? message.getCountPlaceholder() : null;
@ -523,11 +530,15 @@ class LocalizationsGenerator {
String headerFile,
bool useDeferredLoading = false,
String inputsAndOutputsListPath,
bool useSyntheticPackage = true,
String projectPathString,
}) {
setProjectDir(projectPathString);
setInputDirectory(inputPathString);
setOutputDirectory(outputPathString ?? inputPathString);
setOutputDirectory(
outputPathString: outputPathString ?? inputPathString,
useSyntheticPackage: useSyntheticPackage,
);
setTemplateArbFile(templateArbFileName);
setBaseOutputFile(outputFileString);
setPreferredSupportedLocales(preferredSupportedLocaleString);
@ -595,14 +606,29 @@ class LocalizationsGenerator {
/// Sets the reference [Directory] for [outputDirectory].
@visibleForTesting
void setOutputDirectory(String outputPathString) {
if (outputPathString == null)
throw L10nException('outputPathString argument cannot be null');
outputDirectory = _fs.directory(
projectDirectory != null
? _getAbsoluteProjectPath(outputPathString)
: outputPathString
);
void setOutputDirectory({
String outputPathString,
bool useSyntheticPackage = true,
}) {
if (useSyntheticPackage) {
outputDirectory = _fs.directory(
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].
@ -716,6 +742,7 @@ class LocalizationsGenerator {
_inputsAndOutputsListFile = _fs.file(
path.join(inputsAndOutputsListPath, 'gen_l10n_inputs_and_outputs.json'),
);
_inputFileList = <String>[];
_outputFileList = <String>[];
}

View file

@ -16,6 +16,7 @@ import '../../localization/localizations_utils.dart';
import '../common.dart';
final String defaultL10nPathString = path.join('lib', 'l10n');
final String syntheticPackagePath = path.join('.dart_tool', 'flutter_gen', 'gen_l10n');
const String defaultTemplateArbFileName = 'app_en.arb';
const String defaultOutputFileString = 'output-localization-file.dart';
const String defaultClassNameString = 'AppLocalizations';
@ -98,21 +99,28 @@ void main() {
);
});
test('setOutputDirectory fails if output string is null', () {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setOutputDirectory(null);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
test(
'setOutputDirectory fails if output string is null while not using the '
'synthetic package option',
() {
_standardFlutterDirectoryL10nSetup(fs);
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
try {
generator.setOutputDirectory(
outputPathString: null,
useSyntheticPackage: false,
);
} on L10nException catch (e) {
expect(e.message, contains('cannot be null'));
return;
}
fail(
'LocalizationsGenerator.setOutputDirectory should fail if the '
'input string is null.'
);
});
fail(
'LocalizationsGenerator.setOutputDirectory should fail if the '
'input string is null.'
);
},
);
test('setTemplateArbFile fails if inputDirectory is null', () {
final LocalizationsGenerator generator = LocalizationsGenerator(fs);
@ -232,8 +240,9 @@ void main() {
expect(
fs.isFileSync(path.join(
flutterProjectPath,
'lib',
'l10n',
'.dart_tool',
'flutter_gen',
'gen_l10n',
'output-localization-file_en.dart',
)),
true,
@ -241,8 +250,9 @@ void main() {
expect(
fs.isFileSync(path.join(
flutterProjectPath,
'lib',
'l10n',
'.dart_tool',
'flutter_gen',
'gen_l10n',
'output-localization-file_es.dart',
)),
true,
@ -289,7 +299,7 @@ void main() {
generator = LocalizationsGenerator(fs);
try {
generator.setInputDirectory(defaultL10nPathString);
generator.setOutputDirectory(defaultL10nPathString);
generator.setOutputDirectory();
generator.setTemplateArbFile(defaultTemplateArbFileName);
generator.setBaseOutputFile(defaultOutputFileString);
} on L10nException catch (e) {
@ -420,7 +430,7 @@ void main() {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n');
final Directory outputDirectory = fs.directory(syntheticPackagePath);
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);
@ -490,95 +500,111 @@ void main() {
expect(unimplementedOutputString, contains('subtitle'));
});
test('uses inputPathString as outputPathString when the outputPathString is null', () {
_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}');
}
test(
'uses inputPathString as outputPathString when the outputPathString is '
'null while not using the synthetic package option',
() {
_standardFlutterDirectoryL10nSetup(fs);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
// outputPathString is intentionally not defined
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
useSyntheticPackage: false,
)
..loadResources()
..writeOutputFiles();
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n');
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);
});
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n');
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);
},
);
test('correctly generates output files in non-default output directory if it already exists', () {
final Directory l10nDirectory = fs.currentDirectory
.childDirectory('lib')
.childDirectory('l10n')
..createSync(recursive: true);
// Create the directory 'lib/l10n/output'.
l10nDirectory.childDirectory('output');
test(
'correctly generates output files in non-default output directory if it '
'already exists while not using the synthetic package option',
() {
final Directory l10nDirectory = fs.currentDirectory
.childDirectory('lib')
.childDirectory('l10n')
..createSync(recursive: true);
// Create the directory 'lib/l10n/output'.
l10nDirectory.childDirectory('output');
l10nDirectory
.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory
.childFile(esArbFileName)
.writeAsStringSync(singleEsMessageArbFileString);
l10nDirectory
.childFile(defaultTemplateArbFileName)
.writeAsStringSync(singleMessageArbFileString);
l10nDirectory
.childFile(esArbFileName)
.writeAsStringSync(singleEsMessageArbFileString);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: path.join('lib', 'l10n', 'output'),
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
)
..loadResources()
..writeOutputFiles();
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: path.join('lib', 'l10n', 'output'),
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
useSyntheticPackage: false,
)
..loadResources()
..writeOutputFiles();
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output');
expect(outputDirectory.existsSync(), isTrue);
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);
});
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output');
expect(outputDirectory.existsSync(), isTrue);
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);
},
);
test('correctly creates output directory if it does not exist and writes files in it', () {
_standardFlutterDirectoryL10nSetup(fs);
test(
'correctly creates output directory if it does not exist and writes files '
'in it while not using the synthetic package option',
() {
_standardFlutterDirectoryL10nSetup(fs);
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: path.join('lib', 'l10n', 'output'),
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
)
..loadResources()
..writeOutputFiles();
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
LocalizationsGenerator generator;
try {
generator = LocalizationsGenerator(fs);
generator
..initialize(
inputPathString: defaultL10nPathString,
outputPathString: path.join('lib', 'l10n', 'output'),
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
useSyntheticPackage: false,
)
..loadResources()
..writeOutputFiles();
} on L10nException catch (e) {
fail('Generating output should not fail: \n${e.message}');
}
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output');
expect(outputDirectory.existsSync(), isTrue);
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);
});
final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output');
expect(outputDirectory.existsSync(), isTrue);
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);
},
);
test('creates list of inputs and outputs when file path is specified', () {
_standardFlutterDirectoryL10nSetup(fs);
@ -592,7 +618,7 @@ void main() {
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
inputsAndOutputsListPath: defaultL10nPathString,
inputsAndOutputsListPath: syntheticPackagePath,
)
..loadResources()
..writeOutputFiles();
@ -600,10 +626,9 @@ void main() {
fail('Generating output should not fail: \n${e.message}');
}
final File inputsAndOutputsList = fs
.directory('lib')
.childDirectory('l10n')
.childFile('gen_l10n_inputs_and_outputs.json');
final File inputsAndOutputsList = fs.file(
path.join(syntheticPackagePath, 'gen_l10n_inputs_and_outputs.json'),
);
expect(inputsAndOutputsList.existsSync(), isTrue);
final Map<String, dynamic> jsonResult = json.decode(inputsAndOutputsList.readAsStringSync()) as Map<String, dynamic>;
@ -614,9 +639,9 @@ void main() {
expect(jsonResult.containsKey('outputs'), isTrue);
final List<dynamic> outputList = jsonResult['outputs'] as List<dynamic>;
expect(outputList, contains(fs.path.absolute('lib', 'l10n', 'output-localization-file.dart')));
expect(outputList, contains(fs.path.absolute('lib', 'l10n', 'output-localization-file_en.dart')));
expect(outputList, contains(fs.path.absolute('lib', 'l10n', 'output-localization-file_es.dart')));
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')));
});
test('setting both a headerString and a headerFile should fail', () {
@ -1105,11 +1130,11 @@ void main() {
fail('Generating output files should not fail: $e');
}
expect(fs.isFileSync(path.join('lib', 'l10n', 'output-localization-file_en.dart')), true);
expect(fs.isFileSync(path.join('lib', 'l10n', 'output-localization-file_en_US.dart')), false);
expect(fs.isFileSync(path.join(syntheticPackagePath, 'output-localization-file_en.dart')), true);
expect(fs.isFileSync(path.join(syntheticPackagePath, 'output-localization-file_en_US.dart')), false);
final String englishLocalizationsFile = fs.file(
path.join('lib', 'l10n', 'output-localization-file_en.dart')
path.join(syntheticPackagePath, 'output-localization-file_en.dart')
).readAsStringSync();
expect(englishLocalizationsFile, contains('class AppLocalizationsEnCa extends AppLocalizationsEn'));
expect(englishLocalizationsFile, contains('class AppLocalizationsEn extends AppLocalizations'));
@ -1139,7 +1164,7 @@ void main() {
}
final String localizationsFile = fs.file(
path.join('lib', 'l10n', defaultOutputFileString),
path.join(syntheticPackagePath, defaultOutputFileString),
).readAsStringSync();
expect(localizationsFile, contains(
'''
@ -1157,7 +1182,6 @@ import 'output-localization-file_zh.dart';
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
@ -1170,7 +1194,7 @@ import 'output-localization-file_zh.dart';
}
final String localizationsFile = fs.file(
path.join('lib', 'l10n', defaultOutputFileString),
path.join(syntheticPackagePath, defaultOutputFileString),
).readAsStringSync();
expect(localizationsFile, contains(
'''
@ -1204,7 +1228,6 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,
@ -1583,7 +1606,6 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e
try {
generator.initialize(
inputPathString: defaultL10nPathString,
outputPathString: defaultL10nPathString,
templateArbFileName: defaultTemplateArbFileName,
outputFileString: defaultOutputFileString,
classNameString: defaultClassNameString,

View file

@ -12,6 +12,7 @@ import '../../base/io.dart';
import '../../base/logger.dart';
import '../../convert.dart';
import '../../globals.dart' as globals;
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
@ -37,6 +38,21 @@ Future<void> generateLocalizations({
'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>[
dartBinaryPath,
'--disable-dart-dev',
@ -61,6 +77,8 @@ Future<void> generateLocalizations({
'--use-deferred-loading',
if (options.preferredSupportedLocales != null)
'--preferred-supported-locales=${options.preferredSupportedLocales}',
if (!options.useSyntheticPackage)
'--no-synthetic-package'
]);
if (result.exitCode != 0) {
logger.printError(result.stdout + result.stderr as String);
@ -157,7 +175,8 @@ class LocalizationOptions {
this.preferredSupportedLocales,
this.headerFile,
this.deferredLoading,
});
this.useSyntheticPackage = true,
}) : assert(useSyntheticPackage != null);
/// The `--arb-dir` argument.
///
@ -201,6 +220,12 @@ class LocalizationOptions {
/// Whether to generate the Dart localization file with locales imported
/// as deferred.
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].
@ -232,6 +257,7 @@ LocalizationOptions parseLocalizationsOptions({
preferredSupportedLocales: _tryReadString(yamlMap, 'preferred-supported-locales', logger),
headerFile: _tryReadUri(yamlMap, 'header-file', logger),
deferredLoading: _tryReadBool(yamlMap, 'use-deferred-loading', logger),
useSyntheticPackage: _tryReadBool(yamlMap, 'synthetic-package', logger) ?? true,
);
}

View file

@ -6,6 +6,10 @@ import 'dart:async';
import '../base/common.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 '../globals.dart' as globals;
import '../project.dart';
@ -89,9 +93,29 @@ class PackagesGetCommand extends FlutterCommand {
}
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();
try {
await pub.get(context: PubContext.pubGet,
await pub.get(
context: PubContext.pubGet,
directory: directory,
upgrade: upgrade ,
offline: boolArg('offline'),

View file

@ -9,8 +9,10 @@ import '../asset.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../build_system/build_system.dart';
import '../bundle.dart';
import '../cache.dart';
import '../dart/generate_synthetic_packages.dart';
import '../dart/pub.dart';
import '../devfs.dart';
import '../globals.dart' as globals;
@ -164,6 +166,25 @@ class TestCommand extends FlutterCommand {
}
final FlutterProject flutterProject = FlutterProject.current();
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(
context: PubContext.getVerifyContext(name),
skipPubspecYamlCheck: true,

View file

@ -0,0 +1,72 @@
// 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.');
}
}

View file

@ -181,7 +181,8 @@ class _DefaultPub implements Pub {
_fileSystem.path.join(directory, 'pubspec.yaml'));
final File packageConfigFile = _fileSystem.file(
_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 (!skipIfAbsent) {

View file

@ -84,11 +84,11 @@ class FlutterProject {
/// Returns a [FlutterProject] view of the current directory or a ToolExit error,
/// 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,
/// 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.
final Directory directory;

View file

@ -18,9 +18,11 @@ import '../base/terminal.dart';
import '../base/user_messages.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../build_system/build_system.dart';
import '../build_system/targets/icon_tree_shaker.dart' show kIconTreeShakerEnabledDefault;
import '../bundle.dart' as bundle;
import '../cache.dart';
import '../dart/generate_synthetic_packages.dart';
import '../dart/package_map.dart';
import '../dart/pub.dart';
import '../device.dart';
@ -886,6 +888,23 @@ abstract class FlutterCommand extends Command<void> {
if (shouldRunPub) {
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(
context: PubContext.getVerifyContext(name),
generateSyntheticPackage: project.manifest.generateSyntheticPackage,

View file

@ -14,7 +14,7 @@ import '../../../src/context.dart';
void main() {
// Verifies that values are correctly passed through the localizations
// 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 Logger logger = BufferLogger.test();
final String projectDir = fileSystem.path.join('path', 'to', 'flutter_project');
@ -34,7 +34,8 @@ void main() {
'--header-file=header',
'--header=HEADER',
'--use-deferred-loading',
'--preferred-supported-locales=en_US'
'--preferred-supported-locales=en_US',
'--no-synthetic-package',
],
),
]);
@ -57,6 +58,7 @@ void main() {
preferredSupportedLocales: 'en_US',
templateArbFile: Uri.file('example.arb'),
untranslatedMessagesFile: Uri.file('untranslated'),
useSyntheticPackage: false,
);
await generateLocalizations(
options: options,
@ -72,6 +74,54 @@ void main() {
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 {
final FileSystem fileSystem = MemoryFileSystem.test();
final Environment environment = Environment.test(

View file

@ -0,0 +1,260 @@
// 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 {}

View file

@ -1201,6 +1201,7 @@ void main() {
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
.createSync(recursive: true);
globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n');
await residentRunner.runSourceGenerators();
@ -1227,6 +1228,7 @@ void main() {
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
.createSync(recursive: true);
globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n');
await residentRunner.runSourceGenerators();

View file

@ -16,6 +16,7 @@ class GenL10nProject extends Project {
@override
Future<void> setUpIn(Directory dir, {
bool useDeferredLoading = false,
bool useSyntheticPackage = false,
}) {
this.dir = dir;
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en.arb'), appEn);
@ -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_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, 'l10n.yaml'), l10nYaml(useDeferredLoading: useDeferredLoading));
writeFile(globals.fs.path.join(dir.path, 'l10n.yaml'), l10nYaml(
useDeferredLoading: useDeferredLoading,
useSyntheticPackage: useSyntheticPackage,
));
return super.setUpIn(dir);
}
@ -568,12 +572,18 @@ void main() {
String l10nYaml({
@required bool useDeferredLoading,
@required bool useSyntheticPackage,
}) {
String l10nYamlString = '';
if (useDeferredLoading) {
return r'''
use-deferred-loading: false
''';
l10nYamlString += 'use-deferred-loading: true\n';
}
return '';
if (!useSyntheticPackage) {
l10nYamlString += 'synthetic-package: false\n';
}
return l10nYamlString;
}
}