Replace hermetic Xcode installation with CIPD-based flow.

The 'API' to the scripts remains the same, only the delivery mechanism changes.

BUG=797051
R=erikchen@chromium.org, justincohen@chromium.org

Change-Id: I8ee5486b107061f9fb6e64354463ac51de53d4cc
Reviewed-on: https://chromium-review.googlesource.com/887819
Reviewed-by: John Budorick <jbudorick@chromium.org>
Reviewed-by: Erik Chen <erikchen@chromium.org>
Commit-Queue: Sergey Berezin <sergeyberezin@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#553871}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 116dd7c37d473b6be5d34812035c4f14aa360efa
This commit is contained in:
Sergey Berezin 2018-04-26 01:46:08 +00:00 коммит произвёл Commit Bot
Родитель c80ec2841b
Коммит ad615bae42
2 изменённых файлов: 85 добавлений и 199 удалений

Просмотреть файл

@ -30,7 +30,7 @@ def _IsCorpMachine():
def main():
allow_corp = sys.argv[1] == 'mac' and _IsCorpMachine()
if os.environ.get('FORCE_MAC_TOOLCHAIN') or allow_corp:
if not mac_toolchain.PlatformMeetsHermeticXcodeRequirements(sys.argv[1]):
if not mac_toolchain.PlatformMeetsHermeticXcodeRequirements():
return "2"
return "1"
else:

Просмотреть файл

