Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 # You can obtain one at http://mozilla.org/MPL/2.0/.
5 from __future__ import print_function, unicode_literals
7 import os
8 import re
9 import subprocess
10 import sys
11 import tempfile
12 try:
13 from urllib2 import urlopen
14 except ImportError:
15 from urllib.request import urlopen
17 from distutils.version import StrictVersion
19 from mozboot.base import BaseBootstrapper
21 HOMEBREW_BOOTSTRAP = 'https://raw.github.com/Homebrew/homebrew/go/install'
22 XCODE_APP_STORE = 'macappstore://itunes.apple.com/app/id497799835?mt=12'
23 XCODE_LEGACY = 'https://developer.apple.com/downloads/download.action?path=Developer_Tools/xcode_3.2.6_and_ios_sdk_4.3__final/xcode_3.2.6_and_ios_sdk_4.3.dmg'
24 HOMEBREW_AUTOCONF213 = 'https://raw.github.com/Homebrew/homebrew-versions/master/autoconf213.rb'
26 MACPORTS_URL = {'9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.2.1-10.9-Mavericks.pkg',
27 '8': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.8-MountainLion.pkg',
28 '7': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.7-Lion.pkg',
29 '6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.6-SnowLeopard.pkg',}
31 MACPORTS_CLANG_PACKAGE = 'clang-3.3'
33 RE_CLANG_VERSION = re.compile('Apple (?:clang|LLVM) version (\d+\.\d+)')
35 APPLE_CLANG_MINIMUM_VERSION = StrictVersion('4.2')
37 XCODE_REQUIRED = '''
38 Xcode is required to build Firefox. Please complete the install of Xcode
39 through the App Store.
41 It's possible Xcode is already installed on this machine but it isn't being
42 detected. This is possible with developer preview releases of Xcode, for
43 example. To correct this problem, run:
45 `xcode-select --switch /path/to/Xcode.app`.
47 e.g. `sudo xcode-select --switch /Applications/Xcode.app`.
48 '''
50 XCODE_REQUIRED_LEGACY = '''
51 You will need to download and install Xcode to build Firefox.
53 Please complete the Xcode download and then relaunch this script.
54 '''
56 XCODE_NO_DEVELOPER_DIRECTORY = '''
57 xcode-select says you don't have a developer directory configured. We think
58 this is due to you not having Xcode installed (properly). We're going to
59 attempt to install Xcode through the App Store. If the App Store thinks you
60 have Xcode installed, please run xcode-select by hand until it stops
61 complaining and then re-run this script.
62 '''
64 XCODE_COMMAND_LINE_TOOLS_MISSING = '''
65 The Xcode command line tools are required to build Firefox.
66 '''
68 INSTALL_XCODE_COMMAND_LINE_TOOLS_STEPS = '''
69 Perform the following steps to install the Xcode command line tools:
71 1) Open Xcode.app
72 2) Click through any first-run prompts
73 3) From the main Xcode menu, select Preferences (Command ,)
74 4) Go to the Download tab (near the right)
75 5) Install the "Command Line Tools"
77 When that has finished installing, please relaunch this script.
78 '''
80 UPGRADE_XCODE_COMMAND_LINE_TOOLS = '''
81 An old version of the Xcode command line tools is installed. You will need to
82 install a newer version in order to compile Firefox. If Xcode itself is old,
83 its command line tools may be too old even if it claims there are no updates
84 available, so if you are seeing this message multiple times, please update
85 Xcode first.
86 '''
88 PACKAGE_MANAGER_INSTALL = '''
89 We will install the %s package manager to install required packages.
91 You will be prompted to install %s with its default settings. If you
92 would prefer to do this manually, hit CTRL+c, install %s yourself, ensure
93 "%s" is in your $PATH, and relaunch bootstrap.
94 '''
96 PACKAGE_MANAGER_PACKAGES = '''
97 We are now installing all required packages via %s. You will see a lot of
98 output as packages are built.
99 '''
101 PACKAGE_MANAGER_OLD_CLANG = '''
102 We require a newer compiler than what is provided by your version of Xcode.
104 We will install a modern version of Clang through %s.
105 '''
107 PACKAGE_MANAGER_CHOICE = '''
108 Please choose a package manager you'd like:
109 1. Homebrew
110 2. MacPorts
111 Your choice:
112 '''
114 NO_PACKAGE_MANAGER_WARNING = '''
115 It seems you don't have any supported package manager installed.
116 '''
118 PACKAGE_MANAGER_EXISTS = '''
119 Looks like you have %s installed. We will install all required packages via %s.
120 '''
122 MULTI_PACKAGE_MANAGER_EXISTS = '''
123 It looks like you have multiple package managers installed.
124 '''
126 # May add support for other package manager on os x.
127 PACKAGE_MANAGER = {'Homebrew': 'brew',
128 'MacPorts': 'port'}
130 PACKAGE_MANAGER_CHOICES = ['Homebrew', 'MacPorts']
132 PACKAGE_MANAGER_BIN_MISSING = '''
133 A package manager is installed. However, your current shell does
134 not know where to find '%s' yet. You'll need to start a new shell
135 to pick up the environment changes so it can be found.
137 Please start a new shell or terminal window and run this
138 bootstrapper again.
140 If this problem persists, you will likely want to adjust your
141 shell's init script (e.g. ~/.bash_profile) to export a PATH
142 environment variable containing the location of your package
143 manager binary. e.g.
145 export PATH=/usr/local/bin:$PATH
146 '''
148 BAD_PATH_ORDER = '''
149 Your environment's PATH variable lists a system path directory (%s)
150 before the path to your package manager's binaries (%s).
151 This means that the package manager's binaries likely won't be
152 detected properly.
154 Modify your shell's configuration (e.g. ~/.profile or
155 ~/.bash_profile) to have %s appear in $PATH before %s. e.g.
157 export PATH=%s:$PATH
159 Once this is done, start a new shell (likely Command+T) and run
160 this bootstrap again.
161 '''
164 class OSXBootstrapper(BaseBootstrapper):
165 def __init__(self, version):
166 BaseBootstrapper.__init__(self)
168 self.os_version = StrictVersion(version)
170 if self.os_version < StrictVersion('10.6'):
171 raise Exception('OS X 10.6 or above is required.')
173 self.minor_version = version.split('.')[1]
175 def install_system_packages(self):
176 self.ensure_xcode()
178 choice = self.ensure_package_manager()
179 self.package_manager = choice
180 getattr(self, 'ensure_%s_packages' % choice)()
182 def ensure_xcode(self):
183 if self.os_version < StrictVersion('10.7'):
184 if not os.path.exists('/Developer/Applications/Xcode.app'):
185 print(XCODE_REQUIRED_LEGACY)
187 subprocess.check_call(['open', XCODE_LEGACY])
188 sys.exit(1)
190 # OS X 10.7 have Xcode come from the app store. However, users can
191 # still install Xcode into any arbitrary location. We honor the
192 # location of Xcode as set by xcode-select. This should also pick up
193 # developer preview releases of Xcode, which can be installed into
194 # paths like /Applications/Xcode5-DP6.app.
195 elif self.os_version >= StrictVersion('10.7'):
196 select = self.which('xcode-select')
197 try:
198 output = self.check_output([select, '--print-path'],
199 stderr=subprocess.STDOUT)
200 except subprocess.CalledProcessError as e:
201 # This seems to appear on fresh OS X machines before any Xcode
202 # has been installed. It may only occur on OS X 10.9 and later.
203 if 'unable to get active developer directory' in e.output:
204 print(XCODE_NO_DEVELOPER_DIRECTORY)
205 self._install_xcode_app_store()
206 assert False # Above should exit.
208 # This isn't the most robust check in the world. It relies on the
209 # default value not being in an application bundle, which seems to
210 # hold on at least Mavericks.
211 if '.app/' not in output:
212 print(XCODE_REQUIRED)
213 self._install_xcode_app_store()
214 assert False # Above should exit.
216 # Once Xcode is installed, you need to agree to the license before you can
217 # use it.
218 try:
219 output = self.check_output(['/usr/bin/xcrun', 'clang'],
220 stderr=subprocess.STDOUT)
221 except subprocess.CalledProcessError as e:
222 if 'license' in e.output:
223 xcodebuild = self.which('xcodebuild')
224 try:
225 subprocess.check_call([xcodebuild, '-license'],
226 stderr=subprocess.STDOUT)
227 except subprocess.CalledProcessError as e:
228 if 'requires admin privileges' in e.output:
229 self.run_as_root([xcodebuild, '-license'])
231 # Even then we're not done! We need to install the Xcode command line tools.
232 # As of Mountain Lion, apparently the only way to do this is to go through a
233 # menu dialog inside Xcode itself. We're not making this up.
234 if self.os_version >= StrictVersion('10.7'):
235 if not os.path.exists('/usr/bin/clang'):
236 print(XCODE_COMMAND_LINE_TOOLS_MISSING)
237 print(INSTALL_XCODE_COMMAND_LINE_TOOLS_STEPS)
238 sys.exit(1)
240 output = self.check_output(['/usr/bin/clang', '--version'])
241 match = RE_CLANG_VERSION.search(output)
242 if match is None:
243 raise Exception('Could not determine Clang version.')
245 version = StrictVersion(match.group(1))
247 if version < APPLE_CLANG_MINIMUM_VERSION:
248 print(UPGRADE_XCODE_COMMAND_LINE_TOOLS)
249 print(INSTALL_XCODE_COMMAND_LINE_TOOLS_STEPS)
250 sys.exit(1)
252 def _install_xcode_app_store(self):
253 subprocess.check_call(['open', XCODE_APP_STORE])
254 print('Once the install has finished, please relaunch this script.')
255 sys.exit(1)
257 def ensure_homebrew_packages(self):
258 self.brew = self.which('brew')
259 assert self.brew is not None
261 installed = self.check_output([self.brew, 'list']).split()
263 packages = [
264 # We need to install Python because Mercurial requires the Python
265 # development headers which are missing from OS X (at least on
266 # 10.8) and because the build system wants a version newer than
267 # what Apple ships.
268 ('python', 'python'),
269 ('mercurial', 'mercurial'),
270 ('git', 'git'),
271 ('yasm', 'yasm'),
272 ('autoconf213', HOMEBREW_AUTOCONF213),
273 ]
275 # terminal-notifier is only available in Mountain Lion or newer.
276 if self.os_version >= StrictVersion('10.8'):
277 packages.append(('terminal-notifier', 'terminal-notifier'))
279 printed = False
281 for name, package in packages:
282 if name in installed:
283 continue
285 if not printed:
286 print(PACKAGE_MANAGER_PACKAGES % ('Homebrew',))
287 printed = True
289 subprocess.check_call([self.brew, '-v', 'install', package])
291 if self.os_version < StrictVersion('10.7') and 'llvm' not in installed:
292 print(PACKAGE_MANAGER_OLD_CLANG % ('Homebrew',))
294 subprocess.check_call([self.brew, '-v', 'install', 'llvm',
295 '--with-clang', '--all-targets'])
297 def ensure_macports_packages(self):
298 self.port = self.which('port')
299 assert self.port is not None
301 installed = set(self.check_output([self.port, 'installed']).split())
303 packages = ['python27',
304 'mercurial',
305 'yasm',
306 'libidl',
307 'autoconf213']
309 missing = [package for package in packages if package not in installed]
310 if missing:
311 print(PACKAGE_MANAGER_PACKAGES % ('MacPorts',))
312 self.run_as_root([self.port, '-v', 'install'] + missing)
314 if self.os_version < StrictVersion('10.7') and MACPORTS_CLANG_PACKAGE not in installed:
315 print(PACKAGE_MANAGER_OLD_CLANG % ('MacPorts',))
316 self.run_as_root([self.port, '-v', 'install', MACPORTS_CLANG_PACKAGE])
318 self.run_as_root([self.port, 'select', '--set', 'python', 'python27'])
319 self.run_as_root([self.port, 'select', '--set', 'clang', 'mp-' + MACPORTS_CLANG_PACKAGE])
321 def ensure_package_manager(self):
322 '''
323 Search package mgr in sys.path, if none is found, prompt the user to install one.
324 If only one is found, use that one. If both are found, prompt the user to choose
325 one.
326 '''
327 installed = []
328 for name, cmd in PACKAGE_MANAGER.iteritems():
329 if self.which(cmd) is not None:
330 installed.append(name)
332 active_name, active_cmd = None, None
334 if not installed:
335 print(NO_PACKAGE_MANAGER_WARNING)
336 choice = self.prompt_int(prompt=PACKAGE_MANAGER_CHOICE, low=1, high=2)
337 active_name = PACKAGE_MANAGER_CHOICES[choice - 1]
338 active_cmd = PACKAGE_MANAGER[active_name]
339 getattr(self, 'install_%s' % active_name.lower())()
340 elif len(installed) == 1:
341 print(PACKAGE_MANAGER_EXISTS % (installed[0], installed[0]))
342 active_name = installed[0]
343 active_cmd = PACKAGE_MANAGER[active_name]
344 else:
345 print(MULTI_PACKAGE_MANAGER_EXISTS)
346 choice = self.prompt_int(prompt=PACKAGE_MANAGER_CHOICE, low=1, high=2)
348 active_name = PACKAGE_MANAGER_CHOICES[choice - 1]
349 active_cmd = PACKAGE_MANAGER[active_name]
351 # Ensure the active package manager is in $PATH and it comes before
352 # /usr/bin. If it doesn't come before /usr/bin, we'll pick up system
353 # packages before package manager installed packages and the build may
354 # break.
355 p = self.which(active_cmd)
356 if not p:
357 print(PACKAGE_MANAGER_BIN_MISSING % active_cmd)
358 sys.exit(1)
360 p_dir = os.path.dirname(p)
361 for path in os.environ['PATH'].split(os.pathsep):
362 if path == p_dir:
363 break
365 for check in ('/bin', '/usr/bin'):
366 if path == check:
367 print(BAD_PATH_ORDER % (check, p_dir, p_dir, check, p_dir))
368 sys.exit(1)
370 return active_name.lower()
372 def install_homebrew(self):
373 print(PACKAGE_MANAGER_INSTALL % ('Homebrew', 'Homebrew', 'Homebrew', 'brew'))
374 bootstrap = urlopen(url=HOMEBREW_BOOTSTRAP, timeout=20).read()
375 with tempfile.NamedTemporaryFile() as tf:
376 tf.write(bootstrap)
377 tf.flush()
379 subprocess.check_call(['ruby', tf.name])
381 def install_macports(self):
382 url = MACPORTS_URL.get(self.minor_version, None)
383 if not url:
384 raise Exception('We do not have a MacPorts install URL for your '
385 'OS X version. You will need to install MacPorts manually.')
387 print(PACKAGE_MANAGER_INSTALL % ('MacPorts', 'MacPorts', 'MacPorts', 'port'))
388 pkg = urlopen(url=url, timeout=300).read()
389 with tempfile.NamedTemporaryFile(suffix='.pkg') as tf:
390 tf.write(pkg)
391 tf.flush()
393 self.run_as_root(['installer', '-pkg', tf.name, '-target', '/'])
395 def _update_package_manager(self):
396 if self.package_manager == 'homebrew':
397 subprocess.check_call([self.brew, '-v', 'update'])
398 else:
399 assert self.package_manager == 'macports'
400 self.run_as_root([self.port, 'selfupdate'])
402 def _upgrade_package(self, package):
403 self._ensure_package_manager_updated()
405 if self.package_manager == 'homebrew':
406 try:
407 subprocess.check_output([self.brew, '-v', 'upgrade', package],
408 stderr=subprocess.STDOUT)
409 except subprocess.CalledProcessError as e:
410 if 'already installed' not in e.output:
411 raise
412 else:
413 assert self.package_manager == 'macports'
415 self.run_as_root([self.port, 'upgrade', package])
417 def upgrade_mercurial(self, current):
418 self._upgrade_package('mercurial')
420 def upgrade_python(self, current):
421 if self.package_manager == 'homebrew':
422 self._upgrade_package('python')
423 else:
424 self._upgrade_package('python27')