55///
66/// User environment variable FLUTTER_LINT_ALL to run on all files.
77
8- // @dart = 2.9
9-
108import 'dart:async' show Completer;
119import 'dart:convert' show jsonDecode, utf8, LineSplitter;
1210import 'dart:io'
@@ -22,6 +20,8 @@ import 'dart:io'
2220 stderr,
2321 stdout;
2422
23+ import 'package:args/args.dart' ;
24+
2525Platform defaultPlatform = Platform ();
2626
2727/// Exception class for when a process fails to run, so we can catch
@@ -30,7 +30,7 @@ class ProcessRunnerException implements Exception {
3030 ProcessRunnerException (this .message, {this .result});
3131
3232 final String message;
33- final ProcessResult ? result;
33+ final ProcessResult result;
3434
3535 int get exitCode => result? .exitCode ?? - 1 ;
3636
@@ -47,8 +47,7 @@ class ProcessRunnerException implements Exception {
4747}
4848
4949class ProcessRunnerResult {
50- const ProcessRunnerResult (
51- this .exitCode, this .stdout, this .stderr, this .output);
50+ const ProcessRunnerResult (this .exitCode, this .stdout, this .stderr, this .output);
5251 final int exitCode;
5352 final List <int > stdout;
5453 final List <int > stderr;
@@ -65,11 +64,10 @@ class ProcessRunner {
6564
6665 /// Sets the default directory used when `workingDirectory` is not specified
6766 /// to [runProcess] .
68- final Directory ? defaultWorkingDirectory;
67+ final Directory defaultWorkingDirectory;
6968
7069 /// The environment to run processes with.
71- Map <String , String > environment =
72- Map <String , String >.from (Platform .environment);
70+ Map <String , String > environment = Map <String , String >.from (Platform .environment);
7371
7472 /// Run the command and arguments in `commandLine` as a sub-process from
7573 /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses
@@ -79,15 +77,14 @@ class ProcessRunner {
7977 /// command completes with a a non-zero exit code.
8078 Future <ProcessRunnerResult > runProcess (
8179 List <String > commandLine, {
82- Directory ? workingDirectory,
80+ Directory workingDirectory,
8381 bool printOutput = true ,
8482 bool failOk = false ,
85- Stream <List <int >>? stdin,
83+ Stream <List <int >> stdin,
8684 }) async {
8785 workingDirectory ?? = defaultWorkingDirectory ?? Directory .current;
8886 if (printOutput) {
89- stderr.write (
90- 'Running "${commandLine .join (' ' )}" in ${workingDirectory .path }.\n ' );
87+ stderr.write ('Running "${commandLine .join (' ' )}" in ${workingDirectory .path }.\n ' );
9188 }
9289 final List <int > stdoutOutput = < int > [];
9390 final List <int > stderrOutput = < int > [];
@@ -96,11 +93,11 @@ class ProcessRunner {
9693 final Completer <void > stderrComplete = Completer <void >();
9794 final Completer <void > stdinComplete = Completer <void >();
9895
99- Process ? process;
96+ Process process;
10097 Future <int > allComplete () async {
10198 if (stdin != null ) {
10299 await stdinComplete.future;
103- await process? .stdin.close ();
100+ await process? .stdin? .close ();
104101 }
105102 await stderrComplete.future;
106103 await stdoutComplete.future;
@@ -117,7 +114,7 @@ class ProcessRunner {
117114 );
118115 if (stdin != null ) {
119116 stdin.listen ((List <int > data) {
120- process? .stdin.add (data);
117+ process? .stdin? .add (data);
121118 }, onDone: () async => stdinComplete.complete ());
122119 }
123120 process.stdout.listen (
@@ -141,13 +138,11 @@ class ProcessRunner {
141138 onDone: () async => stderrComplete.complete (),
142139 );
143140 } on ProcessException catch (e) {
144- final String message =
145- 'Running "${commandLine .join (' ' )}" in ${workingDirectory .path } '
141+ final String message = 'Running "${commandLine .join (' ' )}" in ${workingDirectory .path } '
146142 'failed with:\n ${e .toString ()}' ;
147143 throw ProcessRunnerException (message);
148144 } on ArgumentError catch (e) {
149- final String message =
150- 'Running "${commandLine .join (' ' )}" in ${workingDirectory .path } '
145+ final String message = 'Running "${commandLine .join (' ' )}" in ${workingDirectory .path } '
151146 'failed with:\n ${e .toString ()}' ;
152147 throw ProcessRunnerException (message);
153148 }
@@ -158,11 +153,11 @@ class ProcessRunner {
158153 'Running "${commandLine .join (' ' )}" in ${workingDirectory .path } failed' ;
159154 throw ProcessRunnerException (
160155 message,
161- result: ProcessResult (0 , exitCode, null , 'exited with code $exitCode \n ${utf8 .decode (combinedOutput )}' ),
156+ result: ProcessResult (
157+ 0 , exitCode, null , 'exited with code $exitCode \n ${utf8 .decode (combinedOutput )}' ),
162158 );
163159 }
164- return ProcessRunnerResult (
165- exitCode, stdoutOutput, stderrOutput, combinedOutput);
160+ return ProcessRunnerResult (exitCode, stdoutOutput, stderrOutput, combinedOutput);
166161 }
167162}
168163
@@ -181,7 +176,7 @@ class WorkerJob {
181176 final List <String > args;
182177
183178 /// The working directory that the command should be executed in.
184- final Directory ? workingDirectory;
179+ final Directory workingDirectory;
185180
186181 /// Whether or not this command should print it's stdout when it runs.
187182 final bool printOutput;
@@ -195,32 +190,28 @@ class WorkerJob {
195190/// A pool of worker processes that will keep [numWorkers] busy until all of the
196191/// (presumably single-threaded) processes are finished.
197192class ProcessPool {
198- ProcessPool ({int ? numWorkers})
199- : numWorkers = numWorkers ?? Platform .numberOfProcessors;
193+ ProcessPool ({int numWorkers}) : numWorkers = numWorkers ?? Platform .numberOfProcessors;
200194
201195 ProcessRunner processRunner = ProcessRunner ();
202196 int numWorkers;
203197 List <WorkerJob > pendingJobs = < WorkerJob > [];
204198 List <WorkerJob > failedJobs = < WorkerJob > [];
205- Map <WorkerJob , Future <List <int >>> inProgressJobs =
206- < WorkerJob , Future <List <int >>> {};
207- Map <WorkerJob , ProcessRunnerResult > completedJobs =
208- < WorkerJob , ProcessRunnerResult > {};
199+ Map <WorkerJob , Future <List <int >>> inProgressJobs = < WorkerJob , Future <List <int >>> {};
200+ Map <WorkerJob , ProcessRunnerResult > completedJobs = < WorkerJob , ProcessRunnerResult > {};
209201 Completer <Map <WorkerJob , ProcessRunnerResult >> completer =
210202 Completer <Map <WorkerJob , ProcessRunnerResult >>();
211203
212204 void _printReport () {
213- final int totalJobs =
214- completedJobs.length + inProgressJobs.length + pendingJobs.length;
215- final String percent = totalJobs == 0
216- ? '100'
217- : ((100 * completedJobs.length) ~ / totalJobs).toString ().padLeft (3 );
205+ final int totalJobs = completedJobs.length + inProgressJobs.length + pendingJobs.length;
206+ final String percent =
207+ totalJobs == 0 ? '100' : ((100 * completedJobs.length) ~ / totalJobs).toString ().padLeft (3 );
218208 final String completed = completedJobs.length.toString ().padLeft (3 );
219209 final String total = totalJobs.toString ().padRight (3 );
220210 final String inProgress = inProgressJobs.length.toString ().padLeft (2 );
221211 final String pending = pendingJobs.length.toString ().padLeft (3 );
222212 stdout.write (
223- 'Jobs: $percent % done, $completed /$total completed, $inProgress in progress, $pending pending. \r ' );
213+ 'Jobs: $percent % done, $completed /$total completed, $inProgress in '
214+ 'progress, $pending pending. \r ' );
224215 }
225216
226217 Future <List <int >> _scheduleJob (WorkerJob job) async {
@@ -256,8 +247,7 @@ class ProcessPool {
256247 return jobDone.future;
257248 }
258249
259- Future <Map <WorkerJob , ProcessRunnerResult >> startWorkers (
260- List <WorkerJob > jobs) async {
250+ Future <Map <WorkerJob , ProcessRunnerResult >> startWorkers (List <WorkerJob > jobs) async {
261251 assert (inProgressJobs.isEmpty);
262252 assert (failedJobs.isEmpty);
263253 assert (completedJobs.isEmpty);
@@ -279,7 +269,8 @@ class ProcessPool {
279269 }
280270}
281271
282- String _linterOutputHeader = '''┌──────────────────────────┐
272+ String _linterOutputHeader = '''
273+ ┌──────────────────────────┐
283274│ Engine Clang Tidy Linter │
284275└──────────────────────────┘
285276The following errors have been reported by the Engine Clang Tidy Linter. For
@@ -300,7 +291,7 @@ Command parseCommand(Map<String, dynamic> map) {
300291 ..file = map['file' ] as String ;
301292}
302293
303- String ? calcTidyArgs (Command command) {
294+ String calcTidyArgs (Command command) {
304295 String result = command.command;
305296 result = result.replaceAll (RegExp (r'\S*clang/bin/clang' ), '' );
306297 result = result.replaceAll (RegExp (r'-MF \S*' ), '' );
@@ -329,15 +320,13 @@ bool containsAny(String str, List<String> queries) {
329320/// Returns a list of all files with current changes or differ from `master` .
330321List <String > getListOfChangedFiles (String repoPath) {
331322 final Set <String > result = < String > {};
332- final ProcessResult diffResult = Process .runSync (
333- 'git' , < String > ['diff' , '--name-only' ],
334- workingDirectory: repoPath);
323+ final ProcessResult diffResult =
324+ Process .runSync ('git' , < String > ['diff' , '--name-only' ], workingDirectory: repoPath);
335325 final ProcessResult diffCachedResult = Process .runSync (
336326 'git' , < String > ['diff' , '--cached' , '--name-only' ],
337327 workingDirectory: repoPath);
338328
339- final ProcessResult fetchResult =
340- Process .runSync ('git' , < String > ['fetch' , 'upstream' , 'master' ]);
329+ final ProcessResult fetchResult = Process .runSync ('git' , < String > ['fetch' , 'upstream' , 'master' ]);
341330 if (fetchResult.exitCode != 0 ) {
342331 Process .runSync ('git' , < String > ['fetch' , 'origin' , 'master' ]);
343332 }
@@ -348,12 +337,9 @@ List<String> getListOfChangedFiles(String repoPath) {
348337 final ProcessResult masterResult = Process .runSync (
349338 'git' , < String > ['diff' , '--name-only' , mergeBase],
350339 workingDirectory: repoPath);
351- result.addAll (diffResult.stdout.split ('\n ' ).where (isNonEmptyString)
352- as Iterable <String >);
353- result.addAll (diffCachedResult.stdout.split ('\n ' ).where (isNonEmptyString)
354- as Iterable <String >);
355- result.addAll (masterResult.stdout.split ('\n ' ).where (isNonEmptyString)
356- as Iterable <String >);
340+ result.addAll (diffResult.stdout.split ('\n ' ).where (isNonEmptyString) as Iterable <String >);
341+ result.addAll (diffCachedResult.stdout.split ('\n ' ).where (isNonEmptyString) as Iterable <String >);
342+ result.addAll (masterResult.stdout.split ('\n ' ).where (isNonEmptyString) as Iterable <String >);
357343 return result.toList ();
358344}
359345
@@ -389,18 +375,56 @@ Future<bool> shouldIgnoreFile(String path) async {
389375 }
390376}
391377
378+ void _usage (ArgParser parser) {
379+ print ('lint.dart [--help] [--lint-all] [--verbose] [--diff-branch]' );
380+ print (parser.usage);
381+ exit (0 );
382+ }
383+
384+ bool verbose = false ;
385+
392386void main (List <String > arguments) async {
393- final String buildCommandsPath = arguments[0 ];
394- final String repoPath = arguments[1 ];
395- final String checks =
396- arguments.length >= 3 ? '--checks=${arguments [2 ]}' : '--config=' ;
387+ final ArgParser parser = ArgParser ();
388+ parser.addFlag ('help' , help: 'Print help.' );
389+ parser.addFlag ('lint-all' ,
390+ help: 'lint all of the sources, regardless of FLUTTER_NOLINT.' , defaultsTo: false );
391+ parser.addFlag ('verbose' , help: 'Print verbose output.' , defaultsTo: verbose);
392+ parser.addOption ('repo' , help: 'Use the given path as the repo path' );
393+ parser.addOption ('compile-commands' ,
394+ help: 'Use the given path as the source of compile_commands.json. This '
395+ 'file is created by running tools/gn' );
396+ parser.addOption ('checks' ,
397+ help: 'Perform the given checks on the code. Defaults to the empty '
398+ 'string, indicating all checks should be performed.' ,
399+ defaultsTo: '' );
400+ final ArgResults options = parser.parse (arguments);
401+
402+ verbose = options['verbose' ] as bool ;
403+
404+ if (options['help' ] as bool ) {
405+ _usage (parser);
406+ }
407+
408+ final String buildCommandsPath = options['compile-commands' ] as String ;
409+ final String repoPath = options['repo' ] as String ;
410+ final String checksArg = options.wasParsed ('checks' ) ? options['checks' ] as String : '' ;
411+ final String checks = checksArg.isNotEmpty ? '--checks=$checksArg ' : '--config=' ;
412+ final bool lintAll =
413+ Platform .environment['FLUTTER_LINT_ALL' ] != null || options['lint-all' ] as bool ;
397414 final List <String > changedFiles =
398- Platform .environment['FLUTTER_LINT_ALL' ] != null
399- ? await dirContents (repoPath)
400- : getListOfChangedFiles (repoPath);
415+ lintAll ? await dirContents (repoPath) : getListOfChangedFiles (repoPath);
401416
402- /// TODO(gaaclarke): Convert FLUTTER_LINT_ALL to a command-line flag and add
403- /// `--verbose` flag.
417+ if (verbose) {
418+ print ('Checking lint in repo at $repoPath .' );
419+ if (checksArg.isNotEmpty) {
420+ print ('Checking for specific checks: $checks .' );
421+ }
422+ if (lintAll) {
423+ print ('Checking all ${changedFiles .length } files the repo dir.' );
424+ } else {
425+ print ('Dectected ${changedFiles .length } files that have changed' );
426+ }
427+ }
404428
405429 final List <dynamic > buildCommandMaps =
406430 jsonDecode (await File (buildCommandsPath).readAsString ()) as List <dynamic >;
@@ -410,16 +434,20 @@ void main(List<String> arguments) async {
410434 final Command firstCommand = buildCommands[0 ];
411435 final String tidyPath = calcTidyPath (firstCommand);
412436 assert (tidyPath.isNotEmpty);
413- final List <Command > changedFileBuildCommands = buildCommands
414- .where ((Command x) => containsAny (x.file, changedFiles))
415- .toList ();
437+ final List <Command > changedFileBuildCommands =
438+ buildCommands.where ((Command x) => containsAny (x.file, changedFiles)).toList ();
439+
440+ if (verbose) {
441+ print ('Found ${changedFileBuildCommands .length } files that have build '
442+ 'commands associated with them and can be lint checked.' );
443+ }
416444
417445 print (_linterOutputHeader);
418446 int exitCode = 0 ;
419447 final List <WorkerJob > jobs = < WorkerJob > [];
420448 for (Command command in changedFileBuildCommands) {
421449 if (! (await shouldIgnoreFile (command.file))) {
422- final String ? tidyArgs = calcTidyArgs (command);
450+ final String tidyArgs = calcTidyArgs (command);
423451 final List <String > args = < String > [command.file, checks, '--' ];
424452 args.addAll (tidyArgs? .split (' ' ) ?? < String > []);
425453 print ('🔶 linting ${command .file }' );
@@ -430,15 +458,14 @@ void main(List<String> arguments) async {
430458 }
431459 }
432460 final ProcessPool pool = ProcessPool ();
433- final Map <WorkerJob , ProcessRunnerResult > results =
434- await pool.startWorkers (jobs);
461+ final Map <WorkerJob , ProcessRunnerResult > results = await pool.startWorkers (jobs);
435462 print ('\n ' );
436463 for (final WorkerJob job in results.keys) {
437- if (results[job]! .stdout.isEmpty) {
464+ if (results[job].stdout.isEmpty) {
438465 continue ;
439466 }
440467 print ('❌ Failures for ${job .name }:' );
441- print (utf8.decode (results[job]! .stdout));
468+ print (utf8.decode (results[job].stdout));
442469 exitCode = 1 ;
443470 }
444471 exit (exitCode);
0 commit comments