44import os
55import shutil
66import signal
7- import subprocess
87import click
8+ import sys
9+ from pathlib import Path
10+ from typing import List
911
1012import tabulate
1113from constraint import *
1214
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+
1318CONTINUE_ON_ERROR = False
1419
15- BOARDS = [
20+ MKS_GENL_BOARDS = [
1621 "mksgenlv21" ,
1722 "mksgenlv2" ,
1823 "mksgenlv1" ,
19- "esp32" ,
24+ ]
25+ AVR_BOARDS = MKS_GENL_BOARDS + [
2026 "ramps" ,
2127]
28+ BOARDS = AVR_BOARDS + [
29+ "esp32" ,
30+ ]
2231
2332STEPPER_TYPES = [
2433 "STEPPER_TYPE_NONE" ,
4251 "DISPLAY_TYPE_LCD_JOY_I2C_SSD1306" ,
4352]
4453
54+ INFO_DISPLAY_TYPES = [
55+ "INFO_DISPLAY_TYPE_NONE" ,
56+ "INFO_DISPLAY_TYPE_I2C_SSD1306_128x64" ,
57+ ]
58+
4559BUILD_FLAGS = {
4660 "CONFIG_VERSION" : "1" ,
4761 "RA_STEPPER_TYPE" : [x for x in STEPPER_TYPES if x != "STEPPER_TYPE_NONE" ],
5771 "FOCUS_STEPPER_TYPE" : STEPPER_TYPES ,
5872 "FOCUS_DRIVER_TYPE" : DRIVER_TYPES ,
5973 "DISPLAY_TYPE" : DISPLAY_TYPES ,
74+ "INFO_DISPLAY_TYPE" : INFO_DISPLAY_TYPES ,
75+ "TEST_VERIFY_MODE" : BOOLEAN_VALUES ,
6076 "DEBUG_LEVEL" : ["DEBUG_NONE" , "DEBUG_ANY" ],
6177 "RA_MOTOR_CURRENT_RATING" : "1" ,
6278 "RA_OPERATING_CURRENT_SETTING" : "1" ,
@@ -230,6 +246,25 @@ def driver_supports_stepper(d, s):
230246 problem .addConstraint (driver_supports_stepper , ["AZ_DRIVER_TYPE" , "AZ_STEPPER_TYPE" ])
231247 problem .addConstraint (driver_supports_stepper , ["FOCUS_DRIVER_TYPE" , "FOCUS_STEPPER_TYPE" ])
232248
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+
233268
234269# Define constraints for excluded tests
235270def set_test_constraints (problem ):
@@ -261,6 +296,14 @@ def set_ci_constraints(problem):
261296 problem .addConstraint (InSetConstraint ({"DISPLAY_TYPE_NONE" , "DISPLAY_TYPE_LCD_KEYPAD" }), ["DISPLAY_TYPE" ])
262297 # problem.addConstraint(InSetConstraint({"DRIVER_TYPE_ULN2003"}), ["ALT_DRIVER_TYPE"])
263298
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+
264307
265308def print_solutions_matrix (solutions , short_strings = False ):
266309 def get_value (vb , vk ):
@@ -279,43 +322,28 @@ def get_value(vb, vk):
279322 print (tabulate .tabulate (rows , tablefmt = "grid" , showindex = map (shorten , keys ), colalign = ("right" ,)))
280323
281324
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 )
300337
301338
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
319347
320348
321349class GracefulKiller :
@@ -353,17 +381,60 @@ def solve(board):
353381 solutions = problem .getSolutions ()
354382 print_solutions_matrix (solutions , short_strings = False )
355383
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 )
367438
368439
369440if __name__ == '__main__' :
0 commit comments