xref: /aosp_15_r20/external/perfetto/infra/luci/recipe_modules/macos_sdk/api.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1# Copyright (C) 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""The `macos_sdk` module provides safe functions to access a semi-hermetic
15XCode installation.
16
17Available only to Google-run bots."""
18
19from contextlib import contextmanager
20
21from recipe_engine import recipe_api
22
23
24class MacOSSDKApi(recipe_api.RecipeApi):
25  """API for using OS X SDK distributed via CIPD."""
26
27  def __init__(self, sdk_properties, *args, **kwargs):
28    super(MacOSSDKApi, self).__init__(*args, **kwargs)
29
30    self._sdk_dir = None
31    self._sdk_version = sdk_properties['sdk_version'].lower()
32    self._tool_package = sdk_properties['tool_package']
33    self._tool_version = sdk_properties['tool_version']
34
35  @property
36  def sdk_dir(self):
37    assert self._sdk_dir
38    return self._sdk_dir
39
40  @contextmanager
41  def __call__(self):
42    """Sets up the XCode SDK environment.
43
44    This call is a no-op on non-Mac platforms.
45
46    This will deploy the helper tool and the XCode.app bundle at
47    `[START_DIR]/cache/macos_sdk`.
48
49    To avoid machines rebuilding these on every run, set up a named cache in
50    your cr-buildbucket.cfg file like:
51
52        caches: {
53          # Cache for mac_toolchain tool and XCode.app
54          name: "macos_sdk"
55          path: "macos_sdk"
56        }
57
58    If you have builders which e.g. use a non-current SDK, you can give them
59    a uniqely named cache:
60
61        caches: {
62          # Cache for N-1 version mac_toolchain tool and XCode.app
63          name: "macos_sdk_old"
64          path: "macos_sdk"
65        }
66
67    Usage:
68      with api.macos_sdk():
69        # sdk with mac build bits
70
71    Raises:
72        StepFailure or InfraFailure.
73    """
74    if not self.m.platform.is_mac:
75      yield
76      return
77
78    try:
79      with self.m.context(infra_steps=True):
80        self._sdk_dir = self._ensure_sdk()
81        self.m.step('select XCode',
82                    ['sudo', 'xcode-select', '--switch', self._sdk_dir])
83      yield
84    finally:
85      with self.m.context(infra_steps=True):
86        self.m.step('reset XCode', ['sudo', 'xcode-select', '--reset'])
87
88  def _ensure_sdk(self):
89    """Ensures the mac_toolchain tool and MacOS SDK packages are installed.
90
91    Returns Path to the installed sdk app bundle."""
92    cache_dir = self.m.path['cache'].join('macos_sdk')
93    pkgs = self.m.cipd.EnsureFile()
94    pkgs.add_package(self._tool_package, self._tool_version)
95    self.m.cipd.ensure(cache_dir, pkgs)
96
97    sdk_dir = cache_dir.join('XCode.app')
98    self.m.step('install xcode', [
99        cache_dir.join('mac_toolchain'),
100        'install',
101        '-kind',
102        'mac',
103        '-xcode-version',
104        self._sdk_version,
105        '-output-dir',
106        sdk_dir,
107    ])
108    return sdk_dir
109