@@ -29,6 +29,17 @@ class _ComputeJobsResult {
2929 final bool sawMalformed;
3030}
3131
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+
3243/// A class that runs clang-tidy on all or only the changed files in a git
3344/// repo.
3445class ClangTidy {
@@ -92,14 +103,14 @@ class ClangTidy {
92103
93104 _outSink.writeln (_linterOutputHeader);
94105
95- final List <io.File > changedFiles = await computeChangedFiles ();
106+ final List <io.File > filesOfInterest = await computeFilesOfInterest ();
96107
97108 if (options.verbose) {
98109 _outSink.writeln ('Checking lint in repo at ${options .repoPath .path }.' );
99110 if (options.checksArg.isNotEmpty) {
100111 _outSink.writeln ('Checking for specific checks: ${options .checks }.' );
101112 }
102- final int changedFilesCount = changedFiles .length;
113+ final int changedFilesCount = filesOfInterest .length;
103114 if (options.lintAll) {
104115 _outSink.writeln ('Checking all $changedFilesCount files the repo dir.' );
105116 } else {
@@ -112,9 +123,16 @@ class ClangTidy {
112123 final List <dynamic > buildCommandsData = jsonDecode (
113124 options.buildCommandsPath.readAsStringSync (),
114125 ) as List <dynamic >;
115- final List <Command > changedFileBuildCommands = await getLintCommandsForChangedFiles (
126+ final List <List <dynamic >> shardBuildCommandsData = options
127+ .shardCommandsPaths
128+ .map ((io.File file) =>
129+ jsonDecode (file.readAsStringSync ()) as List <dynamic >)
130+ .toList ();
131+ final List <Command > changedFileBuildCommands = await getLintCommandsForFiles (
116132 buildCommandsData,
117- changedFiles,
133+ filesOfInterest,
134+ shardBuildCommandsData,
135+ options.shardId,
118136 );
119137
120138 if (changedFileBuildCommands.isEmpty) {
@@ -153,7 +171,7 @@ class ClangTidy {
153171 /// The files with local modifications or all the files if `lintAll` was
154172 /// specified.
155173 @visibleForTesting
156- Future <List <io.File >> computeChangedFiles () async {
174+ Future <List <io.File >> computeFilesOfInterest () async {
157175 if (options.lintAll) {
158176 return options.repoPath
159177 .listSync (recursive: true )
@@ -171,23 +189,82 @@ class ClangTidy {
171189 return repo.changedFiles;
172190 }
173191
192+ Iterable <T > _takeShard <T >(Iterable <T > values, int id, int shardCount) sync * {
193+ int count = 0 ;
194+ for (final T val in values) {
195+ if (count % shardCount == id) {
196+ yield val;
197+ }
198+ count++ ;
199+ }
200+ }
201+
202+ Iterable <_SetStatusCommand > _calcIntersection (Iterable <Command > items, Iterable <List <Command >> sets) sync * {
203+ bool allSetsContain (Command command) {
204+ for (final List <Command > set in sets) {
205+ final Iterable <String > filePaths = set .map ((Command e) => e.filePath);
206+ if (! filePaths.contains (command.filePath)) {
207+ return false ;
208+ }
209+ }
210+ return true ;
211+ }
212+ for (final Command command in items) {
213+ if (allSetsContain (command)) {
214+ yield _SetStatusCommand (_SetStatus .Intersection , command);
215+ } else {
216+ yield _SetStatusCommand (_SetStatus .Difference , command);
217+ }
218+ }
219+ }
220+
174221 /// Given a build commands json file, and the files with local changes,
175222 /// compute the lint commands to run.
176223 @visibleForTesting
177- Future <List <Command >> getLintCommandsForChangedFiles (
224+ Future <List <Command >> getLintCommandsForFiles (
178225 List <dynamic > buildCommandsData,
179- List <io.File > changedFiles,
226+ List <io.File > files,
227+ List <List <dynamic >> sharedBuildCommandsData,
228+ int ? shardId,
180229 ) 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);
230+ final List <Command > totalCommands = < Command > [];
231+ if (sharedBuildCommandsData.isNotEmpty) {
232+ final Iterable <Command > buildCommands = buildCommandsData
233+ .map ((dynamic data) => Command .fromMap (data as Map <String , dynamic >));
234+ final Iterable <List <Command >> shardBuildCommands =
235+ sharedBuildCommandsData.map ((List <dynamic > list) => list
236+ .map ((dynamic data) =>
237+ Command .fromMap (data as Map <String , dynamic >))
238+ .toList ());
239+ final Iterable <_SetStatusCommand > intersectionResults =
240+ _calcIntersection (buildCommands, shardBuildCommands);
241+ totalCommands.addAll (intersectionResults
242+ .where ((_SetStatusCommand element) =>
243+ element.setStatus == _SetStatus .Difference )
244+ .map ((_SetStatusCommand e) => e.command));
245+ final List <Command > intersection = intersectionResults
246+ .where ((_SetStatusCommand element) =>
247+ element.setStatus == _SetStatus .Intersection )
248+ .map ((_SetStatusCommand e) => e.command)
249+ .toList ();
250+ // Make sure to sort results, not sure if there is a defined order in the json file.
251+ intersection
252+ .sort ((Command x, Command y) => x.filePath.compareTo (y.filePath));
253+ totalCommands.addAll (
254+ _takeShard (intersection, shardId! , 1 + shardBuildCommands.length));
255+ } else {
256+ totalCommands.addAll (buildCommandsData.map ((dynamic data) => Command .fromMap (data as Map <String , dynamic >)));
257+ }
258+ Stream <Command > filterCommands () async * {
259+ for (final Command command in totalCommands) {
260+ final LintAction lintAction = await command.lintAction;
261+ // Short-circuit the expensive containsAny call for the many third_party files.
262+ if (lintAction != LintAction .skipThirdParty && command.containsAny (files)) {
263+ yield command;
264+ }
188265 }
189266 }
190- return buildCommands ;
267+ return filterCommands (). toList () ;
191268 }
192269
193270 Future <_ComputeJobsResult > _computeJobs (
0 commit comments