#!/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())