Unverified Commit bcd60fa8 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Make sample analyzer more friendly for running locally. (#27648)

Now the sample analyzer can be run locally with:

dart dev/bots/analyze-sample-code.dart --temp=/tmp/samples

And it leaves /tmp/samples around so you can take a look at the code that was generated to see if it's the artificial environment that the samples are evaluated in that is the culprit for a failed analysis.

Before, you had to specify the whole path to the dart executable, and had to modify the code to keep the tempdir around.
parent 53632b8a
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
import 'dart:io'; import 'dart:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
// To run this: bin/cache/dart-sdk/bin/dart dev/bots/analyze-sample-code.dart // To run this: bin/cache/dart-sdk/bin/dart dev/bots/analyze-sample-code.dart
...@@ -54,14 +55,44 @@ final String _defaultFlutterPackage = path.join(_flutterRoot, 'packages', 'flutt ...@@ -54,14 +55,44 @@ final String _defaultFlutterPackage = path.join(_flutterRoot, 'packages', 'flutt
final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'); final String _flutter = path.join(_flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
void main(List<String> arguments) { void main(List<String> arguments) {
final ArgParser argParser = ArgParser();
argParser.addOption(
'temp',
defaultsTo: null,
help: 'A location where temporary files may be written. Defaults to a '
'directory in the system temp folder. If specified, will not be '
'automatically removed at the end of execution.',
);
argParser.addFlag(
'help',
defaultsTo: false,
negatable: false,
help: 'Print help for this command.',
);
final ArgResults parsedArguments = argParser.parse(arguments);
if (parsedArguments['help']) {
print(argParser.usage);
exit(0);
}
Directory flutterPackage; Directory flutterPackage;
if (arguments.length == 1) { if (parsedArguments.rest.length == 1) {
// Used for testing. // Used for testing.
flutterPackage = Directory(arguments.single); flutterPackage = Directory(parsedArguments.rest.single);
} else { } else {
flutterPackage = Directory(_defaultFlutterPackage); flutterPackage = Directory(_defaultFlutterPackage);
} }
exitCode = SampleChecker(flutterPackage).checkSamples();
Directory tempDirectory;
if (parsedArguments.wasParsed('temp')) {
tempDirectory = Directory(parsedArguments['temp']);
if (!tempDirectory.existsSync()) {
tempDirectory.createSync(recursive: true);
}
}
exitCode = SampleChecker(flutterPackage, tempDirectory: tempDirectory).checkSamples();
} }
/// Checks samples and code snippets for analysis errors. /// Checks samples and code snippets for analysis errors.
...@@ -79,8 +110,10 @@ void main(List<String> arguments) { ...@@ -79,8 +110,10 @@ void main(List<String> arguments) {
/// don't necessarily match. It does, however, print the source of the /// don't necessarily match. It does, however, print the source of the
/// problematic line. /// problematic line.
class SampleChecker { class SampleChecker {
SampleChecker(this._flutterPackage) { SampleChecker(this._flutterPackage, {Directory tempDirectory})
_tempDir = Directory.systemTemp.createTempSync('flutter_analyze_sample_code.'); : _tempDirectory = tempDirectory,
_keepTmp = tempDirectory != null {
_tempDirectory ??= Directory.systemTemp.createTempSync('flutter_analyze_sample_code.');
} }
/// The prefix of each comment line /// The prefix of each comment line
...@@ -104,9 +137,14 @@ class SampleChecker { ...@@ -104,9 +137,14 @@ class SampleChecker {
/// A RegExp that matches a Dart constructor. /// A RegExp that matches a Dart constructor.
static final RegExp _constructorRegExp = RegExp(r'[A-Z][a-zA-Z0-9<>.]*\('); static final RegExp _constructorRegExp = RegExp(r'[A-Z][a-zA-Z0-9<>.]*\(');
/// Whether or not to keep the temp directory around after running.
///
/// Defaults to false.
final bool _keepTmp;
/// The temporary directory where all output is written. This will be deleted /// The temporary directory where all output is written. This will be deleted
/// automatically if there are no errors. /// automatically if there are no errors.
Directory _tempDir; Directory _tempDirectory;
/// The package directory for the flutter package within the flutter root dir. /// The package directory for the flutter package within the flutter root dir.
final Directory _flutterPackage; final Directory _flutterPackage;
...@@ -128,10 +166,17 @@ class SampleChecker { ...@@ -128,10 +166,17 @@ class SampleChecker {
return path.canonicalize(path.join(platformScriptPath, '..', 'snippets', 'lib', 'main.dart')); return path.canonicalize(path.join(platformScriptPath, '..', 'snippets', 'lib', 'main.dart'));
} }
/// Finds the location of the Dart executable.
String get _dartExecutable {
final File dartExecutable = File(Platform.resolvedExecutable);
return dartExecutable.absolute.path;
}
static List<File> _listDartFiles(Directory directory, {bool recursive = false}) { static List<File> _listDartFiles(Directory directory, {bool recursive = false}) {
return directory.listSync(recursive: recursive, followLinks: false) return directory.listSync(recursive: recursive, followLinks: false)
.whereType<File>() .whereType<File>()
.where((File file) => path.extension(file.path) == '.dart').toList(); .where((File file) => path.extension(file.path) == '.dart')
.toList();
} }
/// Computes the headers needed for each sample file. /// Computes the headers needed for each sample file.
...@@ -165,7 +210,7 @@ class SampleChecker { ...@@ -165,7 +210,7 @@ class SampleChecker {
final Map<String, Section> sections = <String, Section>{}; final Map<String, Section> sections = <String, Section>{};
final Map<String, Snippet> snippets = <String, Snippet>{}; final Map<String, Snippet> snippets = <String, Snippet>{};
_extractSamples(sections, snippets); _extractSamples(sections, snippets);
errors = _analyze(_tempDir, sections, snippets); errors = _analyze(_tempDirectory, sections, snippets);
} finally { } finally {
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
for (String filePath in errors.keys) { for (String filePath in errors.keys) {
...@@ -173,10 +218,14 @@ class SampleChecker { ...@@ -173,10 +218,14 @@ class SampleChecker {
} }
stderr.writeln('\nFound ${errors.length} sample code errors.'); stderr.writeln('\nFound ${errors.length} sample code errors.');
} }
try { if (!_keepTmp) {
_tempDir.deleteSync(recursive: true); try {
} on FileSystemException catch (e) { _tempDirectory.deleteSync(recursive: true);
stderr.writeln('Failed to delete ${_tempDir.path}: $e'); } on FileSystemException catch (e) {
stderr.writeln('Failed to delete ${_tempDirectory.path}: $e');
}
} else {
print('Leaving temporary directory ${_tempDirectory.path} around for your perusal.');
} }
// If we made a snapshot, remove it (so as not to clutter up the tree). // If we made a snapshot, remove it (so as not to clutter up the tree).
if (_snippetsSnapshotPath != null) { if (_snippetsSnapshotPath != null) {
...@@ -192,8 +241,7 @@ class SampleChecker { ...@@ -192,8 +241,7 @@ class SampleChecker {
/// Creates a name for the snippets tool to use for the snippet ID from a /// Creates a name for the snippets tool to use for the snippet ID from a
/// filename and starting line number. /// filename and starting line number.
String _createNameFromSource(String prefix, String filename, int start) { String _createNameFromSource(String prefix, String filename, int start) {
String snippetId = path.relative(filename, from: _flutterPackage.path); String snippetId = path.split(filename).join('.');
snippetId = path.split(snippetId).join('.');
snippetId = path.basenameWithoutExtension(snippetId); snippetId = path.basenameWithoutExtension(snippetId);
snippetId = '$prefix.$snippetId.$start'; snippetId = '$prefix.$snippetId.$start';
return snippetId; return snippetId;
...@@ -206,7 +254,7 @@ class SampleChecker { ...@@ -206,7 +254,7 @@ class SampleChecker {
if (_snippetsSnapshotPath == null) { if (_snippetsSnapshotPath == null) {
_snippetsSnapshotPath = '$_snippetsExecutable.snapshot'; _snippetsSnapshotPath = '$_snippetsExecutable.snapshot';
return Process.runSync( return Process.runSync(
path.canonicalize(Platform.executable), _dartExecutable,
<String>[ <String>[
'--snapshot=$_snippetsSnapshotPath', '--snapshot=$_snippetsSnapshotPath',
'--snapshot-kind=app-jit', '--snapshot-kind=app-jit',
...@@ -216,23 +264,23 @@ class SampleChecker { ...@@ -216,23 +264,23 @@ class SampleChecker {
); );
} else { } else {
return Process.runSync( return Process.runSync(
path.canonicalize(Platform.executable), _dartExecutable,
<String>[path.canonicalize(_snippetsSnapshotPath)]..addAll(args), <String>[path.canonicalize(_snippetsSnapshotPath)]..addAll(args),
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
); );
} }
} }
/// Writes out the given [snippet] to an output file in the [_tempDir] and /// Writes out the given [snippet] to an output file in the [_tempDirectory] and
/// returns the output file. /// returns the output file.
File _writeSnippet(Snippet snippet) { File _writeSnippet(Snippet snippet) {
// Generate the snippet. // Generate the snippet.
final String snippetId = _createNameFromSource('snippet', snippet.start.filename, snippet.start.line); final String snippetId = _createNameFromSource('snippet', snippet.start.filename, snippet.start.line);
final String inputName = '$snippetId.input'; final String inputName = '$snippetId.input';
// Now we have a filename like 'lib.src.material.foo_widget.123.dart' for each snippet. // Now we have a filename like 'lib.src.material.foo_widget.123.dart' for each snippet.
final File inputFile = File(path.join(_tempDir.path, inputName))..createSync(recursive: true); final File inputFile = File(path.join(_tempDirectory.path, inputName))..createSync(recursive: true);
inputFile.writeAsStringSync(snippet.input.join('\n')); inputFile.writeAsStringSync(snippet.input.join('\n'));
final File outputFile = File(path.join(_tempDir.path, '$snippetId.dart')); final File outputFile = File(path.join(_tempDirectory.path, '$snippetId.dart'));
final List<String> args = <String>[ final List<String> args = <String>[
'--output=${outputFile.absolute.path}', '--output=${outputFile.absolute.path}',
'--input=${inputFile.absolute.path}', '--input=${inputFile.absolute.path}',
...@@ -449,7 +497,7 @@ linter: ...@@ -449,7 +497,7 @@ linter:
/// Writes out a sample section to the disk and returns the file. /// Writes out a sample section to the disk and returns the file.
File _writeSection(Section section) { File _writeSection(Section section) {
final String sectionId = _createNameFromSource('sample', section.start.filename, section.start.line); final String sectionId = _createNameFromSource('sample', section.start.filename, section.start.line);
final File outputFile = File(path.join(_tempDir.path, '$sectionId.dart'))..createSync(recursive: true); final File outputFile = File(path.join(_tempDirectory.path, '$sectionId.dart'))..createSync(recursive: true);
final List<Line> mainContents = headers.toList(); final List<Line> mainContents = headers.toList();
mainContents.add(const Line('')); mainContents.add(const Line(''));
mainContents.add(Line('// From: ${section.start.filename}:${section.start.line}')); mainContents.add(Line('// From: ${section.start.filename}:${section.start.line}'));
...@@ -520,7 +568,7 @@ linter: ...@@ -520,7 +568,7 @@ linter:
final Match parts = errorPattern.matchAsPrefix(error); final Match parts = errorPattern.matchAsPrefix(error);
if (parts != null) { if (parts != null) {
final String message = parts[2]; final String message = parts[2];
final File file = File(path.join(_tempDir.path, parts[3])); final File file = File(path.join(_tempDirectory.path, parts[3]));
final List<String> fileContents = file.readAsLinesSync(); final List<String> fileContents = file.readAsLinesSync();
final bool isSnippet = path.basename(file.path).startsWith('snippet.'); final bool isSnippet = path.basename(file.path).startsWith('snippet.');
final bool isSample = path.basename(file.path).startsWith('sample.'); final bool isSample = path.basename(file.path).startsWith('sample.');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment