Remove unused parts of migration tooling

Change-Id: Ib18224f42880c47be63e6f6814db99ac05eced48
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278373
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry 2023-01-05 15:59:07 +00:00 committed by Commit Queue
parent e925066175
commit 69f4dc0dcd
7 changed files with 0 additions and 1082 deletions

View file

@ -1,196 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// TODO(jcollins-g): Merge this with similar utilities in dartdoc
// and extract into a separate package, generate testing and mirrors, and
// reimport that into the SDK, before cut and paste gets out of hand.
/// This is a modified version of dartdoc's
/// SubprocessLauncher from test/src/utils.dart, for use with the
/// nnbd_migration script.
library;
import 'dart:convert';
import 'dart:io';
import 'multi_future_tracker.dart';
/// Maximum number of parallel subprocesses. Use this to to avoid overloading
/// your CPU.
final MultiFutureTracker maxParallel =
MultiFutureTracker(Platform.numberOfProcessors);
/// Route all executions of pub through this [MultiFutureTracker] to avoid
/// parallel executions of the pub command.
final MultiFutureTracker pubTracker = MultiFutureTracker(1);
final RegExp quotables = RegExp(r'[ "\r\n\$]');
/// SubprocessLauncher manages one or more launched, non-interactive
/// subprocesses. It handles I/O streams, parses JSON output if
/// available, and logs debugging information so the user can see exactly
/// what was run.
class SubprocessLauncher {
final String context;
final Map<String, String> environmentDefaults;
SubprocessLauncher(this.context, [Map<String, String>? environment])
: environmentDefaults = environment ?? <String, String>{};
/// Wraps [runStreamedImmediate] as a closure around
/// [maxParallel.addFutureFromClosure].
///
/// This essentially implements a 'make -j N' limit for all subcommands.
Future<Iterable<Map>?> runStreamed(String executable, List<String> arguments,
// TODO(jcollins-g): Fix primitive obsession: consolidate parameters into
// another object.
{String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
void Function(String)? perLine,
int retries = 0,
String? instance,
bool allowNonzeroExit = false}) async {
// TODO(jcollins-g): The closure wrapping we've done has made it impossible
// to catch exceptions when calling runStreamed. Fix this.
return maxParallel.runFutureFromClosure(() async {
return retryClosure(
() async => await runStreamedImmediate(executable, arguments,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
perLine: perLine,
instance: instance,
allowNonzeroExit: allowNonzeroExit),
retries: retries);
});
}
/// A wrapper around start/await process.exitCode that will display the
/// output of the executable continuously and fail on non-zero exit codes.
/// It will also parse any valid JSON objects (one per line) it encounters
/// on stdout/stderr, and return them. Returns null if no JSON objects
/// were encountered, or if DRY_RUN is set to 1 in the execution environment.
///
/// Makes running programs in grinder similar to set -ex for bash, even on
/// Windows (though some of the bashisms will no longer make sense).
/// TODO(jcollins-g): refactor to return a stream of stderr/stdout lines
/// and their associated JSON objects.
Future<Iterable<Map>?> runStreamedImmediate(
String executable, List<String> arguments,
{String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
void Function(String)? perLine,
// A tag added to [context] to construct the line prefix.
// Use this to indicate the process or processes with the tag
// share something in common, like a hostname, a package, or a
// multi-step procedure.
String? instance,
bool allowNonzeroExit = false}) async {
String prefix = context.isNotEmpty
? '$context${instance != null ? "-$instance" : ""}: '
: '';
environment ??= {};
environment.addAll(environmentDefaults);
List<Map>? jsonObjects;
/// Parses json objects generated by the subprocess. If a json object
/// contains the key 'message' or the keys 'data' and 'text', return that
/// value as a collection of lines suitable for printing.
Iterable<String> jsonCallback(String line) {
if (perLine != null) perLine(line);
Map? result;
try {
result = json.decoder.convert(line) as Map?;
} on FormatException {
// ignore
}
if (result != null) {
jsonObjects ??= [];
jsonObjects!.add(result);
if (result.containsKey('message')) {
line = result['message'] as String;
} else if (result.containsKey('data') &&
result['data'] is Map &&
(result['data'] as Map).containsKey('key')) {
line = result['data']['text'] as String;
}
}
return line.split('\n');
}
stderr.write('$prefix+ ');
if (workingDirectory != null) stderr.write('(cd "$workingDirectory" && ');
stderr.write(environment.keys.map((String key) {
if (environment![key]!.contains(quotables)) {
return "$key='${environment[key]}'";
} else {
return '$key=${environment[key]}';
}
}).join(' '));
stderr.write(' ');
stderr.write(executable);
if (arguments.isNotEmpty) {
for (String arg in arguments) {
if (arg.contains(quotables)) {
stderr.write(" '$arg'");
} else {
stderr.write(' $arg');
}
}
}
if (workingDirectory != null) stderr.write(')');
stderr.write('\n');
if (Platform.environment.containsKey('DRY_RUN')) return null;
String realExecutable = executable;
final List<String> realArguments = [];
if (Platform.isLinux) {
// Use GNU coreutils to force line buffering. This makes sure that
// subprocesses that die due to fatal signals do not chop off the
// last few lines of their output.
//
// Dart does not actually do this (seems to flush manually) unless
// the VM crashes.
realExecutable = 'stdbuf';
realArguments.addAll(['-o', 'L', '-e', 'L']);
realArguments.add(executable);
}
realArguments.addAll(arguments);
Process process = await Process.start(realExecutable, realArguments,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment);
Future<void> stdoutFuture = _printStream(process.stdout, stdout,
prefix: prefix, filter: jsonCallback);
Future<void> stderrFuture = _printStream(process.stderr, stderr,
prefix: prefix, filter: jsonCallback);
await Future.wait([stderrFuture, stdoutFuture, process.exitCode]);
int exitCode = await process.exitCode;
if (exitCode != 0 && !allowNonzeroExit) {
throw ProcessException(executable, arguments,
'SubprocessLauncher got non-zero exitCode: $exitCode', exitCode);
}
return jsonObjects;
}
/// From flutter:dev/tools/dartdoc.dart, modified.
static Future<void> _printStream(Stream<List<int>> stream, Stdout output,
{String prefix = '', Iterable<String> Function(String line)? filter}) {
filter ??= (line) => [line];
return stream
.transform(utf8.decoder)
.transform(const LineSplitter())
.expand(filter)
.listen((String line) {
output.write('$prefix$line'.trim());
output.write('\n');
}).asFuture();
}
}