@ -1,5 +1,5 @@
#!/usr/bin/env python
# Copyright 2016 The Chromium Authors. All rights reserved.
# 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.
@ -7,40 +7,31 @@
If should_use_hermetic_xcode.py emits "1", and the current toolchain is out of
date:
* Downloads the hermetic mac toolchain
* Requires gsutil to be configured.
* 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 IOS_TOOLCHAIN_REVISION or
MAC_TOOLCHAIN_REVISION with the full revision, e.g. 9A235-1.
The toolchain version can be overridden by setting MAC_TOOLCHAIN_REVISION with
the full revision, e.g. 9A235.
"""
from distutils.version import LooseVersion
import os
import platform
import plistlib
import shutil
import subprocess
import sys
import tarfile
import time
import tempfile
import urllib2
# This can be changed after running /build/package_mac_toolchain.py.
# This can be changed after running:
# mac_toolchain upload -xcode-path path/to/Xcode.app
MAC_TOOLCHAIN_VERSION = '8E2002'
MAC_TOOLCHAIN_SUB_REVISION = 3
MAC_TOOLCHAIN_VERSION = '%s-%s' % (MAC_TOOLCHAIN_VERSION,
MAC_TOOLCHAIN_SUB_REVISION)
# 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
IOS_TOOLCHAIN_VERSION = '9C40b'
IOS_TOOLCHAIN_SUB_REVISION = 1
IOS_TOOLCHAIN_VERSION = '%s-%s' % (IOS_TOOLCHAIN_VERSION,
IOS_TOOLCHAIN_SUB_REVISION)
MAC_TOOLCHAIN_INSTALLER = 'mac_toolchain'
# Absolute path to src/ directory.
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@ -49,226 +40,121 @@ REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
GCLIENT_CONFIG = os.path.join(os.path.dirname(REPO_ROOT), '.gclient')
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
TOOLCHAIN_BUILD_DIR = os.path.join(BASE_DIR, '%s_files', 'Xcode.app')
STAMP_FILE = os.path.join(BASE_DIR, '%s_files', 'toolchain_build_revision')
TOOLCHAIN_URL = 'gs://chrome-mac-sdk/'
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(target_os):
if target_os == 'ios':
return True
def PlatformMeetsHermeticXcodeRequirements():
return int(platform.release().split('.')[0]) >= MAC_MINIMUM_OS_VERSION
def GetPlatforms():
target_os = set(['mac'])
try:
env = {}
execfile(GCLIENT_CONFIG, env, env)
target_os |= set(env.get('target_os', target_os))
except:
pass
return target_os
def ReadStampFile(target_os):
"""Return the contents of the stamp file, or '' if it doesn't exist."""
try:
with open(STAMP_FILE % target_os, 'r') as f:
return f.read().rstrip()
except IOError:
return ''
def WriteStampFile(target_os, s):
"""Write s to the stamp file."""
EnsureDirExists(os.path.dirname(STAMP_FILE % target_os))
with open(STAMP_FILE % target_os, 'w') as f:
f.write(s)
f.write('\n')
def EnsureDirExists(path):
if not os.path.exists(path):
os.makedirs(path)
def DownloadAndUnpack(url, output_dir):
"""Decompresses |url| into a cleared |output_dir|."""
temp_name = tempfile.mktemp(prefix='mac_toolchain')
try:
print 'Downloading new toolchain.'
subprocess.check_call(['gsutil.py', 'cp', url, temp_name])
if os.path.exists(output_dir):
print 'Deleting old toolchain.'
shutil.rmtree(output_dir)
EnsureDirExists(output_dir)
print 'Unpacking new toolchain.'
tarfile.open(mode='r:gz', name=temp_name).extractall(path=output_dir)
finally:
if os.path.exists(temp_name):
os.unlink(temp_name)
def CanAccessToolchainBucket():
"""Checks whether the user has access to |TOOLCHAIN_URL|."""
proc = subprocess.Popen(['gsutil.py', 'ls', TOOLCHAIN_URL],
stdout=subprocess.PIPE)
proc.communicate()
return proc.returncode == 0
def LoadPlist(path):
"""Loads Plist at |path| and returns it as a dictionary."""
fd, name = tempfile.mkstemp()
try:
subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path])
with os.fdopen(fd, 'r') as f:
return plistlib.readPlist(f)
finally:
os.unlink(name)
def FinalizeUnpack(output_dir, target_os):
"""Use xcodebuild to accept new toolchain license and run first launch
installers if necessary. Don't accept the license if a newer license has
already been accepted. This only works if xcodebuild and xcode-select are
passwordless in sudoers."""
# Check old license
try:
target_license_plist_path = os.path.join(
output_dir, 'Contents','Resources','LicenseInfo.plist')
target_license_plist = LoadPlist(target_license_plist_path)
build_type = target_license_plist['licenseType']
build_version = target_license_plist['licenseID']
accepted_license_plist = LoadPlist(
'/Library/Preferences/com.apple.dt.Xcode.plist')
agreed_to_key = 'IDELast%sLicenseAgreedTo' % build_type
last_license_agreed_to = accepted_license_plist[agreed_to_key]
# Historically all Xcode build numbers have been in the format of AANNNN, so
# a simple string compare works. If Xcode's build numbers change this may
# need a more complex compare.
if build_version <= last_license_agreed_to:
# Don't accept the license of older toolchain builds, this will break the
# license of newer builds.
return
except (subprocess.CalledProcessError, KeyError):
# If there's never been a license of type |build_type| accepted,
# |target_license_plist_path| or |agreed_to_key| may not exist.
pass
print "Accepting license."
target_version_plist_path = os.path.join(
output_dir, 'Contents','version.plist')
target_version_plist = LoadPlist(target_version_plist_path)
short_version_string = target_version_plist['CFBundleShortVersionString']
old_path = subprocess.Popen(['/usr/bin/xcode-select', '-p'],
stdout=subprocess.PIPE).communicate()[0].strip()
try:
build_dir = os.path.join(output_dir, 'Contents/Developer')
subprocess.check_call(['sudo', '/usr/bin/xcode-select', '-s', build_dir])
subprocess.check_call(['sudo', '/usr/bin/xcodebuild', '-license', 'accept'])
if target_os == 'ios' and \
LooseVersion(short_version_string) >= LooseVersion("9.0"):
print "Installing packages."
subprocess.check_call(['sudo', '/usr/bin/xcodebuild', '-runFirstLaunch'])
finally:
subprocess.check_call(['sudo', '/usr/bin/xcode-select', '-s', old_path])
def _UseHermeticToolchain(target_os):
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, target_os], stdout=subprocess.PIPE)
proc = subprocess.Popen([script_path, 'mac'], stdout=subprocess.PIPE)
return '1' in proc.stdout.readline()
def RequestGsAuthentication():
"""Requests that the user authenticate to be able to access gs://.
"""
print 'Access to ' + TOOLCHAIN_URL + ' not configured.'
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 need to do a one-time manual'
print 'I\'m sorry for the hassle, but you may need to do a one-time manual'
print 'authentication. Please run:'
print
print ' download_from_google_storage --config'
print ' cipd auth-login'
print
print 'and follow the instructions.'
print
print 'NOTE 1: Use your google.com credentials, not chromium.org.'
print 'NOTE 2: Enter 0 when asked for a "project-id".'
print 'NOTE: Use your google.com credentials, not chromium.org.'
print
print '-----------------------------------------------------------------'
print
sys.stdout.flush()
sys.exit(1)
def DownloadHermeticBuild(target_os, toolchain_version, toolchain_filename):
if not _UseHermeticToolchain(target_os):
return 0
def PrintError(message):
# Flush buffers to ensure correct output ordering.
sys.stdout.flush()
sys.stderr.write(message + '\n')
sys.stderr.flush()
toolchain_output_path = TOOLCHAIN_BUILD_DIR % target_os
if ReadStampFile(target_os) == toolchain_version:
FinalizeUnpack(toolchain_output_path, target_os)
return 0
if not CanAccessToolchainBucket():
RequestGsAuthentication()
return 1
def InstallXcode(xcode_build_version, installer_cmd, xcode_app_path):
"""Installs the requested Xcode build version.
# Reset the stamp file in case the build is unsuccessful.
WriteStampFile(target_os, '')
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.
toolchain_file = '%s.tgz' % toolchain_version
toolchain_full_url = TOOLCHAIN_URL + toolchain_file
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,
]
print 'Updating toolchain to %s...' % toolchain_version
try:
toolchain_file = toolchain_filename % toolchain_version
toolchain_full_url = TOOLCHAIN_URL + toolchain_file
DownloadAndUnpack(toolchain_full_url, toolchain_output_path)
FinalizeUnpack(toolchain_output_path, target_os)
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
print 'Toolchain %s unpacked.' % toolchain_version
WriteStampFile(target_os, toolchain_version)
return 0
except Exception as e:
print 'Failed to download toolchain %s.' % toolchain_file
print 'Exception %s' % e
print 'Exiting.'
return 1
return True
def main():
if sys.platform != 'darwin':
return 0
for target_os in GetPlatforms():
if not PlatformMeetsHermeticXcodeRequirements(target_os):
print 'OS version does not support toolchain.'
continue
if not _UseHermeticToolchain():
print 'Skipping Mac toolchain installation for mac'
return 0
if target_os == 'ios':
toolchain_version = os.environ.get('IOS_TOOLCHAIN_REVISION',
IOS_TOOLCHAIN_VERSION)
toolchain_filename = 'ios-toolchain-%s.tgz'
else:
toolchain_version = os.environ.get('MAC_TOOLCHAIN_REVISION',
MAC_TOOLCHAIN_VERSION)
toolchain_filename = 'toolchain-%s.tgz'
if not PlatformMeetsHermeticXcodeRequirements():
print 'OS version does not support toolchain.'
return 0
return_value = DownloadHermeticBuild(
target_os, toolchain_version, toolchain_filename)
if return_value:
return return_value
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