| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """The `macos_sdk` module provides safe functions to access a semi-hermetic XCode |
| installation. |
| |
| Available only to Google-run bots. |
| """ |
| |
| from contextlib import contextmanager |
| |
| from recipe_engine import recipe_api |
| |
| # Map OSX to default XCode versions. Recipes can override |
| # this value by setting macos_sdk.version. |
| # TODO(b/283993030): We should compress the Mac OSX versions |
| # to one. |
| DEFAULT_MAC_OS_SDK_VERSIONS = [ |
| ("10.13.5", "12D4e"), |
| ("11.6.1", "13C100"), |
| ("13.3.1", "14E222b"), |
| ] |
| MAC_TOOLCHAIN_CIPD_PACKAGE = "infra/tools/mac_toolchain/${platform}" |
| # Version from https://source.chromium.org/chromium/chromium/tools/build/+/bce9a4137e36c26527f15d72e903d8ee5a441164:recipes/recipe_modules/chromium/config.py;l=227;bpv=1 |
| MAC_TOOLCHAIN_CIPD_VERSION = "git_revision:3e597065cb23c1fe03aeb2ebd792d83e0709c5c2" |
| |
| |
| class MacOSSDKApi(recipe_api.RecipeApi): |
| """API for using OS X SDK distributed via CIPD.""" |
| |
| def __init__(self, props, *args, **kwargs): |
| super().__init__(*args, **kwargs) |
| |
| self._sdk_dir = props.sdk_dir |
| self._sdk_version = props.version |
| self._cipd_package = props.cipd_package or MAC_TOOLCHAIN_CIPD_PACKAGE |
| self._cipd_version = props.cipd_version or MAC_TOOLCHAIN_CIPD_VERSION |
| |
| self._sysroot = None |
| self._mac_toolchain = None |
| |
| # Whether the XCode SDK environment is currently set up. Read and |
| # modified only by __call__. |
| self._enabled = False |
| |
| @property |
| def sdk_dir(self): |
| assert self._sdk_dir |
| return self._sdk_dir |
| |
| @property |
| def sysroot(self): |
| assert self._sysroot |
| return self._sysroot |
| |
| @contextmanager |
| def __call__(self, kind="mac", additional_sdks=None): |
| """Sets up the XCode SDK environment. |
| |
| This call is a no-op on non-Mac platforms. |
| |
| This will deploy the helper tool and the XCode.app bundle at |
| `[START_DIR]/cache/macos_sdk`. |
| |
| To avoid machines rebuilding these on every run, set up a named cache in |
| your cr-buildbucket.cfg file like: |
| |
| caches: { |
| # Cache for mac_toolchain tool and XCode.app |
| name: "macos_sdk" |
| path: "macos_sdk" |
| } |
| |
| If you have builders which e.g. use a non-current SDK, you can give them |
| a uniquely named cache: |
| |
| caches: { |
| # Cache for N-1 version mac_toolchain tool and XCode.app |
| name: "macos_sdk_old" |
| path: "macos_sdk" |
| } |
| |
| Usage: |
| with api.macos_sdk(): |
| # sdk with mac build bits |
| |
| Args: |
| kind (str): A type of SDK to install, can be mac or ios (default: mac). |
| additional_sdks (List(tuple)): A list of (sdk_version, xcode_version) |
| where xcode_version contains the sdk_version to be installed in the |
| selected XCode. |
| |
| Raises: |
| StepFailure or InfraFailure. |
| """ |
| # No need to do anything if we're not running on a Mac, or if we're |
| # already running in a macos_sdk context. |
| if not self.m.platform.is_mac or self._enabled: |
| yield |
| return |
| self._enabled = True |
| if not self._sdk_version: |
| current_os = self.m.platform.mac_release |
| for target_os, xcode in reversed(DEFAULT_MAC_OS_SDK_VERSIONS): |
| if current_os >= self.m.version.parse(target_os): |
| self._sdk_version = xcode |
| break |
| if not self._sdk_version: |
| raise self.m.step.InfraFailure( |
| f"Unable to find an XCode version for {current_os}." |
| ) |
| self._sdk_version = self._sdk_version.lower() |
| try: |
| with self.m.context(infra_steps=True), self.m.step.nest( |
| f"ensure XCode {self._sdk_version}" |
| ): |
| cache_dir = self.m.path["cache"].join("macos_sdk") |
| if not self._mac_toolchain: |
| self._mac_toolchain = self.m.cipd.ensure_tool( |
| self._cipd_package, self._cipd_version |
| ) |
| if not self._sdk_dir: |
| self._sdk_dir = self._ensure_xcode( |
| kind, self._sdk_version, cache_dir |
| ) |
| for sdk_version, xcode_version in additional_sdks or []: |
| self._ensure_sdk(kind, sdk_version, xcode_version, cache_dir) |
| self.m.step( |
| f"select XCode {self._sdk_version}", |
| ["sudo", "xcode-select", "--switch", self._sdk_dir], |
| ) |
| assert self._sysroot is None |
| self._sysroot = self._get_sysroot() |
| yield |
| finally: |
| self._enabled = False |
| if not self.m.runtime.in_global_shutdown: |
| self.m.step( |
| "reset XCode", ["sudo", "xcode-select", "--reset"], infra_step=True |
| ) |
| self._sysroot = None |
| |
| def _ensure_xcode(self, kind, version, dir): |
| """Ensures the MacOS SDK packages are installed. |
| |
| Args: |
| kind (str): A type of SDK to install, can be mac or ios. |
| version (str): The XCode version to install. |
| dir (str): The directory to install XCode into. |
| |
| Returns: |
| Path to the installed XCode bundle. |
| """ |
| assert kind in ("mac", "ios"), "invalid kind" |
| sdk_dir = dir.join("XCode.app") |
| self.m.step( |
| f"install XCode {version}", |
| [ |
| self._mac_toolchain, |
| "install", |
| "-kind", |
| kind, |
| "-xcode-version", |
| version, |
| "-output-dir", |
| sdk_dir, |
| ], |
| ) |
| return sdk_dir |
| |
| def _ensure_sdk(self, kind, sdk_version, xcode_version, cache_dir): |
| """Ensures SDK package is installed. |
| |
| Args: |
| kind (str): A type of SDK to install, can be mac or ios. |
| sdk_version (str): The SDK to install. |
| xcode_version (str): The XCode version that contains the SDK. |
| cache_dir (str): The directory to install XCode into. |
| """ |
| xcode_dir = cache_dir.join(xcode_version) |
| sdk_path_part = [ |
| "Contents", |
| "Developer", |
| "Platforms", |
| "MacOSX.platform", |
| "Developer", |
| "SDKs", |
| ] |
| sdk_path = cache_dir.join("XCode.app", *sdk_path_part).join(sdk_version) |
| if not self.m.path.exists(sdk_path): |
| self._ensure_xcode(kind, xcode_version, xcode_dir) |
| self.m.file.symlink( |
| f"install {sdk_version}", |
| xcode_dir.join("XCode.app", *sdk_path_part).join("MacOSX.sdk"), |
| sdk_path, |
| ) |
| |
| def _get_sysroot(self): |
| step = self.m.step( |
| "xcrun", |
| [ |
| "xcrun", |
| "--sdk", |
| "macosx", |
| "--show-sdk-path", |
| ], |
| stdout=self.m.raw_io.output_text( |
| name="sdk-path", |
| add_output_log=True, |
| ), |
| step_test_data=lambda: self.m.raw_io.test_api.stream_output_text( |
| "/some/xcode/path" |
| ), |
| ) |
| return step.stdout.strip() |