Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 7edd218

Browse files
committed
Revert null safety, add command line flags.
1 parent c75e7a3 commit 7edd218

File tree

3 files changed

+143
-71
lines changed

3 files changed

+143
-71
lines changed

ci/lint.dart renamed to ci/bin/lint.dart

Lines changed: 94 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
///
66
/// User environment variable FLUTTER_LINT_ALL to run on all files.
77
8-
// @dart = 2.9
9-
108
import 'dart:async' show Completer;
119
import 'dart:convert' show jsonDecode, utf8, LineSplitter;
1210
import 'dart:io'
@@ -22,6 +20,8 @@ import 'dart:io'
2220
stderr,
2321
stdout;
2422

23+
import 'package:args/args.dart';
24+
2525
Platform 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

4949
class 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.
197192
class 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
└──────────────────────────┘
285276
The 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`.
330321
List<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+
392386
void 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);

ci/lint.sh

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,47 @@
22

33
set -e
44

5-
COMPILE_COMMANDS="out/compile_commands.json"
6-
if [ ! -f $COMPILE_COMMANDS ]; then
7-
./flutter/tools/gn
5+
# Needed because if it is set, cd may print the path it changed to.
6+
unset CDPATH
7+
8+
# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one
9+
# link at a time, and then cds into the link destination and find out where it
10+
# ends up.
11+
#
12+
# The returned filesystem path must be a format usable by Dart's URI parser,
13+
# since the Dart command line tool treats its argument as a file URI, not a
14+
# filename. For instance, multiple consecutive slashes should be reduced to a
15+
# single slash, since double-slashes indicate a URI "authority", and these are
16+
# supposed to be filenames. There is an edge case where this will return
17+
# multiple slashes: when the input resolves to the root directory. However, if
18+
# that were the case, we wouldn't be running this shell, so we don't do anything
19+
# about it.
20+
#
21+
# The function is enclosed in a subshell to avoid changing the working directory
22+
# of the caller.
23+
function follow_links() (
24+
cd -P "$(dirname -- "$1")"
25+
file="$PWD/$(basename -- "$1")"
26+
while [[ -h "$file" ]]; do
27+
cd -P "$(dirname -- "$file")"
28+
file="$(readlink -- "$file")"
29+
cd -P "$(dirname -- "$file")"
30+
file="$PWD/$(basename -- "$file")"
31+
done
32+
echo "$file"
33+
)
34+
PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")"
35+
CI_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
36+
SRC_DIR="$(cd "$CI_DIR/../.."; pwd -P)"
37+
38+
COMPILE_COMMANDS="$SRC_DIR/out/compile_commands.json"
39+
if [ ! -f "$COMPILE_COMMANDS" ]; then
40+
(cd $SRC_DIR; ./flutter/tools/gn)
841
fi
942

10-
dart --enable-experiment=non-nullable flutter/ci/lint.dart $COMPILE_COMMANDS flutter/
43+
cd "$CI_DIR"
44+
exec dart \
45+
bin/lint.dart \
46+
--compile-commands="$COMPILE_COMMANDS" \
47+
--repo="$SRC_DIR/flutter" \
48+
"$@"

0 commit comments

Comments
 (0)