mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland after https://github.com/flutter/flutter/issues/169844. Switch Flutter to use pub workspaces as a preparation to unpin selected packages. Assumptions: 1. No packages in this repository are published to pub.dev --> We can use `any` dependencies in most local pubspecs, as the global constraint defines the version. An exception are the packages used outside of this repo with an `sdk` dependency, namely `flutter_localizations`, `flutter_test`, and `flutter`. 2. The "universes" `{flutter_tools}` and `{flutter, flutter_localizations, flutter_goldens}` can use different packages versions, as they are not resolved together. --> We do not need to upgrade them in sync, we can first do one "universe", then the other. Based on these assumptions, we use https://github.com/mosuem/pubspec_merger.dart to merge all packages in the `flutter` universe into a top-level pub workspace. The `flutter` and `flutter_tools` workspaces being separate also ensures that changes to `flutter` will not inadvertently break `flutter_tools`, with not-so-nice consequences for our users which would be unable to run `flutter upgrade`. There is a third "top-level" pubspec besides `./pubspec.yaml` and `packages/flutter_tools/pubspec.yaml`, namely `packages/flutter_tools/.../widget_preview_scaffold/pubspec.yaml`. This is an artifact due to it living under `flutter_tools`, so it can't be part of the `./pubspec.yaml` workspace. Moving it would be a larger change, and out of the scope of this PR. This required a rewrite of the update-packages tool, but the main functionality stays the same, as well as the argument names, to ensure a seamless transition. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
267 lines
9.1 KiB
Dart
267 lines
9.1 KiB
Dart
// 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 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import '../localizations_utils.dart';
|
|
|
|
const String _kCommandName = 'gen_date_localizations.dart';
|
|
|
|
// Used to let _jsonToMap know what locale it's date symbols converting for.
|
|
// Date symbols for the Kannada locale ('kn') are handled specially because
|
|
// some of the strings contain characters that can crash Emacs on Linux.
|
|
// See packages/flutter_localizations/lib/src/l10n/README for more information.
|
|
String? currentLocale;
|
|
|
|
/// This program extracts localized date symbols and patterns from the intl
|
|
/// package for the subset of locales supported by the flutter_localizations
|
|
/// package.
|
|
///
|
|
/// The extracted data is written into:
|
|
/// packages/flutter_localizations/lib/src/l10n/generated_date_localizations.dart
|
|
///
|
|
/// ## Usage
|
|
///
|
|
/// Run this program from the root of the git repository.
|
|
///
|
|
/// The following outputs the generated Dart code to the console as a dry run:
|
|
///
|
|
/// ```bash
|
|
/// dart dev/tools/localization/bin/gen_date_localizations.dart
|
|
/// ```
|
|
///
|
|
/// If the data looks good, use the `--overwrite` option to overwrite the
|
|
/// lib/src/l10n/date_localizations.dart file:
|
|
///
|
|
/// ```bash
|
|
/// dart dev/tools/localization/bin/gen_date_localizations.dart --overwrite
|
|
/// ```
|
|
Future<void> main(List<String> rawArgs) async {
|
|
checkCwdIsRepoRoot(_kCommandName);
|
|
|
|
final bool writeToFile = parseArgs(rawArgs).writeToFile;
|
|
|
|
final File packageConfigFile = File(path.join('.dart_tool', 'package_config.json'));
|
|
final bool packageConfigExists = packageConfigFile.existsSync();
|
|
|
|
if (!packageConfigExists) {
|
|
exitWithError(
|
|
'File not found: ${packageConfigFile.path}. $_kCommandName must be run '
|
|
'after a successful "flutter update-packages".',
|
|
);
|
|
}
|
|
|
|
final List<Object?> packages =
|
|
(json.decode(packageConfigFile.readAsStringSync()) as Map<String, Object?>)['packages']!
|
|
as List<Object?>;
|
|
|
|
String? pathToIntl;
|
|
for (final Object? package in packages) {
|
|
final Map<String, Object?> packageAsMap = package! as Map<String, Object?>;
|
|
if (packageAsMap['name'] == 'intl') {
|
|
pathToIntl = Uri.parse(packageAsMap['rootUri']! as String).toFilePath();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pathToIntl == null) {
|
|
exitWithError(
|
|
'Could not find "intl" package. $_kCommandName must be run '
|
|
'after a successful "flutter update-packages".',
|
|
);
|
|
}
|
|
|
|
final Directory dateSymbolsDirectory = Directory(
|
|
path.join(pathToIntl!, 'lib', 'src', 'data', 'dates', 'symbols'),
|
|
);
|
|
final Map<String, File> symbolFiles = _listIntlData(dateSymbolsDirectory);
|
|
final Directory datePatternsDirectory = Directory(
|
|
path.join(pathToIntl, 'lib', 'src', 'data', 'dates', 'patterns'),
|
|
);
|
|
final Map<String, File> patternFiles = _listIntlData(datePatternsDirectory);
|
|
final StringBuffer buffer = StringBuffer();
|
|
final Set<String> supportedLocales = _supportedLocales();
|
|
|
|
buffer.writeln('''
|
|
// 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.
|
|
|
|
// This file has been automatically generated. Please do not edit it manually.
|
|
// To regenerate run (omit --overwrite to print to console instead of the file):
|
|
// dart --enable-asserts dev/tools/localization/bin/gen_date_localizations.dart --overwrite
|
|
|
|
import 'package:intl/date_symbols.dart' as intl;
|
|
|
|
''');
|
|
buffer.writeln('''
|
|
/// The subset of date symbols supported by the intl package which are also
|
|
/// supported by flutter_localizations.''');
|
|
buffer.writeln('final Map<String, intl.DateSymbols> dateSymbols = <String, intl.DateSymbols> {');
|
|
symbolFiles.forEach((String locale, File data) {
|
|
currentLocale = locale;
|
|
if (supportedLocales.contains(locale)) {
|
|
final Map<String, Object?> objData =
|
|
json.decode(data.readAsStringSync()) as Map<String, Object?>;
|
|
buffer.writeln("'$locale': intl.DateSymbols(");
|
|
objData.forEach((String key, Object? value) {
|
|
if (value == null) {
|
|
return;
|
|
}
|
|
buffer.writeln(_jsonToConstructorEntry(key, value));
|
|
});
|
|
buffer.writeln('),');
|
|
}
|
|
});
|
|
currentLocale = null;
|
|
buffer.writeln('};');
|
|
|
|
// Code that uses datePatterns expects it to contain values of type
|
|
// Map<String, String> not Map<String, dynamic>.
|
|
buffer.writeln('''
|
|
/// The subset of date patterns supported by the intl package which are also
|
|
/// supported by flutter_localizations.''');
|
|
buffer.writeln(
|
|
'const Map<String, Map<String, String>> datePatterns = <String, Map<String, String>> {',
|
|
);
|
|
patternFiles.forEach((String locale, File data) {
|
|
if (supportedLocales.contains(locale)) {
|
|
final Map<String, dynamic> patterns =
|
|
json.decode(data.readAsStringSync()) as Map<String, dynamic>;
|
|
buffer.writeln("'$locale': <String, String>{");
|
|
patterns.forEach((String key, dynamic value) {
|
|
assert(value is String);
|
|
buffer.writeln(_jsonToMapEntry(key, value));
|
|
});
|
|
buffer.writeln('},');
|
|
}
|
|
});
|
|
buffer.writeln('};');
|
|
|
|
if (writeToFile) {
|
|
final File dateLocalizationsFile = File(
|
|
path.join(
|
|
'packages',
|
|
'flutter_localizations',
|
|
'lib',
|
|
'src',
|
|
'l10n',
|
|
'generated_date_localizations.dart',
|
|
),
|
|
);
|
|
dateLocalizationsFile.writeAsStringSync(buffer.toString());
|
|
final String extension = Platform.isWindows ? '.exe' : '';
|
|
final ProcessResult result = Process.runSync(
|
|
path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart$extension'),
|
|
<String>['format', dateLocalizationsFile.path],
|
|
);
|
|
if (result.exitCode != 0) {
|
|
print(result.exitCode);
|
|
print(result.stdout);
|
|
print(result.stderr);
|
|
}
|
|
} else {
|
|
print(buffer);
|
|
}
|
|
}
|
|
|
|
String _jsonToConstructorEntry(String key, dynamic value) {
|
|
return '$key: ${_jsonToObject(value)},';
|
|
}
|
|
|
|
String _jsonToMapEntry(String key, dynamic value) {
|
|
return "'$key': ${_jsonToMap(value)},";
|
|
}
|
|
|
|
String _jsonToObject(dynamic json) {
|
|
switch (json) {
|
|
case null || num() || bool():
|
|
return '$json';
|
|
case String():
|
|
return generateEncodedString(currentLocale, json);
|
|
case Iterable<Object?>():
|
|
final Type type = json.first.runtimeType;
|
|
final StringBuffer buffer = StringBuffer('const <$type>[');
|
|
for (final dynamic value in json) {
|
|
buffer.writeln('${_jsonToMap(value)},');
|
|
}
|
|
buffer.write(']');
|
|
return buffer.toString();
|
|
case Map<String, dynamic>():
|
|
final StringBuffer buffer = StringBuffer('<String, Object>{');
|
|
json.forEach((String key, dynamic value) {
|
|
buffer.writeln(_jsonToMapEntry(key, value));
|
|
});
|
|
buffer.write('}');
|
|
return buffer.toString();
|
|
}
|
|
|
|
throw 'Unsupported JSON type ${json.runtimeType} of value $json.';
|
|
}
|
|
|
|
String _jsonToMap(dynamic json) {
|
|
switch (json) {
|
|
case null || num() || bool():
|
|
return '$json';
|
|
case String():
|
|
return generateEncodedString(currentLocale, json);
|
|
case Iterable<dynamic>():
|
|
final StringBuffer buffer = StringBuffer('<String>[');
|
|
for (final dynamic value in json) {
|
|
buffer.writeln('${_jsonToMap(value)},');
|
|
}
|
|
buffer.write(']');
|
|
return buffer.toString();
|
|
case Map<String, dynamic>():
|
|
final StringBuffer buffer = StringBuffer('<String, Object>{');
|
|
json.forEach((String key, dynamic value) {
|
|
buffer.writeln(_jsonToMapEntry(key, value));
|
|
});
|
|
buffer.write('}');
|
|
return buffer.toString();
|
|
}
|
|
|
|
throw 'Unsupported JSON type ${json.runtimeType} of value $json.';
|
|
}
|
|
|
|
Set<String> _supportedLocales() {
|
|
// Assumes that en_US is a supported locale by default. Without this, usage
|
|
// of the intl package APIs before Flutter populates its set of supported i18n
|
|
// date patterns and symbols may cause problems.
|
|
//
|
|
// For more context, see https://github.com/flutter/flutter/issues/67644.
|
|
final Set<String> supportedLocales = <String>{'en_US'};
|
|
final RegExp filenameRE = RegExp(r'(?:material|cupertino)_(\w+)\.arb$');
|
|
final Directory supportedLocalesDirectory = Directory(
|
|
path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'),
|
|
);
|
|
for (final FileSystemEntity entity in supportedLocalesDirectory.listSync()) {
|
|
final String filePath = entity.path;
|
|
if (FileSystemEntity.isFileSync(filePath) && filenameRE.hasMatch(filePath)) {
|
|
supportedLocales.add(filenameRE.firstMatch(filePath)![1]!);
|
|
}
|
|
}
|
|
|
|
return supportedLocales;
|
|
}
|
|
|
|
Map<String, File> _listIntlData(Directory directory) {
|
|
final Map<String, File> localeFiles = <String, File>{};
|
|
final Iterable<File> files = directory.listSync().whereType<File>().where(
|
|
(File file) => file.path.endsWith('.json'),
|
|
);
|
|
for (final File file in files) {
|
|
final String locale = path.basenameWithoutExtension(file.path);
|
|
localeFiles[locale] = file;
|
|
}
|
|
|
|
final List<String> locales = localeFiles.keys.toList(growable: false);
|
|
locales.sort();
|
|
return Map<String, File>.fromIterable(locales, value: (dynamic locale) => localeFiles[locale]!);
|
|
}
|