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