[flutter_tools] reland: integrate l10n tool into hot reload/restart/build (#57510)
Reland: #56167
This commit is contained in:
parent
27a6705aa4
commit
70b889a9a3
@ -88,6 +88,12 @@ class CommandHelp {
|
|||||||
'Detach (terminate "flutter run" but leave application running).',
|
'Detach (terminate "flutter run" but leave application running).',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CommandHelpOption _g;
|
||||||
|
CommandHelpOption get g => _g ??= _makeOption(
|
||||||
|
'g',
|
||||||
|
'Run source code generators.'
|
||||||
|
);
|
||||||
|
|
||||||
CommandHelpOption _h;
|
CommandHelpOption _h;
|
||||||
CommandHelpOption get h => _h ??= _makeOption(
|
CommandHelpOption get h => _h ??= _makeOption(
|
||||||
'h',
|
'h',
|
||||||
|
@ -487,6 +487,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (debuggingOptions.buildInfo.isDebug) {
|
if (debuggingOptions.buildInfo.isDebug) {
|
||||||
|
await runSourceGenerators();
|
||||||
// Full restart is always false for web, since the extra recompile is wasteful.
|
// Full restart is always false for web, since the extra recompile is wasteful.
|
||||||
final UpdateFSReport report = await _updateDevFS(fullRestart: false);
|
final UpdateFSReport report = await _updateDevFS(fullRestart: false);
|
||||||
if (report.success) {
|
if (report.success) {
|
||||||
|
@ -134,6 +134,12 @@ abstract class Target {
|
|||||||
/// A list of zero or more depfiles, located directly under {BUILD_DIR}.
|
/// A list of zero or more depfiles, located directly under {BUILD_DIR}.
|
||||||
List<String> get depfiles => const <String>[];
|
List<String> get depfiles => const <String>[];
|
||||||
|
|
||||||
|
/// Whether this target can be executed with the given [environment].
|
||||||
|
///
|
||||||
|
/// Returning `true` will cause [build] to be skipped. This is equivalent
|
||||||
|
/// to a build that produces no outputs.
|
||||||
|
bool canSkip(Environment environment) => false;
|
||||||
|
|
||||||
/// The action which performs this build step.
|
/// The action which performs this build step.
|
||||||
Future<void> build(Environment environment);
|
Future<void> build(Environment environment);
|
||||||
|
|
||||||
@ -773,18 +779,26 @@ class _BuildInstance {
|
|||||||
updateGraph();
|
updateGraph();
|
||||||
return succeeded;
|
return succeeded;
|
||||||
}
|
}
|
||||||
logger.printTrace('${node.target.name}: Starting due to ${node.invalidatedReasons}');
|
// Clear old inputs. These will be replaced with new inputs/outputs
|
||||||
await node.target.build(environment);
|
// after the target is run. In the case of a runtime skip, each list
|
||||||
logger.printTrace('${node.target.name}: Complete');
|
// must be empty to ensure the previous outputs are purged.
|
||||||
|
node.inputs.clear();
|
||||||
|
node.outputs.clear();
|
||||||
|
|
||||||
node.inputs
|
// Check if we can skip via runtime dependencies.
|
||||||
..clear()
|
final bool runtimeSkip = node.target.canSkip(environment);
|
||||||
..addAll(node.target.resolveInputs(environment).sources);
|
if (runtimeSkip) {
|
||||||
node.outputs
|
logger.printTrace('Skipping target: ${node.target.name}');
|
||||||
..clear()
|
skipped = true;
|
||||||
..addAll(node.target.resolveOutputs(environment).sources);
|
} else {
|
||||||
|
logger.printTrace('${node.target.name}: Starting due to ${node.invalidatedReasons}');
|
||||||
|
await node.target.build(environment);
|
||||||
|
logger.printTrace('${node.target.name}: Complete');
|
||||||
|
node.inputs.addAll(node.target.resolveInputs(environment).sources);
|
||||||
|
node.outputs.addAll(node.target.resolveOutputs(environment).sources);
|
||||||
|
}
|
||||||
|
|
||||||
// If we were missing the depfile, resolve input files after executing the
|
// If we were missing the depfile, resolve input files after executing the
|
||||||
// target so that all file hashes are up to date on the next run.
|
// target so that all file hashes are up to date on the next run.
|
||||||
if (node.missingDepfile) {
|
if (node.missingDepfile) {
|
||||||
await fileCache.diffFileList(node.inputs);
|
await fileCache.diffFileList(node.inputs);
|
||||||
|
@ -17,6 +17,7 @@ import '../depfile.dart';
|
|||||||
import '../exceptions.dart';
|
import '../exceptions.dart';
|
||||||
import 'assets.dart';
|
import 'assets.dart';
|
||||||
import 'icon_tree_shaker.dart';
|
import 'icon_tree_shaker.dart';
|
||||||
|
import 'localizations.dart';
|
||||||
|
|
||||||
/// The define to pass a [BuildMode].
|
/// The define to pass a [BuildMode].
|
||||||
const String kBuildMode = 'BuildMode';
|
const String kBuildMode = 'BuildMode';
|
||||||
@ -183,7 +184,9 @@ class KernelSnapshot extends Target {
|
|||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Target> get dependencies => <Target>[];
|
List<Target> get dependencies => const <Target>[
|
||||||
|
GenerateLocalizationsTarget(),
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> build(Environment environment) async {
|
Future<void> build(Environment environment) async {
|
||||||
|
@ -0,0 +1,272 @@
|
|||||||
|
// 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:process/process.dart';
|
||||||
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
|
import '../../artifacts.dart';
|
||||||
|
import '../../base/file_system.dart';
|
||||||
|
import '../../base/io.dart';
|
||||||
|
import '../../base/logger.dart';
|
||||||
|
import '../../convert.dart';
|
||||||
|
import '../../globals.dart' as globals;
|
||||||
|
import '../build_system.dart';
|
||||||
|
import '../depfile.dart';
|
||||||
|
|
||||||
|
const String _kDependenciesFileName = 'gen_l10n_inputs_and_outputs.json';
|
||||||
|
|
||||||
|
/// Run the localizations generation script with the configuration [options].
|
||||||
|
Future<void> generateLocalizations({
|
||||||
|
@required LocalizationOptions options,
|
||||||
|
@required String flutterRoot,
|
||||||
|
@required FileSystem fileSystem,
|
||||||
|
@required ProcessManager processManager,
|
||||||
|
@required Logger logger,
|
||||||
|
@required Directory projectDir,
|
||||||
|
@required String dartBinaryPath,
|
||||||
|
@required Directory dependenciesDir,
|
||||||
|
}) async {
|
||||||
|
final String genL10nPath = fileSystem.path.join(
|
||||||
|
flutterRoot,
|
||||||
|
'dev',
|
||||||
|
'tools',
|
||||||
|
'localization',
|
||||||
|
'bin',
|
||||||
|
'gen_l10n.dart',
|
||||||
|
);
|
||||||
|
final ProcessResult result = await processManager.run(<String>[
|
||||||
|
dartBinaryPath,
|
||||||
|
genL10nPath,
|
||||||
|
'--gen-inputs-and-outputs-list=${dependenciesDir.path}',
|
||||||
|
if (options.arbDirectory != null)
|
||||||
|
'--arb-dir=${options.arbDirectory.toFilePath()}',
|
||||||
|
if (options.templateArbFile != null)
|
||||||
|
'--template-arb-file=${options.templateArbFile.toFilePath()}',
|
||||||
|
if (options.outputLocalizationsFile != null)
|
||||||
|
'--output-localization-file=${options.outputLocalizationsFile.toFilePath()}',
|
||||||
|
if (options.untranslatedMessagesFile != null)
|
||||||
|
'--untranslated-messages-file=${options.untranslatedMessagesFile.toFilePath()}',
|
||||||
|
if (options.outputClass != null)
|
||||||
|
'--output-class=${options.outputClass}',
|
||||||
|
if (options.headerFile != null)
|
||||||
|
'--header-file=${options.headerFile.toFilePath()}',
|
||||||
|
if (options.header != null)
|
||||||
|
'--header=${options.header}',
|
||||||
|
if (options.deferredLoading != null)
|
||||||
|
'--use-deferred-loading',
|
||||||
|
if (options.preferredSupportedLocales != null)
|
||||||
|
'--preferred-supported-locales=${options.preferredSupportedLocales}',
|
||||||
|
]);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
logger.printError(result.stdout + result.stderr as String);
|
||||||
|
throw Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A build step that runs the generate localizations script from
|
||||||
|
/// dev/tool/localizations.
|
||||||
|
class GenerateLocalizationsTarget extends Target {
|
||||||
|
const GenerateLocalizationsTarget();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Target> get dependencies => <Target>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Source> get inputs => <Source>[
|
||||||
|
// This is added as a convenience for developing the tool.
|
||||||
|
const Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/localizations.dart'),
|
||||||
|
// TODO(jonahwilliams): once https://github.com/flutter/flutter/issues/56321 is
|
||||||
|
// complete, we should add the artifact as a dependency here. Since the tool runs
|
||||||
|
// this code from source, looking up each dependency will be cumbersome.
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'gen_localizations';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Source> get outputs => <Source>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> get depfiles => <String>['gen_localizations.d'];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canSkip(Environment environment) {
|
||||||
|
final File configFile = environment.projectDir.childFile('l10n.yaml');
|
||||||
|
return !configFile.existsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> build(Environment environment) async {
|
||||||
|
final File configFile = environment.projectDir.childFile('l10n.yaml');
|
||||||
|
assert(configFile.existsSync());
|
||||||
|
|
||||||
|
final LocalizationOptions options = parseLocalizationsOptions(
|
||||||
|
file: configFile,
|
||||||
|
logger: globals.logger,
|
||||||
|
);
|
||||||
|
final DepfileService depfileService = DepfileService(
|
||||||
|
logger: environment.logger,
|
||||||
|
fileSystem: environment.fileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
await generateLocalizations(
|
||||||
|
fileSystem: environment.fileSystem,
|
||||||
|
flutterRoot: environment.flutterRootDir.path,
|
||||||
|
logger: environment.logger,
|
||||||
|
processManager: environment.processManager,
|
||||||
|
options: options,
|
||||||
|
projectDir: environment.projectDir,
|
||||||
|
dartBinaryPath: environment.artifacts
|
||||||
|
.getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
dependenciesDir: environment.buildDir,
|
||||||
|
);
|
||||||
|
final Map<String, Object> dependencies = json
|
||||||
|
.decode(environment.buildDir.childFile(_kDependenciesFileName).readAsStringSync()) as Map<String, Object>;
|
||||||
|
final Depfile depfile = Depfile(
|
||||||
|
<File>[
|
||||||
|
configFile,
|
||||||
|
for (dynamic inputFile in dependencies['inputs'] as List<dynamic>)
|
||||||
|
environment.fileSystem.file(inputFile)
|
||||||
|
],
|
||||||
|
<File>[
|
||||||
|
for (dynamic outputFile in dependencies['outputs'] as List<dynamic>)
|
||||||
|
environment.fileSystem.file(outputFile)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
depfileService.writeToFile(
|
||||||
|
depfile,
|
||||||
|
environment.buildDir.childFile('gen_localizations.d'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Typed configuration from the localizations config file.
|
||||||
|
class LocalizationOptions {
|
||||||
|
const LocalizationOptions({
|
||||||
|
this.arbDirectory,
|
||||||
|
this.templateArbFile,
|
||||||
|
this.outputLocalizationsFile,
|
||||||
|
this.untranslatedMessagesFile,
|
||||||
|
this.header,
|
||||||
|
this.outputClass,
|
||||||
|
this.preferredSupportedLocales,
|
||||||
|
this.headerFile,
|
||||||
|
this.deferredLoading,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The `--arb-dir` argument.
|
||||||
|
///
|
||||||
|
/// The directory where all localization files should reside.
|
||||||
|
final Uri arbDirectory;
|
||||||
|
|
||||||
|
/// The `--template-arb-file` argument.
|
||||||
|
///
|
||||||
|
/// This URI is relative to [arbDirectory].
|
||||||
|
final Uri templateArbFile;
|
||||||
|
|
||||||
|
/// The `--output-localization-file` argument.
|
||||||
|
///
|
||||||
|
/// This URI is relative to [arbDirectory].
|
||||||
|
final Uri outputLocalizationsFile;
|
||||||
|
|
||||||
|
/// The `--untranslated-messages-file` argument.
|
||||||
|
///
|
||||||
|
/// This URI is relative to [arbDirectory].
|
||||||
|
final Uri untranslatedMessagesFile;
|
||||||
|
|
||||||
|
/// The `--header` argument.
|
||||||
|
///
|
||||||
|
/// The header to prepend to the generated Dart localizations
|
||||||
|
final String header;
|
||||||
|
|
||||||
|
/// The `--output-class` argument.
|
||||||
|
final String outputClass;
|
||||||
|
|
||||||
|
/// The `--preferred-supported-locales` argument.
|
||||||
|
final String preferredSupportedLocales;
|
||||||
|
|
||||||
|
/// The `--header-file` argument.
|
||||||
|
///
|
||||||
|
/// A file containing the header to preprend to the generated
|
||||||
|
/// Dart localizations.
|
||||||
|
final Uri headerFile;
|
||||||
|
|
||||||
|
/// The `--use-deferred-loading` argument.
|
||||||
|
///
|
||||||
|
/// Whether to generate the Dart localization file with locales imported
|
||||||
|
/// as deferred.
|
||||||
|
final bool deferredLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the localizations configuration options from [file].
|
||||||
|
///
|
||||||
|
/// Throws [Exception] if any of the contents are invalid. Returns a
|
||||||
|
/// [LocalizationOptions] with all fields as `null` if the config file exists
|
||||||
|
/// but is empty.
|
||||||
|
LocalizationOptions parseLocalizationsOptions({
|
||||||
|
@required File file,
|
||||||
|
@required Logger logger,
|
||||||
|
}) {
|
||||||
|
final String contents = file.readAsStringSync();
|
||||||
|
if (contents.trim().isEmpty) {
|
||||||
|
return const LocalizationOptions();
|
||||||
|
}
|
||||||
|
final YamlNode yamlNode = loadYamlNode(file.readAsStringSync());
|
||||||
|
if (yamlNode is! YamlMap) {
|
||||||
|
logger.printError('Expected ${file.path} to contain a map, instead was $yamlNode');
|
||||||
|
throw Exception();
|
||||||
|
}
|
||||||
|
final YamlMap yamlMap = yamlNode as YamlMap;
|
||||||
|
return LocalizationOptions(
|
||||||
|
arbDirectory: _tryReadUri(yamlMap, 'arb-dir', logger),
|
||||||
|
templateArbFile: _tryReadUri(yamlMap, 'template-arb-file', logger),
|
||||||
|
outputLocalizationsFile: _tryReadUri(yamlMap, 'output-localization-file', logger),
|
||||||
|
untranslatedMessagesFile: _tryReadUri(yamlMap, 'untranslated-messages-file', logger),
|
||||||
|
header: _tryReadString(yamlMap, 'header', logger),
|
||||||
|
outputClass: _tryReadString(yamlMap, 'output-class', logger),
|
||||||
|
preferredSupportedLocales: _tryReadString(yamlMap, 'preferred-supported-locales', logger),
|
||||||
|
headerFile: _tryReadUri(yamlMap, 'header-file', logger),
|
||||||
|
deferredLoading: _tryReadBool(yamlMap, 'use-deferred-loading', logger),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read a `bool` value or null from `yamlMap`, otherwise throw.
|
||||||
|
bool _tryReadBool(YamlMap yamlMap, String key, Logger logger) {
|
||||||
|
final Object value = yamlMap[key];
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value is! bool) {
|
||||||
|
logger.printError('Expected "$key" to have a bool value, instead was "$value"');
|
||||||
|
throw Exception();
|
||||||
|
}
|
||||||
|
return value as bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read a `String` value or null from `yamlMap`, otherwise throw.
|
||||||
|
String _tryReadString(YamlMap yamlMap, String key, Logger logger) {
|
||||||
|
final Object value = yamlMap[key];
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (value is! String) {
|
||||||
|
logger.printError('Expected "$key" to have a String value, instead was "$value"');
|
||||||
|
throw Exception();
|
||||||
|
}
|
||||||
|
return value as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read a valid `Uri` or null from `yamlMap`, otherwise throw.
|
||||||
|
Uri _tryReadUri(YamlMap yamlMap, String key, Logger logger) {
|
||||||
|
final String value = _tryReadString(yamlMap, key, logger);
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Uri uri = Uri.tryParse(value);
|
||||||
|
if (uri == null) {
|
||||||
|
logger.printError('"$value" must be a relative file URI');
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
@ -15,6 +15,7 @@ import '../build_system.dart';
|
|||||||
import '../depfile.dart';
|
import '../depfile.dart';
|
||||||
import 'assets.dart';
|
import 'assets.dart';
|
||||||
import 'dart.dart';
|
import 'dart.dart';
|
||||||
|
import 'localizations.dart';
|
||||||
|
|
||||||
/// Whether web builds should call the platform initialization logic.
|
/// Whether web builds should call the platform initialization logic.
|
||||||
const String kInitializePlatform = 'InitializePlatform';
|
const String kInitializePlatform = 'InitializePlatform';
|
||||||
@ -132,7 +133,8 @@ class Dart2JSTarget extends Target {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Target> get dependencies => const <Target>[
|
List<Target> get dependencies => const <Target>[
|
||||||
WebEntrypointTarget()
|
WebEntrypointTarget(),
|
||||||
|
GenerateLocalizationsTarget(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:vm_service/vm_service.dart' as vm_service;
|
|
||||||
import 'package:devtools_server/devtools_server.dart' as devtools_server;
|
import 'package:devtools_server/devtools_server.dart' as devtools_server;
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:package_config/package_config.dart';
|
import 'package:package_config/package_config.dart';
|
||||||
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
||||||
|
|
||||||
import 'application_package.dart';
|
import 'application_package.dart';
|
||||||
import 'artifacts.dart';
|
import 'artifacts.dart';
|
||||||
@ -21,7 +21,10 @@ import 'base/signals.dart';
|
|||||||
import 'base/terminal.dart';
|
import 'base/terminal.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/localizations.dart';
|
||||||
import 'bundle.dart';
|
import 'bundle.dart';
|
||||||
|
import 'cache.dart';
|
||||||
import 'codegen.dart';
|
import 'codegen.dart';
|
||||||
import 'compile.dart';
|
import 'compile.dart';
|
||||||
import 'convert.dart';
|
import 'convert.dart';
|
||||||
@ -800,6 +803,40 @@ abstract class ResidentRunner {
|
|||||||
throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
|
throw '${fullRestart ? 'Restart' : 'Reload'} is not supported in $mode mode';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BuildResult _lastBuild;
|
||||||
|
Environment _environment;
|
||||||
|
Future<void> runSourceGenerators() async {
|
||||||
|
_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: globals.fs.currentDirectory,
|
||||||
|
);
|
||||||
|
globals.logger.printTrace('Starting incremental build...');
|
||||||
|
_lastBuild = await globals.buildSystem.buildIncremental(
|
||||||
|
const GenerateLocalizationsTarget(),
|
||||||
|
_environment,
|
||||||
|
_lastBuild,
|
||||||
|
);
|
||||||
|
if (!_lastBuild.success) {
|
||||||
|
for (final ExceptionMeasurement exceptionMeasurement in _lastBuild.exceptions.values) {
|
||||||
|
globals.logger.printError(
|
||||||
|
exceptionMeasurement.exception.toString(),
|
||||||
|
stackTrace: globals.logger.isVerbose
|
||||||
|
? exceptionMeasurement.stackTrace
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globals.logger.printTrace('complete');
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggle whether canvaskit is being used for rendering, returning the new
|
/// Toggle whether canvaskit is being used for rendering, returning the new
|
||||||
/// state.
|
/// state.
|
||||||
///
|
///
|
||||||
@ -1200,6 +1237,7 @@ abstract class ResidentRunner {
|
|||||||
commandHelp.p.print();
|
commandHelp.p.print();
|
||||||
commandHelp.o.print();
|
commandHelp.o.print();
|
||||||
commandHelp.z.print();
|
commandHelp.z.print();
|
||||||
|
commandHelp.g.print();
|
||||||
} else {
|
} else {
|
||||||
commandHelp.S.print();
|
commandHelp.S.print();
|
||||||
commandHelp.U.print();
|
commandHelp.U.print();
|
||||||
@ -1337,6 +1375,9 @@ class TerminalHandler {
|
|||||||
case 'D':
|
case 'D':
|
||||||
await residentRunner.detach();
|
await residentRunner.detach();
|
||||||
return true;
|
return true;
|
||||||
|
case 'g':
|
||||||
|
await residentRunner.runSourceGenerators();
|
||||||
|
return true;
|
||||||
case 'h':
|
case 'h':
|
||||||
case 'H':
|
case 'H':
|
||||||
case '?':
|
case '?':
|
||||||
|
@ -7,8 +7,8 @@ import 'package:package_config/package_config.dart';
|
|||||||
import 'package:vm_service/vm_service.dart' as vm_service;
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:pool/pool.dart';
|
import 'package:pool/pool.dart';
|
||||||
import 'base/async_guard.dart';
|
|
||||||
|
|
||||||
|
import 'base/async_guard.dart';
|
||||||
import 'base/context.dart';
|
import 'base/context.dart';
|
||||||
import 'base/file_system.dart';
|
import 'base/file_system.dart';
|
||||||
import 'base/logger.dart';
|
import 'base/logger.dart';
|
||||||
@ -365,6 +365,7 @@ class HotRunner extends ResidentRunner {
|
|||||||
// build, reducing overall initialization time. This is safe because the first
|
// build, reducing overall initialization time. This is safe because the first
|
||||||
// invocation of the frontend server produces a full dill file that the
|
// invocation of the frontend server produces a full dill file that the
|
||||||
// subsequent invocation in devfs will not overwrite.
|
// subsequent invocation in devfs will not overwrite.
|
||||||
|
await runSourceGenerators();
|
||||||
if (device.generator != null) {
|
if (device.generator != null) {
|
||||||
startupTasks.add(
|
startupTasks.add(
|
||||||
device.generator.recompile(
|
device.generator.recompile(
|
||||||
@ -674,6 +675,10 @@ class HotRunner extends ResidentRunner {
|
|||||||
emulator = false;
|
emulator = false;
|
||||||
}
|
}
|
||||||
final Stopwatch timer = Stopwatch()..start();
|
final Stopwatch timer = Stopwatch()..start();
|
||||||
|
|
||||||
|
// Run source generation if needed.
|
||||||
|
await runSourceGenerators();
|
||||||
|
|
||||||
if (fullRestart) {
|
if (fullRestart) {
|
||||||
final OperationResult result = await _fullRestartHelper(
|
final OperationResult result = await _fullRestartHelper(
|
||||||
targetPlatform: targetPlatform,
|
targetPlatform: targetPlatform,
|
||||||
@ -1190,7 +1195,7 @@ class ProjectFileInvalidator {
|
|||||||
static const String _pubCachePathWindows = 'Pub/Cache';
|
static const String _pubCachePathWindows = 'Pub/Cache';
|
||||||
|
|
||||||
// As of writing, Dart supports up to 32 asynchronous I/O threads per
|
// As of writing, Dart supports up to 32 asynchronous I/O threads per
|
||||||
// isolate. We also want to avoid hitting platform limits on open file
|
// isolate. We also want to avoid hitting platform limits on open file
|
||||||
// handles/descriptors.
|
// handles/descriptors.
|
||||||
//
|
//
|
||||||
// This value was chosen based on empirical tests scanning a set of
|
// This value was chosen based on empirical tests scanning a set of
|
||||||
@ -1223,7 +1228,6 @@ class ProjectFileInvalidator {
|
|||||||
if (_isNotInPubCache(uri)) uri,
|
if (_isNotInPubCache(uri)) uri,
|
||||||
];
|
];
|
||||||
final List<Uri> invalidatedFiles = <Uri>[];
|
final List<Uri> invalidatedFiles = <Uri>[];
|
||||||
|
|
||||||
if (asyncScanning) {
|
if (asyncScanning) {
|
||||||
final Pool pool = Pool(_kMaxPendingStats);
|
final Pool pool = Pool(_kMaxPendingStats);
|
||||||
final List<Future<void>> waitList = <Future<void>>[];
|
final List<Future<void>> waitList = <Future<void>>[];
|
||||||
|
@ -57,6 +57,7 @@ void _testMessageLength({
|
|||||||
expect(commandHelp.U.toString().length, lessThanOrEqualTo(expectedWidth));
|
expect(commandHelp.U.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
expect(commandHelp.a.toString().length, lessThanOrEqualTo(expectedWidth));
|
expect(commandHelp.a.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
expect(commandHelp.d.toString().length, lessThanOrEqualTo(expectedWidth));
|
expect(commandHelp.d.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
|
expect(commandHelp.g.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
expect(commandHelp.h.toString().length, lessThanOrEqualTo(expectedWidth));
|
expect(commandHelp.h.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
expect(commandHelp.i.toString().length, lessThanOrEqualTo(expectedWidth));
|
expect(commandHelp.i.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
expect(commandHelp.k.toString().length, lessThanOrEqualTo(expectedWidth));
|
expect(commandHelp.k.toString().length, lessThanOrEqualTo(expectedWidth));
|
||||||
@ -88,6 +89,7 @@ void main() {
|
|||||||
expect(commandHelp.U.toString(), startsWith('\x1B[1mU\x1B[22m'));
|
expect(commandHelp.U.toString(), startsWith('\x1B[1mU\x1B[22m'));
|
||||||
expect(commandHelp.a.toString(), startsWith('\x1B[1ma\x1B[22m'));
|
expect(commandHelp.a.toString(), startsWith('\x1B[1ma\x1B[22m'));
|
||||||
expect(commandHelp.d.toString(), startsWith('\x1B[1md\x1B[22m'));
|
expect(commandHelp.d.toString(), startsWith('\x1B[1md\x1B[22m'));
|
||||||
|
expect(commandHelp.g.toString(), startsWith('\x1B[1mg\x1B[22m'));
|
||||||
expect(commandHelp.h.toString(), startsWith('\x1B[1mh\x1B[22m'));
|
expect(commandHelp.h.toString(), startsWith('\x1B[1mh\x1B[22m'));
|
||||||
expect(commandHelp.i.toString(), startsWith('\x1B[1mi\x1B[22m'));
|
expect(commandHelp.i.toString(), startsWith('\x1B[1mi\x1B[22m'));
|
||||||
expect(commandHelp.o.toString(), startsWith('\x1B[1mo\x1B[22m'));
|
expect(commandHelp.o.toString(), startsWith('\x1B[1mo\x1B[22m'));
|
||||||
@ -164,6 +166,7 @@ void main() {
|
|||||||
expect(commandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order. \x1B[1;30m(debugDumpSemantics)\x1B[39m'));
|
expect(commandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order. \x1B[1;30m(debugDumpSemantics)\x1B[39m'));
|
||||||
expect(commandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods. \x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m'));
|
expect(commandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods. \x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m'));
|
||||||
expect(commandHelp.d.toString(), equals('\x1B[1md\x1B[22m Detach (terminate "flutter run" but leave application running).'));
|
expect(commandHelp.d.toString(), equals('\x1B[1md\x1B[22m Detach (terminate "flutter run" but leave application running).'));
|
||||||
|
expect(commandHelp.g.toString(), equals('\x1B[1mg\x1B[22m Run source code generators.'));
|
||||||
expect(commandHelp.h.toString(), equals('\x1B[1mh\x1B[22m Repeat this help message.'));
|
expect(commandHelp.h.toString(), equals('\x1B[1mh\x1B[22m Repeat this help message.'));
|
||||||
expect(commandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector. \x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m'));
|
expect(commandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector. \x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m'));
|
||||||
expect(commandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems. \x1B[1;30m(defaultTargetPlatform)\x1B[39m'));
|
expect(commandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems. \x1B[1;30m(defaultTargetPlatform)\x1B[39m'));
|
||||||
@ -190,6 +193,7 @@ void main() {
|
|||||||
expect(commandHelp.U.toString(), equals('U Dump accessibility tree in inverse hit test order. (debugDumpSemantics)'));
|
expect(commandHelp.U.toString(), equals('U Dump accessibility tree in inverse hit test order. (debugDumpSemantics)'));
|
||||||
expect(commandHelp.a.toString(), equals('a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)'));
|
expect(commandHelp.a.toString(), equals('a Toggle timeline events for all widget build methods. (debugProfileWidgetBuilds)'));
|
||||||
expect(commandHelp.d.toString(), equals('d Detach (terminate "flutter run" but leave application running).'));
|
expect(commandHelp.d.toString(), equals('d Detach (terminate "flutter run" but leave application running).'));
|
||||||
|
expect(commandHelp.g.toString(), equals('g Run source code generators.'));
|
||||||
expect(commandHelp.h.toString(), equals('h Repeat this help message.'));
|
expect(commandHelp.h.toString(), equals('h Repeat this help message.'));
|
||||||
expect(commandHelp.i.toString(), equals('i Toggle widget inspector. (WidgetsApp.showWidgetInspectorOverride)'));
|
expect(commandHelp.i.toString(), equals('i Toggle widget inspector. (WidgetsApp.showWidgetInspectorOverride)'));
|
||||||
expect(commandHelp.o.toString(), equals('o Simulate different operating systems. (defaultTargetPlatform)'));
|
expect(commandHelp.o.toString(), equals('o Simulate different operating systems. (defaultTargetPlatform)'));
|
||||||
|
@ -581,6 +581,50 @@ void main() {
|
|||||||
expect(fileSystem.file('output/debug'), isNot(exists));
|
expect(fileSystem.file('output/debug'), isNot(exists));
|
||||||
expect(fileSystem.file('output/release'), exists);
|
expect(fileSystem.file('output/release'), exists);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWithoutContext('A target using canSkip can create a conditional output', () async {
|
||||||
|
final BuildSystem buildSystem = setUpBuildSystem(fileSystem);
|
||||||
|
final File bar = environment.buildDir.childFile('bar');
|
||||||
|
final File foo = environment.buildDir.childFile('foo');
|
||||||
|
|
||||||
|
// The target will write a file `foo`, but only if `bar` already exists.
|
||||||
|
final TestTarget target = TestTarget(
|
||||||
|
(Environment environment) async {
|
||||||
|
foo.writeAsStringSync(bar.readAsStringSync());
|
||||||
|
environment.buildDir
|
||||||
|
.childFile('example.d')
|
||||||
|
.writeAsStringSync('${foo.path}: ${bar.path}');
|
||||||
|
},
|
||||||
|
(Environment environment) {
|
||||||
|
return !environment.buildDir.childFile('bar').existsSync();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
..depfiles = const <String>['example.d'];
|
||||||
|
|
||||||
|
// bar does not exist, there should be no inputs/outputs.
|
||||||
|
final BuildResult firstResult = await buildSystem.build(target, environment);
|
||||||
|
|
||||||
|
expect(foo, isNot(exists));
|
||||||
|
expect(firstResult.inputFiles, isEmpty);
|
||||||
|
expect(firstResult.outputFiles, isEmpty);
|
||||||
|
|
||||||
|
// bar is created, the target should be able to run.
|
||||||
|
bar.writeAsStringSync('content-1');
|
||||||
|
final BuildResult secondResult = await buildSystem.build(target, environment);
|
||||||
|
|
||||||
|
expect(foo, exists);
|
||||||
|
expect(secondResult.inputFiles.map((File file) => file.path), <String>[bar.path]);
|
||||||
|
expect(secondResult.outputFiles.map((File file) => file.path), <String>[foo.path]);
|
||||||
|
|
||||||
|
// bar is destroyed, foo is also deleted.
|
||||||
|
bar.deleteSync();
|
||||||
|
final BuildResult thirdResult = await buildSystem.build(target, environment);
|
||||||
|
|
||||||
|
expect(foo, isNot(exists));
|
||||||
|
expect(thirdResult.inputFiles, isEmpty);
|
||||||
|
expect(thirdResult.outputFiles, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildSystem setUpBuildSystem(FileSystem fileSystem) {
|
BuildSystem setUpBuildSystem(FileSystem fileSystem) {
|
||||||
@ -592,10 +636,20 @@ BuildSystem setUpBuildSystem(FileSystem fileSystem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestTarget extends Target {
|
class TestTarget extends Target {
|
||||||
TestTarget([this._build]);
|
TestTarget([this._build, this._canSkip]);
|
||||||
|
|
||||||
final Future<void> Function(Environment environment) _build;
|
final Future<void> Function(Environment environment) _build;
|
||||||
|
|
||||||
|
final bool Function(Environment environment) _canSkip;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool canSkip(Environment environment) {
|
||||||
|
if (_canSkip != null) {
|
||||||
|
return _canSkip(environment);
|
||||||
|
}
|
||||||
|
return super.canSkip(environment);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> build(Environment environment) => _build(environment);
|
Future<void> build(Environment environment) => _build(environment);
|
||||||
|
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
// 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/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/targets/localizations.dart';
|
||||||
|
|
||||||
|
import '../../../src/common.dart';
|
||||||
|
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 {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final Logger logger = BufferLogger.test();
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
'dart',
|
||||||
|
'dev/tools/localization/bin/gen_l10n.dart',
|
||||||
|
'--gen-inputs-and-outputs-list=/',
|
||||||
|
'--arb-dir=arb',
|
||||||
|
'--template-arb-file=example.arb',
|
||||||
|
'--output-localization-file=bar',
|
||||||
|
'--untranslated-messages-file=untranslated',
|
||||||
|
'--output-class=Foo',
|
||||||
|
'--header-file=header',
|
||||||
|
'--header=HEADER',
|
||||||
|
'--use-deferred-loading',
|
||||||
|
'--preferred-supported-locales=en_US'
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final Directory arbDirectory = fileSystem.directory('arb')
|
||||||
|
..createSync();
|
||||||
|
arbDirectory.childFile('foo.arb').createSync();
|
||||||
|
arbDirectory.childFile('bar.arb').createSync();
|
||||||
|
|
||||||
|
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'),
|
||||||
|
);
|
||||||
|
await generateLocalizations(
|
||||||
|
options: options,
|
||||||
|
logger: logger,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
processManager: processManager,
|
||||||
|
projectDir: fileSystem.currentDirectory,
|
||||||
|
dartBinaryPath: 'dart',
|
||||||
|
flutterRoot: '',
|
||||||
|
dependenciesDir: fileSystem.currentDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(processManager.hasRemainingExpectations, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('generateLocalizations is skipped if l10n.yaml does not exist.', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final Environment environment = Environment.test(
|
||||||
|
fileSystem.currentDirectory,
|
||||||
|
artifacts: null,
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
processManager: FakeProcessManager.any(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(const GenerateLocalizationsTarget().canSkip(environment), true);
|
||||||
|
|
||||||
|
environment.projectDir.childFile('l10n.yaml').createSync();
|
||||||
|
|
||||||
|
expect(const GenerateLocalizationsTarget().canSkip(environment), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('parseLocalizationsOptions handles valid yaml configuration', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final File configFile = fileSystem.file('l10n.yaml')
|
||||||
|
..writeAsStringSync('''
|
||||||
|
arb-dir: arb
|
||||||
|
template-arb-file: example.arb
|
||||||
|
output-localization-file: bar
|
||||||
|
untranslated-messages-file: untranslated
|
||||||
|
output-class: Foo
|
||||||
|
header-file: header
|
||||||
|
header: HEADER
|
||||||
|
use-deferred-loading: true
|
||||||
|
preferred-supported-locales: en_US
|
||||||
|
''');
|
||||||
|
|
||||||
|
final LocalizationOptions options = parseLocalizationsOptions(
|
||||||
|
file: configFile,
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(options.arbDirectory, Uri.parse('arb'));
|
||||||
|
expect(options.templateArbFile, Uri.parse('example.arb'));
|
||||||
|
expect(options.outputLocalizationsFile, Uri.parse('bar'));
|
||||||
|
expect(options.untranslatedMessagesFile, Uri.parse('untranslated'));
|
||||||
|
expect(options.outputClass, 'Foo');
|
||||||
|
expect(options.headerFile, Uri.parse('header'));
|
||||||
|
expect(options.header, 'HEADER');
|
||||||
|
expect(options.deferredLoading, true);
|
||||||
|
expect(options.preferredSupportedLocales, 'en_US');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('parseLocalizationsOptions throws exception on invalid yaml configuration', () async {
|
||||||
|
final FileSystem fileSystem = MemoryFileSystem.test();
|
||||||
|
final File configFile = fileSystem.file('l10n.yaml')
|
||||||
|
..writeAsStringSync('''
|
||||||
|
use-deferred-loading: string
|
||||||
|
''');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => parseLocalizationsOptions(
|
||||||
|
file: configFile,
|
||||||
|
logger: BufferLogger.test(),
|
||||||
|
),
|
||||||
|
throwsA(isA<Exception>()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
import 'package:vm_service/vm_service.dart' as vm_service;
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:file_testing/file_testing.dart';
|
import 'package:file_testing/file_testing.dart';
|
||||||
@ -537,6 +538,61 @@ void main() {
|
|||||||
expect(cacheDill, isNot(exists));
|
expect(cacheDill, isNot(exists));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
test('ResidentRunner can run source generation', () => testbed.run(() async {
|
||||||
|
final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
|
||||||
|
final Directory dependencies = globals.fs.directory(
|
||||||
|
globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1'));
|
||||||
|
processManager.addCommand(FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
globals.fs.path.join(Cache.flutterRoot, 'dev', 'tools', 'localization', 'bin', 'gen_l10n.dart'),
|
||||||
|
'--gen-inputs-and-outputs-list=${dependencies.absolute.path}',
|
||||||
|
],
|
||||||
|
onRun: () {
|
||||||
|
dependencies
|
||||||
|
.childFile('gen_l10n_inputs_and_outputs.json')
|
||||||
|
..createSync()
|
||||||
|
..writeAsStringSync('{"inputs":[],"outputs":[]}');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
|
||||||
|
.createSync(recursive: true);
|
||||||
|
globals.fs.file('l10n.yaml').createSync();
|
||||||
|
|
||||||
|
await residentRunner.runSourceGenerators();
|
||||||
|
|
||||||
|
expect(testLogger.errorText, isEmpty);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
test('ResidentRunner can run source generation - generation fails', () => testbed.run(() async {
|
||||||
|
final FakeProcessManager processManager = globals.processManager as FakeProcessManager;
|
||||||
|
final Directory dependencies = globals.fs.directory(
|
||||||
|
globals.fs.path.join('build', '6ec2559087977927717927ede0a147f1'));
|
||||||
|
processManager.addCommand(FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
|
||||||
|
globals.fs.path.join(Cache.flutterRoot, 'dev', 'tools', 'localization', 'bin', 'gen_l10n.dart'),
|
||||||
|
'--gen-inputs-and-outputs-list=${dependencies.absolute.path}',
|
||||||
|
],
|
||||||
|
exitCode: 1,
|
||||||
|
stderr: 'stderr'
|
||||||
|
));
|
||||||
|
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
|
||||||
|
.createSync(recursive: true);
|
||||||
|
globals.fs.file('l10n.yaml').createSync();
|
||||||
|
|
||||||
|
await residentRunner.runSourceGenerators();
|
||||||
|
|
||||||
|
expect(testLogger.errorText, allOf(
|
||||||
|
contains('stderr'), // Message from gen_l10n.dart
|
||||||
|
contains('Exception') // Message from build_system
|
||||||
|
));
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
ProcessManager: () => FakeProcessManager.list(<FakeCommand>[]),
|
||||||
|
}));
|
||||||
|
|
||||||
test('ResidentRunner printHelpDetails', () => testbed.run(() {
|
test('ResidentRunner printHelpDetails', () => testbed.run(() {
|
||||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
||||||
when(mockDevice.supportsHotRestart).thenReturn(true);
|
when(mockDevice.supportsHotRestart).thenReturn(true);
|
||||||
@ -573,6 +629,7 @@ void main() {
|
|||||||
commandHelp.p,
|
commandHelp.p,
|
||||||
commandHelp.o,
|
commandHelp.o,
|
||||||
commandHelp.z,
|
commandHelp.z,
|
||||||
|
commandHelp.g,
|
||||||
commandHelp.M,
|
commandHelp.M,
|
||||||
commandHelp.v,
|
commandHelp.v,
|
||||||
commandHelp.P,
|
commandHelp.P,
|
||||||
|
@ -6,15 +6,14 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
|
||||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
||||||
import 'package:process/process.dart';
|
|
||||||
|
|
||||||
import '../src/common.dart';
|
import '../src/common.dart';
|
||||||
import 'test_data/gen_l10n_project.dart';
|
import 'test_data/gen_l10n_project.dart';
|
||||||
import 'test_driver.dart';
|
import 'test_driver.dart';
|
||||||
import 'test_utils.dart';
|
import 'test_utils.dart';
|
||||||
|
|
||||||
|
final GenL10nProject project = GenL10nProject();
|
||||||
|
|
||||||
// Verify that the code generated by gen_l10n executes correctly.
|
// Verify that the code generated by gen_l10n executes correctly.
|
||||||
// It can fail if gen_l10n produces a lib/l10n/app_localizations.dart that:
|
// It can fail if gen_l10n produces a lib/l10n/app_localizations.dart that:
|
||||||
// - Does not analyze cleanly.
|
// - Does not analyze cleanly.
|
||||||
@ -23,50 +22,23 @@ import 'test_utils.dart';
|
|||||||
// loaded workstation, so the test could time out on a heavily loaded bot.
|
// loaded workstation, so the test could time out on a heavily loaded bot.
|
||||||
void main() {
|
void main() {
|
||||||
Directory tempDir;
|
Directory tempDir;
|
||||||
final GenL10nProject _project = GenL10nProject();
|
FlutterRunTestDriver flutter;
|
||||||
FlutterRunTestDriver _flutter;
|
|
||||||
|
|
||||||
setUp(() async {
|
setUp(() async {
|
||||||
tempDir = createResolvedTempDirectorySync('gen_l10n_test.');
|
tempDir = createResolvedTempDirectorySync('gen_l10n_test.');
|
||||||
await _project.setUpIn(tempDir);
|
|
||||||
_flutter = FlutterRunTestDriver(tempDir);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() async {
|
tearDown(() async {
|
||||||
await _flutter.stop();
|
await flutter.stop();
|
||||||
tryToDelete(tempDir);
|
tryToDelete(tempDir);
|
||||||
});
|
});
|
||||||
|
|
||||||
void runCommand(List<String> command) {
|
|
||||||
final ProcessResult result = const LocalProcessManager().runSync(
|
|
||||||
command,
|
|
||||||
workingDirectory: tempDir.path,
|
|
||||||
environment: <String, String>{ 'FLUTTER_ROOT': getFlutterRoot() },
|
|
||||||
);
|
|
||||||
if (result.exitCode != 0) {
|
|
||||||
throw Exception('FAILED [${result.exitCode}]: ${command.join(' ')}\n${result.stderr}\n${result.stdout}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUpAndRunGenL10n({List<String> args}) {
|
|
||||||
// Get the intl packages before running gen_l10n.
|
|
||||||
final String flutterBin = globals.platform.isWindows ? 'flutter.bat' : 'flutter';
|
|
||||||
final String flutterPath = globals.fs.path.join(getFlutterRoot(), 'bin', flutterBin);
|
|
||||||
runCommand(<String>[flutterPath, 'pub', 'get']);
|
|
||||||
|
|
||||||
// Generate lib/l10n/app_localizations.dart
|
|
||||||
final String genL10nPath = globals.fs.path.join(getFlutterRoot(), 'dev', 'tools', 'localization', 'bin', 'gen_l10n.dart');
|
|
||||||
final String dartBin = globals.platform.isWindows ? 'dart.exe' : 'dart';
|
|
||||||
final String dartPath = globals.fs.path.join(getFlutterRoot(), 'bin', 'cache', 'dart-sdk', 'bin', dartBin);
|
|
||||||
runCommand(<String>[dartPath, genL10nPath, args?.join(' ')]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<StringBuffer> runApp() async {
|
Future<StringBuffer> runApp() async {
|
||||||
// Run the app defined in GenL10nProject.main and wait for it to
|
// Run the app defined in GenL10nProject.main and wait for it to
|
||||||
// send '#l10n END' to its stdout.
|
// send '#l10n END' to its stdout.
|
||||||
final Completer<void> l10nEnd = Completer<void>();
|
final Completer<void> l10nEnd = Completer<void>();
|
||||||
final StringBuffer stdout = StringBuffer();
|
final StringBuffer stdout = StringBuffer();
|
||||||
final StreamSubscription<String> subscription = _flutter.stdout.listen((String line) {
|
final StreamSubscription<String> subscription = flutter.stdout.listen((String line) {
|
||||||
if (line.contains('#l10n')) {
|
if (line.contains('#l10n')) {
|
||||||
stdout.writeln(line.substring(line.indexOf('#l10n')));
|
stdout.writeln(line.substring(line.indexOf('#l10n')));
|
||||||
}
|
}
|
||||||
@ -74,7 +46,7 @@ void main() {
|
|||||||
l10nEnd.complete();
|
l10nEnd.complete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await _flutter.run();
|
await flutter.run();
|
||||||
await l10nEnd.future;
|
await l10nEnd.future;
|
||||||
await subscription.cancel();
|
await subscription.cancel();
|
||||||
return stdout;
|
return stdout;
|
||||||
@ -180,13 +152,15 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test('generated l10n classes produce expected localized strings', () async {
|
test('generated l10n classes produce expected localized strings', () async {
|
||||||
setUpAndRunGenL10n();
|
await project.setUpIn(tempDir);
|
||||||
|
flutter = FlutterRunTestDriver(tempDir);
|
||||||
final StringBuffer stdout = await runApp();
|
final StringBuffer stdout = await runApp();
|
||||||
expectOutput(stdout);
|
expectOutput(stdout);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('generated l10n classes produce expected localized strings with deferred loading', () async {
|
test('generated l10n classes produce expected localized strings with deferred loading', () async {
|
||||||
setUpAndRunGenL10n(args: <String>['--use-deferred-loading']);
|
await project.setUpIn(tempDir, useDeferredLoading: true);
|
||||||
|
flutter = FlutterRunTestDriver(tempDir);
|
||||||
final StringBuffer stdout = await runApp();
|
final StringBuffer stdout = await runApp();
|
||||||
expectOutput(stdout);
|
expectOutput(stdout);
|
||||||
});
|
});
|
||||||
|
@ -7,13 +7,16 @@ import 'dart:async';
|
|||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../test_utils.dart';
|
import '../test_utils.dart';
|
||||||
import 'project.dart';
|
import 'project.dart';
|
||||||
|
|
||||||
class GenL10nProject extends Project {
|
class GenL10nProject extends Project {
|
||||||
@override
|
@override
|
||||||
Future<void> setUpIn(Directory dir) {
|
Future<void> setUpIn(Directory dir, {
|
||||||
|
bool useDeferredLoading = 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);
|
||||||
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en_CA.arb'), appEnCa);
|
writeFile(globals.fs.path.join(dir.path, 'lib', 'l10n', 'app_en_CA.arb'), appEnCa);
|
||||||
@ -24,6 +27,7 @@ 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));
|
||||||
return super.setUpIn(dir);
|
return super.setUpIn(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,4 +565,15 @@ void main() {
|
|||||||
"helloWorld": "台灣繁體你好世界"
|
"helloWorld": "台灣繁體你好世界"
|
||||||
}
|
}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
String l10nYaml({
|
||||||
|
@required bool useDeferredLoading,
|
||||||
|
}) {
|
||||||
|
if (useDeferredLoading) {
|
||||||
|
return r'''
|
||||||
|
use-deferred-loading: false
|
||||||
|
''';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user