blob: 123e5e0d3c0596fe2fb5db214be11477c86ce871 [file] [log] [blame]
justincohen6a03a3d2016-03-26 21:44:381#!/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
erikchenda016262016-11-09 04:32:136"""
7If should_use_hermetic_xcode.py emits "1", and the current toolchain is out of
8date:
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.
justincohen6a03a3d2016-03-26 21:44:3814"""
15
16import os
justincohen10dc90382016-05-09 17:47:2217import plistlib
justincohen6a03a3d2016-03-26 21:44:3818import shutil
19import subprocess
20import sys
21import tarfile
22import time
23import tempfile
24import urllib2
25
26# This can be changed after running /build/package_mac_toolchain.py.
justincohenabbd93dc2016-11-30 20:11:2827MAC_TOOLCHAIN_VERSION = '5B1008'
28MAC_TOOLCHAIN_SUB_REVISION = 3
29MAC_TOOLCHAIN_VERSION = '%s-%s' % (MAC_TOOLCHAIN_VERSION,
30 MAC_TOOLCHAIN_SUB_REVISION)
justincohen3c4d50882017-01-26 16:55:5931IOS_TOOLCHAIN_VERSION = '8C1002'
32IOS_TOOLCHAIN_SUB_REVISION = 1
justincohenabbd93dc2016-11-30 20:11:2833IOS_TOOLCHAIN_VERSION = '%s-%s' % (IOS_TOOLCHAIN_VERSION,
34 IOS_TOOLCHAIN_SUB_REVISION)
35
36# Absolute path to src/ directory.
37REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
38
39# Absolute path to a file with gclient solutions.
40GCLIENT_CONFIG = os.path.join(os.path.dirname(REPO_ROOT), '.gclient')
justincohen6a03a3d2016-03-26 21:44:3841
42BASE_DIR = os.path.abspath(os.path.dirname(__file__))
sdefresneaacc28452017-01-12 10:33:3743TOOLCHAIN_BUILD_DIR = os.path.join(BASE_DIR, '%s_files', 'Xcode.app')
44STAMP_FILE = os.path.join(BASE_DIR, '%s_files', 'toolchain_build_revision')
justincohen6a03a3d2016-03-26 21:44:3845TOOLCHAIN_URL = 'gs://chrome-mac-sdk/'
46
sdefresneaacc28452017-01-12 10:33:3747def GetPlatforms():
48 default_target_os = ["mac"]
justincohenabbd93dc2016-11-30 20:11:2849 try:
50 env = {}
51 execfile(GCLIENT_CONFIG, env, env)
sdefresneaacc28452017-01-12 10:33:3752 return env.get('target_os', default_target_os)
justincohenabbd93dc2016-11-30 20:11:2853 except:
54 pass
sdefresneaacc28452017-01-12 10:33:3755 return default_target_os
justincohenabbd93dc2016-11-30 20:11:2856
justincohen6a03a3d2016-03-26 21:44:3857
sdefresneaacc28452017-01-12 10:33:3758def ReadStampFile(target_os):
justincohen6a03a3d2016-03-26 21:44:3859 """Return the contents of the stamp file, or '' if it doesn't exist."""
60 try:
sdefresneaacc28452017-01-12 10:33:3761 with open(STAMP_FILE % target_os, 'r') as f:
justincohen6a03a3d2016-03-26 21:44:3862 return f.read().rstrip()
63 except IOError:
64 return ''
65
66
sdefresneaacc28452017-01-12 10:33:3767def WriteStampFile(target_os, s):
justincohen6a03a3d2016-03-26 21:44:3868 """Write s to the stamp file."""
sdefresneaacc28452017-01-12 10:33:3769 EnsureDirExists(os.path.dirname(STAMP_FILE % target_os))
70 with open(STAMP_FILE % target_os, 'w') as f:
justincohen6a03a3d2016-03-26 21:44:3871 f.write(s)
72 f.write('\n')
73
74
75def EnsureDirExists(path):
76 if not os.path.exists(path):
77 os.makedirs(path)
78
79
80def 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
97def 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
sdefresneaacc28452017-01-12 10:33:37104
justincohen10dc90382016-05-09 17:47:22105def 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
justincohen6a03a3d2016-03-26 21:44:38115
sdefresneaacc28452017-01-12 10:33:37116def AcceptLicense(target_os):
justincohen10dc90382016-05-09 17:47:22117 """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 = \
sdefresneaacc28452017-01-12 10:33:37124 os.path.join(TOOLCHAIN_BUILD_DIR % target_os,
justincohen10dc90382016-05-09 17:47:22125 *['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
tikuta1a1610f12016-05-16 02:28:10142 except (subprocess.CalledProcessError, KeyError):
justincohen10dc90382016-05-09 17:47:22143 # 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."
justincohen66d5aafd2016-03-29 23:45:18148 old_path = subprocess.Popen(['/usr/bin/xcode-select', '-p'],
149 stdout=subprocess.PIPE).communicate()[0].strip()
justincohen10dc90382016-05-09 17:47:22150 try:
sdefresneaacc28452017-01-12 10:33:37151 build_dir = os.path.join(
152 TOOLCHAIN_BUILD_DIR % target_os, 'Contents/Developer')
justincohen10dc90382016-05-09 17:47:22153 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])
justincohen6a03a3d2016-03-26 21:44:38157
158
sdefresneaacc28452017-01-12 10:33:37159def _UseHermeticToolchain(target_os):
erikchenda016262016-11-09 04:32:13160 current_dir = os.path.dirname(os.path.realpath(__file__))
161 script_path = os.path.join(current_dir, 'mac/should_use_hermetic_xcode.py')
justincohencf1c482d2016-12-08 20:39:34162 proc = subprocess.Popen([script_path, target_os], stdout=subprocess.PIPE)
erikchenda016262016-11-09 04:32:13163 return '1' in proc.stdout.readline()
justincohen6a03a3d2016-03-26 21:44:38164
erikchenda016262016-11-09 04:32:13165
166def 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)
justincohen6a03a3d2016-03-26 21:44:38188
189
sdefresneaacc28452017-01-12 10:33:37190def DownloadHermeticBuild(target_os, default_version, toolchain_filename):
191 if not _UseHermeticToolchain(target_os):
192 print 'Using local toolchain for %s.' % target_os
justincohen6a03a3d2016-03-26 21:44:38193 return 0
194
justincohenabbd93dc2016-11-30 20:11:28195 toolchain_version = os.environ.get('MAC_TOOLCHAIN_REVISION',
196 default_version)
197
sdefresneaacc28452017-01-12 10:33:37198 if ReadStampFile(target_os) == toolchain_version:
justincohenabbd93dc2016-11-30 20:11:28199 print 'Toolchain (%s) is already up to date.' % toolchain_version
sdefresneaacc28452017-01-12 10:33:37200 AcceptLicense(target_os)
justincohen6a03a3d2016-03-26 21:44:38201 return 0
202
203 if not CanAccessToolchainBucket():
erikchenda016262016-11-09 04:32:13204 RequestGsAuthentication()
205 return 1
justincohen6a03a3d2016-03-26 21:44:38206
207 # Reset the stamp file in case the build is unsuccessful.
sdefresneaacc28452017-01-12 10:33:37208 WriteStampFile(target_os, '')
justincohen6a03a3d2016-03-26 21:44:38209
justincohenabbd93dc2016-11-30 20:11:28210 toolchain_file = '%s.tgz' % toolchain_version
justincohen6a03a3d2016-03-26 21:44:38211 toolchain_full_url = TOOLCHAIN_URL + toolchain_file
212
justincohenabbd93dc2016-11-30 20:11:28213 print 'Updating toolchain to %s...' % toolchain_version
justincohen6a03a3d2016-03-26 21:44:38214 try:
justincohenabbd93dc2016-11-30 20:11:28215 toolchain_file = toolchain_filename % toolchain_version
justincohen6a03a3d2016-03-26 21:44:38216 toolchain_full_url = TOOLCHAIN_URL + toolchain_file
sdefresneaacc28452017-01-12 10:33:37217 DownloadAndUnpack(toolchain_full_url, TOOLCHAIN_BUILD_DIR % target_os)
218 AcceptLicense(target_os)
justincohen6a03a3d2016-03-26 21:44:38219
justincohenabbd93dc2016-11-30 20:11:28220 print 'Toolchain %s unpacked.' % toolchain_version
sdefresneaacc28452017-01-12 10:33:37221 WriteStampFile(target_os, toolchain_version)
justincohen6a03a3d2016-03-26 21:44:38222 return 0
tikuta1a1610f12016-05-16 02:28:10223 except Exception as e:
justincohen6a03a3d2016-03-26 21:44:38224 print 'Failed to download toolchain %s.' % toolchain_file
tikuta1a1610f12016-05-16 02:28:10225 print 'Exception %s' % e
justincohen6a03a3d2016-03-26 21:44:38226 print 'Exiting.'
227 return 1
228
sdefresneaacc28452017-01-12 10:33:37229
230def 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
justincohen6a03a3d2016-03-26 21:44:38250if __name__ == '__main__':
251 sys.exit(main())