Add context support for cipd recipe.

Ruby packages require xcode to build the dependencies.

Bug: https://github.com/flutter/flutter/issues/136377
Change-Id: Id73326fd6f7643ba1beb2966e4d89fcdbe80dbdc
Reviewed-on: https://flutter-review.googlesource.com/c/recipes/+/51561
Reviewed-by: Keyong Han <keyonghan@google.com>
Commit-Queue: Godofredo Contreras <godofredoc@google.com>
diff --git a/recipe_modules/flutter_deps/api.py b/recipe_modules/flutter_deps/api.py
index 576f3cf..9c39860 100644
--- a/recipe_modules/flutter_deps/api.py
+++ b/recipe_modules/flutter_deps/api.py
@@ -715,6 +715,7 @@
         'metric_center_token': self.m.token_util.metric_center_token,
         'android_virtual_device': self.m.android_virtual_device,
         'osx_sdk': self.m.osx_sdk,
+        'osx_sdk_devicelab': self.m.osx_sdk,
     }
 
   def enter_contexts(self, exit_stack, contexts, env, env_prefixes):
@@ -727,8 +728,10 @@
       env_prefixes(dict):  Current environment prefixes variables.
     """
     available_contexts = self.contexts()
+    params = (env, env_prefixes)
     for context in contexts:
-      exit_stack.enter_context(
-          available_contexts[context](env, env_prefixes)
-          if context != 'osx_sdk' else available_contexts[context]('ios')
-      )
+      if context == 'osx_sdk':
+        params = ('ios',)
+      if context == 'osx_sdk_devicelab':
+        params = ('ios', True)
+      exit_stack.enter_context(available_contexts[context](*params))
diff --git a/recipe_modules/flutter_deps/examples/full.expected/mac.json b/recipe_modules/flutter_deps/examples/full.expected/mac.json
index e2f7d88..f6761e7 100644
--- a/recipe_modules/flutter_deps/examples/full.expected/mac.json
+++ b/recipe_modules/flutter_deps/examples/full.expected/mac.json
@@ -1065,6 +1065,190 @@
   },
   {
     "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "/opt/flutter/xcode/9f2000"
+    ],
+    "infra_step": true,
+    "name": "Cleaning up Xcode cache (2)"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_6/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (9)",
+    "~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": [
+      "[CLEANUP]/tmp_tmp_6/osx_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "ios",
+      "-xcode-version",
+      "9f2000",
+      "-output-dir",
+      "/opt/flutter/xcode/9f2000/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=True"
+    ],
+    "infra_step": true,
+    "name": "install xcode (3)"
+  },
+  {
+    "cmd": [
+      "killall",
+      "-9",
+      "com.apple.CoreSimulator.CoreSimulatorDevice"
+    ],
+    "infra_step": true,
+    "name": "kill dart (3)"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--switch",
+      "/opt/flutter/xcode/9f2000/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode (3)"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "simctl",
+      "list"
+    ],
+    "infra_step": true,
+    "name": "list simulators (3)"
+  },
+  {
+    "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/9f2000"
+    ],
+    "infra_step": true,
+    "name": "Cleaning up Xcode cache (3)"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_7/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (10)",
+    "~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": [
+      "[CLEANUP]/tmp_tmp_7/osx_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "ios",
+      "-xcode-version",
+      "9f2000",
+      "-output-dir",
+      "/opt/flutter/xcode/9f2000/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=True"
+    ],
+    "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/9f2000/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode (4)"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "simctl",
+      "list"
+    ],
+    "infra_step": true,
+    "name": "list simulators (4)"
+  },
+  {
+    "cmd": [
       "sudo",
       "xcode-select",
       "--reset"
@@ -1074,6 +1258,15 @@
   },
   {
     "cmd": [
+      "sudo",
+      "xcode-select",
+      "--reset"
+    ],
+    "infra_step": true,
+    "name": "reset XCode (2)"
+  },
+  {
+    "cmd": [
       "git",
       "rev-parse",
       "HEAD"
diff --git a/recipe_modules/flutter_deps/examples/full.expected/mac_old.json b/recipe_modules/flutter_deps/examples/full.expected/mac_old.json
index b8bfe82..bab760c 100644
--- a/recipe_modules/flutter_deps/examples/full.expected/mac_old.json
+++ b/recipe_modules/flutter_deps/examples/full.expected/mac_old.json
@@ -1065,6 +1065,190 @@
   },
   {
     "cmd": [
+      "vpython3",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "/opt/flutter/xcode/9f2000"
+    ],
+    "infra_step": true,
+    "name": "Cleaning up Xcode cache (2)"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_6/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (9)",
+    "~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": [
+      "[CLEANUP]/tmp_tmp_6/osx_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "ios",
+      "-xcode-version",
+      "9f2000",
+      "-output-dir",
+      "/opt/flutter/xcode/9f2000/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=True"
+    ],
+    "infra_step": true,
+    "name": "install xcode (3)"
+  },
+  {
+    "cmd": [
+      "killall",
+      "-9",
+      "com.apple.CoreSimulator.CoreSimulatorDevice"
+    ],
+    "infra_step": true,
+    "name": "kill dart (3)"
+  },
+  {
+    "cmd": [
+      "sudo",
+      "xcode-select",
+      "--switch",
+      "/opt/flutter/xcode/9f2000/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode (3)"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "simctl",
+      "list"
+    ],
+    "infra_step": true,
+    "name": "list simulators (3)"
+  },
+  {
+    "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/9f2000"
+    ],
+    "infra_step": true,
+    "name": "Cleaning up Xcode cache (3)"
+  },
+  {
+    "cmd": [
+      "cipd",
+      "ensure",
+      "-root",
+      "[CLEANUP]/tmp_tmp_7/osx_sdk",
+      "-ensure-file",
+      "infra/tools/mac_toolchain/${platform} latest",
+      "-max-threads",
+      "0",
+      "-json-output",
+      "/path/to/tmp/json"
+    ],
+    "infra_step": true,
+    "name": "ensure_installed (10)",
+    "~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": [
+      "[CLEANUP]/tmp_tmp_7/osx_sdk/mac_toolchain",
+      "install",
+      "-kind",
+      "ios",
+      "-xcode-version",
+      "9f2000",
+      "-output-dir",
+      "/opt/flutter/xcode/9f2000/XCode.app",
+      "-cipd-package-prefix",
+      "flutter_internal/ios/xcode",
+      "-with-runtime=True"
+    ],
+    "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/9f2000/XCode.app"
+    ],
+    "infra_step": true,
+    "name": "select XCode (4)"
+  },
+  {
+    "cmd": [
+      "xcrun",
+      "simctl",
+      "list"
+    ],
+    "infra_step": true,
+    "name": "list simulators (4)"
+  },
+  {
+    "cmd": [
       "sudo",
       "xcode-select",
       "--reset"
@@ -1074,6 +1258,15 @@
   },
   {
     "cmd": [
+      "sudo",
+      "xcode-select",
+      "--reset"
+    ],
+    "infra_step": true,
+    "name": "reset XCode (2)"
+  },
+  {
+    "cmd": [
       "git",
       "rev-parse",
       "HEAD"
diff --git a/recipe_modules/flutter_deps/examples/full.py b/recipe_modules/flutter_deps/examples/full.py
index 246c52d..a5a746e 100644
--- a/recipe_modules/flutter_deps/examples/full.py
+++ b/recipe_modules/flutter_deps/examples/full.py
@@ -75,6 +75,9 @@
   api.flutter_deps.contexts()
   with contextlib.ExitStack() as exit_stack:
     api.flutter_deps.enter_contexts(exit_stack, ['osx_sdk'], env, env_prefixes)
+    api.flutter_deps.enter_contexts(
+        exit_stack, ['osx_sdk_devicelab'], env, env_prefixes
+    )
   if api.platform.is_linux:
     api.flutter_deps.gh_cli(env, env_prefixes, 'latest')
 
diff --git a/recipes/cocoon/cipd.expected/cipd_mac_no_upload.json b/recipes/cocoon/cipd.expected/cipd_mac_no_upload.json
index 09cb35d..2207027 100644
--- a/recipes/cocoon/cipd.expected/cipd_mac_no_upload.json
+++ b/recipes/cocoon/cipd.expected/cipd_mac_no_upload.json
@@ -229,7 +229,6 @@
       "-json-output",
       "/path/to/tmp/json"
     ],
-    "cwd": "[START_DIR]/cocoon/cipd_packages/codesign",
     "luci_context": {
       "realm": {
         "name": "project:try"
diff --git a/recipes/cocoon/cipd.expected/cipd_win_upload.json b/recipes/cocoon/cipd.expected/cipd_win_upload.json
index 3af72fc..5c64912 100644
--- a/recipes/cocoon/cipd.expected/cipd_win_upload.json
+++ b/recipes/cocoon/cipd.expected/cipd_win_upload.json
@@ -228,7 +228,6 @@
       "-json-output",
       "/path/to/tmp/json"
     ],
-    "cwd": "[START_DIR]\\cocoon\\cipd_packages\\device_doctor",
     "luci_context": {
       "realm": {
         "name": "project:prod"
@@ -262,7 +261,6 @@
       "-json-output",
       "/path/to/tmp/json"
     ],
-    "cwd": "[START_DIR]\\cocoon\\cipd_packages\\device_doctor",
     "luci_context": {
       "realm": {
         "name": "project:prod"
diff --git a/recipes/cocoon/cipd.py b/recipes/cocoon/cipd.py
index 99210a1..610ec80 100644
--- a/recipes/cocoon/cipd.py
+++ b/recipes/cocoon/cipd.py
@@ -2,12 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import contextlib
+
 from platform import platform
 from recipe_engine.recipe_api import Property
 
 from RECIPE_MODULES.flutter.repo_util.api import REPOS
 
 DEPS = [
+    'flutter/flutter_deps',
     'flutter/repo_util',
     'flutter/yaml',
     'recipe_engine/buildbucket',
@@ -42,7 +45,6 @@
   #       add_recipes_cq: "true"
   #     script: device_doctor/tool/build.sh
   #     cipd_name: flutter/device_doctor/mac-amd64
-
   script_path_list = api.properties.get('script')
   cipd_full_name = api.properties.get('cipd_name')
   project_name = cipd_full_name.split('/')[1]
@@ -51,17 +53,24 @@
   build_file = cocoon_dir.join(script_path_list)
 
   cmd = [build_file]
-  if not api.platform.is_win:
-    cmd = ['bash', build_file]
+  env = {}
+  env_prefixes = {}
+  with contextlib.ExitStack() as exit_stack:
+    api.flutter_deps.enter_contexts(
+        exit_stack, api.properties.get('contexts', []), env, env_prefixes
+    )
+    with api.context(env=env, env_prefixes=env_prefixes):
+      if not api.platform.is_win:
+        cmd = ['bash', build_file]
 
-  with api.context(cwd=project_path):
-    api.step('build package', cmd)
+      with api.context(cwd=project_path):
+        api.step('build package', cmd)
 
-    cipd_zip_path = project_name + '.zip'
+  cipd_zip_path = project_name + '.zip'
 
-    api.cipd.build(project_path.join('build'), cipd_zip_path, cipd_full_name)
-    if api.buildbucket.build.builder.bucket == 'prod' and should_upload:
-      api.cipd.register(cipd_full_name, cipd_zip_path, refs=["latest"])
+  api.cipd.build(project_path.join('build'), cipd_zip_path, cipd_full_name)
+  if api.buildbucket.build.builder.bucket == 'prod' and should_upload:
+    api.cipd.register(cipd_full_name, cipd_zip_path, refs=["latest"])
 
 
 def GenTests(api):