View file

@ -1,131 +0,0 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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:io';
import 'package:nnbd_migration/src/utilities/subprocess_launcher.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(SubprocessLauncherTest);
});
}
@reflectiveTest
class SubprocessLauncherTest {
Function(String)? outputCallback;
List<String>? output;
late Directory tempDir;
void setUp() async {
output = [];
outputCallback = output!.add;
tempDir = await Directory.systemTemp.createTemp();
}
void tearDown() async {
await tempDir.delete(recursive: true);
}
Future<void> test_subprocessPassesArgs() async {
SubprocessLauncher launcher =
SubprocessLauncher('test_subprocessPassesArgs');
File testScript =
File(path.join(tempDir.path, 'subprocess_test_script.dart'));
await testScript.writeAsString(r'''
import 'dart:io';
main(List<String> args) {
print('args: $args');
}''');
await launcher.runStreamedImmediate(
Platform.resolvedExecutable, [testScript.path, 'testArgument'],
perLine: outputCallback);
expect(output, anyElement(contains('args: [testArgument]')));
}
Future<void> test_subprocessPassesEnvironment() async {
SubprocessLauncher launcher =
SubprocessLauncher('test_subprocessPassesEnvironment');
File testScript =
File(path.join(tempDir.path, 'subprocess_test_script.dart'));
await testScript.writeAsString(r'''
import 'dart:io';
main(List<String> args) {
print('environment: ${Platform.environment}');
}''');
await launcher.runStreamedImmediate(
Platform.resolvedExecutable, [testScript.path],
environment: {'__SUBPROCESS_PASSES_ENVIRONMENT_TEST': 'yes'},
perLine: outputCallback);
expect(
output,
anyElement(contains(RegExp(
'^environment: .*__SUBPROCESS_PASSES_ENVIRONMENT_TEST: yes'))));
}
Future<void> test_subprocessRunsValidExecutable() async {
SubprocessLauncher launcher =
SubprocessLauncher('test_subprocessRunsValidExecutable');
await launcher.runStreamedImmediate(
Platform.resolvedExecutable, ['--version'],
perLine: outputCallback);
expect(output, anyElement(contains('Dart')));
}
Future<void> test_subprocessSetsWorkingDirectory() async {
SubprocessLauncher launcher =
SubprocessLauncher('test_subprocessSetsWorkingDirectory');
File testScript =
File(path.join(tempDir.path, 'subprocess_test_script.dart'));
await testScript.writeAsString(r'''
import 'dart:io';
main() {
print('working directory: ${Directory.current.path}');
}''');
await launcher.runStreamedImmediate(
Platform.resolvedExecutable, [testScript.path],
workingDirectory: tempDir.path, perLine: outputCallback);
expect(
output,
anyElement(contains(
'working directory: ${tempDir.resolveSymbolicLinksSync()}')));
}
Future<void> test_subprocessThrowsOnNonzeroExitCode() async {
SubprocessLauncher launcher =
SubprocessLauncher('test_subprocessThrowsOnNonzeroExitCode');
File testScript =
File(path.join(tempDir.path, 'subprocess_test_script.dart'));
await testScript.writeAsString(r'''
import 'dart:io';
main() {
exit(1);
}''');
await expectLater(
() async => await launcher.runStreamedImmediate(
Platform.resolvedExecutable, [testScript.path],
perLine: outputCallback),
throwsA(TypeMatcher<ProcessException>()));
}
Future<void> test_subprocessWorksViaParallelSubprocessLimit() async {
SubprocessLauncher launcher =
SubprocessLauncher('test_subprocessWorksViaParallelSubprocessLimit');
await launcher.runStreamed(Platform.resolvedExecutable, ['--version'],
perLine: outputCallback);
expect(output, anyElement(contains('Dart')));
}
}

