Check each runtime when detecting xcode cache cleanup

Exiting logic just makes sure there exists some runtime, but doesn't enforce a check on all required runtimes.
This CL makes sure we check every runtime that is necessary.

Led run which detects the missing runtime, cleans up old xcode and re-installs:
https://luci-milo.appspot.com/raw/build/logs.chromium.org/flutter/led/keyonghan_google.com/982451e4ac666d4e859ba6c30be72d9de67c4d245db6a6d9e3d816f0be58735a/+/build.proto

Change-Id: I69afad5330ec715ca472903ddb8c7bf801f6ae26
Bug: https://github.com/flutter/flutter/issues/125273
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/49482
Reviewed-by: Victoria Ashworth <vashworth@google.com>
Commit-Queue: Keyong Han <keyonghan@google.com>
Reviewed-by: Ricardo Amador <ricardoamador@google.com>
diff --git a/recipe_modules/osx_sdk/api.py b/recipe_modules/osx_sdk/api.py
index 6a4bc16..4cbb36d 100644
--- a/recipe_modules/osx_sdk/api.py
+++ b/recipe_modules/osx_sdk/api.py
@@ -129,12 +129,7 @@
             'list runtimes', ['xcrun', 'simctl', 'list', 'runtimes'],
             stdout=self.m.raw_io.output_text()
         ).stdout.splitlines()
-        #  If no runtime exists, we should do a fresh re-install of xcode.
-        #
-        #  Skips the Runtimes header when checking. Example of the output:
-        #    == Runtimes ==
-        #    iOS 16.4 (16.4 - 20E247) - com.apple.CoreSimulator.SimRuntime.iOS-16-4
-        if not runtime_simulators[1:]:
+        if self._missing_runtime(runtime_simulators[1:]):
           self._cleanup_cache = True
           self._setup_osx_sdk(kind, devicelab)
       yield
@@ -142,6 +137,31 @@
       with self.m.context(infra_steps=True):
         self.m.step('reset XCode', ['sudo', 'xcode-select', '--reset'])
 
+  def _missing_runtime(self, runtime_simulators):
+    """Check if there is any missing runtime.
+
+    If no explicit `_runtime_versions` is specified, we assume `runtime_simulators`
+    at least has the default runtime and should not be empty.
+
+    If there is explicit `_runtime_versions` defined, we need to check if the number
+    of installed runtime matches the number of required.
+
+    The runtime_simulators follows:
+    [
+      "iOS 16.2 (16.2 - 20C52) - com.apple.CoreSimulator.SimRuntime.iOS-16-2",
+      "iOS 16.4 (16.4 - 20E247) - com.apple.CoreSimulator.SimRuntime.iOS-16-4"
+    ]
+
+    The property `_runtime_versions` follows:
+    [
+      "ios-16-4_14e300c",
+      "ios-16-2_14c18"
+    ]
+    """
+    if not self._runtime_versions:
+      return not runtime_simulators
+    return len(self._runtime_versions) != len(runtime_simulators)
+
   def _setup_osx_sdk(self, kind, devicelab):
     app = None
     self._clean_cache(devicelab)
