@@ -240,7 +240,7 @@ def main(argv):
240240 device_name = FLAGS .ios_name
241241 device_os = FLAGS .ios_version
242242
243- device_id = _create_and_boot_simulator ("iOS" , device_name , device_os )
243+ device_id , device_name , device_os = _create_and_boot_simulator ("iOS" , device_name , device_os )
244244 if not device_id :
245245 logging .error ("simulator created fail" )
246246 return 21
@@ -274,7 +274,7 @@ def main(argv):
274274 device_name = FLAGS .tvos_name
275275 device_os = FLAGS .tvos_version
276276
277- device_id = _create_and_boot_simulator ("tvOS" , device_name , device_os )
277+ device_id , device_name , device_os = _create_and_boot_simulator ("tvOS" , device_name , device_os )
278278 if not device_id :
279279 logging .error ("simulator created fail" )
280280 return 21
@@ -500,49 +500,113 @@ def _shutdown_simulator():
500500
501501
502502def _create_and_boot_simulator (apple_platform , device_name , device_os ):
503- """Create a simulator locally. Will wait until this simulator booted."""
504- _shutdown_simulator ()
505- command = "xcrun xctrace list devices 2>&1 | grep \" %s Simulator (%s)\" | awk -F'[()]' '{print $4}'" % (device_name , device_os )
506- logging .info ("Get test simulator: %s" , command )
507- result = subprocess .Popen (command , universal_newlines = True , shell = True , stdout = subprocess .PIPE )
508- device_id = result .stdout .readline ().strip ()
503+ """Create a simulator locally. Will wait until this simulator booted.
509504
510- if not device_id :
511- # download and create device
512- args = ["brew" , "install" , "xcodesorg/made/xcodes" ]
513- logging .info ("Download xcodes: %s" , " " .join (args ))
514- subprocess .run (args = args , check = True )
505+ Returns a tuple of (device_id, device_name, device_os).
506+ """
507+ _shutdown_simulator ()
515508
516- # Get the set of available versions for the given Apple platform
517- args = ["xcodes" , "runtimes" ]
518- runtimes = subprocess .run (args = args , capture_output = True , text = True , check = True )
519- available_versions = re .findall ('{0} ([\d|.]+)' .format (apple_platform ), runtimes .stdout .strip ())
520- logging .info ("Found available versions for %s: %s" , apple_platform , ", " .join (available_versions ))
509+ # Find available runtimes using simctl
510+ try :
511+ runtimes_result = subprocess .run (
512+ ["xcrun" , "simctl" , "list" , "runtimes" , "-j" ],
513+ capture_output = True , text = True , check = True )
514+ runtimes_data = json .loads (runtimes_result .stdout )
515+ except Exception :
516+ logging .exception ("Failed to get runtimes list from simctl" )
517+ return None , None , None
518+
519+ # Filter runtimes by platform and availability
520+ available_runtimes = []
521+ for r in runtimes_data .get ("runtimes" , []):
522+ if r .get ("isAvailable" ) and r .get ("platform" ) == apple_platform :
523+ available_runtimes .append (r )
524+
525+ if not available_runtimes :
526+ logging .error ("No available runtimes found for platform: %s" , apple_platform )
527+ return None , None , None
528+
529+ # Find matching runtime version
530+ target_runtime = None
531+ for r in available_runtimes :
532+ version = r .get ("version" )
533+ if version == device_os or version .startswith (device_os ):
534+ target_runtime = r
535+ break
521536
522- # If the requested version is available, use it, otherwise default to the latest
523- if (device_os not in available_versions ):
524- logging .warning ("Unable to find version %s, will fall back to %s" , device_os , available_versions [- 1 ])
525- if FLAGS .ci :
526- print ("::warning ::Unable to find %s version %s, will fall back to %s" % (apple_platform , device_os , available_versions [- 1 ]))
527- device_os = available_versions [- 1 ]
537+ if not target_runtime :
538+ # Fall back to the latest runtime version
539+ def parse_version (version_str ):
540+ sanitized = re .sub (r'[^\d.]' , '' , version_str )
541+ parts = []
542+ for x in sanitized .split ('.' ):
543+ if x .isdigit ():
544+ parts .append (int (x ))
545+ return parts
546+
547+ available_runtimes .sort (key = lambda r : parse_version (r .get ("version" )))
548+ target_runtime = available_runtimes [- 1 ]
549+ fallback_os = target_runtime .get ("version" )
550+ logging .warning ("Requested %s version %s is not available. Falling back to %s" ,
551+ apple_platform , device_os , fallback_os )
552+ if FLAGS .ci :
553+ print ("::warning ::Requested %s version %s is not available, falling back to %s" %
554+ (apple_platform , device_os , fallback_os ))
555+ device_os = fallback_os
556+
557+ runtime_id = target_runtime .get ("identifier" )
558+ logging .info ("Using runtime: %s (ID: %s)" , target_runtime .get ("name" ), runtime_id )
559+
560+ # Check if a matching simulator device already exists
561+ try :
562+ devices_result = subprocess .run (
563+ ["xcrun" , "simctl" , "list" , "devices" , "-j" ],
564+ capture_output = True , text = True , check = True )
565+ devices_data = json .loads (devices_result .stdout )
566+ except Exception :
567+ logging .exception ("Failed to get devices list from simctl" )
568+ return None , None , None
569+
570+ devices_under_runtime = devices_data .get ("devices" , {}).get (runtime_id , [])
571+ device_id = None
572+ for d in devices_under_runtime :
573+ if d .get ("isAvailable" ) and d .get ("name" ) == device_name :
574+ device_id = d .get ("udid" )
575+ logging .info ("Found existing simulator device: %s (%s)" , device_name , device_id )
576+ break
528577
529- args = ["sudo" , "xcodes" , "runtimes" , "install" , "%s %s" % (apple_platform , device_os )]
530- logging .info ("Download simulator: %s" , " " .join (args ))
531- subprocess .run (args = args , check = False )
578+ if not device_id :
579+ # Create new device if not found
580+ args = ["xcrun" , "simctl" , "create" , "test_simulator" , device_name , runtime_id ]
581+ logging .info ("Creating test simulator: %s" , " " .join (args ))
582+ try :
583+ result = subprocess .run (args = args , capture_output = True , text = True , check = True )
584+ device_id = result .stdout .strip ()
585+ logging .info ("Created simulator: %s" , device_id )
586+ except subprocess .CalledProcessError as e :
587+ logging .error ("Failed to create simulator: %s" , e .stderr )
588+ # Fall back to the first available device under this runtime
589+ for d in devices_under_runtime :
590+ if d .get ("isAvailable" ):
591+ device_id = d .get ("udid" )
592+ device_name = d .get ("name" )
593+ logging .info ("Fallback to first available device: %s (%s)" , device_name , device_id )
594+ break
532595
533- args = ["xcrun" , "simctl" , "create" , "test_simulator" , device_name , "%s%s" % (apple_platform , device_os )]
534- logging .info ("Create test simulator: %s" , " " .join (args ))
535- result = subprocess .run (args = args , capture_output = True , text = True , check = True )
536- device_id = result .stdout .strip ()
596+ if not device_id :
597+ logging .error ("No simulator device available or created" )
598+ return None , None , None
537599
600+ # Boot the simulator
538601 args = ["xcrun" , "simctl" , "boot" , device_id ]
539- logging .info ("Boot my simulator: %s" , " " .join (args ))
602+ logging .info ("Boot simulator: %s" , " " .join (args ))
540603 subprocess .run (args = args , check = True )
541604
542605 args = ["xcrun" , "simctl" , "bootstatus" , device_id ]
543606 logging .info ("Wait for simulator to boot: %s" , " " .join (args ))
544607 subprocess .run (args = args , check = True )
545- return device_id
608+
609+ return device_id , device_name , device_os
546610
547611
548612def _delete_simulator (device_id ):
0 commit comments