View file

@ -8,7 +8,6 @@ import 'multi_future_tracker_test.dart' as multi_future_tracker_test;
import 'scoped_set_test.dart' as scoped_set_test;
import 'source_edit_diff_formatter_test.dart'
as source_edit_diff_formatter_test;
import 'subprocess_launcher_test.dart' as subprocess_launcher_test;
import 'where_not_null_transformer_test.dart'
as where_not_null_transformer_test;
import 'where_or_null_transformer_test.dart' as where_or_null_transformer_test;
@ -18,7 +17,6 @@ main() {
multi_future_tracker_test.main();
scoped_set_test.main();
source_edit_diff_formatter_test.main();
subprocess_launcher_test.main();
where_not_null_transformer_test.main();
where_or_null_transformer_test.main();
});

View file

@ -1,220 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Abstractions for the different sources of truth for different packages.
library;
import 'dart:io';
import 'package:nnbd_migration/src/utilities/subprocess_launcher.dart';
import 'package:path/path.dart' as path;
final String defaultPlaygroundPath =
Platform.environment['TRIAL_MIGRATION_PLAYGROUND'] ??
resolveTildePath('~/.nnbd_trial_migration');
/// The pub cache inherited by this process.
final String defaultPubCache =
Platform.environment['PUB_CACHE'] ?? resolveTildePath('~/.pub-cache');
/// Returns the path to the SDK repository this script is a part of.
final String thisSdkRepo = () {
var maybeSdkRepoDir = Platform.script.toFilePath();
while (maybeSdkRepoDir != path.dirname(maybeSdkRepoDir)) {
maybeSdkRepoDir = path.dirname(maybeSdkRepoDir);
if (File(path.join(maybeSdkRepoDir, 'README.dart-sdk')).existsSync()) {
return maybeSdkRepoDir;
}
}
throw UnsupportedError(
'Script ${Platform.script} using this library must be within the SDK repository');
}();
Uri get thisSdkUri => Uri.file(thisSdkRepo);
/// Return a resolved path including the home directory in place of tilde
/// references.
String resolveTildePath(String originalPath) {
if (!originalPath.startsWith('~/')) {
return originalPath;
}
String homeDir;
if (Platform.isWindows) {
homeDir = path.absolute(Platform.environment['USERPROFILE']!);
} else {
homeDir = path.absolute(Platform.environment['HOME']!);
}
return path.join(homeDir, originalPath.substring(2));
}
/// Abstraction for a package fetched via Git.
class GitPackage extends Package {
static final RegExp _pathAndPeriodSplitter = RegExp('[\\/.]');
final String _clonePath;
final bool? _keepUpdated;
final String label;
final Playground _playground;
SubprocessLauncher? _launcher;
String? _packagePath;
GitPackage._(this._clonePath, this._playground, this._keepUpdated,
{String? name, this.label = 'master'})
: super(name ?? _buildName(_clonePath));
SubprocessLauncher get launcher =>
_launcher ??= SubprocessLauncher('$name-$label', _playground.env);
@override
List<String?> get migrationPaths => [_packagePath];
String get packagePath =>
// TODO(jcollins-g): allow packages from subdirectories of clones
_packagePath ??= path.join(_playground.playgroundPath, '$name-$label');
@override
String toString() {
return '$_clonePath ($label)${_keepUpdated! ? ' [synced]' : ''}';
}
/// Initialize the package with a shallow clone. Run only once per
/// [GitPackage] instance.
Future<void> _init() async {
if (_keepUpdated! || !await Directory(packagePath).exists()) {
// Clone or update.
if (await Directory(packagePath).exists()) {
await launcher.runStreamed('git', ['pull'],
workingDirectory: packagePath);
} else {
await launcher.runStreamed('git',
['clone', '--branch=$label', '--depth=1', _clonePath, packagePath],
workingDirectory: _playground.playgroundPath);
await launcher.runStreamed('git', ['checkout', '-b', '_test_migration'],
workingDirectory: packagePath);
await launcher.runStreamed(
'git', ['branch', '--set-upstream-to', 'origin/$label'],
workingDirectory: packagePath);
// TODO(jcollins-g): allow for migrating dependencies?
}
await pubTracker.runFutureFromClosure(() =>
launcher.runStreamed('pub', ['get'], workingDirectory: packagePath));
}
}
static Future<GitPackage> gitPackageFactory(
String clonePath, Playground playground, bool? keepUpdated,
{String? name, String label = 'master'}) async {
GitPackage gitPackage = GitPackage._(clonePath, playground, keepUpdated,
name: name, label: label);
await gitPackage._init();
return gitPackage;
}
/// Calculate the "humanish" name of the clone (see `git help clone`).
static String _buildName(String clonePath) {
if (Directory(clonePath).existsSync()) {
// assume we are cloning locally
return path.basename(clonePath);
}
List<String> pathParts = clonePath.split(_pathAndPeriodSplitter);
int indexOfName = pathParts.lastIndexOf('git') - 1;
if (indexOfName < 0) {
throw ArgumentError(
'GitPackage can not figure out the name for $clonePath, pass it in manually?');
}
return pathParts[indexOfName];
}
}
/// Abstraction for an unmanaged package.
class ManualPackage extends Package {
final String _packagePath;
ManualPackage(this._packagePath) : super(_packagePath);
@override
List<String> get migrationPaths => [_packagePath];
}
/// Base class for pub, github, SDK, or possibly other package sources.
abstract class Package {
final String name;
Package(this.name);
/// Returns the set of directories for this package.
List<String?> get migrationPaths;
@override
String toString() => name;
}
class Playground {
final String playgroundPath;
/// If [clean] is true, this will delete the playground. Otherwise,
/// if it exists it will assume it is properly constructed.
Playground(this.playgroundPath, bool clean) {
Directory playground = Directory(playgroundPath);
if (clean) {
if (playground.existsSync()) {
playground.deleteSync(recursive: true);
}
}
if (!playground.existsSync()) playground.createSync();
}
/// Build an environment for subprocesses.
Map<String, String> get env => {'PUB_CACHE': pubCachePath};
String get pubCachePath => path.join(playgroundPath, '.pub-cache');
}
/// Abstraction for a package fetched via pub.
class PubPackage extends Package {
PubPackage(super.name, [String? version]) {
throw UnimplementedError();
}
@override
// TODO: implement packagePath
List<String> get migrationPaths => throw UnimplementedError();
}
/// Abstraction for compiled Dart SDKs (not this repository).
class Sdk {
/// The root of the compiled SDK.
late final String sdkPath;
Sdk(String sdkPath) {
this.sdkPath = path.canonicalize(sdkPath);
}
}
/// Abstraction for a package located within pkg or third_party/pkg.
class SdkPackage extends Package {
/// Where to find packages. Constructor searches in-order.
static final List<String> _searchPaths = [
'pkg',
path.join('third_party', 'pkg'),
];
late final String _packagePath;
SdkPackage(String name) : super(name) {
for (String potentialPath
in _searchPaths.map((p) => path.join(thisSdkRepo, p, name))) {
if (Directory(potentialPath).existsSync()) {
_packagePath = potentialPath;
}
}
}
@override
List<String> get migrationPaths => [_packagePath];
@override
String toString() => path.relative(_packagePath, from: thisSdkRepo);
}

