| #!/usr/bin/env python |
| # Copyright 2018 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| If should_use_hermetic_xcode.py emits "1", and the current toolchain is out of |
| date: |
| * Downloads the hermetic mac toolchain |
| * Requires CIPD authentication. Run `cipd auth-login`, use Google account. |
| * Accepts the license. |
| * If xcode-select and xcodebuild are not passwordless in sudoers, requires |
| user interaction. |
| |
| The toolchain version can be overridden by setting MAC_TOOLCHAIN_REVISION with |
| the full revision, e.g. 9A235. |
| """ |
| |
| import os |
| import platform |
| import shutil |
| import subprocess |
| import sys |
| |
| |
| # This can be changed after running: |
| # mac_toolchain upload -xcode-path path/to/Xcode.app |
| MAC_TOOLCHAIN_VERSION = '8E2002' |
| |
| # The toolchain will not be downloaded if the minimum OS version is not met. |
| # 16 is the major version number for macOS 10.12. |
| MAC_MINIMUM_OS_VERSION = 16 |
| |
| MAC_TOOLCHAIN_INSTALLER = 'mac_toolchain' |
| |
| # Absolute path to src/ directory. |
| REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| |
| # Absolute path to a file with gclient solutions. |
| GCLIENT_CONFIG = os.path.join(os.path.dirname(REPO_ROOT), '.gclient') |
| |
| BASE_DIR = os.path.abspath(os.path.dirname(__file__)) |
| TOOLCHAIN_ROOT = os.path.join(BASE_DIR, 'mac_files') |
| TOOLCHAIN_BUILD_DIR = os.path.join(TOOLCHAIN_ROOT, 'Xcode.app') |
| STAMP_FILE = os.path.join(TOOLCHAIN_ROOT, 'toolchain_build_revision') |
| |
| |
| def PlatformMeetsHermeticXcodeRequirements(): |
| return int(platform.release().split('.')[0]) >= MAC_MINIMUM_OS_VERSION |
| |
| |
| def _UseHermeticToolchain(): |
| current_dir = os.path.dirname(os.path.realpath(__file__)) |
| script_path = os.path.join(current_dir, 'mac/should_use_hermetic_xcode.py') |
| proc = subprocess.Popen([script_path, 'mac'], stdout=subprocess.PIPE) |
| return '1' in proc.stdout.readline() |
| |
| |
| def RequestCipdAuthentication(): |
| """Requests that the user authenticate to access Xcode CIPD packages.""" |
| |
| print 'Access to Xcode CIPD package requires authentication.' |
| print '-----------------------------------------------------------------' |
| print |
| print 'You appear to be a Googler.' |
| print |
| print 'I\'m sorry for the hassle, but you may need to do a one-time manual' |
| print 'authentication. Please run:' |
| print |
| print ' cipd auth-login' |
| print |
| print 'and follow the instructions.' |
| print |
| print 'NOTE: Use your google.com credentials, not chromium.org.' |
| print |
| print '-----------------------------------------------------------------' |
| print |
| sys.stdout.flush() |
| |
| |
| def PrintError(message): |
| # Flush buffers to ensure correct output ordering. |
| sys.stdout.flush() |
| sys.stderr.write(message + '\n') |
| sys.stderr.flush() |
| |
| |
| def InstallXcode(xcode_build_version, installer_cmd, xcode_app_path): |
| """Installs the requested Xcode build version. |
| |
| Args: |
| xcode_build_version: (string) Xcode build version to install. |
| installer_cmd: (string) Path to mac_toolchain command to install Xcode. |
| See https://chromium.googlesource.com/infra/infra/+/master/go/src/infra/cmd/mac_toolchain/ |
| xcode_app_path: (string) Path to install the contents of Xcode.app. |
| |
| Returns: |
| True if installation was successful. False otherwise. |
| """ |
| args = [ |
| installer_cmd, 'install', |
| '-kind', 'mac', |
| '-xcode-version', xcode_build_version.lower(), |
| '-output-dir', xcode_app_path, |
| ] |
| |
| # Buildbot slaves need to use explicit credentials. LUCI bots should NOT set |
| # this variable. |
| creds = os.environ.get('MAC_TOOLCHAIN_CREDS') |
| if creds: |
| args.extend(['--service-account-json', creds]) |
| |
| try: |
| subprocess.check_call(args) |
| except subprocess.CalledProcessError as e: |
| PrintError('Xcode build version %s failed to install: %s\n' % ( |
| xcode_build_version, e)) |
| RequestCipdAuthentication() |
| return False |
| except OSError as e: |
| PrintError(('Xcode installer "%s" failed to execute' |
| ' (not on PATH or not installed).') % installer_cmd) |
| return False |
| |
| return True |
| |
| |
| def main(): |
| if sys.platform != 'darwin': |
| return 0 |
| |
| if not _UseHermeticToolchain(): |
| print 'Skipping Mac toolchain installation for mac' |
| return 0 |
| |
| if not PlatformMeetsHermeticXcodeRequirements(): |
| print 'OS version does not support toolchain.' |
| return 0 |
| |
| toolchain_version = os.environ.get('MAC_TOOLCHAIN_REVISION', |
| MAC_TOOLCHAIN_VERSION) |
| |
| # On developer machines, mac_toolchain tool is provided by |
| # depot_tools. On the bots, the recipe is responsible for installing |
| # it and providing the path to the executable. |
| installer_cmd = os.environ.get('MAC_TOOLCHAIN_INSTALLER', |
| MAC_TOOLCHAIN_INSTALLER) |
| |
| toolchain_root = TOOLCHAIN_ROOT |
| xcode_app_path = TOOLCHAIN_BUILD_DIR |
| stamp_file = STAMP_FILE |
| |
| # Delete the old "hermetic" installation if detected. |
| # TODO(crbug.com/797051): remove this once the old "hermetic" solution is no |
| # longer in use. |
| if os.path.exists(stamp_file): |
| print 'Detected old hermetic installation at %s. Deleting.' % ( |
| toolchain_root) |
| shutil.rmtree(toolchain_root) |
| |
| success = InstallXcode(toolchain_version, installer_cmd, xcode_app_path) |
| if not success: |
| return 1 |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |