@@ -29,6 +29,17 @@ class _ComputeJobsResult {
29
29
final bool sawMalformed;
30
30
}
31
31
32
+ enum _SetStatus {
33
+ Intersection ,
34
+ Difference ,
35
+ }
36
+
37
+ class _SetStatusCommand {
38
+ _SetStatusCommand (this .setStatus, this .command);
39
+ final _SetStatus setStatus;
40
+ final Command command;
41
+ }
42
+
32
43
/// A class that runs clang-tidy on all or only the changed files in a git
33
44
/// repo.
34
45
class ClangTidy {
@@ -92,14 +103,14 @@ class ClangTidy {
92
103
93
104
_outSink.writeln (_linterOutputHeader);
94
105
95
- final List <io.File > changedFiles = await computeChangedFiles ();
106
+ final List <io.File > filesOfInterest = await computeFilesOfInterest ();
96
107
97
108
if (options.verbose) {
98
109
_outSink.writeln ('Checking lint in repo at ${options .repoPath .path }.' );
99
110
if (options.checksArg.isNotEmpty) {
100
111
_outSink.writeln ('Checking for specific checks: ${options .checks }.' );
101
112
}
102
- final int changedFilesCount = changedFiles .length;
113
+ final int changedFilesCount = filesOfInterest .length;
103
114
if (options.lintAll) {
104
115
_outSink.writeln ('Checking all $changedFilesCount files the repo dir.' );
105
116
} else {
@@ -109,12 +120,19 @@ class ClangTidy {
109
120
}
110
121
}
111
122
112
- final List <dynamic > buildCommandsData = jsonDecode (
123
+ final List <Object ? > buildCommandsData = jsonDecode (
113
124
options.buildCommandsPath.readAsStringSync (),
114
- ) as List <dynamic >;
115
- final List <Command > changedFileBuildCommands = await getLintCommandsForChangedFiles (
125
+ ) as List <Object ?>;
126
+ final List <List <Object ?>> shardBuildCommandsData = options
127
+ .shardCommandsPaths
128
+ .map ((io.File file) =>
129
+ jsonDecode (file.readAsStringSync ()) as List <Object ?>)
130
+ .toList ();
131
+ final List <Command > changedFileBuildCommands = await getLintCommandsForFiles (
116
132
buildCommandsData,
117
- changedFiles,
133
+ filesOfInterest,
134
+ shardBuildCommandsData,
135
+ options.shardId,
118
136
);
119
137
120
138
if (changedFileBuildCommands.isEmpty) {
@@ -153,7 +171,7 @@ class ClangTidy {
153
171
/// The files with local modifications or all the files if `lintAll` was
154
172
/// specified.
155
173
@visibleForTesting
156
- Future <List <io.File >> computeChangedFiles () async {
174
+ Future <List <io.File >> computeFilesOfInterest () async {
157
175
if (options.lintAll) {
158
176
return options.repoPath
159
177
.listSync (recursive: true )
@@ -171,23 +189,99 @@ class ClangTidy {
171
189
return repo.changedFiles;
172
190
}
173
191
174
- /// Given a build commands json file, and the files with local changes,
175
- /// compute the lint commands to run.
192
+ /// Returns f(n) = value(n * [shardCount] + [id] ).
193
+ Iterable <T > _takeShard <T >(Iterable <T > values, int id, int shardCount) sync * {
194
+ int count = 0 ;
195
+ for (final T val in values) {
196
+ if (count % shardCount == id) {
197
+ yield val;
198
+ }
199
+ count++ ;
200
+ }
201
+ }
202
+
203
+ /// This returns a `_SetStatusCommand` for each [Command] in [items] .
204
+ /// `Intersection` if the Command shows up in [items] and its filePath in all
205
+ /// [filePathSets] , otherwise `Difference` .
206
+ Iterable <_SetStatusCommand > _calcIntersection (
207
+ Iterable <Command > items, Iterable <Set <String >> filePathSets) sync * {
208
+ bool allSetsContain (Command command) {
209
+ for (final Set <String > filePathSet in filePathSets) {
210
+ if (! filePathSet.contains (command.filePath)) {
211
+ return false ;
212
+ }
213
+ }
214
+ return true ;
215
+ }
216
+ for (final Command command in items) {
217
+ if (allSetsContain (command)) {
218
+ yield _SetStatusCommand (_SetStatus .Intersection , command);
219
+ } else {
220
+ yield _SetStatusCommand (_SetStatus .Difference , command);
221
+ }
222
+ }
223
+ }
224
+
225
+ /// Given a build commands json file's contents in [buildCommandsData] , and
226
+ /// the [files] with local changes, compute the lint commands to run. If
227
+ /// build commands are supplied in [sharedBuildCommandsData] the intersection
228
+ /// of those build commands will be calculated and distributed across
229
+ /// instances via the [shardId] .
176
230
@visibleForTesting
177
- Future <List <Command >> getLintCommandsForChangedFiles (
178
- List <dynamic > buildCommandsData,
179
- List <io.File > changedFiles,
180
- ) async {
181
- final List <Command > buildCommands = < Command > [];
182
- for (final dynamic data in buildCommandsData) {
183
- final Command command = Command .fromMap (data as Map <String , dynamic >);
184
- final LintAction lintAction = await command.lintAction;
185
- // Short-circuit the expensive containsAny call for the many third_party files.
186
- if (lintAction != LintAction .skipThirdParty && command.containsAny (changedFiles)) {
187
- buildCommands.add (command);
231
+ Future <List <Command >> getLintCommandsForFiles (
232
+ List <Object ?> buildCommandsData,
233
+ List <io.File > files,
234
+ List <List <Object ?>> sharedBuildCommandsData,
235
+ int ? shardId,
236
+ ) {
237
+ final List <Command > totalCommands = < Command > [];
238
+ if (sharedBuildCommandsData.isNotEmpty) {
239
+ final List <Command > buildCommands = < Command > [
240
+ for (Object ? data in buildCommandsData)
241
+ Command .fromMap ((data as Map <String , Object ?>? )! )
242
+ ];
243
+ final List <Set <String >> shardFilePaths = < Set <String >> [
244
+ for (List <Object ?> list in sharedBuildCommandsData)
245
+ < String > {
246
+ for (Object ? data in list)
247
+ Command .fromMap ((data as Map <String , Object ?>? )! ).filePath
248
+ }
249
+ ];
250
+ final Iterable <_SetStatusCommand > intersectionResults =
251
+ _calcIntersection (buildCommands, shardFilePaths);
252
+ for (final _SetStatusCommand result in intersectionResults) {
253
+ if (result.setStatus == _SetStatus .Difference ) {
254
+ totalCommands.add (result.command);
255
+ }
188
256
}
257
+ final List <Command > intersection = < Command > [
258
+ for (_SetStatusCommand result in intersectionResults)
259
+ if (result.setStatus == _SetStatus .Intersection ) result.command
260
+ ];
261
+ // Make sure to sort results so the sharding scheme is guaranteed to work
262
+ // since we are not sure if there is a defined order in the json file.
263
+ intersection
264
+ .sort ((Command x, Command y) => x.filePath.compareTo (y.filePath));
265
+ totalCommands.addAll (
266
+ _takeShard (intersection, shardId! , 1 + sharedBuildCommandsData.length));
267
+ } else {
268
+ totalCommands.addAll (< Command > [
269
+ for (Object ? data in buildCommandsData)
270
+ Command .fromMap ((data as Map <String , Object ?>? )! )
271
+ ]);
189
272
}
190
- return buildCommands;
273
+ return () async {
274
+ final List <Command > result = < Command > [];
275
+ for (final Command command in totalCommands) {
276
+ final LintAction lintAction = await command.lintAction;
277
+ // Short-circuit the expensive containsAny call for the many third_party files.
278
+ if (lintAction != LintAction .skipThirdParty &&
279
+ command.containsAny (files)) {
280
+ result.add (command);
281
+ }
282
+ }
283
+ return result;
284
+ }();
191
285
}
192
286
193
287
Future <_ComputeJobsResult > _computeJobs (
0 commit comments