justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2016 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
erikchen | da01626 | 2016-11-09 04:32:13 | [diff] [blame] | 6 | """ |
| 7 | If should_use_hermetic_xcode.py emits "1", and the current toolchain is out of |
| 8 | date: |
| 9 | * Downloads the hermetic mac toolchain |
| 10 | * Requires gsutil to be configured. |
| 11 | * Accepts the license. |
| 12 | * If xcode-select and xcodebuild are not passwordless in sudoers, requires |
| 13 | user interaction. |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 14 | """ |
| 15 | |
| 16 | import os |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 17 | import plistlib |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 18 | import shutil |
| 19 | import subprocess |
| 20 | import sys |
| 21 | import tarfile |
| 22 | import time |
| 23 | import tempfile |
| 24 | import urllib2 |
| 25 | |
| 26 | # This can be changed after running /build/package_mac_toolchain.py. |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 27 | MAC_TOOLCHAIN_VERSION = '5B1008' |
| 28 | MAC_TOOLCHAIN_SUB_REVISION = 3 |
| 29 | MAC_TOOLCHAIN_VERSION = '%s-%s' % (MAC_TOOLCHAIN_VERSION, |
| 30 | MAC_TOOLCHAIN_SUB_REVISION) |
justincohen | 3c4d5088 | 2017-01-26 16:55:59 | [diff] [blame] | 31 | IOS_TOOLCHAIN_VERSION = '8C1002' |
| 32 | IOS_TOOLCHAIN_SUB_REVISION = 1 |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 33 | IOS_TOOLCHAIN_VERSION = '%s-%s' % (IOS_TOOLCHAIN_VERSION, |
| 34 | IOS_TOOLCHAIN_SUB_REVISION) |
| 35 | |
| 36 | # Absolute path to src/ directory. |
| 37 | REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 38 | |
| 39 | # Absolute path to a file with gclient solutions. |
| 40 | GCLIENT_CONFIG = os.path.join(os.path.dirname(REPO_ROOT), '.gclient') |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 41 | |
| 42 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 43 | TOOLCHAIN_BUILD_DIR = os.path.join(BASE_DIR, '%s_files', 'Xcode.app') |
| 44 | STAMP_FILE = os.path.join(BASE_DIR, '%s_files', 'toolchain_build_revision') |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 45 | TOOLCHAIN_URL = 'gs://chrome-mac-sdk/' |
| 46 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 47 | def GetPlatforms(): |
| 48 | default_target_os = ["mac"] |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 49 | try: |
| 50 | env = {} |
| 51 | execfile(GCLIENT_CONFIG, env, env) |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 52 | return env.get('target_os', default_target_os) |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 53 | except: |
| 54 | pass |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 55 | return default_target_os |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 56 | |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 57 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 58 | def ReadStampFile(target_os): |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 59 | """Return the contents of the stamp file, or '' if it doesn't exist.""" |
| 60 | try: |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 61 | with open(STAMP_FILE % target_os, 'r') as f: |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 62 | return f.read().rstrip() |
| 63 | except IOError: |
| 64 | return '' |
| 65 | |
| 66 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 67 | def WriteStampFile(target_os, s): |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 68 | """Write s to the stamp file.""" |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 69 | EnsureDirExists(os.path.dirname(STAMP_FILE % target_os)) |
| 70 | with open(STAMP_FILE % target_os, 'w') as f: |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 71 | f.write(s) |
| 72 | f.write('\n') |
| 73 | |
| 74 | |
| 75 | def EnsureDirExists(path): |
| 76 | if not os.path.exists(path): |
| 77 | os.makedirs(path) |
| 78 | |
| 79 | |
| 80 | def DownloadAndUnpack(url, output_dir): |
| 81 | """Decompresses |url| into a cleared |output_dir|.""" |
| 82 | temp_name = tempfile.mktemp(prefix='mac_toolchain') |
| 83 | try: |
| 84 | print 'Downloading new toolchain.' |
| 85 | subprocess.check_call(['gsutil.py', 'cp', url, temp_name]) |
| 86 | if os.path.exists(output_dir): |
| 87 | print 'Deleting old toolchain.' |
| 88 | shutil.rmtree(output_dir) |
| 89 | EnsureDirExists(output_dir) |
| 90 | print 'Unpacking new toolchain.' |
| 91 | tarfile.open(mode='r:gz', name=temp_name).extractall(path=output_dir) |
| 92 | finally: |
| 93 | if os.path.exists(temp_name): |
| 94 | os.unlink(temp_name) |
| 95 | |
| 96 | |
| 97 | def CanAccessToolchainBucket(): |
| 98 | """Checks whether the user has access to |TOOLCHAIN_URL|.""" |
| 99 | proc = subprocess.Popen(['gsutil.py', 'ls', TOOLCHAIN_URL], |
| 100 | stdout=subprocess.PIPE) |
| 101 | proc.communicate() |
| 102 | return proc.returncode == 0 |
| 103 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 104 | |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 105 | def LoadPlist(path): |
| 106 | """Loads Plist at |path| and returns it as a dictionary.""" |
| 107 | fd, name = tempfile.mkstemp() |
| 108 | try: |
| 109 | subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path]) |
| 110 | with os.fdopen(fd, 'r') as f: |
| 111 | return plistlib.readPlist(f) |
| 112 | finally: |
| 113 | os.unlink(name) |
| 114 | |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 115 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 116 | def AcceptLicense(target_os): |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 117 | """Use xcodebuild to accept new toolchain license if necessary. Don't accept |
| 118 | the license if a newer license has already been accepted. This only works if |
| 119 | xcodebuild and xcode-select are passwordless in sudoers.""" |
| 120 | |
| 121 | # Check old license |
| 122 | try: |
| 123 | target_license_plist_path = \ |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 124 | os.path.join(TOOLCHAIN_BUILD_DIR % target_os, |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 125 | *['Contents','Resources','LicenseInfo.plist']) |
| 126 | target_license_plist = LoadPlist(target_license_plist_path) |
| 127 | build_type = target_license_plist['licenseType'] |
| 128 | build_version = target_license_plist['licenseID'] |
| 129 | |
| 130 | accepted_license_plist = LoadPlist( |
| 131 | '/Library/Preferences/com.apple.dt.Xcode.plist') |
| 132 | agreed_to_key = 'IDELast%sLicenseAgreedTo' % build_type |
| 133 | last_license_agreed_to = accepted_license_plist[agreed_to_key] |
| 134 | |
| 135 | # Historically all Xcode build numbers have been in the format of AANNNN, so |
| 136 | # a simple string compare works. If Xcode's build numbers change this may |
| 137 | # need a more complex compare. |
| 138 | if build_version <= last_license_agreed_to: |
| 139 | # Don't accept the license of older toolchain builds, this will break the |
| 140 | # license of newer builds. |
| 141 | return |
tikuta | 1a1610f1 | 2016-05-16 02:28:10 | [diff] [blame] | 142 | except (subprocess.CalledProcessError, KeyError): |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 143 | # If there's never been a license of type |build_type| accepted, |
| 144 | # |target_license_plist_path| or |agreed_to_key| may not exist. |
| 145 | pass |
| 146 | |
| 147 | print "Accepting license." |
justincohen | 66d5aafd | 2016-03-29 23:45:18 | [diff] [blame] | 148 | old_path = subprocess.Popen(['/usr/bin/xcode-select', '-p'], |
| 149 | stdout=subprocess.PIPE).communicate()[0].strip() |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 150 | try: |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 151 | build_dir = os.path.join( |
| 152 | TOOLCHAIN_BUILD_DIR % target_os, 'Contents/Developer') |
justincohen | 10dc9038 | 2016-05-09 17:47:22 | [diff] [blame] | 153 | subprocess.check_call(['sudo', '/usr/bin/xcode-select', '-s', build_dir]) |
| 154 | subprocess.check_call(['sudo', '/usr/bin/xcodebuild', '-license', 'accept']) |
| 155 | finally: |
| 156 | subprocess.check_call(['sudo', '/usr/bin/xcode-select', '-s', old_path]) |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 157 | |
| 158 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 159 | def _UseHermeticToolchain(target_os): |
erikchen | da01626 | 2016-11-09 04:32:13 | [diff] [blame] | 160 | current_dir = os.path.dirname(os.path.realpath(__file__)) |
| 161 | script_path = os.path.join(current_dir, 'mac/should_use_hermetic_xcode.py') |
justincohen | cf1c482d | 2016-12-08 20:39:34 | [diff] [blame] | 162 | proc = subprocess.Popen([script_path, target_os], stdout=subprocess.PIPE) |
erikchen | da01626 | 2016-11-09 04:32:13 | [diff] [blame] | 163 | return '1' in proc.stdout.readline() |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 164 | |
erikchen | da01626 | 2016-11-09 04:32:13 | [diff] [blame] | 165 | |
| 166 | def RequestGsAuthentication(): |
| 167 | """Requests that the user authenticate to be able to access gs://. |
| 168 | """ |
| 169 | print 'Access to ' + TOOLCHAIN_URL + ' not configured.' |
| 170 | print '-----------------------------------------------------------------' |
| 171 | print |
| 172 | print 'You appear to be a Googler.' |
| 173 | print |
| 174 | print 'I\'m sorry for the hassle, but you need to do a one-time manual' |
| 175 | print 'authentication. Please run:' |
| 176 | print |
| 177 | print ' download_from_google_storage --config' |
| 178 | print |
| 179 | print 'and follow the instructions.' |
| 180 | print |
| 181 | print 'NOTE 1: Use your google.com credentials, not chromium.org.' |
| 182 | print 'NOTE 2: Enter 0 when asked for a "project-id".' |
| 183 | print |
| 184 | print '-----------------------------------------------------------------' |
| 185 | print |
| 186 | sys.stdout.flush() |
| 187 | sys.exit(1) |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 188 | |
| 189 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 190 | def DownloadHermeticBuild(target_os, default_version, toolchain_filename): |
| 191 | if not _UseHermeticToolchain(target_os): |
| 192 | print 'Using local toolchain for %s.' % target_os |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 193 | return 0 |
| 194 | |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 195 | toolchain_version = os.environ.get('MAC_TOOLCHAIN_REVISION', |
| 196 | default_version) |
| 197 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 198 | if ReadStampFile(target_os) == toolchain_version: |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 199 | print 'Toolchain (%s) is already up to date.' % toolchain_version |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 200 | AcceptLicense(target_os) |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 201 | return 0 |
| 202 | |
| 203 | if not CanAccessToolchainBucket(): |
erikchen | da01626 | 2016-11-09 04:32:13 | [diff] [blame] | 204 | RequestGsAuthentication() |
| 205 | return 1 |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 206 | |
| 207 | # Reset the stamp file in case the build is unsuccessful. |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 208 | WriteStampFile(target_os, '') |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 209 | |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 210 | toolchain_file = '%s.tgz' % toolchain_version |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 211 | toolchain_full_url = TOOLCHAIN_URL + toolchain_file |
| 212 | |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 213 | print 'Updating toolchain to %s...' % toolchain_version |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 214 | try: |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 215 | toolchain_file = toolchain_filename % toolchain_version |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 216 | toolchain_full_url = TOOLCHAIN_URL + toolchain_file |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 217 | DownloadAndUnpack(toolchain_full_url, TOOLCHAIN_BUILD_DIR % target_os) |
| 218 | AcceptLicense(target_os) |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 219 | |
justincohen | abbd93dc | 2016-11-30 20:11:28 | [diff] [blame] | 220 | print 'Toolchain %s unpacked.' % toolchain_version |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 221 | WriteStampFile(target_os, toolchain_version) |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 222 | return 0 |
tikuta | 1a1610f1 | 2016-05-16 02:28:10 | [diff] [blame] | 223 | except Exception as e: |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 224 | print 'Failed to download toolchain %s.' % toolchain_file |
tikuta | 1a1610f1 | 2016-05-16 02:28:10 | [diff] [blame] | 225 | print 'Exception %s' % e |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 226 | print 'Exiting.' |
| 227 | return 1 |
| 228 | |
sdefresne | aacc2845 | 2017-01-12 10:33:37 | [diff] [blame] | 229 | |
| 230 | def main(): |
| 231 | if sys.platform != 'darwin': |
| 232 | return 0 |
| 233 | |
| 234 | for target_os in GetPlatforms(): |
| 235 | if target_os == 'ios': |
| 236 | default_version = IOS_TOOLCHAIN_VERSION |
| 237 | toolchain_filename = 'ios-toolchain-%s.tgz' |
| 238 | else: |
| 239 | default_version = MAC_TOOLCHAIN_VERSION |
| 240 | toolchain_filename = 'toolchain-%s.tgz' |
| 241 | |
| 242 | return_value = DownloadHermeticBuild( |
| 243 | target_os, default_version, toolchain_filename) |
| 244 | if return_value: |
| 245 | return return_value |
| 246 | |
| 247 | return 0 |
| 248 | |
| 249 | |
justincohen | 6a03a3d | 2016-03-26 21:44:38 | [diff] [blame] | 250 | if __name__ == '__main__': |
| 251 | sys.exit(main()) |