blob: c329e268cceccd8a10b3c6a7885df0ce9ea3b97a [file] [log] [blame]
#!/usr/bin/env dart
// 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.
// @dart = 2.9
/// Command-line tool that runs dartanalyzer on a sdk under the perspective of
/// one tool.
// TODO(sigmund): generalize this to support other tools, not just ddc.
import 'dart:io';
import 'package:args/args.dart';
import 'package:front_end/src/fasta/resolve_input_uri.dart';
import 'patch_sdk.dart' as patch;
void main(List<String> argv) {
if (Platform.isWindows) {
print('Golden file does not support Windows. Skipping.');
return;
}
var args = _parser.parse(argv);
if (args['help'] as bool) {
print('Apply patch file to the SDK and report analysis errors from the '
'resulting libraries.\n\n'
'Usage: ${Platform.script.pathSegments.last} [options...]\n\n'
'${_parser.usage}');
return;
}
var baseDir = args['out'] as String;
if (baseDir == null) {
var tmp = Directory.systemTemp.createTempSync('check_sdk-');
baseDir = tmp.path;
}
var baseUri = resolveInputUri(baseDir.endsWith('/') ? baseDir : '$baseDir/');
var sdkDir = baseUri.resolve('sdk/').toFilePath();
print('Generating a patched sdk at ${baseUri.path}');
var librariesJson = args['libraries'] != null
? resolveInputUri(args['libraries'] as String)
: Platform.script.resolve('../../../sdk/lib/libraries.json');
var target = args['target'] as String;
patch.main([
'--libraries',
librariesJson.toFilePath(),
'--target',
target,
'--out',
sdkDir,
'--merge-parts',
'--nnbd',
]);
var isWeb = false;
var isNative = false;
switch (target) {
case 'dartdevc':
case 'dart2js':
isWeb = true;
break;
case 'flutter':
case 'vm':
isNative = true;
break;
}
var core = '''
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:core';
import 'dart:developer';
import 'dart:math';
import 'dart:typed_data';
''';
var web = !isWeb
? ''
: '''
import 'dart:js';
import 'dart:js_util';
import 'dart:indexed_db';
import 'dart:html';
import 'dart:html_common';
import 'dart:svg';
import 'dart:web_audio';
import 'dart:web_gl';
import 'dart:web_sql';
''';
var native = !isNative
? '''
import 'dart:io';
import 'dart:isolate';
'''
: '''
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:mirrors';
''';
var emptyProgramUri = baseUri.resolve('empty_program.dart');
File.fromUri(emptyProgramUri).writeAsStringSync('''
$core
$web
$native
main() {}
''');
print('Running dartanalyzer');
var dart = resolveInputUri(Platform.resolvedExecutable);
var analyzerSnapshot =
dart.resolve('snapshots/dartanalyzer.dart.snapshot').toFilePath();
var result = Process.runSync(dart.toFilePath(), [
// The NNBD dart binaries / snapshots require this flag to be enabled at
// VM level.
if (analyzerSnapshot.contains('NNBD')) '--enable-experiment=non-nullable',
analyzerSnapshot,
'--dart-sdk=${sdkDir}',
'--format',
'machine',
'--sdk-warnings',
'--no-hints',
'--enable-experiment',
'non-nullable',
emptyProgramUri.toFilePath()
]);
stdout.write(result.stdout);
var errors = result.stderr as String;
// Trim temporary directory paths and sort errors.
errors = errors.replaceAll(sdkDir, '');
if (!(args['keep-lines'] as bool)) {
// Golden files change frequenty if line numbers are recorded.
// We remove them by default but provide an option to show them if
// they can be helpful.
errors = errors.replaceAll(RegExp(r'\|[0-9]*\|[0-9]*\|[0-9]*\|'), '|');
}
var errorList = errors.isEmpty ? <String>[] : errors.trim().split('\n');
var count = errorList.length;
print('$count analyzer errors.');
errorList.sort();
errors = errorList.join('\n') + '\n';
var errorFile = baseUri.resolve('errors.txt');
print('Errors emitted to ${errorFile.path}');
File.fromUri(errorFile).writeAsStringSync(errors, flush: true);
// Check against golden file.
var goldenFile =
Platform.script.resolve('${target}_nnbd_sdk_error_golden.txt');
var golden = File.fromUri(goldenFile).readAsStringSync();
if (errors != golden) {
if (args['update-golden'] as bool) {
// Update the golden file.
File.fromUri(goldenFile).writeAsStringSync(errors, flush: true);
print('Golden file updated.');
exit(0);
} else {
// Fail.
print('Golden file does not match.');
print('\nTo update the golden file, run:'
'\n ${Platform.executable} ${Platform.script} '
'${argv.join(' ')} --update-golden');
// Compare the two sorted lists to show what errors changed. Note, we
// don't use `diff` as an external tool because it is not available on
// windows bots.
var toAdd = <String>[];
var toRemove = <String>[];
var goldenList = golden.trim().split('\n');
var i = 0, j = 0;
for (; i < errorList.length && j < goldenList.length;) {
var compare = errorList[i].compareTo(goldenList[j]);
if (compare == 0) {
i++;
j++;
} else if (compare < 0) {
toAdd.add(errorList[i]);
i++;
} else {
toRemove.add(goldenList[j]);
j++;
}
}
for (; i < errorList.length; i++) {
toAdd.add(errorList[i]);
}
for (; j < goldenList.length; j++) {
toRemove.add(goldenList[j]);
}
print('\nNew errors:');
print(toAdd.join('\n'));
print('\nErrors that can be removed from the golden file:');
print(toRemove.join('\n'));
exit(1);
}
}
exit(0);
}
final _parser = ArgParser()
..addOption('libraries',
help: 'Path to the nnbd libraries.json (defaults to the one under '
'sdk/lib/libraries.json.')
..addOption('out',
help: 'Path to an output folder (defaults to a new tmp folder).')
..addOption('target',
help: 'The target tool. '
'This name matches one of the possible targets in libraries.json '
'and it is used to pick which patch files will be applied.',
allowed: ['dartdevc', 'dart2js', 'dart2js_server', 'vm', 'flutter'],
defaultsTo: 'dartdevc')
..addFlag('update-golden', help: 'Update the golden file.', defaultsTo: false)
..addFlag('keep-lines',
help: 'Show line numbers on errors.', defaultsTo: false)
..addFlag('help', abbr: 'h', help: 'Display this message.');