mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-06-25 04:29:38 +00:00
525 lines
20 KiB
Python
525 lines
20 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from __future__ import print_function, unicode_literals
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
try:
|
|
from urllib2 import urlopen
|
|
except ImportError:
|
|
from urllib.request import urlopen
|
|
|
|
from distutils.version import StrictVersion
|
|
|
|
from mozboot.base import BaseBootstrapper
|
|
|
|
HOMEBREW_BOOTSTRAP = 'https://raw.githubusercontent.com/Homebrew/install/master/install'
|
|
XCODE_APP_STORE = 'macappstore://itunes.apple.com/app/id497799835?mt=12'
|
|
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'
|
|
HOMEBREW_AUTOCONF213 = 'https://raw.github.com/Homebrew/homebrew-versions/master/autoconf213.rb'
|
|
|
|
MACPORTS_URL = {'9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.2.1-10.9-Mavericks.pkg',
|
|
'8': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.8-MountainLion.pkg',
|
|
'7': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.7-Lion.pkg',
|
|
'6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.6-SnowLeopard.pkg', }
|
|
|
|
MACPORTS_CLANG_PACKAGE = 'clang-3.3'
|
|
|
|
RE_CLANG_VERSION = re.compile('Apple (?:clang|LLVM) version (\d+\.\d+)')
|
|
|
|
APPLE_CLANG_MINIMUM_VERSION = StrictVersion('4.2')
|
|
|
|
XCODE_REQUIRED = '''
|
|
Xcode is required to build Firefox. Please complete the install of Xcode
|
|
through the App Store.
|
|
|
|
It's possible Xcode is already installed on this machine but it isn't being
|
|
detected. This is possible with developer preview releases of Xcode, for
|
|
example. To correct this problem, run:
|
|
|
|
`xcode-select --switch /path/to/Xcode.app`.
|
|
|
|
e.g. `sudo xcode-select --switch /Applications/Xcode.app`.
|
|
'''
|
|
|
|
XCODE_REQUIRED_LEGACY = '''
|
|
You will need to download and install Xcode to build Firefox.
|
|
|
|
Please complete the Xcode download and then relaunch this script.
|
|
'''
|
|
|
|
XCODE_NO_DEVELOPER_DIRECTORY = '''
|
|
xcode-select says you don't have a developer directory configured. We think
|
|
this is due to you not having Xcode installed (properly). We're going to
|
|
attempt to install Xcode through the App Store. If the App Store thinks you
|
|
have Xcode installed, please run xcode-select by hand until it stops
|
|
complaining and then re-run this script.
|
|
'''
|
|
|
|
XCODE_COMMAND_LINE_TOOLS_MISSING = '''
|
|
The Xcode command line tools are required to build Firefox.
|
|
'''
|
|
|
|
INSTALL_XCODE_COMMAND_LINE_TOOLS_STEPS = '''
|
|
Perform the following steps to install the Xcode command line tools:
|
|
|
|
1) Open Xcode.app
|
|
2) Click through any first-run prompts
|
|
3) From the main Xcode menu, select Preferences (Command ,)
|
|
4) Go to the Download tab (near the right)
|
|
5) Install the "Command Line Tools"
|
|
|
|
When that has finished installing, please relaunch this script.
|
|
'''
|
|
|
|
UPGRADE_XCODE_COMMAND_LINE_TOOLS = '''
|
|
An old version of the Xcode command line tools is installed. You will need to
|
|
install a newer version in order to compile Firefox. If Xcode itself is old,
|
|
its command line tools may be too old even if it claims there are no updates
|
|
available, so if you are seeing this message multiple times, please update
|
|
Xcode first.
|
|
'''
|
|
|
|
PACKAGE_MANAGER_INSTALL = '''
|
|
We will install the %s package manager to install required packages.
|
|
|
|
You will be prompted to install %s with its default settings. If you
|
|
would prefer to do this manually, hit CTRL+c, install %s yourself, ensure
|
|
"%s" is in your $PATH, and relaunch bootstrap.
|
|
'''
|
|
|
|
PACKAGE_MANAGER_PACKAGES = '''
|
|
We are now installing all required packages via %s. You will see a lot of
|
|
output as packages are built.
|
|
'''
|
|
|
|
PACKAGE_MANAGER_OLD_CLANG = '''
|
|
We require a newer compiler than what is provided by your version of Xcode.
|
|
|
|
We will install a modern version of Clang through %s.
|
|
'''
|
|
|
|
PACKAGE_MANAGER_CHOICE = '''
|
|
Please choose a package manager you'd like:
|
|
1. Homebrew
|
|
2. MacPorts (Does not yet support bootstrapping Firefox for Android.)
|
|
Your choice:
|
|
'''
|
|
|
|
NO_PACKAGE_MANAGER_WARNING = '''
|
|
It seems you don't have any supported package manager installed.
|
|
'''
|
|
|
|
PACKAGE_MANAGER_EXISTS = '''
|
|
Looks like you have %s installed. We will install all required packages via %s.
|
|
'''
|
|
|
|
MULTI_PACKAGE_MANAGER_EXISTS = '''
|
|
It looks like you have multiple package managers installed.
|
|
'''
|
|
|
|
# May add support for other package manager on os x.
|
|
PACKAGE_MANAGER = {'Homebrew': 'brew',
|
|
'MacPorts': 'port'}
|
|
|
|
PACKAGE_MANAGER_CHOICES = ['Homebrew', 'MacPorts']
|
|
|
|
PACKAGE_MANAGER_BIN_MISSING = '''
|
|
A package manager is installed. However, your current shell does
|
|
not know where to find '%s' yet. You'll need to start a new shell
|
|
to pick up the environment changes so it can be found.
|
|
|
|
Please start a new shell or terminal window and run this
|
|
bootstrapper again.
|
|
|
|
If this problem persists, you will likely want to adjust your
|
|
shell's init script (e.g. ~/.bash_profile) to export a PATH
|
|
environment variable containing the location of your package
|
|
manager binary. e.g.
|
|
|
|
export PATH=/usr/local/bin:$PATH
|
|
'''
|
|
|
|
BAD_PATH_ORDER = '''
|
|
Your environment's PATH variable lists a system path directory (%s)
|
|
before the path to your package manager's binaries (%s).
|
|
This means that the package manager's binaries likely won't be
|
|
detected properly.
|
|
|
|
Modify your shell's configuration (e.g. ~/.profile or
|
|
~/.bash_profile) to have %s appear in $PATH before %s. e.g.
|
|
|
|
export PATH=%s:$PATH
|
|
|
|
Once this is done, start a new shell (likely Command+T) and run
|
|
this bootstrap again.
|
|
'''
|
|
|
|
JAVA_LICENSE_NOTICE = '''
|
|
We installed a recent Java toolchain for you. We agreed to the Oracle Java
|
|
license for you by downloading the JDK. If this is unacceptable you should
|
|
uninstall.
|
|
'''
|
|
|
|
|
|
class OSXBootstrapper(BaseBootstrapper):
|
|
def __init__(self, version, **kwargs):
|
|
BaseBootstrapper.__init__(self, **kwargs)
|
|
|
|
self.os_version = StrictVersion(version)
|
|
|
|
if self.os_version < StrictVersion('10.6'):
|
|
raise Exception('OS X 10.6 or above is required.')
|
|
|
|
self.minor_version = version.split('.')[1]
|
|
|
|
def install_system_packages(self):
|
|
self.ensure_xcode()
|
|
|
|
choice = self.ensure_package_manager()
|
|
self.package_manager = choice
|
|
getattr(self, 'ensure_%s_system_packages' % self.package_manager)()
|
|
|
|
def install_browser_packages(self):
|
|
getattr(self, 'ensure_%s_browser_packages' % self.package_manager)()
|
|
|
|
def install_mobile_android_packages(self):
|
|
getattr(self, 'ensure_%s_mobile_android_packages' % self.package_manager)()
|
|
|
|
def suggest_mobile_android_mozconfig(self):
|
|
getattr(self, 'suggest_%s_mobile_android_mozconfig' % self.package_manager)()
|
|
|
|
def ensure_xcode(self):
|
|
if self.os_version < StrictVersion('10.7'):
|
|
if not os.path.exists('/Developer/Applications/Xcode.app'):
|
|
print(XCODE_REQUIRED_LEGACY)
|
|
|
|
subprocess.check_call(['open', XCODE_LEGACY])
|
|
sys.exit(1)
|
|
|
|
# OS X 10.7 have Xcode come from the app store. However, users can
|
|
# still install Xcode into any arbitrary location. We honor the
|
|
# location of Xcode as set by xcode-select. This should also pick up
|
|
# developer preview releases of Xcode, which can be installed into
|
|
# paths like /Applications/Xcode5-DP6.app.
|
|
elif self.os_version >= StrictVersion('10.7'):
|
|
select = self.which('xcode-select')
|
|
try:
|
|
output = self.check_output([select, '--print-path'],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
# This seems to appear on fresh OS X machines before any Xcode
|
|
# has been installed. It may only occur on OS X 10.9 and later.
|
|
if b'unable to get active developer directory' in e.output:
|
|
print(XCODE_NO_DEVELOPER_DIRECTORY)
|
|
self._install_xcode_app_store()
|
|
assert False # Above should exit.
|
|
|
|
output = e.output
|
|
|
|
# This isn't the most robust check in the world. It relies on the
|
|
# default value not being in an application bundle, which seems to
|
|
# hold on at least Mavericks.
|
|
if b'.app/' not in output:
|
|
print(XCODE_REQUIRED)
|
|
self._install_xcode_app_store()
|
|
assert False # Above should exit.
|
|
|
|
# Once Xcode is installed, you need to agree to the license before you can
|
|
# use it.
|
|
try:
|
|
output = self.check_output(['/usr/bin/xcrun', 'clang'],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
if b'license' in e.output:
|
|
xcodebuild = self.which('xcodebuild')
|
|
try:
|
|
subprocess.check_call([xcodebuild, '-license'],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
if b'requires admin privileges' in e.output:
|
|
self.run_as_root([xcodebuild, '-license'])
|
|
|
|
# Even then we're not done! We need to install the Xcode command line tools.
|
|
# As of Mountain Lion, apparently the only way to do this is to go through a
|
|
# menu dialog inside Xcode itself. We're not making this up.
|
|
if self.os_version >= StrictVersion('10.7'):
|
|
if not os.path.exists('/usr/bin/clang'):
|
|
print(XCODE_COMMAND_LINE_TOOLS_MISSING)
|
|
print(INSTALL_XCODE_COMMAND_LINE_TOOLS_STEPS)
|
|
sys.exit(1)
|
|
|
|
output = self.check_output(['/usr/bin/clang', '--version'])
|
|
match = RE_CLANG_VERSION.search(output)
|
|
if match is None:
|
|
raise Exception('Could not determine Clang version.')
|
|
|
|
version = StrictVersion(match.group(1))
|
|
|
|
if version < APPLE_CLANG_MINIMUM_VERSION:
|
|
print(UPGRADE_XCODE_COMMAND_LINE_TOOLS)
|
|
print(INSTALL_XCODE_COMMAND_LINE_TOOLS_STEPS)
|
|
sys.exit(1)
|
|
|
|
def _install_xcode_app_store(self):
|
|
subprocess.check_call(['open', XCODE_APP_STORE])
|
|
print('Once the install has finished, please relaunch this script.')
|
|
sys.exit(1)
|
|
|
|
def _ensure_homebrew_packages(self, packages, extra_brew_args=[]):
|
|
self.brew = self.which('brew')
|
|
assert self.brew is not None
|
|
cmd = [self.brew] + extra_brew_args
|
|
|
|
installed = self.check_output(cmd + ['list']).split()
|
|
|
|
printed = False
|
|
|
|
for name, package in packages:
|
|
if name in installed:
|
|
continue
|
|
|
|
if not printed:
|
|
print(PACKAGE_MANAGER_PACKAGES % ('Homebrew',))
|
|
printed = True
|
|
|
|
subprocess.check_call(cmd + ['install', package])
|
|
|
|
return printed
|
|
|
|
def _ensure_homebrew_casks(self, casks):
|
|
# Change |brew install cask| into |brew cask install cask|.
|
|
return self._ensure_homebrew_packages(casks, extra_brew_args=['cask'])
|
|
|
|
def ensure_homebrew_system_packages(self):
|
|
packages = [
|
|
# We need to install Python because Mercurial requires the Python
|
|
# development headers which are missing from OS X (at least on
|
|
# 10.8) and because the build system wants a version newer than
|
|
# what Apple ships.
|
|
('python', 'python'),
|
|
('mercurial', 'mercurial'),
|
|
('git', 'git'),
|
|
('autoconf213', HOMEBREW_AUTOCONF213),
|
|
('gnu-tar', 'gnu-tar'),
|
|
('watchman', 'watchman',)
|
|
]
|
|
self._ensure_homebrew_packages(packages)
|
|
|
|
def ensure_homebrew_browser_packages(self):
|
|
packages = [
|
|
('yasm', 'yasm'),
|
|
]
|
|
self._ensure_homebrew_packages(packages)
|
|
|
|
installed = self.check_output([self.brew, 'list']).split()
|
|
if self.os_version < StrictVersion('10.7') and b'llvm' not in installed:
|
|
print(PACKAGE_MANAGER_OLD_CLANG % ('Homebrew',))
|
|
|
|
subprocess.check_call([self.brew, '-v', 'install', 'llvm',
|
|
'--with-clang', '--all-targets'])
|
|
|
|
def ensure_homebrew_mobile_android_packages(self):
|
|
# Multi-part process:
|
|
# 1. System packages.
|
|
# 2. Android SDK and NDK.
|
|
# 3. Android packages.
|
|
|
|
import android
|
|
|
|
# 1. System packages.
|
|
packages = [
|
|
('ant', 'ant'),
|
|
('brew-cask', 'caskroom/cask/brew-cask'), # For installing Java later.
|
|
('wget', 'wget'),
|
|
]
|
|
self._ensure_homebrew_packages(packages)
|
|
|
|
casks = [
|
|
('java', 'java'),
|
|
]
|
|
installed = self._ensure_homebrew_casks(casks)
|
|
if installed:
|
|
print(JAVA_LICENSE_NOTICE) # We accepted a license agreement for the user.
|
|
|
|
# 2. The user may have an external Android SDK (in which case we save
|
|
# them a lengthy download), or they may have already completed the
|
|
# download. We unpack to ~/.mozbuild/{android-sdk-linux, android-ndk-r10e}.
|
|
mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(os.path.join('~', '.mozbuild')))
|
|
self.sdk_path = os.environ.get('ANDROID_SDK_HOME', os.path.join(mozbuild_path, 'android-sdk-macosx'))
|
|
self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r10e'))
|
|
self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-macosx.zip'
|
|
is_64bits = sys.maxsize > 2**32
|
|
if is_64bits:
|
|
self.ndk_url = android.android_ndk_url('darwin')
|
|
else:
|
|
raise Exception('You need a 64-bit version of Mac OS X to build Firefox for Android.')
|
|
|
|
android.ensure_android_sdk_and_ndk(path=mozbuild_path,
|
|
sdk_path=self.sdk_path, sdk_url=self.sdk_url,
|
|
ndk_path=self.ndk_path, ndk_url=self.ndk_url)
|
|
|
|
# 3. We expect the |android| tool to at
|
|
# ~/.mozbuild/android-sdk-macosx/tools/android.
|
|
android_tool = os.path.join(self.sdk_path, 'tools', 'android')
|
|
android.ensure_android_packages(android_tool=android_tool)
|
|
|
|
def suggest_homebrew_mobile_android_mozconfig(self):
|
|
import android
|
|
android.suggest_mozconfig(sdk_path=self.sdk_path,
|
|
ndk_path=self.ndk_path)
|
|
|
|
def _ensure_macports_packages(self, packages):
|
|
self.port = self.which('port')
|
|
assert self.port is not None
|
|
|
|
installed = set(self.check_output([self.port, 'installed']).split())
|
|
|
|
missing = [package for package in packages if package not in installed]
|
|
if missing:
|
|
print(PACKAGE_MANAGER_PACKAGES % ('MacPorts',))
|
|
self.run_as_root([self.port, '-v', 'install'] + missing)
|
|
|
|
def ensure_macports_system_packages(self):
|
|
packages = [
|
|
'python27',
|
|
'mercurial',
|
|
'autoconf213',
|
|
'gnutar',
|
|
'watchman',
|
|
]
|
|
|
|
self._ensure_macports_packages(packages)
|
|
self.run_as_root([self.port, 'select', '--set', 'python', 'python27'])
|
|
|
|
def ensure_macports_browser_packages(self):
|
|
packages = ['yasm']
|
|
|
|
self._ensure_macports_packages(packages)
|
|
|
|
installed = set(self.check_output([self.port, 'installed']).split())
|
|
if self.os_version < StrictVersion('10.7') and MACPORTS_CLANG_PACKAGE not in installed:
|
|
print(PACKAGE_MANAGER_OLD_CLANG % ('MacPorts',))
|
|
self.run_as_root([self.port, '-v', 'install', MACPORTS_CLANG_PACKAGE])
|
|
self.run_as_root([self.port, 'select', '--set', 'clang', 'mp-' + MACPORTS_CLANG_PACKAGE])
|
|
|
|
def ensure_macports_mobile_android_packages(self):
|
|
raise NotImplementedError("We don't yet support bootstrapping Firefox for Android with Macports. " +
|
|
"We don't know of a package that installs the Java 7 JDK. " +
|
|
"See https://bugzilla.mozilla.org/show_bug.cgi?id=1114382.")
|
|
|
|
def suggest_macports_mobile_android_mozconfig(self):
|
|
raise NotImplementedError("We don't yet support bootstrapping Firefox for Android with Macports. " +
|
|
"We don't know of a package that installs the Java 7 JDK." +
|
|
"See https://bugzilla.mozilla.org/show_bug.cgi?id=1114382.")
|
|
|
|
def ensure_package_manager(self):
|
|
'''
|
|
Search package mgr in sys.path, if none is found, prompt the user to install one.
|
|
If only one is found, use that one. If both are found, prompt the user to choose
|
|
one.
|
|
'''
|
|
installed = []
|
|
for name, cmd in PACKAGE_MANAGER.iteritems():
|
|
if self.which(cmd) is not None:
|
|
installed.append(name)
|
|
|
|
active_name, active_cmd = None, None
|
|
|
|
if not installed:
|
|
print(NO_PACKAGE_MANAGER_WARNING)
|
|
choice = self.prompt_int(prompt=PACKAGE_MANAGER_CHOICE, low=1, high=2)
|
|
active_name = PACKAGE_MANAGER_CHOICES[choice - 1]
|
|
active_cmd = PACKAGE_MANAGER[active_name]
|
|
getattr(self, 'install_%s' % active_name.lower())()
|
|
elif len(installed) == 1:
|
|
print(PACKAGE_MANAGER_EXISTS % (installed[0], installed[0]))
|
|
active_name = installed[0]
|
|
active_cmd = PACKAGE_MANAGER[active_name]
|
|
else:
|
|
print(MULTI_PACKAGE_MANAGER_EXISTS)
|
|
choice = self.prompt_int(prompt=PACKAGE_MANAGER_CHOICE, low=1, high=2)
|
|
|
|
active_name = PACKAGE_MANAGER_CHOICES[choice - 1]
|
|
active_cmd = PACKAGE_MANAGER[active_name]
|
|
|
|
# Ensure the active package manager is in $PATH and it comes before
|
|
# /usr/bin. If it doesn't come before /usr/bin, we'll pick up system
|
|
# packages before package manager installed packages and the build may
|
|
# break.
|
|
p = self.which(active_cmd)
|
|
if not p:
|
|
print(PACKAGE_MANAGER_BIN_MISSING % active_cmd)
|
|
sys.exit(1)
|
|
|
|
p_dir = os.path.dirname(p)
|
|
for path in os.environ['PATH'].split(os.pathsep):
|
|
if path == p_dir:
|
|
break
|
|
|
|
for check in ('/bin', '/usr/bin'):
|
|
if path == check:
|
|
print(BAD_PATH_ORDER % (check, p_dir, p_dir, check, p_dir))
|
|
sys.exit(1)
|
|
|
|
return active_name.lower()
|
|
|
|
def install_homebrew(self):
|
|
print(PACKAGE_MANAGER_INSTALL % ('Homebrew', 'Homebrew', 'Homebrew', 'brew'))
|
|
bootstrap = urlopen(url=HOMEBREW_BOOTSTRAP, timeout=20).read()
|
|
with tempfile.NamedTemporaryFile() as tf:
|
|
tf.write(bootstrap)
|
|
tf.flush()
|
|
|
|
subprocess.check_call(['ruby', tf.name])
|
|
|
|
def install_macports(self):
|
|
url = MACPORTS_URL.get(self.minor_version, None)
|
|
if not url:
|
|
raise Exception('We do not have a MacPorts install URL for your '
|
|
'OS X version. You will need to install MacPorts manually.')
|
|
|
|
print(PACKAGE_MANAGER_INSTALL % ('MacPorts', 'MacPorts', 'MacPorts', 'port'))
|
|
pkg = urlopen(url=url, timeout=300).read()
|
|
with tempfile.NamedTemporaryFile(suffix='.pkg') as tf:
|
|
tf.write(pkg)
|
|
tf.flush()
|
|
|
|
self.run_as_root(['installer', '-pkg', tf.name, '-target', '/'])
|
|
|
|
def _update_package_manager(self):
|
|
if self.package_manager == 'homebrew':
|
|
subprocess.check_call([self.brew, '-v', 'update'])
|
|
else:
|
|
assert self.package_manager == 'macports'
|
|
self.run_as_root([self.port, 'selfupdate'])
|
|
|
|
def _upgrade_package(self, package):
|
|
self._ensure_package_manager_updated()
|
|
|
|
if self.package_manager == 'homebrew':
|
|
try:
|
|
subprocess.check_output([self.brew, '-v', 'upgrade', package],
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
if b'already installed' not in e.output:
|
|
raise
|
|
else:
|
|
assert self.package_manager == 'macports'
|
|
|
|
self.run_as_root([self.port, 'upgrade', package])
|
|
|
|
def upgrade_mercurial(self, current):
|
|
self._upgrade_package('mercurial')
|
|
|
|
def upgrade_python(self, current):
|
|
if self.package_manager == 'homebrew':
|
|
self._upgrade_package('python')
|
|
else:
|
|
self._upgrade_package('python27')
|