blob: 54586f788c2c1a1793b720cf4646fb29787db1d6 [file] [log] [blame]
smutceafcfba2016-05-06 23:44:041#!/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
8Usage:
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
40import argparse
41import json
42import os
43import subprocess
44import sys
45
46
47def 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
66def 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
83def 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 """
Sergey Berezin8263e5f2017-11-29 22:51:3692 try:
93 version, build_version = get_xcode_version('xcodebuild')
94 path = subprocess.check_output(['xcode-select', '--print-path']).rstrip()
95 except subprocess.CalledProcessError:
96 version = build_version = path = None
smutceafcfba2016-05-06 23:44:0497
98 return {
Sergey Berezin8263e5f2017-11-29 22:51:3699 'path': path,
smutceafcfba2016-05-06 23:44:04100 'version': version,
101 'build': build_version,
102 }
103
104
105def find_xcode(target_version=None):
106 """Finds all Xcode versions, switching to the given Xcode version.
107
108 Args:
109 target_version: The version of Xcode to switch to, or None if the
110 Xcode version should not be switched.
111
112 Returns:
113 A summary dict as described in the usage section above.
114 """
115 xcode_info = {
116 'installations': {
117 },
118 'current version': {
119 },
120 }
121
122 if target_version:
123 xcode_info['found'] = False
124 xcode_info['matches'] = {}
125 xcode_info['previous version'] = get_current_xcode_info()
126
127 if xcode_info['previous version']['version'] == target_version:
128 xcode_info['found'] = True
129
130 for app in os.listdir(os.path.join('/', 'Applications')):
131 if app.startswith('Xcode'):
132 installation_path = os.path.join('/', 'Applications', app)
133 xcodebuild = get_xcodebuild_path(installation_path)
134
135 if os.path.exists(xcodebuild):
136 version, build_version = get_xcode_version(xcodebuild)
137
138 xcode_info['installations'][installation_path] = "%s (%s)" % (
139 version,
140 build_version,
141 )
142
143 if target_version and version == target_version:
144 xcode_info['matches'][installation_path] = "%s (%s)" % (
145 version,
146 build_version,
147 )
148
149 # If this is the first match, switch to it.
150 if not xcode_info['found']:
151 subprocess.check_call([
152 'sudo',
153 'xcode-select',
154 '-switch',
155 installation_path,
156 ])
157
158 xcode_info['found'] = True
159
160 xcode_info['current version'] = get_current_xcode_info()
161
162 if target_version and not xcode_info['found']:
163 # Flush buffers to ensure correct output ordering for buildbot.
164 sys.stdout.flush()
165 sys.stderr.write('Target Xcode version not found: %s\n' % target_version)
166 sys.stderr.flush()
167
168 return xcode_info
169
170
171def main(args):
172 xcode_info = find_xcode(args.version)
173
174 if args.json_file:
175 with open(args.json_file, 'w') as json_file:
176 json.dump(xcode_info, json_file)
177
178 if args.version and not xcode_info['found']:
179 return 1
180
181 return 0
182
183
184if __name__ == '__main__':
185 parser = argparse.ArgumentParser()
186 parser.add_argument(
187 '-j',
188 '--json-file',
189 help='Location to write a JSON summary.',
190 metavar='file',
191 )
192 parser.add_argument(
193 '-v',
194 '--version',
195 help='Xcode version to find and switch to.',
196 metavar='ver',
197 )
198
199 sys.exit(main(parser.parse_args()))