api.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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:59ddedfe3849abf560cbe0b41bb8e431041cd2bb',
  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', '13c100'),
  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. @contextmanager
  45. def __call__(self, kind):
  46. """Sets up the XCode SDK environment.
  47. Is a no-op on non-mac platforms.
  48. This will deploy the helper tool and the XCode.app bundle at
  49. `[START_DIR]/cache/osx_sdk`.
  50. To avoid machines rebuilding these on every run, set up a named cache in
  51. your cr-buildbucket.cfg file like:
  52. caches: {
  53. # Cache for mac_toolchain tool and XCode.app
  54. name: "osx_sdk"
  55. path: "osx_sdk"
  56. }
  57. If you have builders which e.g. use a non-current SDK, you can give them
  58. a uniqely named cache:
  59. caches: {
  60. # Cache for N-1 version mac_toolchain tool and XCode.app
  61. name: "osx_sdk_old"
  62. path: "osx_sdk"
  63. }
  64. Similarly, if you have mac and iOS builders you may want to distinguish the
  65. cache name by adding '_ios' to it. However, if you're sharing the same bots
  66. for both mac and iOS, consider having a single cache and just always
  67. fetching the iOS version. This will lead to lower overall disk utilization
  68. and should help to reduce cache thrashing.
  69. Usage:
  70. with api.osx_sdk('mac'):
  71. # sdk with mac build bits
  72. with api.osx_sdk('ios'):
  73. # sdk with mac+iOS build bits
  74. Args:
  75. kind ('mac'|'ios'): How the SDK should be configured. iOS includes the
  76. base XCode distribution, as well as the iOS simulators (which can be
  77. quite large).
  78. Raises:
  79. StepFailure or InfraFailure.
  80. """
  81. assert kind in ('mac', 'ios'), 'Invalid kind %r' % (kind,)
  82. if not self.m.platform.is_mac:
  83. yield
  84. return
  85. try:
  86. with self.m.context(infra_steps=True):
  87. app = self._ensure_sdk(kind)
  88. self.m.step('select XCode', ['sudo', 'xcode-select', '--switch', app])
  89. yield
  90. finally:
  91. with self.m.context(infra_steps=True):
  92. self.m.step('reset XCode', ['sudo', 'xcode-select', '--reset'])
  93. def _ensure_sdk(self, kind):
  94. """Ensures the mac_toolchain tool and OS X SDK packages are installed.
  95. Returns Path to the installed sdk app bundle."""
  96. cache_dir = self.m.path['cache'].join('osx_sdk')
  97. ef = self.m.cipd.EnsureFile()
  98. ef.add_package(self._tool_pkg, self._tool_ver)
  99. self.m.cipd.ensure(cache_dir, ef)
  100. if self._sdk_version is None:
  101. find_os = self.m.step(
  102. 'find macOS version', ['sw_vers', '-productVersion'],
  103. stdout=self.m.raw_io.output_text(),
  104. step_test_data=(
  105. lambda: self.m.raw_io.test_api.stream_output_text('14.4')))
  106. cur_os = self.m.version.parse(find_os.stdout.strip())
  107. find_os.presentation.step_text = f'Running on {str(cur_os)!r}.'
  108. for target_os, xcode in reversed(_DEFAULT_VERSION_MAP):
  109. if cur_os >= self.m.version.parse(target_os):
  110. self._sdk_version = xcode
  111. break
  112. else:
  113. self._sdk_version = _DEFAULT_VERSION_MAP[0][-1]
  114. sdk_app = cache_dir.join('XCode.app')
  115. self.m.step('install xcode', [
  116. cache_dir.join('mac_toolchain'), 'install',
  117. '-kind', kind,
  118. '-xcode-version', self._sdk_version,
  119. '-output-dir', sdk_app,
  120. ])
  121. return sdk_app