Retry Xcode install and clear cache when install fails

Github Issue: https://github.com/flutter/flutter/issues/138238

Change-Id: If4e72597b2716daa974816fb3e12e599fc5b90e8
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/52421
Reviewed-by: Godofredo Contreras <godofredoc@google.com>
Commit-Queue: Victoria Ashworth <vashworth@google.com>
diff --git a/recipe_modules/osx_sdk/api.py b/recipe_modules/osx_sdk/api.py
index 43d73fa..417a6f8 100644
--- a/recipe_modules/osx_sdk/api.py
+++ b/recipe_modules/osx_sdk/api.py
@@ -228,17 +228,33 @@
     ef.add_package(self._tool_pkg, self._tool_ver)
     self.m.cipd.ensure(tool_dir, ef)
 
-  def _install_xcode(self, tool_dir, kind, app_dir):
+  def _try_install_xcode(self, tool_dir, kind, app_dir, devicelab):
+    """Installs xcode using mac_toolchain. If install fails, clear the cache and try again."""
+    try:
+      self._install_xcode(tool_dir, kind, app_dir, devicelab)
+    except self.m.step.StepFailure:
+      self._install_xcode(tool_dir, kind, app_dir, devicelab)
+
+  def _install_xcode(self, tool_dir, kind, app_dir, devicelab):
     """Installs xcode using mac_toolchain."""
-    self.m.step(
-        'install xcode',
-        [
-            tool_dir.join('mac_toolchain'), 'install', '-kind', kind,
-            '-xcode-version', self._sdk_version, '-output-dir', app_dir,
-            '-cipd-package-prefix', self._xcode_cipd_package_source,
-            '-with-runtime=%s' % (not bool(self._runtime_versions))
-        ],
-    )
+    try:
+      self._ensure_mac_toolchain(tool_dir)
+      self.m.step(
+          'install xcode',
+          [
+              tool_dir.join('mac_toolchain'), 'install', '-kind', kind,
+              '-xcode-version', self._sdk_version, '-output-dir', app_dir,
+              '-cipd-package-prefix', self._xcode_cipd_package_source,
+              '-with-runtime=%s' % (not bool(self._runtime_versions))
+          ],
+      )
+    except self.m.step.StepFailure:
+      self._cleanup_cache = True
+      self._clean_xcode_cache(devicelab)
+      self.m.step.empty(
+          'Failed to install Xcode',
+          status=self.m.step.INFRA_FAILURE,
+      )
 
   def _ensure_sdk(self, kind, devicelab):
     """Ensures the mac_toolchain tool and OSX SDK packages are installed.
@@ -246,9 +262,8 @@
     Returns Path to the installed sdk app bundle."""
     app_dir = self._xcode_dir(devicelab)
     tool_dir = self.m.path.mkdtemp().join('osx_sdk') if devicelab else app_dir
-    self._ensure_mac_toolchain(tool_dir)
     sdk_app = self.m.path.join(app_dir, 'XCode.app')
-    self._install_xcode(tool_dir, kind, sdk_app)
+    self._try_install_xcode(tool_dir, kind, sdk_app, devicelab)
 
     self._cleanup_runtimes_cache(sdk_app)
 
@@ -257,7 +272,10 @@
     return sdk_app
 
   def _show_xcode_cache(self):
