Skip to content

Commit 2b616ca

Browse files
authored
created a linter that runs clang-tidy (flutter#18607)
1 parent af14d2a commit 2b616ca

File tree

5 files changed

+143
-0
lines changed

5 files changed

+143
-0
lines changed

.cirrus.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,7 @@ task:
146146
build_script: |
147147
cd $ENGINE_PATH/src/flutter
148148
./ci/build.sh
149+
- name: lint_test
150+
lint_script: |
151+
cd $ENGINE_PATH/src
152+
./flutter/ci/lint.sh

.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Checks: 'google-*'

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ LIBRARY: tonic
1010
LIBRARY: txt
1111
ORIGIN: ../../../flutter/LICENSE
1212
TYPE: LicenseType.bsd
13+
FILE: ../../../flutter/.clang-tidy
1314
FILE: ../../../flutter/DEPS
1415
FILE: ../../../flutter/assets/asset_manager.cc
1516
FILE: ../../../flutter/assets/asset_manager.h

ci/lint.dart

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/// Runs clang-tidy on files with changes.
2+
///
3+
/// usage:
4+
/// dart lint.dart <path to compile_commands.json> <path to git repository> [clang-tidy checks]
5+
///
6+
/// User environment variable FLUTTER_LINT_ALL to run on all files.
7+
8+
import 'dart:io'
9+
show
10+
File,
11+
Process,
12+
ProcessResult,
13+
exit,
14+
Directory,
15+
FileSystemEntity,
16+
Platform;
17+
import 'dart:convert' show jsonDecode, utf8;
18+
import 'dart:async' show Completer;
19+
20+
class Command {
21+
String directory;
22+
String command;
23+
String file;
24+
}
25+
26+
Command parseCommand(Map<String, dynamic> map) {
27+
return Command()
28+
..directory = map['directory']
29+
..command = map['command']
30+
..file = map['file'];
31+
}
32+
33+
String calcTidyArgs(Command command) {
34+
String result = command.command;
35+
result = result.replaceAll(RegExp(r'\S*clang/bin/clang'), '');
36+
result = result.replaceAll(RegExp(r'-MF \S*'), '');
37+
return result;
38+
}
39+
40+
String calcTidyPath(Command command) {
41+
final RegExp regex = RegExp(r'\S*clang/bin/clang');
42+
return regex
43+
.stringMatch(command.command)
44+
.replaceAll('clang/bin/clang', 'clang/bin/clang-tidy');
45+
}
46+
47+
bool isNonEmptyString(String str) => str.length > 0;
48+
49+
bool containsAny(String str, List<String> queries) {
50+
for (String query in queries) {
51+
if (str.contains(query)) {
52+
return true;
53+
}
54+
}
55+
return false;
56+
}
57+
58+
/// Returns a list of all files with current changes or differ from `master`.
59+
List<String> getListOfChangedFiles(String repoPath) {
60+
final Set<String> result = Set<String>();
61+
final ProcessResult diffResult = Process.runSync(
62+
'git', ['diff', '--name-only'],
63+
workingDirectory: repoPath);
64+
final ProcessResult diffCachedResult = Process.runSync(
65+
'git', ['diff', '--cached', '--name-only'],
66+
workingDirectory: repoPath);
67+
68+
final ProcessResult mergeBaseResult = Process.runSync(
69+
'git', ['merge-base', 'master', 'HEAD'],
70+
workingDirectory: repoPath);
71+
final String mergeBase = mergeBaseResult.stdout.trim();
72+
final ProcessResult masterResult = Process.runSync(
73+
'git', ['diff', '--name-only', mergeBase],
74+
workingDirectory: repoPath);
75+
result.addAll(diffResult.stdout.split('\n').where(isNonEmptyString));
76+
result.addAll(diffCachedResult.stdout.split('\n').where(isNonEmptyString));
77+
result.addAll(masterResult.stdout.split('\n').where(isNonEmptyString));
78+
return result.toList();
79+
}
80+
81+
Future<List<String>> dirContents(String repoPath) {
82+
Directory dir = Directory(repoPath);
83+
var files = <String>[];
84+
var completer = new Completer<List<String>>();
85+
var lister = dir.list(recursive: true);
86+
lister.listen((FileSystemEntity file) => files.add(file.path),
87+
// should also register onError
88+
onDone: () => completer.complete(files));
89+
return completer.future;
90+
}
91+
92+
void main(List<String> arguments) async {
93+
final String buildCommandsPath = arguments[0];
94+
final String repoPath = arguments[1];
95+
final String checks =
96+
arguments.length >= 3 ? '--checks=${arguments[2]}' : '--config=';
97+
final List<String> changedFiles =
98+
Platform.environment['FLUTTER_LINT_ALL'] != null
99+
? await dirContents(repoPath)
100+
: getListOfChangedFiles(repoPath);
101+
102+
final List<dynamic> buildCommandMaps =
103+
jsonDecode(await new File(buildCommandsPath).readAsString());
104+
final List<Command> buildCommands =
105+
buildCommandMaps.map((x) => parseCommand(x)).toList();
106+
final Command firstCommand = buildCommands[0];
107+
final String tidyPath = calcTidyPath(firstCommand);
108+
final List<Command> changedFileBuildCommands =
109+
buildCommands.where((x) => containsAny(x.file, changedFiles)).toList();
110+
111+
int exitCode = 0;
112+
//TODO(aaclarke): Coalesce this into one call using the `-p` arguement.
113+
for (Command command in changedFileBuildCommands) {
114+
final String tidyArgs = calcTidyArgs(command);
115+
final List<String> args = [command.file, checks, '--'];
116+
args.addAll(tidyArgs.split(' '));
117+
print('# linting ${command.file}');
118+
final Process process = await Process.start(tidyPath, args,
119+
workingDirectory: command.directory, runInShell: false);
120+
process.stdout.transform(utf8.decoder).listen((data) {
121+
print(data);
122+
exitCode = 1;
123+
});
124+
await process.exitCode;
125+
}
126+
exit(exitCode);
127+
}

ci/lint.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
COMPILE_COMMANDS="out/compile_commands.json"
6+
if [ ! -f $COMPILE_COMMANDS ]; then
7+
./flutter/tools/gn
8+
fi
9+
10+
dart flutter/ci/lint.dart $COMPILE_COMMANDS flutter/

0 commit comments

Comments
 (0)