api.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. # Copyright 2018 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """The `osx_sdk` module provides safe functions to access a semi-hermetic
  5. XCode installation.
  6. Available only to Google-run bots."""
  7. from contextlib import contextmanager
  8. from recipe_engine import recipe_api
  9. # TODO(iannucci): replace this with something sane when PROPERTIES is
  10. # implemented with a proto message.
  11. _PROPERTY_DEFAULTS = {
  12. 'toolchain_pkg': 'infra/tools/mac_toolchain/${platform}',
  13. 'toolchain_ver': 'git_revision:3e597065cb23c1fe03aeb2ebd792d83e0709c5c2',
  14. }
  15. # Rationalized from https://en.wikipedia.org/wiki/Xcode.
  16. #
  17. # Maps from OS version to the maximum supported version of Xcode for that OS.
  18. #
  19. # Keep this sorted by OS version.
  20. _DEFAULT_VERSION_MAP = [
  21. ('10.12.6', '9c40b'),
  22. ('10.13.2', '9f2000'),
  23. ('10.13.6', '10b61'),
  24. ('10.14.3', '10g8'),
  25. ('10.14.4', '11b52'),
  26. ('10.15.4', '12d4e'),
  27. ('11.3', '13a233'),
  28. ('13.3', '14c18')
  29. ]
  30. class OSXSDKApi(recipe_api.RecipeApi):
  31. """API for using OS X SDK distributed via CIPD."""
  32. def __init__(self, sdk_properties, *args, **kwargs):
  33. super(OSXSDKApi, self).__init__(*args, **kwargs)
  34. self._sdk_properties = _PROPERTY_DEFAULTS.copy()
  35. self._sdk_properties.update(sdk_properties)
  36. self._sdk_version = None
  37. self._tool_pkg = self._sdk_properties['toolchain_pkg']
  38. self._tool_ver = self._sdk_properties['toolchain_ver']
  39. def initialize(self):
  40. if not self.m.platform.is_mac:
  41. return
  42. if 'sdk_version' in self._sdk_properties:
  43. self._sdk_version = self._sdk_properties['sdk_version'].lower()
  44. else:
  45. cur_os = self.m.platform.mac_release
  46. for target_os, xcode in reversed(_DEFAULT_VERSION_MAP):
  47. if cur_os >= self.m.version.parse(target_os):
  48. self._sdk_version = xcode
  49. break
  50. else:
  51. self._sdk_version = _DEFAULT_VERSION_MAP[0][-1]
  52. @contextmanager
  53. def __call__(self, kind):
  54. """Sets up the XCode SDK environment.
  55. Is a no-op on non-mac platforms.
  56. This will deploy the helper tool and the XCode.app bundle at
  57. `[START_DIR]/cache/osx_sdk`.
  58. To avoid machines rebuilding these on every run, set up a named cache in
  59. your cr-buildbucket.cfg file like:
  60. caches: {
  61. # Cache for mac_toolchain tool and XCode.app
  62. name: "osx_sdk"
  63. path: "osx_sdk"
  64. }
  65. If you have builders which e.g. use a non-current SDK, you can give them
  66. a uniqely named cache:
  67. caches: {
  68. # Cache for N-1 version mac_toolchain tool and XCode.app
  69. name: "osx_sdk_old"
  70. path: "osx_sdk"
  71. }
  72. Similarly, if you have mac and iOS builders you may want to distinguish the
  73. cache name by adding '_ios' to it. However, if you're sharing the same bots
  74. for both mac and iOS, consider having a single cache and just always
  75. fetching the iOS version. This will lead to lower overall disk utilization
  76. and should help to reduce cache thrashing.
  77. Usage:
  78. with api.osx_sdk('mac'):
  79. # sdk with mac build bits
  80. with api.osx_sdk('ios'):
  81. # sdk with mac+iOS build bits
  82. Args:
  83. kind ('mac'|'ios'): How the SDK should be configured. iOS includes the
  84. base XCode distribution, as well as the iOS simulators (which can be
  85. quite large).
  86. Raises:
  87. StepFailure or InfraFailure.
  88. """
  89. assert kind in ('mac', 'ios'), 'Invalid kind %r' % (kind,)
  90. if not self.m.platform.is_mac:
  91. yield
  92. return
  93. try:
  94. with self.m.context(infra_steps=True):
  95. app = self._ensure_sdk(kind)
  96. self.m.step('select XCode', ['sudo', 'xcode-select', '--switch', app])
  97. yield
  98. finally:
  99. with self.m.context(infra_steps=True):
  100. self.m.step('reset XCode', ['sudo', 'xcode-select', '--reset'])
  101. def _ensure_sdk(self, kind):
  102. """Ensures the mac_toolchain tool and OS X SDK packages are installed.
  103. Returns Path to the installed sdk app bundle."""
  104. cache_dir = self.m.path['cache'].join('osx_sdk')
  105. ef = self.m.cipd.EnsureFile()
  106. ef.add_package(self._tool_pkg, self._tool_ver)
  107. self.m.cipd.ensure(cache_dir, ef)
  108. sdk_app = cache_dir.join('XCode.app')
  109. self.m.step('install xcode', [
  110. cache_dir.join('mac_toolchain'), 'install',
  111. '-kind', kind,
  112. '-xcode-version', self._sdk_version,
  113. '-output-dir', sdk_app,
  114. ])
  115. return sdk_app