4
4
import os
5
5
import shutil
6
6
import signal
7
- import subprocess
8
7
import click
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import List
9
11
10
12
import tabulate
11
13
from constraint import *
12
14
15
+ from matrix_build_parallel import Executor , execute , get_available_executor_idx , get_finished_executor_idx , \
16
+ cleanup_tempdirs , create_executors , get_source_files_to_link , wait_for_executor_to_finish , copy_caches_to_executors
17
+
13
18
CONTINUE_ON_ERROR = False
14
19
15
- BOARDS = [
20
+ MKS_GENL_BOARDS = [
16
21
"mksgenlv21" ,
17
22
"mksgenlv2" ,
18
23
"mksgenlv1" ,
19
- "esp32" ,
24
+ ]
25
+ AVR_BOARDS = MKS_GENL_BOARDS + [
20
26
"ramps" ,
21
27
]
28
+ BOARDS = AVR_BOARDS + [
29
+ "esp32" ,
30
+ ]
22
31
23
32
STEPPER_TYPES = [
24
33
"STEPPER_TYPE_NONE" ,
42
51
"DISPLAY_TYPE_LCD_JOY_I2C_SSD1306" ,
43
52
]
44
53
54
+ INFO_DISPLAY_TYPES = [
55
+ "INFO_DISPLAY_TYPE_NONE" ,
56
+ "INFO_DISPLAY_TYPE_I2C_SSD1306_128x64" ,
57
+ ]
58
+
45
59
BUILD_FLAGS = {
46
60
"CONFIG_VERSION" : "1" ,
47
61
"RA_STEPPER_TYPE" : [x for x in STEPPER_TYPES if x != "STEPPER_TYPE_NONE" ],
57
71
"FOCUS_STEPPER_TYPE" : STEPPER_TYPES ,
58
72
"FOCUS_DRIVER_TYPE" : DRIVER_TYPES ,
59
73
"DISPLAY_TYPE" : DISPLAY_TYPES ,
74
+ "INFO_DISPLAY_TYPE" : INFO_DISPLAY_TYPES ,
75
+ "TEST_VERIFY_MODE" : BOOLEAN_VALUES ,
60
76
"DEBUG_LEVEL" : ["DEBUG_NONE" , "DEBUG_ANY" ],
61
77
"RA_MOTOR_CURRENT_RATING" : "1" ,
62
78
"RA_OPERATING_CURRENT_SETTING" : "1" ,
@@ -230,6 +246,25 @@ def driver_supports_stepper(d, s):
230
246
problem .addConstraint (driver_supports_stepper , ["AZ_DRIVER_TYPE" , "AZ_STEPPER_TYPE" ])
231
247
problem .addConstraint (driver_supports_stepper , ["FOCUS_DRIVER_TYPE" , "FOCUS_STEPPER_TYPE" ])
232
248
249
+ # AVR boards can't have both DISPLAY_TYPE and INFO_DISPLAY_TYPE enabled
250
+ def avr_display_exclusivity (board , display , info_display ):
251
+ if board not in AVR_BOARDS :
252
+ return True
253
+ return (
254
+ display == "DISPLAY_TYPE_NONE" or
255
+ info_display == "INFO_DISPLAY_TYPE_NONE"
256
+ )
257
+ problem .addConstraint (avr_display_exclusivity , ["BOARD" , "DISPLAY_TYPE" , "INFO_DISPLAY_TYPE" ])
258
+
259
+ # MKS GenL boards must not have a focus stepper when info display is enabled
260
+ def mksgenl_focus_exclusivity (board , info_display , focus_stepper ):
261
+ if board not in MKS_GENL_BOARDS :
262
+ return True
263
+ if info_display != "INFO_DISPLAY_TYPE_NONE" :
264
+ return focus_stepper == "STEPPER_TYPE_NONE"
265
+ return True
266
+ problem .addConstraint (mksgenl_focus_exclusivity , ["BOARD" , "INFO_DISPLAY_TYPE" , "FOCUS_STEPPER_TYPE" ])
267
+
233
268
234
269
# Define constraints for excluded tests
235
270
def set_test_constraints (problem ):
@@ -261,6 +296,14 @@ def set_ci_constraints(problem):
261
296
problem .addConstraint (InSetConstraint ({"DISPLAY_TYPE_NONE" , "DISPLAY_TYPE_LCD_KEYPAD" }), ["DISPLAY_TYPE" ])
262
297
# problem.addConstraint(InSetConstraint({"DRIVER_TYPE_ULN2003"}), ["ALT_DRIVER_TYPE"])
263
298
299
+ # Restrict INFO_DISPLAY_TYPE_I2C_SSD1306_128x64 to mksgenlv21 and esp32 only
300
+ # (just to reduce compile times)
301
+ def info_display_constraint (board , info_display ):
302
+ if info_display == "INFO_DISPLAY_TYPE_I2C_SSD1306_128x64" :
303
+ return board in ["mksgenlv21" , "esp32" ]
304
+ return True
305
+ problem .addConstraint (info_display_constraint , ["BOARD" , "INFO_DISPLAY_TYPE" ])
306
+
264
307
265
308
def print_solutions_matrix (solutions , short_strings = False ):
266
309
def get_value (vb , vk ):
@@ -279,43 +322,28 @@ def get_value(vb, vk):
279
322
print (tabulate .tabulate (rows , tablefmt = "grid" , showindex = map (shorten , keys ), colalign = ("right" ,)))
280
323
281
324
282
- def generate_config_file (flag_values ):
283
- content = "#pragma once\n \n "
284
- for key , value in flag_values .items ():
285
- content += "#define {} {}\n " .format (key , value )
286
-
287
- with open ("Configuration_local_matrix.hpp" , 'w' ) as f :
288
- f .write (content )
289
- print ("Generated local config" )
290
- print ("Path: {}" .format (os .path .abspath (f .name )))
291
- print ("Content:" )
292
- print (content )
293
-
294
-
295
- def create_run_environment (flag_values ):
296
- build_env = dict (os .environ )
297
- build_flags = " " .join (["-D{}={}" .format (key , value ) for key , value in flag_values .items ()])
298
- build_env ["PLATFORMIO_BUILD_FLAGS" ] = build_flags
299
- return build_env
325
+ def print_failed_executor (executor : Executor ):
326
+ print (f'Error for the following configuration ({ executor .proj_dir } ):' , file = sys .stderr )
327
+ print_solutions_matrix ([executor .solution ])
328
+ configuration_path = Path (executor .proj_dir , 'Configuration_local_matrix.hpp' )
329
+ print (f'{ configuration_path } :' )
330
+ with open (configuration_path , 'r' ) as fp :
331
+ print (fp .read ())
332
+ out_bytes , err_bytes = executor .proc .communicate ()
333
+ if out_bytes :
334
+ print (out_bytes .decode ())
335
+ if err_bytes :
336
+ print (err_bytes .decode (), file = sys .stderr )
300
337
301
338
302
- def execute (board , flag_values , use_config_file = True ):
303
- if use_config_file :
304
- build_env = dict (os .environ )
305
- build_env ["PLATFORMIO_BUILD_FLAGS" ] = "-DMATRIX_LOCAL_CONFIG=1"
306
- generate_config_file (flag_values )
307
- else :
308
- build_env = create_run_environment (flag_values )
309
-
310
- proc = subprocess .Popen (
311
- "pio run -e {}" .format (board ),
312
- # stdout=subprocess.PIPE,
313
- # stderr=subprocess.PIPE,
314
- shell = True ,
315
- env = build_env ,
316
- )
317
- (stdout , stderr ) = proc .communicate ()
318
- return stdout , stdout , proc .returncode
339
+ def run_solution_blocking (executor : Executor , solution : dict ) -> int :
340
+ executor .solution = copy .deepcopy (solution )
341
+ board = solution .pop ("BOARD" )
342
+ executor .proc = execute (executor .proj_dir , board , solution , jobs = os .cpu_count (), out_pipe = False )
343
+ executor .proc .wait ()
344
+ if executor .proc .returncode != 0 :
345
+ print_failed_executor (executor )
346
+ return executor .proc .returncode
319
347
320
348
321
349
class GracefulKiller :
@@ -353,17 +381,60 @@ def solve(board):
353
381
solutions = problem .getSolutions ()
354
382
print_solutions_matrix (solutions , short_strings = False )
355
383
356
- print ("Testing {} combinations" .format (len (solutions )))
357
-
358
- for num , solution in enumerate (solutions , start = 1 ):
359
- print ("[{}/{}] Building ..." .format (num , len (solutions )), flush = True )
360
- print_solutions_matrix ([solution ])
361
-
362
- board = solution .pop ("BOARD" )
363
- (o , e , c ) = execute (board , solution )
364
- if c and not CONTINUE_ON_ERROR :
365
- exit (c )
366
- print (flush = True )
384
+ total_solutions = len (solutions )
385
+ print (f'Testing { total_solutions } combinations' )
386
+
387
+ nproc = min (os .cpu_count (), len (solutions ))
388
+
389
+ local_paths_to_link = get_source_files_to_link ()
390
+ executor_list : List [Executor ] = create_executors (nproc , local_paths_to_link )
391
+
392
+ print ('First run to fill cache' )
393
+ solution = solutions .pop ()
394
+ retcode = run_solution_blocking (executor_list [0 ], solution )
395
+ if retcode != 0 and not CONTINUE_ON_ERROR :
396
+ exit (retcode )
397
+
398
+ copy_caches_to_executors (executor_list [0 ].proj_dir , executor_list [1 :])
399
+
400
+ solutions_built = 2 # We've already built one solution, and we're 1-indexing
401
+ exit_early = False # Exit trigger
402
+ while solutions :
403
+ # First fill any open execution slots
404
+ while get_available_executor_idx (executor_list ) is not None :
405
+ available_executor_idx = get_available_executor_idx (executor_list )
406
+ executor = executor_list [available_executor_idx ]
407
+ try :
408
+ solution = solutions .pop ()
409
+ except IndexError :
410
+ # No more solutions to try!
411
+ break
412
+ print (f'[{ solutions_built } /{ total_solutions } ] Building ...' )
413
+ executor .solution = copy .deepcopy (solution )
414
+ board = solution .pop ("BOARD" )
415
+ executor .proc = execute (executor .proj_dir , board , solution )
416
+ solutions_built += 1
417
+
418
+ # Next wait for any processes to finish
419
+ wait_for_executor_to_finish (executor_list )
420
+
421
+ # Go through all the finished processes and check their status
422
+ while get_finished_executor_idx (executor_list ) is not None :
423
+ finished_executor_idx = get_finished_executor_idx (executor_list )
424
+ executor = executor_list [finished_executor_idx ]
425
+ if executor .proc .returncode != 0 :
426
+ print_failed_executor (executor )
427
+ if not CONTINUE_ON_ERROR :
428
+ exit_early = True
429
+ del executor .proc
430
+ executor .proc = None
431
+
432
+ if exit_early :
433
+ break
434
+ if exit_early :
435
+ exit (1 )
436
+ print ('Done!' )
437
+ cleanup_tempdirs (executor_list )
367
438
368
439
369
440
if __name__ == '__main__' :
0 commit comments