View file

@ -1,359 +0,0 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// This is a hacked-together client of the NNBD migration API, intended for
// early testing of the migration process. It runs a small hardcoded set of
// packages through the migration engine and outputs statistics about the
// result of migration, as well as categories (and counts) of exceptions that
// occurred.
import 'dart:io';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/diagnostic/diagnostic.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:args/args.dart';
import 'package:nnbd_migration/nnbd_migration.dart';
import 'package:path/path.dart' as path;
import 'src/package.dart';
void main(List<String> args) async {
ArgResults parsedArgs = parseArguments(args)!;
Sdk sdk = Sdk(parsedArgs['sdk'] as String);
warnOnNoAssertions();
Playground playground =
Playground(defaultPlaygroundPath, parsedArgs['clean'] as bool);
List<Package> packages = [
for (String package in parsedArgs['packages'] as Iterable<String>)
SdkPackage(package),
for (String package in parsedArgs['manual_packages'] as Iterable<String>)
ManualPackage(package),
];
var packageNames = parsedArgs['git_packages'] as Iterable<String>;
await Future.wait(packageNames.map((n) async => packages.add(
await GitPackage.gitPackageFactory(
n, playground, parsedArgs['update'] as bool?))));
String? categoryOfInterest =
parsedArgs.rest.isEmpty ? null : parsedArgs.rest.single;
var listener = _Listener(categoryOfInterest,
printExceptionNodeOnly: parsedArgs['exception_node_only'] as bool?);
assert(listener.numExceptions == 0);
var overallStartTime = DateTime.now();
for (var package in packages) {
print('Migrating $package');
var startTime = DateTime.now();
listener.currentPackage = package.name;
var contextCollection = AnalysisContextCollectionImpl(
includedPaths: package.migrationPaths as List<String>,
sdkPath: sdk.sdkPath);
var files = <String>{};
var previousExceptionCount = listener.numExceptions;
for (var context in contextCollection.contexts) {
var localFiles =
context.contextRoot.analyzedFiles().where((s) => s.endsWith('.dart'));
files.addAll(localFiles);
var session = context.currentSession;
var migration = NullabilityMigration(listener, permissive: true);
for (var file in localFiles) {
var resolvedUnit =
await session.getResolvedUnit(file) as ResolvedUnitResult;
if (!resolvedUnit.errors.any((e) => e.severity == Severity.error)) {
migration.prepareInput(resolvedUnit);
} else {
print(' Skipping $file; it has errors.');
}
}
for (var file in localFiles) {
var resolvedUnit =
await session.getResolvedUnit(file) as ResolvedUnitResult;
if (!resolvedUnit.errors.any((e) => e.severity == Severity.error)) {
migration.processInput(resolvedUnit);
}
}
for (var file in localFiles) {
var resolvedUnit =
await session.getResolvedUnit(file) as ResolvedUnitResult;
if (!resolvedUnit.errors.any((e) => e.severity == Severity.error)) {
migration.finalizeInput(resolvedUnit);
}
}
migration.finish();
}
var endTime = DateTime.now();
print(' Migrated $package in ${endTime.difference(startTime).inSeconds} '
'seconds');
print(' ${files.length} files found');
var exceptionCount = listener.numExceptions - previousExceptionCount;
print(' $exceptionCount exceptions in this package');
}
var overallDuration = DateTime.now().difference(overallStartTime);
print('${packages.length} packages migrated in ${overallDuration.inSeconds} '
'seconds');
print('${listener.numTypesMadeNullable} types made nullable');
print('${listener.numNullChecksAdded} null checks added');
print('${listener.numVariablesMarkedLate} variables marked late');
print('${listener.numInsertedCasts} casts inserted');
print('${listener.numInsertedParenthesis} parenthesis groupings inserted');
print('${listener.numMetaImportsAdded} meta imports added');
print('${listener.numRequiredAnnotationsAdded} required annotations added');
print('${listener.numDeadCodeSegmentsFound} dead code segments found');
print('and ${listener.numOtherEdits} other edits not categorized');
print('${listener.numExceptions} exceptions in '
'${listener.groupedExceptions.length} categories');
var sortedExceptions = [
for (var entry in listener.groupedExceptions.entries)
ExceptionCategory(entry.key, entry.value)
]..sort((category1, category2) => category2.count.compareTo(category1.count));
var exceptionalPackages =
sortedExceptions.expand((category) => category.packageNames).toSet();
print('Packages with exceptions: $exceptionalPackages');
print('Exception categories:');
for (var category in sortedExceptions) {
print(' $category');
}
if (categoryOfInterest == null) {
print('\n(Note: to show stack traces & nodes for a particular failure,'
' rerun with a search string as an argument.)');
}
}
ArgResults? parseArguments(List<String> args) {
ArgParser argParser = ArgParser();
ArgResults? parsedArgs;
argParser.addFlag('clean',
abbr: 'c',
defaultsTo: false,
help: 'Recursively delete the playground directory before beginning.');
argParser.addFlag('help', abbr: 'h', help: 'Display options');
argParser.addFlag('exception_node_only',
defaultsTo: false,
negatable: true,
help: 'Only print the exception node instead of the full stack trace.');
argParser.addFlag('update',
abbr: 'u',
defaultsTo: false,
negatable: true,
help: 'Auto-update fetched packages in the playground.');
argParser.addOption('sdk',
abbr: 's',
defaultsTo: path.dirname(path.dirname(Platform.resolvedExecutable)),
help: 'Select the root of the SDK to analyze against for this run '
'(compiled with --nnbd). For example: ../../xcodebuild/DebugX64NNBD/dart-sdk');
argParser.addMultiOption(
'git_packages',
abbr: 'g',
defaultsTo: [],
help: 'Shallow-clone the given git repositories into a playground area,'
' run pub get on them, and migrate them.',
);
argParser.addMultiOption(
'manual_packages',
abbr: 'm',
defaultsTo: [],
help: 'Run migration against packages in these directories. Does not '
'run pub get, any git commands, or any other preparation.',
);
argParser.addMultiOption(
'packages',
abbr: 'p',
defaultsTo: [],
help: 'The list of SDK packages to run the migration against.',
);
try {
parsedArgs = argParser.parse(args);
} on ArgParserException {
stderr.writeln(argParser.usage);
exit(1);
}
if (parsedArgs['help'] as bool) {
print(argParser.usage);
exit(0);
}
if (parsedArgs.rest.length > 1) {
throw 'invalid args. Specify *one* argument to get exceptions of interest.';
}
return parsedArgs;
}
void printWarning(String warn) {
stderr.writeln('''
!!!
!!! Warning! $warn
!!!
''');
}
void warnOnNoAssertions() {
try {
assert(false);
} catch (e) {
return;
}
printWarning("You didn't --enable-asserts!");
}
class ExceptionCategory {
final String topOfStack;
final List<MapEntry<String?, int>> exceptionCountPerPackage;
ExceptionCategory(this.topOfStack, Map<String?, int> exceptions)
: exceptionCountPerPackage = exceptions.entries.toList()
..sort((e1, e2) => e2.value.compareTo(e1.value));
int get count => exceptionCountPerPackage.length;
List<String?> get packageNames =>
[for (var entry in exceptionCountPerPackage) entry.key];
Iterable<String> get packageNamesAndCounts =>
exceptionCountPerPackage.map((entry) => '${entry.key} x${entry.value}');
String toString() => '$topOfStack (${packageNamesAndCounts.join(', ')})';
}
class _Listener implements NullabilityMigrationListener {
/// Set this to `true` to cause just the exception nodes to be printed when
/// `_Listener.categoryOfInterest` is non-null. Set this to `false` to cause
/// the full stack trace to be printed.
final bool? printExceptionNodeOnly;
/// Set this to a non-null value to cause any exception to be printed in full
/// if its category contains the string.
final String? categoryOfInterest;
/// Exception mapped to a map of packages & exception counts.
final groupedExceptions = <String, Map<String?, int>>{};
int numExceptions = 0;
int numTypesMadeNullable = 0;
int numVariablesMarkedLate = 0;
int numInsertedCasts = 0;
int numInsertedParenthesis = 0;
int numNullChecksAdded = 0;
int numMetaImportsAdded = 0;
int numRequiredAnnotationsAdded = 0;
int numDeadCodeSegmentsFound = 0;
int numOtherEdits = 0;
String? currentPackage;
_Listener(this.categoryOfInterest, {this.printExceptionNodeOnly = false});
@override
void addEdit(Source source, SourceEdit edit) {
if (edit.replacement == '') {
return;
}
if (edit.replacement.contains('!')) {
++numNullChecksAdded;
}
if (edit.replacement.contains('(')) {
++numInsertedParenthesis;
}
if (edit.replacement == '?' && edit.length == 0) {
++numTypesMadeNullable;
} else if (edit.replacement == "import 'package:meta/meta.dart';\n" &&
edit.length == 0) {
++numMetaImportsAdded;
} else if (edit.replacement == 'required ' && edit.length == 0) {
++numRequiredAnnotationsAdded;
} else if (edit.replacement == 'late ' && edit.length == 0) {
++numVariablesMarkedLate;
} else if (edit.replacement.startsWith(' as ') && edit.length == 0) {
++numInsertedCasts;
} else if ((edit.replacement == '/* ' ||
edit.replacement == ' /*' ||
edit.replacement == '; /*') &&
edit.length == 0) {
++numDeadCodeSegmentsFound;
} else if ((edit.replacement == '*/ ' ||
edit.replacement == ' */' ||
edit.replacement == ')' ||
edit.replacement == '!' ||
edit.replacement == '(') &&
edit.length == 0) {
} else {
numOtherEdits++;
}
}
@override
void addSuggestion(String descriptions, Location location) {}
@override
void reportException(
Source? source, AstNode? node, Object exception, StackTrace stackTrace) {
var category = _classifyStackTrace(stackTrace.toString().split('\n'));
String detail = '''
In file $source
While processing $node
Exception $exception
$stackTrace
''';
if (categoryOfInterest != null && category.contains(categoryOfInterest!)) {
if (printExceptionNodeOnly!) {
print('$node');
} else {
print(detail);
}
}
(groupedExceptions[category] ??= <String?, int>{})
.update(currentPackage, (value) => ++value, ifAbsent: () => 1);
++numExceptions;
}
String _classifyStackTrace(List<String> stackTrace) {
for (var entry in stackTrace) {
if (entry.contains('EdgeBuilder._unimplemented')) continue;
if (entry.contains('_AssertionError._doThrowNew')) continue;
if (entry.contains('_AssertionError._throwNew')) continue;
if (entry.contains('NodeBuilder._unimplemented')) continue;
if (entry.contains('Object.noSuchMethod')) continue;
if (entry.contains('List.[] (dart:core-patch/growable_array.dart')) {
continue;
}
return entry;
}
return '???';
}
}

