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
157 lines
5.2 KiB
Dart
157 lines
5.2 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:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:file/file.dart';
|
|
import 'package:file/local.dart';
|
|
import 'package:package_config/package_config.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:vm_snapshot_analysis/program_info.dart';
|
|
import 'package:vm_snapshot_analysis/v8_profile.dart';
|
|
|
|
const FileSystem fs = LocalFileSystem();
|
|
|
|
Future<void> main(List<String> args) async {
|
|
final Options options = Options.fromArgs(args);
|
|
final String json = options.snapshot.readAsStringSync();
|
|
final Snapshot snapshot = Snapshot.fromJson(jsonDecode(json) as Map<String, dynamic>);
|
|
final ProgramInfo programInfo = toProgramInfo(snapshot);
|
|
|
|
final List<String> foundForbiddenTypes = <String>[];
|
|
bool fail = false;
|
|
for (final String forbiddenType in options.forbiddenTypes) {
|
|
final int slash = forbiddenType.indexOf('/');
|
|
final int doubleColons = forbiddenType.indexOf('::');
|
|
if (slash == -1 || doubleColons < 2) {
|
|
print(
|
|
'Invalid forbidden type "$forbiddenType". The format must be <package_uri>::<type_name>, e.g. package:flutter/src/widgets/framework.dart::Widget',
|
|
);
|
|
fail = true;
|
|
continue;
|
|
}
|
|
|
|
if (!await validateType(forbiddenType, options.packageConfig)) {
|
|
foundForbiddenTypes.add('Forbidden type "$forbiddenType" does not seem to exist.');
|
|
continue;
|
|
}
|
|
|
|
final List<String> lookupPath = <String>[
|
|
forbiddenType.substring(0, slash),
|
|
forbiddenType.substring(0, doubleColons),
|
|
forbiddenType.substring(doubleColons + 2),
|
|
];
|
|
if (programInfo.lookup(lookupPath) != null) {
|
|
foundForbiddenTypes.add(forbiddenType);
|
|
}
|
|
}
|
|
if (fail) {
|
|
print('Invalid forbidden type formats. Exiting.');
|
|
exit(-1);
|
|
}
|
|
if (foundForbiddenTypes.isNotEmpty) {
|
|
print('The output contained the following forbidden types:');
|
|
print(foundForbiddenTypes.join('\n'));
|
|
exit(-1);
|
|
}
|
|
|
|
print('No forbidden types found.');
|
|
}
|
|
|
|
Future<bool> validateType(String forbiddenType, File packageConfigFile) async {
|
|
if (!forbiddenType.startsWith('package:')) {
|
|
print('Warning: Unable to validate $forbiddenType. Continuing.');
|
|
return true;
|
|
}
|
|
|
|
final Uri packageUri = Uri.parse(forbiddenType.substring(0, forbiddenType.indexOf('::')));
|
|
final String typeName = forbiddenType.substring(forbiddenType.indexOf('::') + 2);
|
|
|
|
final PackageConfig packageConfig = PackageConfig.parseString(
|
|
packageConfigFile.readAsStringSync(),
|
|
packageConfigFile.uri,
|
|
);
|
|
final Uri? packageFileUri = packageConfig.resolve(packageUri);
|
|
final File packageFile = fs.file(packageFileUri);
|
|
if (!packageFile.existsSync()) {
|
|
print('File $packageFile does not exist - forbidden type has moved or been removed.');
|
|
return false;
|
|
}
|
|
|
|
// This logic is imperfect. It will not detect mixed in types the way that
|
|
// the snapshot has them, e.g. TypeName&MixedIn&Whatever. It also assumes
|
|
// there is at least one space before and after the type name, which is not
|
|
// strictly required by the language.
|
|
final List<String> contents = packageFile.readAsStringSync().split('\n');
|
|
for (final String line in contents) {
|
|
// Ignore comments.
|
|
// This will fail for multi- and intra-line comments (i.e. /* */).
|
|
if (line.trim().startsWith('//')) {
|
|
continue;
|
|
}
|
|
if (line.contains(' $typeName ')) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class Options {
|
|
const Options({
|
|
required this.snapshot,
|
|
required this.packageConfig,
|
|
required this.forbiddenTypes,
|
|
});
|
|
|
|
factory Options.fromArgs(List<String> args) {
|
|
final ArgParser argParser = ArgParser();
|
|
argParser.addOption(
|
|
'snapshot',
|
|
help: 'The path V8 snapshot file.',
|
|
valueHelp: '/tmp/snapshot.arm64-v8a.json',
|
|
);
|
|
argParser.addOption(
|
|
'package-config',
|
|
help: 'Dart package_config.json file generated by `pub get`.',
|
|
valueHelp: path.join(r'$FLUTTER_ROOT', '.dart_tool', 'package_config.json'),
|
|
defaultsTo: path.join(fs.currentDirectory.path, '.dart_tool', 'package_config.json'),
|
|
);
|
|
argParser.addMultiOption(
|
|
'forbidden-type',
|
|
help:
|
|
'Type name(s) to forbid from release compilation, e.g. "package:flutter/src/widgets/framework.dart::Widget".',
|
|
valueHelp: '<package_uri>::<type_name>',
|
|
);
|
|
|
|
argParser.addFlag('help', help: 'Prints usage.', negatable: false);
|
|
final ArgResults argResults = argParser.parse(args);
|
|
|
|
if (argResults['help'] == true) {
|
|
print(argParser.usage);
|
|
exit(0);
|
|
}
|
|
|
|
return Options(
|
|
snapshot: _getFileArg(argResults, 'snapshot'),
|
|
packageConfig: _getFileArg(argResults, 'package-config'),
|
|
forbiddenTypes: Set<String>.from(argResults['forbidden-type'] as List<String>),
|
|
);
|
|
}
|
|
|
|
final File snapshot;
|
|
final File packageConfig;
|
|
final Set<String> forbiddenTypes;
|
|
|
|
static File _getFileArg(ArgResults argResults, String argName) {
|
|
final File result = fs.file(argResults[argName] as String);
|
|
if (!result.existsSync()) {
|
|
print('The $argName file at $result could not be found.');
|
|
exit(-1);
|
|
}
|
|
return result;
|
|
}
|
|
}
|