blob: 457e5f2952464616367520cd68dbba30057ef1ca [file] [log] [blame]
# 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
class MacOSSDKApi(recipe_api.RecipeApi):
"""API for using OS X SDK distributed via CIPD."""
def __init__(self, sdk_dir, version, cipd_package, cipd_version, *args, **kwargs):
super(MacOSSDKApi, self).__init__(*args, **kwargs)
self._sdk_dir = sdk_dir
self._sdk_version = version.lower()
self._cipd_package = cipd_package
self._cipd_version = cipd_version
self._mac_toolchain = None
@property
def sdk_dir(self):
assert self._sdk_dir
return self._sdk_dir
@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 uniqely 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.
"""
if not self.m.platform.is_mac:
yield
return
try:
with self.m.context(infra_steps=True), self.m.step.nest(
"ensure XCode %s" % 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(
"select XCode %s" % self._sdk_version,
["sudo", "xcode-select", "--switch", self._sdk_dir],
)
yield
finally:
with self.m.context(infra_steps=True):
self.m.step("reset XCode", ["sudo", "xcode-select", "--reset"])
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(
"install XCode %s" % 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(
"install %s" % sdk_version,
xcode_dir.join("XCode.app", *sdk_path_part).join("MacOSX.sdk"),
sdk_path,
)