OLD | NEW |
| (Empty) |
1 #!/usr/bin/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 | |
6 """Finds Xcode installations, optionally switching to a desired version. | |
7 | |
8 Usage: | |
9 ./find_xcode.py -j /tmp/out.json -v 6.0.1 | |
10 | |
11 Finds Xcode 6.0.1 and switches to it. Writes a summary to /tmp/out.json | |
12 that includes the Xcode installations that were found, the Xcode version | |
13 that was active before running this script, and the Xcode version that | |
14 is active after running this script. | |
15 | |
16 e.g. { | |
17 "installations": { | |
18 "/Applications/Xcode5.app": "5.1.1 (5B1008)", | |
19 "/Applications/Xcode6.app": "6.0 (6A313)", | |
20 "/Applications/Xcode6.0.1.app": "6.0.1 (6A317)", | |
21 "/Applications/Xcode6.1.app": "6.1 (6A1046a)", | |
22 }, | |
23 "matches": { | |
24 "/Applications/Xcode6.0.1.app": "6.0.1 (6A317)", | |
25 }, | |
26 "previous version": { | |
27 "path": "/Application/Xcode5.app", | |
28 "version": "5.1.1", | |
29 "build": "(5B1008)", | |
30 }, | |
31 "current version": { | |
32 "path": "/Applications/Xcode6.0.1.app", | |
33 "version": "6.0.1", | |
34 "build": "6A317", | |
35 }, | |
36 "found": true, | |
37 } | |
38 """ | |
39 | |
40 import argparse | |
41 import json | |
42 import os | |
43 import subprocess | |
44 import sys | |
45 | |
46 | |
47 def get_xcodebuild_path(xcode_app): | |
48 """Returns the path to xcodebuild under the given Xcode app. | |
49 | |
50 Args: | |
51 xcode_app: The path to an installed Xcode.app. e.g. /Applications/Xcode.app. | |
52 | |
53 Returns: | |
54 The absolute path to the xcodebuild binary under the given Xcode app. | |
55 """ | |
56 return os.path.join( | |
57 xcode_app, | |
58 'Contents', | |
59 'Developer', | |
60 'usr', | |
61 'bin', | |
62 'xcodebuild', | |
63 ) | |
64 | |
65 | |
66 def get_xcode_version(xcodebuild): | |
67 """Returns the Xcode version and build version. | |
68 | |
69 Args: | |
70 xcodebuild: The absolute path to the xcodebuild binary. | |
71 | |
72 Returns: | |
73 A tuple of (version string, build version string). | |
74 e.g. ("6.0.1", "6A317") | |
75 """ | |
76 # Sample output: | |
77 # Xcode 6.0.1 | |
78 # Build version 6A317 | |
79 out = subprocess.check_output([xcodebuild, '-version']).splitlines() | |
80 return out[0].split(' ')[-1], out[1].split(' ')[-1] | |
81 | |
82 | |
83 def get_current_xcode_info(): | |
84 """Returns the current Xcode path, version, and build number. | |
85 | |
86 Returns: | |
87 A dict with 'path', 'version', and 'build' keys. | |
88 'path': The absolute path to the Xcode installation. | |
89 'version': The Xcode version. | |
90 'build': The Xcode build version. | |
91 """ | |
92 version, build_version = get_xcode_version('xcodebuild') | |
93 | |
94 return { | |
95 'path': subprocess.check_output(['xcode-select', '--print-path']).rstrip(), | |
96 'version': version, | |
97 'build': build_version, | |
98 } | |
99 | |
100 | |
101 def find_xcode(target_version=None): | |
102 """Finds all Xcode versions, switching to the given Xcode version. | |
103 | |
104 Args: | |
105 target_version: The version of Xcode to switch to, or None if the | |
106 Xcode version should not be switched. | |
107 | |
108 Returns: | |
109 A summary dict as described in the usage section above. | |
110 """ | |
111 xcode_info = { | |
112 'installations': { | |
113 }, | |
114 'current version': { | |
115 }, | |
116 } | |
117 | |
118 if target_version: | |
119 xcode_info['found'] = False | |
120 xcode_info['matches'] = {} | |
121 xcode_info['previous version'] = get_current_xcode_info() | |
122 | |
123 if xcode_info['previous version']['version'] == target_version: | |
124 xcode_info['found'] = True | |
125 | |
126 for app in os.listdir(os.path.join('/', 'Applications')): | |
127 if app.startswith('Xcode'): | |
128 installation_path = os.path.join('/', 'Applications', app) | |
129 xcodebuild = get_xcodebuild_path(installation_path) | |
130 | |
131 if os.path.exists(xcodebuild): | |
132 version, build_version = get_xcode_version(xcodebuild) | |
133 | |
134 xcode_info['installations'][installation_path] = "%s (%s)" % ( | |
135 version, | |
136 build_version, | |
137 ) | |
138 | |
139 if target_version and version == target_version: | |
140 xcode_info['matches'][installation_path] = "%s (%s)" % ( | |
141 version, | |
142 build_version, | |
143 ) | |
144 | |
145 # If this is the first match, switch to it. | |
146 if not xcode_info['found']: | |
147 subprocess.check_call([ | |
148 'sudo', | |
149 'xcode-select', | |
150 '-switch', | |
151 installation_path, | |
152 ]) | |
153 | |
154 xcode_info['found'] = True | |
155 | |
156 xcode_info['current version'] = get_current_xcode_info() | |
157 | |
158 if target_version and not xcode_info['found']: | |
159 # Flush buffers to ensure correct output ordering for buildbot. | |
160 sys.stdout.flush() | |
161 sys.stderr.write('Target Xcode version not found: %s\n' % target_version) | |
162 sys.stderr.flush() | |
163 | |
164 return xcode_info | |
165 | |
166 | |
167 def main(args): | |
168 xcode_info = find_xcode(args.version) | |
169 | |
170 if args.json_file: | |
171 with open(args.json_file, 'w') as json_file: | |
172 json.dump(xcode_info, json_file) | |
173 | |
174 if args.version and not xcode_info['found']: | |
175 return 1 | |
176 | |
177 return 0 | |
178 | |
179 | |
180 if __name__ == '__main__': | |
181 parser = argparse.ArgumentParser() | |
182 parser.add_argument( | |
183 '-j', | |
184 '--json-file', | |
185 help='Location to write a JSON summary.', | |
186 metavar='file', | |
187 ) | |
188 parser.add_argument( | |
189 '-v', | |
190 '--version', | |
191 help='Xcode version to find and switch to.', | |
192 metavar='ver', | |
193 ) | |
194 | |
195 sys.exit(main(parser.parse_args())) | |
OLD | NEW |