-    self.m.step('Show xcode cache', ['ls', self.m.path['cache'].join(_XCODE_CACHE_PATH)])
+    self.m.step(
+        'Show xcode cache',
+        ['ls', self.m.path['cache'].join(_XCODE_CACHE_PATH)]
+    )
 
   def _install_runtimes(self, devicelab, app_dir, tool_dir, sdk_app, kind):
     '''Ensure runtimes are installed.
@@ -283,12 +301,12 @@
       # install Xcode, and then clean up the runtimes cache. It happens in this
       # order because removing runtimes requires Xcode developer tools so Xcode
       # must be installed first. However, when no explicit runtimes are defined,
-      # the runtime is also installed in the `_install_xcode` function. So after
+      # the runtime is also installed in the `_try_install_xcode` function. So after
       # cleaning the runtimes, the runtime that was installed may have been
-      # removed. So re-call `_install_xcode` to reinstall the removed runtime.
+      # removed. So re-call `_try_install_xcode` to reinstall the removed runtime.
       if self.macos_13_or_later and self._cleanup_cache:
         with self.m.step.nest('install runtimes'):
-          self._install_xcode(tool_dir, kind, sdk_app)
+          self._try_install_xcode(tool_dir, kind, sdk_app, devicelab)
       return
 
     if devicelab:
diff --git a/recipe_modules/osx_sdk/examples/full.expected/mac_13_arm_host.json b/recipe_modules/osx_sdk/examples/full.expected/mac_13_arm_host.json
index cab3e03..f8d0016 100644
--- a/recipe_modules/osx_sdk/examples/full.expected/mac_13_arm_host.json
+++ b/recipe_modules/osx_sdk/examples/full.expected/mac_13_arm_host.json
@@ -198,6 +198,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/osx_sdk/xcode_deadbeef",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} ARM_toolchain_verison",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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-ARM_toolchain_ve\",@@@",
+      "@@@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/mac_toolchain",
       "install",
       "-kind",
@@ -388,6 +418,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_1/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} ARM_toolchain_verison",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes (2).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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-ARM_toolchain_ve\",@@@",
+      "@@@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",
@@ -555,6 +615,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_2/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} ARM_toolchain_verison",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes (3).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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-ARM_toolchain_ve\",@@@",
+      "@@@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",
diff --git a/recipe_modules/osx_sdk/examples/full.expected/mac_13_cleanup_no_runtimes.json b/recipe_modules/osx_sdk/examples/full.expected/mac_13_cleanup_no_runtimes.json
index 46ae04b..4db7af5 100644
--- a/recipe_modules/osx_sdk/examples/full.expected/mac_13_cleanup_no_runtimes.json
+++ b/recipe_modules/osx_sdk/examples/full.expected/mac_13_cleanup_no_runtimes.json
@@ -114,6 +114,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/osx_sdk/xcode_deadbeef",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} 123abc",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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/mac_toolchain",
       "install",
       "-kind",
@@ -277,6 +307,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/osx_sdk/xcode_deadbeef",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} 123abc",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes (2).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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/mac_toolchain",
       "install",
       "-kind",
@@ -467,6 +527,36 @@
   },
   {
     "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": "install runtimes (3).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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",
@@ -634,6 +724,36 @@
   },
   {
     "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": "install runtimes (4).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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",
diff --git a/recipe_modules/osx_sdk/examples/full.expected/mac_13_x86_host.json b/recipe_modules/osx_sdk/examples/full.expected/mac_13_x86_host.json
index c4367a1..f615dd1 100644
--- a/recipe_modules/osx_sdk/examples/full.expected/mac_13_x86_host.json
+++ b/recipe_modules/osx_sdk/examples/full.expected/mac_13_x86_host.json
@@ -198,6 +198,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/osx_sdk/xcode_deadbeef",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} INTEL_toolchain_version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes.ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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-INTEL_toolchain_\",@@@",
+      "@@@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/mac_toolchain",
       "install",
       "-kind",
@@ -388,6 +418,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_1/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} INTEL_toolchain_version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes (2).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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-INTEL_toolchain_\",@@@",
+      "@@@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",
@@ -555,6 +615,36 @@
   },
   {
     "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_2/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} INTEL_toolchain_version",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "install runtimes (3).ensure_installed",
+    "~followup_annotations": [
+      "@@@STEP_NEST_LEVEL@1@@@",
+      "@@@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-INTEL_toolchain_\",@@@",
+      "@@@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",
diff --git a/recipe_modules/osx_sdk/examples/full.expected/xcode_install_fails.json b/recipe_modules/osx_sdk/examples/full.expected/xcode_install_fails.json
new file mode 100644
index 0000000..4ad9723
--- /dev/null
+++ b/recipe_modules/osx_sdk/examples/full.expected/xcode_install_fails.json
@@ -0,0 +1,163 @@
+[
+  {
+    "cmd": [
+      "ls",
+      "[CACHE]/osx_sdk"
+    ],
+    "infra_step": true,
+    "name": "Show xcode cache"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/osx_sdk/xcode_deadbeef",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed",
+    "~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-latest----------\",@@@",
+      "@@@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/mac_toolchain",
+      "install",
+      "-kind",
+      "mac",
+      "-xcode-version",
+      "deadbeef",
+      "-output-dir",
+      "[CACHE]/osx_sdk/xcode_deadbeef/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=True"
+    ],
+    "infra_step": true,
+    "name": "install xcode",
+    "~followup_annotations": [
+      "@@@STEP_EXCEPTION@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[CACHE]/osx_sdk/xcode_deadbeef"
+    ],
+    "infra_step": true,
+    "name": "Cleaning up Xcode cache"
+  },
+  {
+    "cmd": [],
+    "name": "Failed to install Xcode",
+    "~followup_annotations": [
+      "@@@STEP_EXCEPTION@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CACHE]/osx_sdk/xcode_deadbeef",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} latest",
+      "-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-latest----------\",@@@",
+      "@@@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/mac_toolchain",
+      "install",
+      "-kind",
+      "mac",
+      "-xcode-version",
+      "deadbeef",
+      "-output-dir",
+      "[CACHE]/osx_sdk/xcode_deadbeef/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=True"
+    ],
+    "infra_step": true,
+    "name": "install xcode (2)",
+    "~followup_annotations": [
+      "@@@STEP_EXCEPTION@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[CACHE]/osx_sdk/xcode_deadbeef"
+    ],
+    "infra_step": true,
+    "name": "Cleaning up Xcode cache (2)"
+  },
+  {
+    "cmd": [],
+    "name": "Failed to install Xcode (2)",
+    "~followup_annotations": [
+      "@@@STEP_EXCEPTION@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--reset"
+    ],
+    "infra_step": true,
+    "name": "reset XCode"
+  },
+  {
+    "failure": {
+      "humanReason": "Infra Failure: Step('Failed to install Xcode (2)') (retcode: 0)"
+    },
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/recipe_modules/osx_sdk/examples/full.py b/recipe_modules/osx_sdk/examples/full.py
index 049d083..aca7a3a 100644
--- a/recipe_modules/osx_sdk/examples/full.py
+++ b/recipe_modules/osx_sdk/examples/full.py
@@ -410,3 +410,12 @@
           }
       ),
   )
+
+  yield api.test(
+      'xcode_install_fails',
+      api.platform.name('mac'),
+      api.properties(**{'$flutter/osx_sdk': {'sdk_version': 'deadbeef',}}),
+      api.step_data('install xcode', retcode=1),
+      api.step_data('install xcode (2)', retcode=1),
+      status='INFRA_FAILURE'
+  )