diff --git a/recipe_modules/osx_sdk/examples/full.expected/explicit_runtime_version.json b/recipe_modules/osx_sdk/examples/full.expected/explicit_runtime_version.json
index b754e69..b7e8180 100644
--- a/recipe_modules/osx_sdk/examples/full.expected/explicit_runtime_version.json
+++ b/recipe_modules/osx_sdk/examples/full.expected/explicit_runtime_version.json
@@ -158,166 +158,6 @@
   },
   {
     "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0"
-    ],
-    "infra_step": true,
-    "name": "Cleaning up Xcode cache"
-  },
-  {
-    "cmd": [
-      "cipd",
-      "ensure",
-      "-root",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0",
-      "-ensure-file",
-      "infra/tools/mac_toolchain/${platform} 123abc",
-      "-max-threads",
-      "0",
-      "-json-output",
-      "/path/to/tmp/json"
-    ],
-    "infra_step": true,
-    "name": "ensure_installed (2)",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-123abc----------\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    ]@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/mac_toolchain",
-      "install",
-      "-kind",
-      "mac",
-      "-xcode-version",
-      "deadbeef",
-      "-output-dir",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app",
-      "-cipd-package-prefix",
-      "flutter_internal/ios/xcode",
-      "-with-runtime=False"
-    ],
-    "infra_step": true,
-    "name": "install xcode (2)"
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "ensure-directory",
-      "--mode",
-      "0777",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes"
-    ],
-    "infra_step": true,
-    "name": "Ensuring runtimes directory (2)"
-  },
-  {
-    "cmd": [
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/mac_toolchain",
-      "install-runtime",
-      "-cipd-package-prefix",
-      "flutter_internal/ios/xcode",
-      "-runtime-version",
-      "ios-14-0",
-      "-output-dir",
-      "[CACHE]/osx_sdk/xcode_runtime_ios-14-0"
-    ],
-    "infra_step": true,
-    "name": "install xcode runtime ios-14-0 (2)"
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copytree",
-      "--symlinks",
-      "[CACHE]/osx_sdk/xcode_runtime_ios-14-0/iOS.simruntime",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 14.0.simruntime"
-    ],
-    "infra_step": true,
-    "name": "Copy runtime to [CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 14.0.simruntime (2)"
-  },
-  {
-    "cmd": [
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/mac_toolchain",
-      "install-runtime",
-      "-cipd-package-prefix",
-      "flutter_internal/ios/xcode",
-      "-runtime-version",
-      "ios-13-0",
-      "-output-dir",
-      "[CACHE]/osx_sdk/xcode_runtime_ios-13-0"
-    ],
-    "infra_step": true,
-    "name": "install xcode runtime ios-13-0 (2)"
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "copytree",
-      "--symlinks",
-      "[CACHE]/osx_sdk/xcode_runtime_ios-13-0/iOS.simruntime",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime"
-    ],
-    "infra_step": true,
-    "name": "Copy runtime to [CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 13.0.simruntime (2)"
-  },
-  {
-    "cmd": [
-      "killall",
-      "-9",
-      "com.apple.CoreSimulator.CoreSimulatorDevice"
-    ],
-    "infra_step": true,
-    "name": "kill dart (2)"
-  },
-  {
-    "cmd": [
-      "sudo",
-      "xcode-select",
-      "--switch",
-      "[CACHE]/osx_sdk/xcode_deadbeef_runtime_ios-14-0_ios-13-0/XCode.app"
-    ],
-    "infra_step": true,
-    "name": "select XCode (2)"
-  },
-  {
-    "cmd": [
-      "xcrun",
-      "simctl",
-      "list"
-    ],
-    "infra_step": true,
-    "name": "list simulators (2)"
-  },
-  {
-    "cmd": [
       "gn",
       "gen",
       "out/Release"
@@ -343,6 +183,90 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_1/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} 123abc",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (2)",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@json.output@{@@@",
+      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
+      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
+      "@@@STEP_LOG_LINE@json.output@      {@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-123abc----------\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
+      "@@@STEP_LOG_LINE@json.output@      }@@@",
+      "@@@STEP_LOG_LINE@json.output@    ]@@@",
+      "@@@STEP_LOG_LINE@json.output@  }@@@",
+      "@@@STEP_LOG_LINE@json.output@}@@@",
+      "@@@STEP_LOG_END@json.output@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[CLEANUP]/tmp_tmp_1/osx_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "mac",
+      "-xcode-version",
+      "deadbeef",
+      "-output-dir",
+      "/opt/flutter/xcode/deadbeef/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=False"
+    ],
+    "infra_step": true,
+    "name": "install xcode (2)"
+  },
+  {
+    "cmd": [
+      "killall",
+      "-9",
+      "com.apple.CoreSimulator.CoreSimulatorDevice"
+    ],
+    "infra_step": true,
+    "name": "kill dart (2)"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--switch",
+      "/opt/flutter/xcode/deadbeef/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode (2)"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "simctl",
+      "list"
+    ],
+    "infra_step": true,
+    "name": "list simulators (2)"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "simctl",
+      "list",
+      "runtimes"
+    ],
+    "infra_step": true,
+    "name": "list runtimes (2)"
+  },
+  {
+    "cmd": [
       "vpython3",
       "-u",
       "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
@@ -352,14 +276,14 @@
       "/opt/flutter/xcode/deadbeef"
     ],
     "infra_step": true,
-    "name": "Cleaning up Xcode cache (2)"
+    "name": "Cleaning up Xcode cache"
   },
   {
     "cmd": [
       "cipd",
       "ensure",
       "-root",
-      "[CLEANUP]/tmp_tmp_1/osx_sdk",
+      "[CLEANUP]/tmp_tmp_2/osx_sdk",
       "-ensure-file",
       "infra/tools/mac_toolchain/${platform} 123abc",
       "-max-threads",
@@ -385,7 +309,7 @@
   },
   {
     "cmd": [
-      "[CLEANUP]/tmp_tmp_1/osx_sdk/mac_toolchain",
+      "[CLEANUP]/tmp_tmp_2/osx_sdk/mac_toolchain",
       "install",
       "-kind",
       "mac",
@@ -430,103 +354,6 @@
   },
   {
     "cmd": [
-      "xcrun",
-      "simctl",
-      "list",
-      "runtimes"
-    ],
-    "infra_step": true,
-    "name": "list runtimes (2)"
-  },
-  {
-    "cmd": [
-      "vpython3",
-      "-u",
-      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
-      "--json-output",
-      "/path/to/tmp/json",
-      "rmtree",
-      "/opt/flutter/xcode/deadbeef"
-    ],
-    "infra_step": true,
-    "name": "Cleaning up Xcode cache (3)"
-  },
-  {
-    "cmd": [
-      "cipd",
-      "ensure",
-      "-root",
-      "[CLEANUP]/tmp_tmp_2/osx_sdk",
-      "-ensure-file",
-      "infra/tools/mac_toolchain/${platform} 123abc",
-      "-max-threads",
-      "0",
-      "-json-output",
-      "/path/to/tmp/json"
-    ],
-    "infra_step": true,
-    "name": "ensure_installed (4)",
-    "~followup_annotations": [
-      "@@@STEP_LOG_LINE@json.output@{@@@",
-      "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
-      "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-123abc----------\",@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
-      "@@@STEP_LOG_LINE@json.output@      }@@@",
-      "@@@STEP_LOG_LINE@json.output@    ]@@@",
-      "@@@STEP_LOG_LINE@json.output@  }@@@",
-      "@@@STEP_LOG_LINE@json.output@}@@@",
-      "@@@STEP_LOG_END@json.output@@@"
-    ]
-  },
-  {
-    "cmd": [
-      "[CLEANUP]/tmp_tmp_2/osx_sdk/mac_toolchain",
-      "install",
-      "-kind",
-      "mac",
-      "-xcode-version",
-      "deadbeef",
-      "-output-dir",
-      "/opt/flutter/xcode/deadbeef/XCode.app",
-      "-cipd-package-prefix",
-      "flutter_internal/ios/xcode",
-      "-with-runtime=False"
-    ],
-    "infra_step": true,
-    "name": "install xcode (4)"
-  },
-  {
-    "cmd": [
-      "killall",
-      "-9",
-      "com.apple.CoreSimulator.CoreSimulatorDevice"
-    ],
-    "infra_step": true,
-    "name": "kill dart (4)"
-  },
-  {
-    "cmd": [
-      "sudo",
-      "xcode-select",
-      "--switch",
-      "/opt/flutter/xcode/deadbeef/XCode.app"
-    ],
-    "infra_step": true,
-    "name": "select XCode (4)"
-  },
-  {
-    "cmd": [
-      "xcrun",
-      "simctl",
-      "list"
-    ],
-    "infra_step": true,
-    "name": "list simulators (4)"
-  },
-  {
-    "cmd": [
       "gn",
       "gen",
       "out/Release"
diff --git a/recipe_modules/osx_sdk/examples/full.py b/recipe_modules/osx_sdk/examples/full.py
index de89cdf..ca8ff74 100644
--- a/recipe_modules/osx_sdk/examples/full.py
+++ b/recipe_modules/osx_sdk/examples/full.py
@@ -9,6 +9,7 @@
     'recipe_engine/path',
     'recipe_engine/platform',
     'recipe_engine/properties',
+    'recipe_engine/raw_io',
     'recipe_engine/step',
 ]
 
@@ -53,6 +54,14 @@
               }
           }
       ),
+      api.step_data(
+          'list runtimes',
+          stdout=api.raw_io.output_text(
+              '== Runtimes ==\n' +
+              'iOS 13.0 - com.apple.CoreSimulator.SimRuntime.iOS-13-0\n' +
+              'iOS 14.0 - com.apple.CoreSimulator.SimRuntime.iOS-14-0'
+          )
+      ),
       api.os_utils.is_symlink(False),
   )