View file

@ -1,26 +0,0 @@
#!/usr/bin/env bash
#
# Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
#
TRIAL_MIGRATION=`dirname "$0"`/trial_migration.dart
# Priority One, Group One, as defined at go/dart-null-safety-migration-order.
p1g1 () {
for n in charcode collection logging path pedantic term_glyph typed_data ; do
echo "-g https://dart.googlesource.com/${n}.git"
done
# Some packages do not have googlesource mirrors; use GitHub directly.
echo "-g https://github.com/google/vector_math.dart.git"
# SDK-only packages.
echo "-p meta"
}
# The current "official" set of parameters for the trial_migration script.
set -x
dart --enable-asserts ${TRIAL_MIGRATION} \
$(p1g1) \
"$@"

View file

@ -1,148 +0,0 @@
#!/usr/bin/env bash
#
# Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
#
TRIAL_MIGRATION=`dirname "$0"`/trial_migration.dart
# Priority Two, Group One, as defined at go/dart-null-safety-migration-order.
p2g1 () {
echo "-g https://github.com/google/ansicolor-dart.git"
echo "-g https://dart.googlesource.com/args.git"
echo "-p dart_internal"
echo "-g https://dart.googlesource.com/fixnum.git"
echo "-g https://github.com/google/inject.dart.git"
echo "-p js"
echo "-g https://github.com/a14n/dart-js-wrapping.git"
echo "-g https://dart.googlesource.com/mime.git"
echo "-g https://github.com/xxgreg/mustache.git"
echo "-g https://github.com/leonsenft/path_to_regexp.git"
echo "-g https://github.com/petitparser/dart-petitparser.git"
echo "-g https://github.com/google/platform.dart.git"
echo "-g https://github.com/dart-lang/stream_transform.git"
echo "-g https://github.com/dart-lang/sync_http.git"
echo "-g https://github.com/srawlins/timezone.git"
}
# Priority Two, Group Two, as defined at go/dart-null-safety-migration-order.
p2g2 () {
echo "-g https://github.com/google/ansicolor-dart.git"
echo "-g https://dart.googlesource.com/cli_util.git"
echo "-g https://github.com/dart-lang/clock.git"
echo "-g https://github.com/kevmoo/completion.dart.git"
echo "-g https://dart.googlesource.com/convert.git"
echo "-g https://github.com/a14n/dart-google-maps.git"
echo "-g https://github.com/dart-lang/http_server.git"
echo "-g https://dart.googlesource.com/intl.git"
echo "-p kernel"
echo "-g https://dart.googlesource.com/package_config.git"
# TODO(srawlins): Add protobuf, from monorepo
# https://github.com/dart-lang/protobuf.
echo "-g https://dart.googlesource.com/pub_semver.git"
echo "-g https://github.com/google/quiver-dart.git"
}
# Priority Two, Group Three, as defined at go/dart-null-safety-migration-order.
p2g3 () {
# TODO(srawlins): Add android_intent, from monorepo
# https://github.com/flutter/plugins/tree/master/packages/android_intent.
# SDK-only packages.
echo "-g https://dart.googlesource.com/bazel_worker.git"
echo "-g https://github.com/google/built_collection.dart.git"
# TODO(srawlins): Add charts_common, from monorepo
# https://github.com/google/charts/tree/master/charts_common.
echo "-g https://github.com/jathak/cli_repl.git"
echo "-g https://dart.googlesource.com/crypto.git"
echo "-g https://dart.googlesource.com/csslib.git"
echo "-g https://github.com/google/file.dart.git"
# TODO(srawlins): Add front_end, which currently crashes.
echo "-g https://github.com/reyerstudio/google-maps-markerclusterer.git"
# TODO(srawlins): Add google_sign_in, from monorepo
# https://github.com/flutter/plugins/tree/master/packages/google_sign_in.
echo "-g https://dart.googlesource.com/http_multi_server.git"
echo "-g https://github.com/dart-lang/observable.git"
# TODO(srawlins): Add package_info, from monorepo
# https://github.com/flutter/plugins/tree/master/packages/package_info.
echo "-g https://dart.googlesource.com/pool.git"
# TODO(srawlins): Add protoc_plugin, from monorepo
# https://github.com/dart-lang/protobuf.
echo "-g https://github.com/google/quiver-log.git"
# TODO(srawlins): Add shared_preferences, from monorepo
# https://github.com/flutter/plugins/tree/master/packages/shared_preferences.
echo "-g https://dart.googlesource.com/source_maps.git"
echo "-g https://dart.googlesource.com/string_scanner.git"
echo "-g https://github.com/renggli/dart-xml.git"
}
# Priority Two, Group Four, as defined at go/dart-null-safety-migration-order.
p2g4 () {
echo "-g https://github.com/brendan-duncan/archive.git"
# TODO(srawlins): Add built_value, from monorepo
# https://github.com/google/built_value.dart
# Not including charted; concern is internal copy; not old published copy.
echo "-g https://dart.googlesource.com/glob.git"
echo "-g https://dart.googlesource.com/html.git"
echo "-g https://dart.googlesource.com/http_parser.git"
echo "-g https://dart.googlesource.com/json_rpc_2.git"
# Not including observe; concern is internal copy; not old published copy.
echo "-g https://github.com/google/process.dart.git"
# Not including scissors; concern is internal copy; not old published copy.
}
# Priority Two, Group Five, as defined at go/dart-null-safety-migration-order.
p2g5 () {
echo "-p analyzer"
# Not including angular_forms; concern is internal copy; not old published copy.
# Not including angular_router; concern is internal copy; not old published copy.
# Not including angular_test; concern is internal copy; not old published copy.
echo "-g https://github.com/dart-lang/code_builder.git"
echo "-g https://dart.googlesource.com/http.git"
echo "-g https://github.com/brendan-duncan/image.git"
echo "-g https://dart.googlesource.com/shelf.git"
}
# Priority Two, Group Six, as defined at go/dart-null-safety-migration-order.
p2g6 () {
echo "-p analyzer_plugin"
# TODO(srawlins): Add build, from monorepo
# https://github.com/dart-lang/build/tree/master/build.
echo "-g https://github.com/dart-lang/coverage.git"
echo "-g https://dart.googlesource.com/dart_style.git"
# TODO(srawlins): Add flutter_test.
echo "-g https://github.com/dart-lang/googleapis_auth.git"
echo "-g https://github.com/dart-lang/intl_translation.git"
echo "-g https://dart.googlesource.com/mockito.git"
echo "-g https://dart.googlesource.com/package_resolver.git"
# Not including pageloader ("2"); concern is internal copy; not old published copy.
echo "-g https://dart.googlesource.com/shelf_static.git"
echo "-g https://dart.googlesource.com/shelf_web_socket.git"
}
# Priority Two, Group Seven, as defined at go/dart-null-safety-migration-order.
p2g7 () {
echo "-g https://github.com/dart-lang/grpc-dart.git"
echo "-g https://github.com/google/pageloader.git" # This is pageloader3.
echo "-g https://github.com/sass/dart-sass.git"
echo "-g https://dart.googlesource.com/shelf_packages_handler.git"
# TODO(srawlins): Add source_gen.
echo "-g https://dart.googlesource.com/source_map_stack_trace.git"
}
# Priority Two, Group Eight, as defined at go/dart-null-safety-migration-order.
p2g8 () {
# Not including angular_compiler; concern is internal copy; not old published copy.
# TODO(srawlins): Add built_value_generator, from monorepo
# https://github.com/google/built_value.dart.
# TODO(srawlins): Add flutter_tools, from monorepo
# https://github.com/flutter/flutter.
# TODO(srawlins): Locate and add rpc_client.
echo ""
}
# The current "official" set of parameters for the trial_migration script.
set -x
dart --enable-asserts ${TRIAL_MIGRATION} \
$(p2g1) $(p2g2) $(p2g3) $(p2g4) $(p2g5) $(p2g6) $(p2g7) $(p2g8) \
"$@"