11function plan = buildfile
22% BUILDFILE LabKit build and validation entry points.
3+ %
4+ % User-facing commands:
5+ % buildtool changed conservative validation routed from the git diff
6+ % buildtool changedFast faster local iteration routed from the git diff
7+ % buildtool headless full non-GUI validation
8+ % buildtool gui full automated GUI validation with hidden figures
9+ % buildtool coverage coverage report for manual or scheduled runs
10+ % buildtool listTasks print the public task catalog
11+ %
12+ % The buildfile deliberately stays thin. It exposes stable tasks and passes
13+ % suite/tag choices to tests/runLabKitTests.m. Ownership-specific routing
14+ % lives in the runner and changed-file planner, derived from
15+ % tests/cases/<kind>/<owner>/<area>/ paths.
316
417 plan = buildplan(localfunctions );
518 plan.DefaultTasks = " headless" ;
@@ -30,26 +43,6 @@ function coverageTask(~)
3043 runCatalogTask(" coverage" );
3144end
3245
33- function ciUnitLabKitTask(~)
34- runCatalogTask(" ciUnitLabKit" );
35- end
36-
37- function ciUnitAppsTask(~)
38- runCatalogTask(" ciUnitApps" );
39- end
40-
41- function ciUnitProjectTask(~)
42- runCatalogTask(" ciUnitProject" );
43- end
44-
45- function ciIntegrationAppsTask(~)
46- runCatalogTask(" ciIntegrationApps" );
47- end
48-
49- function ciIntegrationProjectTask(~)
50- runCatalogTask(" ciIntegrationProject" );
51- end
52-
5346function listTasksTask(~)
5447 printTaskCatalog(taskCatalog());
5548end
@@ -61,11 +54,6 @@ function listTasksTask(~)
6154 taskSpec(" headless" , " Run the full non-GUI validation set." , " IncludeGui" , false ), ...
6255 taskSpec(" gui" , " Run noninteractive GUI launch, layout, and gesture checks." , " Suites" , " gui" , " IncludeGui" , true , " GuiMode" , " hidden" ), ...
6356 taskSpec(" coverage" , " Run official tests with coverage artifacts." , " Tags" , [" Unit" , " Integration" ], " IncludeCoverage" , true ), ...
64- taskSpec(" ciUnitLabKit" , " Run CI LabKit unit shard." , " Visibility" , " ci" , " Suites" , " labkit" , " Tags" , " Unit" , " IncludeGui" , false , " HtmlReport" , false ), ...
65- taskSpec(" ciUnitApps" , " Run CI app unit shard." , " Visibility" , " ci" , " Suites" , " apps" , " Tags" , " Unit" , " IncludeGui" , false , " HtmlReport" , false ), ...
66- taskSpec(" ciUnitProject" , " Run CI project unit shard." , " Visibility" , " ci" , " Suites" , " project" , " Tags" , " Unit" , " IncludeGui" , false , " HtmlReport" , false ), ...
67- taskSpec(" ciIntegrationApps" , " Run CI app integration shard." , " Visibility" , " ci" , " Suites" , " apps" , " Tags" , " Integration" , " IncludeGui" , false , " HtmlReport" , false ), ...
68- taskSpec(" ciIntegrationProject" , " Run CI project integration shard." , " Visibility" , " ci" , " Suites" , " project" , " Tags" , " Integration" , " IncludeGui" , false , " HtmlReport" , false ), ...
6957 taskSpec(" listTasks" , " List official LabKit build tasks." , " RunTests" , false )];
7058end
7159
@@ -108,6 +96,9 @@ function runCatalogTask(runName)
10896 end
10997
11098 args = taskRunArguments(spec );
99+ if runWithInternalShards(spec , args )
100+ return ;
101+ end
111102 runBuildTests(spec .Name, args{: });
112103end
113104
@@ -154,6 +145,138 @@ function runBuildTests(runName, varargin)
154145 " ArtifactsRoot" , fullfile(root , " artifacts" ));
155146end
156147
148+ function handled = runWithInternalShards(spec , args )
149+ handled = false ;
150+ if spec .Name ~= " headless" || isInternalShardWorker() || ispc
151+ return ;
152+ end
153+
154+ root = fileparts(mfilename(" fullpath" ));
155+ addpath(fullfile(root , " tests" ));
156+ try
157+ probe = runLabKitTests(args{: }, ...
158+ " ListOnly" , true , ...
159+ " FailIfNoTests" , false , ...
160+ " RunName" , spec .Name + " _probe" , ...
161+ " ArtifactsRoot" , fullfile(root , " artifacts" ));
162+ catch
163+ return ;
164+ end
165+
166+ shardCount = recommendedShardCount(probe .count);
167+ if shardCount <= 1
168+ return ;
169+ end
170+
171+ fprintf(" LabKit shard probe: %d test(s) matched; running %d internal headless shard(s).\n" , ...
172+ probe .count, shardCount );
173+ runInternalShardWorkers(root , spec .Name, args , shardCount );
174+ handled = true ;
175+ end
176+
177+ function tf = isInternalShardWorker()
178+ tf = string(getenv(" LABKIT_INTERNAL_SHARD_WORKER" )) == " 1" ;
179+ end
180+
181+ function shardCount = recommendedShardCount(testCount )
182+ if testCount >= 300
183+ shardCount = 3 ;
184+ elseif testCount >= 80
185+ shardCount = 2 ;
186+ else
187+ shardCount = 1 ;
188+ end
189+ end
190+
191+ function runInternalShardWorkers(root , runName , args , shardCount )
192+ logsRoot = fullfile(root , " artifacts" , " logs" , runName + " -orchestrator" );
193+ ensureFolder(logsRoot );
194+ scriptPath = fullfile(logsRoot , " run_shards.sh" );
195+ fid = fopen(scriptPath , " w" );
196+ if fid < 0
197+ error(" LabKit:Build:ShardScript" , " Could not create shard script." );
198+ end
199+ cleanup = onCleanup(@() fclose(fid ));
200+
201+ fprintf(fid , " #!/bin/sh\n" );
202+ fprintf(fid , " status=0\n" );
203+ matlabExe = fullfile(matlabroot , " bin" , " matlab" );
204+ for k = 0 : (shardCount - 1 )
205+ shardName = sprintf(" %s-shard-%d" , runName , k );
206+ workerLog = fullfile(logsRoot , shardName + " .log" );
207+ batch = shardBatchCommand(root , args , shardName , shardCount , k );
208+ fprintf(fid , " LABKIT_INTERNAL_SHARD_WORKER=1 %s -batch %s > %s 2>&1 &\n" , ...
209+ shellQuote(matlabExe ), shellQuote(batch ), shellQuote(workerLog ));
210+ fprintf(fid , " pids_%d=$!\n" , k + 1 );
211+ end
212+ for k = 1 : shardCount
213+ fprintf(fid , " wait $pids_%d || status=1\n" , k );
214+ end
215+ fprintf(fid , ' if [ "$status" -ne 0 ]; then\n ' );
216+ fprintf(fid , " cat %s/*.log\n" , shellQuote(logsRoot ));
217+ fprintf(fid , " fi\n" );
218+ fprintf(fid , " exit $status\n" );
219+ clear cleanup ;
220+ fileattrib(scriptPath , " +x" );
221+
222+ [status , output ] = system(shellQuote(scriptPath ));
223+ if strlength(string(output )) > 0
224+ fprintf(" %s" , output );
225+ end
226+ if status ~= 0
227+ error(" LabKit:Build:ShardFailure" , ...
228+ " One or more internal test shards failed. Logs: %s" , logsRoot );
229+ end
230+ end
231+
232+ function batch = shardBatchCommand(root , args , runName , shardCount , shardIndex )
233+ shardArgs = [args , { ...
234+ " ShardCount" , shardCount , ...
235+ " ShardIndex" , shardIndex , ...
236+ " RunName" , string(runName ), ...
237+ " ArtifactsRoot" , fullfile(root , " artifacts" )}];
238+ batch = " addpath(" + matlabLiteral(fullfile(root , " tests" )) + " ); " + ...
239+ " runLabKitTests(" + matlabArgumentList(shardArgs ) + " );" ;
240+ end
241+
242+ function text = matlabArgumentList(args )
243+ parts = strings(1 , numel(args ));
244+ for k = 1 : numel(args )
245+ parts(k ) = matlabLiteral(args{k });
246+ end
247+ text = strjoin(parts , " , " );
248+ end
249+
250+ function text = matlabLiteral(value )
251+ if islogical(value )
252+ if value
253+ text = " true" ;
254+ else
255+ text = " false" ;
256+ end
257+ elseif isnumeric(value )
258+ text = string(value );
259+ else
260+ value = string(value );
261+ if isscalar(value )
262+ text = " "" " + replace(value , " "" " , " """" " ) + " "" " ;
263+ else
264+ quoted = " "" " + replace(value , " "" " , " """" " ) + " "" " ;
265+ text = " [" + strjoin(quoted , " , " ) + " ]" ;
266+ end
267+ end
268+ end
269+
270+ function text = shellQuote(value )
271+ text = " '" + replace(string(value ), " '" , " '\''" ) + " '" ;
272+ end
273+
274+ function ensureFolder(folder )
275+ if exist(folder , " dir" ) ~= 7
276+ mkdir(folder );
277+ end
278+ end
279+
157280function printTaskCatalog(catalog )
158281 fprintf(" LabKit build tasks:\n" );
159282 for k = 1 : numel(catalog )
0 commit comments