From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- python/mozboot/README.rst | 19 + python/mozboot/bin/bootstrap-msys2.vbs | 116 +++++ python/mozboot/bin/bootstrap.py | 170 ++++++ python/mozboot/mozboot/__init__.py | 0 python/mozboot/mozboot/android.py | 270 ++++++++++ python/mozboot/mozboot/archlinux.py | 223 ++++++++ python/mozboot/mozboot/base.py | 452 ++++++++++++++++ python/mozboot/mozboot/bootstrap.py | 437 ++++++++++++++++ python/mozboot/mozboot/centosfedora.py | 153 ++++++ python/mozboot/mozboot/debian.py | 188 +++++++ python/mozboot/mozboot/freebsd.py | 63 +++ python/mozboot/mozboot/gentoo.py | 33 ++ python/mozboot/mozboot/mach_commands.py | 67 +++ python/mozboot/mozboot/mozillabuild.py | 77 +++ python/mozboot/mozboot/openbsd.py | 45 ++ python/mozboot/mozboot/osx.py | 577 ++++++++++++++++++++ python/mozboot/mozboot/util.py | 20 + python/mozboot/mozboot/windows.py | 95 ++++ python/mozboot/setup.py | 16 + python/mozboot/support/ConEmu.xml | 897 ++++++++++++++++++++++++++++++++ 20 files changed, 3918 insertions(+) create mode 100644 python/mozboot/README.rst create mode 100644 python/mozboot/bin/bootstrap-msys2.vbs create mode 100755 python/mozboot/bin/bootstrap.py create mode 100644 python/mozboot/mozboot/__init__.py create mode 100644 python/mozboot/mozboot/android.py create mode 100644 python/mozboot/mozboot/archlinux.py create mode 100644 python/mozboot/mozboot/base.py create mode 100644 python/mozboot/mozboot/bootstrap.py create mode 100644 python/mozboot/mozboot/centosfedora.py create mode 100644 python/mozboot/mozboot/debian.py create mode 100644 python/mozboot/mozboot/freebsd.py create mode 100644 python/mozboot/mozboot/gentoo.py create mode 100644 python/mozboot/mozboot/mach_commands.py create mode 100644 python/mozboot/mozboot/mozillabuild.py create mode 100644 python/mozboot/mozboot/openbsd.py create mode 100644 python/mozboot/mozboot/osx.py create mode 100644 python/mozboot/mozboot/util.py create mode 100644 python/mozboot/mozboot/windows.py create mode 100644 python/mozboot/setup.py create mode 100755 python/mozboot/support/ConEmu.xml (limited to 'python/mozboot') diff --git a/python/mozboot/README.rst b/python/mozboot/README.rst new file mode 100644 index 000000000..a1366eea2 --- /dev/null +++ b/python/mozboot/README.rst @@ -0,0 +1,19 @@ +mozboot - Bootstrap your system to build Mozilla projects +========================================================= + +This package contains code used for bootstrapping a system to build +mozilla-central. + +This code is not part of the build system per se. Instead, it is related +to everything up to invoking the actual build system. + +If you have a copy of the source tree, you run: + + python bin/bootstrap.py + +If you don't have a copy of the source tree, you can run: + + curl https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py | python - + +The bootstrap script will download everything it needs from hg.mozilla.org +automatically! diff --git a/python/mozboot/bin/bootstrap-msys2.vbs b/python/mozboot/bin/bootstrap-msys2.vbs new file mode 100644 index 000000000..304d4f9df --- /dev/null +++ b/python/mozboot/bin/bootstrap-msys2.vbs @@ -0,0 +1,116 @@ +' 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/. + +' This script downloads and install MSYS2 and the preferred terminal emulator ConEmu + +Sub Download(uri, path) + Dim httpRequest, stream + + Set httpRequest = CreateObject("MSXML2.ServerXMLHTTP.6.0") + Set stream = CreateObject("Adodb.Stream") + + httpRequest.Open "GET", uri, False + httpRequest.Send + + With stream + .type = 1 + .open + .write httpRequest.responseBody + .savetofile path, 2 + End With +End Sub + +Function GetInstallPath() + Dim message, prompt + + message = "When you click OK, we will download and extract a build environment to the directory specified. You should see various windows appear. Do NOT interact with them until one explicitly prompts you to continue." & vbCrLf & vbCrLf & "Installation Path:" + title = "Select Installation Location" + GetInstallPath = InputBox(message, title, "c:\mozdev") +end Function + +Dim installPath, msysPath, conemuPath, conemuSettingsPath, conemuExecutable, bashExecutable +Dim conemuSettingsURI, settingsFile, settingsText, fso, shell, msysArchive, appShell, errorCode +Dim mingwExecutable + +' Set up OS interaction like filesystem and shell +Set fso = CreateObject("Scripting.FileSystemObject") +Set shell = CreateObject("WScript.Shell") +Set appShell = CreateObject("Shell.Application") + +' Get where MSYS2 and ConEmu should be installed, create directories if necessary +installPath = GetInstallPath() +msysPath = fso.BuildPath(installPath, "msys64") +conemuPath = fso.BuildPath(installPath, "ConEmu") +If NOT fso.FolderExists(installPath) Then + fso.CreateFolder(installPath) + fso.CreateFolder(msysPath) +End If +If NOT fso.FolderExists(installPath) Then + MsgBox("Failed to create folder. Do you have permission to install in this directory?") + WScript.Quit 1 +End If + +On Error Resume Next +' Download and move MSYS2 into the right place +Download "https://api.pub.build.mozilla.org/tooltool/sha512/f93a685c8a10abbd349cbef5306441ba235c4cbfba1cc000299e11b58f258e9953cbe23463515407925eeca94c3f5d8e5f637c95be387e620845efa43cdcb0c0", "msys2.zip" +Set FilesInZip = appShell.NameSpace(fso.GetAbsolutePathName("msys2.zip")).Items() +appShell.NameSpace(msysPath).CopyHere(FilesInZip) +' MSYS2 archive doesn't have tmp directory... +fso.CreateFolder(fso.BuildPath(msysPath, "tmp")) +fso.DeleteFile("msys2.zip") +If Err.Number <> 0 Then + MsgBox("Error downloading and installing MSYS2. Make sure you have internet connection. If you think this is a bug, please file one in Bugzilla https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Build%20Config") + WScript.Quit 1 +End If +On Error GoTo 0 + +' Install ConEmu +' Download installer +On Error Resume Next +Download "https://conemu.github.io/install2.ps1", "install2.ps1" +conemuSettingsURI = "https://api.pub.build.mozilla.org/tooltool/sha512/9aa384ecc8025a974999e913c83064b3b797e05d19806e62ef558c8300e4c3f72967e9464ace59759f76216fc2fc66f338a1e5cdea3b9aa264529487f091d929" +' Run installer +errorCode = shell.Run("powershell.exe -NoProfile -ExecutionPolicy Unrestricted set dst '" & conemuPath & "'; set ver 'stable'; set lnk 'Mozilla Development Shell'; set xml '" & conemuSettingsURI & "'; set run $FALSE; .\install2.ps1", 0, true) +' Delete ConEmu installer +fso.DeleteFile("install2.ps1") +If Err.Number <> 0 Then + MsgBox("Error downloading and installing ConEmu. Make sure you have internet connection and Powershell installed. If you think this is a bug, please file one in Bugzilla https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Build%20Config") + WScript.Quit 1 +End If +On Error GoTo 0 + +' Replace paths in ConEmu settings file +conemuSettingsPath = fso.BuildPath(conemuPath, "ConEmu.xml") +Set settingsFile = fso.OpenTextFile(conemuSettingsPath, 1) +settingsText = settingsFile.ReadAll +settingsFile.Close +settingsText = Replace(settingsText, "%MSYS2_PATH", msysPath) +Set settingsFile = fso.OpenTextFile(conemuSettingsPath, 2) +settingsFile.WriteLine settingsText +settingsFile.Close + +' Make MSYS2 Mozilla-ready +bashExecutable = fso.BuildPath(msysPath, fso.BuildPath("usr", fso.BuildPath("bin", "bash.exe"))) +conemuExecutable = fso.BuildPath(conemuPath, "ConEmu.exe") +' There may be spaces in the paths to the executable, this ensures they're parsed correctly +bashExecutable = """" & bashExecutable & """" +conemuExecutable = """" & conemuExecutable & """" + +errorCode = shell.Run(bashExecutable & " -l -c 'logout", 1, true) +If errorCode <> 0 Then + MsgBox("MSYS2 initial setup failed. Make sure you have full access to the path you specified. If you think this is a bug, please file one in Bugzilla at https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Build%20Config") + WScript.Quit 1 +End If + +errorCode = shell.Run(bashExecutable & " -l -c 'pacman -Syu --noconfirm wget mingw-w64-x86_64-python2-pip && logout'", 1, true) +If errorCode <> 0 Then + MsgBox("Package update failed. Make sure you have internet access. If you think this is a bug, please file one in Bugzilla at https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Build%20Config") + WScript.Quit 1 +End If + +errorCode = shell.Run(conemuExecutable & " -run set CHERE_INVOKING=1 & set MSYSTEM=MINGW64 & " & bashExecutable & " -cil 'export MOZ_WINDOWS_BOOTSTRAP=1 && cd """ & installPath & """ && wget -q https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py -O /tmp/bootstrap.py && python /tmp/bootstrap.py'", 1, true) +If errorCode <> 0 Then + MsgBox("Bootstrap failed. Make sure you have internet access. If you think this is a bug, please file one in Bugzilla https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Build%20Config") + WScript.Quit 1 +End If diff --git a/python/mozboot/bin/bootstrap.py b/python/mozboot/bin/bootstrap.py new file mode 100755 index 000000000..d916351e7 --- /dev/null +++ b/python/mozboot/bin/bootstrap.py @@ -0,0 +1,170 @@ +#!/usr/bin/env 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/. + +# This script provides one-line bootstrap support to configure systems to build +# the tree. +# +# The role of this script is to load the Python modules containing actual +# bootstrap support. It does this through various means, including fetching +# content from the upstream source repository. + +# If we add unicode_literals, optparse breaks on Python 2.6.1 (which is needed +# to support OS X 10.6). + +from __future__ import print_function + +WRONG_PYTHON_VERSION_MESSAGE = ''' +Bootstrap currently only runs on Python 2.7 or Python 2.6. Please try re-running with python2.7 or python2.6. + +If these aren't available on your system, you may need to install them. Look for a "python2" or "python27" package in your package manager. +''' + +import sys +if sys.version_info[:2] not in [(2, 6), (2, 7)]: + print(WRONG_PYTHON_VERSION_MESSAGE) + sys.exit(1) + +import os +import shutil +from StringIO import StringIO +import tempfile +try: + from urllib2 import urlopen +except ImportError: + from urllib.request import urlopen +import zipfile + +from optparse import OptionParser + +# The next two variables define where in the repository the Python files +# reside. This is used to remotely download file content when it isn't +# available locally. +REPOSITORY_PATH_PREFIX = 'python/mozboot/' + +TEMPDIR = None + + +def setup_proxy(): + # Some Linux environments define ALL_PROXY, which is a SOCKS proxy + # intended for all protocols. Python doesn't currently automatically + # detect this like it does for http_proxy and https_proxy. + if 'ALL_PROXY' in os.environ and 'https_proxy' not in os.environ: + os.environ['https_proxy'] = os.environ['ALL_PROXY'] + if 'ALL_PROXY' in os.environ and 'http_proxy' not in os.environ: + os.environ['http_proxy'] = os.environ['ALL_PROXY'] + + +def fetch_files(repo_url, repo_type): + setup_proxy() + repo_url = repo_url.rstrip('/') + + files = {} + + if repo_type == 'hgweb': + url = repo_url + '/archive/default.zip/python/mozboot' + req = urlopen(url=url, timeout=30) + data = StringIO(req.read()) + data.seek(0) + zip = zipfile.ZipFile(data, 'r') + for f in zip.infolist(): + # The paths are prefixed with the repo and revision name before the + # directory name. + offset = f.filename.find(REPOSITORY_PATH_PREFIX) + len(REPOSITORY_PATH_PREFIX) + name = f.filename[offset:] + + # We only care about the Python modules. + if not name.startswith('mozboot/'): + continue + + files[name] = zip.read(f) + else: + raise NotImplementedError('Not sure how to handle repo type.', repo_type) + + return files + + +def ensure_environment(repo_url=None, repo_type=None): + """Ensure we can load the Python modules necessary to perform bootstrap.""" + + try: + from mozboot.bootstrap import Bootstrapper + return Bootstrapper + except ImportError: + # The first fallback is to assume we are running from a tree checkout + # and have the files in a sibling directory. + pardir = os.path.join(os.path.dirname(__file__), os.path.pardir) + include = os.path.normpath(pardir) + + sys.path.append(include) + try: + from mozboot.bootstrap import Bootstrapper + return Bootstrapper + except ImportError: + sys.path.pop() + + # The next fallback is to download the files from the source + # repository. + files = fetch_files(repo_url, repo_type) + + # Install them into a temporary location. They will be deleted + # after this script has finished executing. + global TEMPDIR + TEMPDIR = tempfile.mkdtemp() + + for relpath in files.keys(): + destpath = os.path.join(TEMPDIR, relpath) + destdir = os.path.dirname(destpath) + + if not os.path.exists(destdir): + os.makedirs(destdir) + + with open(destpath, 'wb') as fh: + fh.write(files[relpath]) + + # This should always work. + sys.path.append(TEMPDIR) + from mozboot.bootstrap import Bootstrapper + return Bootstrapper + + +def main(args): + parser = OptionParser() + parser.add_option('-r', '--repo-url', dest='repo_url', + default='https://hg.mozilla.org/mozilla-central/', + help='Base URL of source control repository where bootstrap files can ' + 'be downloaded.') + parser.add_option('--repo-type', dest='repo_type', + default='hgweb', + help='The type of the repository. This defines how we fetch file ' + 'content. Like --repo, you should not need to set this.') + + parser.add_option('--application-choice', dest='application_choice', + help='Pass in an application choice (desktop/android) instead of using the ' + 'default interactive prompt.') + parser.add_option('--no-interactive', dest='no_interactive', action='store_true', + help='Answer yes to any (Y/n) interactive prompts.') + + options, leftover = parser.parse_args(args) + + try: + try: + cls = ensure_environment(options.repo_url, options.repo_type) + except Exception as e: + print('Could not load the bootstrap Python environment.\n') + print('This should never happen. Consider filing a bug.\n') + print('\n') + print(e) + return 1 + dasboot = cls(choice=options.application_choice, no_interactive=options.no_interactive) + dasboot.bootstrap() + + return 0 + finally: + if TEMPDIR is not None: + shutil.rmtree(TEMPDIR) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/python/mozboot/mozboot/__init__.py b/python/mozboot/mozboot/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/mozboot/mozboot/android.py b/python/mozboot/mozboot/android.py new file mode 100644 index 000000000..cac000610 --- /dev/null +++ b/python/mozboot/mozboot/android.py @@ -0,0 +1,270 @@ +# 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/. + +# If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks. +from __future__ import print_function + +import errno +import os +import stat +import subprocess +import sys + +# These are the platform and build-tools versions for building +# mobile/android, respectively. Try to keep these in synch with the +# build system and Mozilla's automation. +ANDROID_TARGET_SDK = '23' +ANDROID_BUILD_TOOLS_VERSION = '23.0.3' + +# These are the "Android packages" needed for building Firefox for Android. +# Use |android list sdk --extended| to see these identifiers. +ANDROID_PACKAGES = [ + 'tools', + 'platform-tools', + 'build-tools-%s' % ANDROID_BUILD_TOOLS_VERSION, + 'android-%s' % ANDROID_TARGET_SDK, + 'extra-google-m2repository', + 'extra-android-m2repository', +] + +ANDROID_NDK_EXISTS = ''' +Looks like you have the Android NDK installed at: +%s +''' + +ANDROID_SDK_EXISTS = ''' +Looks like you have the Android SDK installed at: +%s +We will install all required Android packages. +''' + +NOT_INSTALLING_ANDROID_PACKAGES = ''' +It looks like you already have the following Android packages: +%s +No need to update! +''' + +INSTALLING_ANDROID_PACKAGES = ''' +We are now installing the following Android packages: +%s +You may be prompted to agree to the Android license. You may see some of +output as packages are downloaded and installed. +''' + +MISSING_ANDROID_PACKAGES = ''' +We tried to install the following Android packages: +%s +But it looks like we couldn't install: +%s +Install these Android packages manually and run this bootstrapper again. +''' + +MOBILE_ANDROID_MOZCONFIG_TEMPLATE = ''' +Paste the lines between the chevrons (>>> and <<<) into your mozconfig file: + +<<< +# Build Firefox for Android: +ac_add_options --enable-application=mobile/android +ac_add_options --target=arm-linux-androideabi + +# With the following Android SDK and NDK: +ac_add_options --with-android-sdk="%s" +ac_add_options --with-android-ndk="%s" +>>> +''' + +MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE = ''' +Paste the lines between the chevrons (>>> and <<<) into your mozconfig file: + +<<< +# Build Firefox for Android Artifact Mode: +ac_add_options --enable-application=mobile/android +ac_add_options --target=arm-linux-androideabi +ac_add_options --enable-artifact-builds + +# With the following Android SDK: +ac_add_options --with-android-sdk="%s" + +# Write build artifacts to: +mk_add_options MOZ_OBJDIR=./objdir-frontend +>>> +''' + + +def check_output(*args, **kwargs): + """Run subprocess.check_output even if Python doesn't provide it.""" + from base import BaseBootstrapper + fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output) + + return fn(*args, **kwargs) + + +def list_missing_android_packages(android_tool, packages): + ''' + Use the given |android| tool to return the sub-list of Android + |packages| given that are not installed. + ''' + missing = [] + + # There's no obvious way to see what's been installed already, + # but packages that are installed don't appear in the list of + # available packages. + lines = check_output([android_tool, + 'list', 'sdk', '--no-ui', '--extended']).splitlines() + + # Lines look like: 'id: 59 or "extra-google-simulators"' + for line in lines: + is_id_line = False + try: + is_id_line = line.startswith("id:") + except: + # Some lines contain non-ASCII characters. Ignore them. + pass + if not is_id_line: + continue + + for package in packages: + if '"%s"' % package in line: + # Not installed! + missing.append(package) + + return missing + + +def install_mobile_android_sdk_or_ndk(url, path): + ''' + Fetch an Android SDK or NDK from |url| and unpack it into + the given |path|. + + We expect wget to be installed and found on the system path. + + We use, and wget respects, https. We could also include SHAs for a + small improvement in the integrity guarantee we give. But this script is + bootstrapped over https anyway, so it's a really minor improvement. + + We use |wget --continue| as a cheap cache of the downloaded artifacts, + writing into |path|/mozboot. We don't yet clean the cache; it's better + to waste disk and not require a long re-download than to wipe the cache + prematurely. + ''' + + old_path = os.getcwd() + try: + download_path = os.path.join(path, 'mozboot') + try: + os.makedirs(download_path) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(download_path): + pass + else: + raise + + os.chdir(download_path) + subprocess.check_call(['wget', '--continue', url]) + file = url.split('/')[-1] + + os.chdir(path) + abspath = os.path.join(download_path, file) + if file.endswith('.tar.gz') or file.endswith('.tgz'): + cmd = ['tar', 'zxf', abspath] + elif file.endswith('.tar.bz2'): + cmd = ['tar', 'jxf', abspath] + elif file.endswith('.zip'): + cmd = ['unzip', '-q', abspath] + elif file.endswith('.bin'): + # Execute the .bin file, which unpacks the content. + mode = os.stat(path).st_mode + os.chmod(abspath, mode | stat.S_IXUSR) + cmd = [abspath] + else: + raise NotImplementedError("Don't know how to unpack file: %s" % file) + + print('Unpacking %s...' % abspath) + + with open(os.devnull, "w") as stdout: + # These unpack commands produce a ton of output; ignore it. The + # .bin files are 7z archives; there's no command line flag to quiet + # output, so we use this hammer. + subprocess.check_call(cmd, stdout=stdout) + + print('Unpacking %s... DONE' % abspath) + + finally: + os.chdir(old_path) + + +def ensure_android_sdk_and_ndk(path, sdk_path, sdk_url, ndk_path, ndk_url, artifact_mode): + ''' + Ensure the Android SDK and NDK are found at the given paths. If not, fetch + and unpack the SDK and/or NDK from the given URLs into |path|. + ''' + + # It's not particularly bad to overwrite the NDK toolchain, but it does take + # a while to unpack, so let's avoid the disk activity if possible. The SDK + # may prompt about licensing, so we do this first. + # Check for Android NDK only if we are not in artifact mode. + if not artifact_mode: + if os.path.isdir(ndk_path): + print(ANDROID_NDK_EXISTS % ndk_path) + else: + install_mobile_android_sdk_or_ndk(ndk_url, path) + + # We don't want to blindly overwrite, since we use the |android| tool to + # install additional parts of the Android toolchain. If we overwrite, + # we lose whatever Android packages the user may have already installed. + if os.path.isdir(sdk_path): + print(ANDROID_SDK_EXISTS % sdk_path) + else: + install_mobile_android_sdk_or_ndk(sdk_url, path) + + +def ensure_android_packages(android_tool, packages=None): + ''' + Use the given android tool (like 'android') to install required Android + packages. + ''' + + if not packages: + packages = ANDROID_PACKAGES + + # Bug 1171232: The |android| tool behaviour has changed; we no longer can + # see what packages are installed easily. Force installing everything until + # we find a way to actually see the missing packages. + missing = packages + if not missing: + print(NOT_INSTALLING_ANDROID_PACKAGES % ', '.join(packages)) + return + + # This tries to install all the required Android packages. The user + # may be prompted to agree to the Android license. + print(INSTALLING_ANDROID_PACKAGES % ', '.join(missing)) + subprocess.check_call([android_tool, + 'update', 'sdk', '--no-ui', '--all', + '--filter', ','.join(missing)]) + + # Bug 1171232: The |android| tool behaviour has changed; we no longer can + # see what packages are installed easily. Don't check until we find a way + # to actually verify. + failing = [] + if failing: + raise Exception(MISSING_ANDROID_PACKAGES % (', '.join(missing), ', '.join(failing))) + + +def suggest_mozconfig(sdk_path=None, ndk_path=None, artifact_mode=False): + if artifact_mode: + print(MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE % (sdk_path)) + else: + print(MOBILE_ANDROID_MOZCONFIG_TEMPLATE % (sdk_path, ndk_path)) + + +def android_ndk_url(os_name, ver='r11b'): + # Produce a URL like 'https://dl.google.com/android/repository/android-ndk-r11b-linux-x86_64.zip + base_url = 'https://dl.google.com/android/repository/android-ndk' + + if sys.maxsize > 2**32: + arch = 'x86_64' + else: + arch = 'x86' + + return '%s-%s-%s-%s.zip' % (base_url, ver, os_name, arch) diff --git a/python/mozboot/mozboot/archlinux.py b/python/mozboot/mozboot/archlinux.py new file mode 100644 index 000000000..12ce6b4a4 --- /dev/null +++ b/python/mozboot/mozboot/archlinux.py @@ -0,0 +1,223 @@ +# 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/. + +import os +import sys +import tempfile +import subprocess +import glob + +from mozboot.base import BaseBootstrapper + + +class ArchlinuxBootstrapper(BaseBootstrapper): + '''Archlinux experimental bootstrapper.''' + + SYSTEM_PACKAGES = [ + 'autoconf2.13', + 'base-devel', + 'ccache', + 'mercurial', + 'python2', + 'python2-setuptools', + 'unzip', + 'zip', + ] + + BROWSER_PACKAGES = [ + 'alsa-lib', + 'dbus-glib', + 'desktop-file-utils', + 'gconf', + 'gtk2', + 'gtk3', + 'hicolor-icon-theme', + 'hunspell', + 'icu', + 'libevent', + 'libvpx', + 'libxt', + 'mime-types', + 'mozilla-common', + 'nss', + 'sqlite', + 'startup-notification', + 'diffutils', + 'gst-plugins-base-libs', + 'imake', + 'inetutils', + 'libpulse', + 'mercurial', + 'mesa', + 'python2', + 'unzip', + 'xorg-server-xvfb', + 'yasm', + 'zip', + 'gst-libav', + 'gst-plugins-good', + 'networkmanager', + ] + + BROWSER_AUR_PACKAGES = [ + 'https://aur.archlinux.org/cgit/aur.git/snapshot/uuid.tar.gz', + ] + + MOBILE_ANDROID_COMMON_PACKAGES = [ + 'zlib', # mobile/android requires system zlib. + 'jdk7-openjdk', # It would be nice to handle alternative JDKs. See https://wiki.archlinux.org/index.php/Java. + 'wget', # For downloading the Android SDK and NDK. + 'multilib/lib32-libstdc++5', # See comment about 32 bit binaries and multilib below. + 'multilib/lib32-ncurses', + 'multilib/lib32-readline', + 'multilib/lib32-zlib', + ] + + def __init__(self, version, dist_id, **kwargs): + print 'Using an experimental bootstrapper for Archlinux.' + BaseBootstrapper.__init__(self, **kwargs) + + def install_system_packages(self): + self.pacman_install(*self.SYSTEM_PACKAGES) + + def install_browser_packages(self): + self.ensure_browser_packages() + + def install_browser_artifact_mode_packages(self): + self.ensure_browser_packages(artifact_mode=True) + + def install_mobile_android_packages(self): + self.ensure_mobile_android_packages() + + def install_mobile_android_artifact_mode_packages(self): + self.ensure_mobile_android_packages(artifact_mode=True) + + def ensure_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + self.aur_install(*self.BROWSER_AUR_PACKAGES) + self.pacman_install(*self.BROWSER_PACKAGES) + + def ensure_mobile_android_packages(self, artifact_mode=False): + import android + + # Multi-part process: + # 1. System packages. + # 2. Android SDK. Android NDK only if we are not in artifact mode. + # 3. Android packages. + + # 1. This is hard to believe, but the Android SDK binaries are 32-bit + # and that conflicts with 64-bit Arch installations out of the box. The + # solution is to add the multilibs repository; unfortunately, this + # requires manual intervention. + try: + self.pacman_install(*self.MOBILE_ANDROID_COMMON_PACKAGES) + except e: + print('Failed to install all packages. The Android developer ' + 'toolchain requires 32 bit binaries be enabled (see ' + 'https://wiki.archlinux.org/index.php/Android). You may need to ' + 'manually enable the multilib repository following the instructions ' + 'at https://wiki.archlinux.org/index.php/Multilib.') + raise e + + # 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-r11b}. + 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-linux')) + self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r11b')) + self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz' + self.ndk_url = android.android_ndk_url('linux') + + 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, + artifact_mode=artifact_mode) + android_tool = os.path.join(self.sdk_path, 'tools', 'android') + android.ensure_android_packages(android_tool=android_tool) + + def suggest_mobile_android_mozconfig(self, artifact_mode=False): + import android + android.suggest_mozconfig(sdk_path=self.sdk_path, + ndk_path=self.ndk_path, + artifact_mode=artifact_mode) + + def suggest_mobile_android_artifact_mode_mozconfig(self): + self.suggest_mobile_android_mozconfig(artifact_mode=True) + + def _update_package_manager(self): + self.pacman_update + + def upgrade_mercurial(self, current): + self.pacman_install('mercurial') + + def upgrade_python(self, current): + self.pacman_install('python2') + + def pacman_install(self, *packages): + command = ['pacman', '-S', '--needed'] + if self.no_interactive: + command.append('--noconfirm') + + command.extend(packages) + + self.run_as_root(command) + + def pacman_update(self): + command = ['pacman', '-S', '--refresh'] + + self.run_as_root(command) + + def run(self, command, env=None): + subprocess.check_call(command, stdin=sys.stdin, env=env) + + def download(self, uri): + command = ['curl', '-L', '-O', uri] + self.run(command) + + def unpack(self, path, name, ext): + if ext == 'gz': + compression = '-z' + elif ext == 'bz': + compression == '-j' + elif exit == 'xz': + compression == 'x' + + name = os.path.join(path, name) + '.tar.' + ext + command = ['tar', '-x', compression, '-f', name, '-C', path] + self.run(command) + + def makepkg(self, name): + command = ['makepkg', '-s'] + makepkg_env = os.environ.copy() + makepkg_env['PKGEXT'] = '.pkg.tar.xz' + self.run(command, env=makepkg_env) + pack = glob.glob(name + '*.pkg.tar.xz')[0] + command = ['pacman', '-U'] + if self.no_interactive: + command.append('--noconfirm') + command.append(pack) + self.run_as_root(command) + + def aur_install(self, *packages): + path = tempfile.mkdtemp() + if not self.no_interactive: + print('WARNING! This script requires to install packages from the AUR ' + 'This is potentially unsecure so I recommend that you carefully ' + 'read each package description and check the sources.' + 'These packages will be built in ' + path + '.') + choice = raw_input('Do you want to continue? (yes/no) [no]') + if choice != 'yes': + sys.exit(1) + + base_dir = os.getcwd() + os.chdir(path) + for package in packages: + name, _, ext = package.split('/')[-1].split('.') + directory = os.path.join(path, name) + self.download(package) + self.unpack(path, name, ext) + os.chdir(directory) + self.makepkg(name) + + os.chdir(base_dir) diff --git a/python/mozboot/mozboot/base.py b/python/mozboot/mozboot/base.py new file mode 100644 index 000000000..0e1871da9 --- /dev/null +++ b/python/mozboot/mozboot/base.py @@ -0,0 +1,452 @@ +# 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 hashlib +import os +import re +import subprocess +import sys +import urllib2 + +from distutils.version import LooseVersion + + +NO_MERCURIAL = ''' +Could not find Mercurial (hg) in the current shell's path. Try starting a new +shell and running the bootstrapper again. +''' + +MERCURIAL_UNABLE_UPGRADE = ''' +You are currently running Mercurial %s. Running %s or newer is +recommended for performance and stability reasons. + +Unfortunately, this bootstrapper currently does not know how to automatically +upgrade Mercurial on your machine. + +You can usually install Mercurial through your package manager or by +downloading a package from http://mercurial.selenic.com/. +''' + +MERCURIAL_UPGRADE_FAILED = ''' +We attempted to upgrade Mercurial to a modern version (%s or newer). +However, you appear to have version %s still. + +It's possible your package manager doesn't support a modern version of +Mercurial. It's also possible Mercurial is not being installed in the search +path for this shell. Try creating a new shell and run this bootstrapper again. + +If it continues to fail, consider installing Mercurial by following the +instructions at http://mercurial.selenic.com/. +''' + +PYTHON_UNABLE_UPGRADE = ''' +You are currently running Python %s. Running %s or newer (but +not 3.x) is required. + +Unfortunately, this bootstrapper does not currently know how to automatically +upgrade Python on your machine. + +Please search the Internet for how to upgrade your Python and try running this +bootstrapper again to ensure your machine is up to date. +''' + +PYTHON_UPGRADE_FAILED = ''' +We attempted to upgrade Python to a modern version (%s or newer). +However, you appear to still have version %s. + +It's possible your package manager doesn't yet expose a modern version of +Python. It's also possible Python is not being installed in the search path for +this shell. Try creating a new shell and run this bootstrapper again. + +If this continues to fail and you are sure you have a modern Python on your +system, ensure it is on the $PATH and try again. If that fails, you'll need to +install Python manually and ensure the path with the python binary is listed in +the $PATH environment variable. + +We recommend the following tools for installing Python: + + pyenv -- https://github.com/yyuu/pyenv) + pythonz -- https://github.com/saghul/pythonz + official installers -- http://www.python.org/ +''' + +BROWSER_ARTIFACT_MODE_MOZCONFIG = ''' +Paste the lines between the chevrons (>>> and <<<) into your mozconfig file: + +<<< +# Automatically download and use compiled C++ components: +ac_add_options --enable-artifact-builds +>>> +''' + +# Upgrade Mercurial older than this. +# This should match OLDEST_NON_LEGACY_VERSION from +# the hg setup wizard in version-control-tools. +MODERN_MERCURIAL_VERSION = LooseVersion('3.7.3') + +# Upgrade Python older than this. +MODERN_PYTHON_VERSION = LooseVersion('2.7.3') + + +class BaseBootstrapper(object): + """Base class for system bootstrappers.""" + + def __init__(self, no_interactive=False): + self.package_manager_updated = False + self.no_interactive = no_interactive + + def install_system_packages(self): + ''' + Install packages shared by all applications. These are usually + packages required by the development (like mercurial) or the + build system (like autoconf). + ''' + raise NotImplementedError('%s must implement install_system_packages()' % + __name__) + + def install_browser_packages(self): + ''' + Install packages required to build Firefox for Desktop (application + 'browser'). + ''' + raise NotImplementedError('Cannot bootstrap Firefox for Desktop: ' + '%s does not yet implement install_browser_packages()' % + __name__) + + def suggest_browser_mozconfig(self): + ''' + Print a message to the console detailing what the user's mozconfig + should contain. + + Firefox for Desktop can in simple cases determine its build environment + entirely from configure. + ''' + pass + + def install_browser_artifact_mode_packages(self): + ''' + Install packages required to build Firefox for Desktop (application + 'browser') in Artifact Mode. + ''' + raise NotImplementedError( + 'Cannot bootstrap Firefox for Desktop Artifact Mode: ' + '%s does not yet implement install_browser_artifact_mode_packages()' % + __name__) + + def suggest_browser_artifact_mode_mozconfig(self): + ''' + Print a message to the console detailing what the user's mozconfig + should contain. + + Firefox for Desktop Artifact Mode needs to enable artifact builds and + a path where the build artifacts will be written to. + ''' + print(BROWSER_ARTIFACT_MODE_MOZCONFIG) + + def install_mobile_android_packages(self): + ''' + Install packages required to build Firefox for Android (application + 'mobile/android', also known as Fennec). + ''' + raise NotImplementedError('Cannot bootstrap Firefox for Android: ' + '%s does not yet implement install_mobile_android_packages()' + % __name__) + + def suggest_mobile_android_mozconfig(self): + ''' + Print a message to the console detailing what the user's mozconfig + should contain. + + Firefox for Android needs an application and an ABI set, and it needs + paths to the Android SDK and NDK. + ''' + raise NotImplementedError('%s does not yet implement suggest_mobile_android_mozconfig()' % + __name__) + + def install_mobile_android_artifact_mode_packages(self): + ''' + Install packages required to build Firefox for Android (application + 'mobile/android', also known as Fennec) in Artifact Mode. + ''' + raise NotImplementedError( + 'Cannot bootstrap Firefox for Android Artifact Mode: ' + '%s does not yet implement install_mobile_android_artifact_mode_packages()' + % __name__) + + def suggest_mobile_android_artifact_mode_mozconfig(self): + ''' + Print a message to the console detailing what the user's mozconfig + should contain. + + Firefox for Android Artifact Mode needs an application and an ABI set, + and it needs paths to the Android SDK. + ''' + raise NotImplementedError( + '%s does not yet implement suggest_mobile_android_artifact_mode_mozconfig()' + % __name__) + + def which(self, name): + """Python implementation of which. + + It returns the path of an executable or None if it couldn't be found. + """ + for path in os.environ['PATH'].split(os.pathsep): + test = os.path.join(path, name) + if os.path.exists(test) and os.access(test, os.X_OK): + return test + + return None + + def run_as_root(self, command): + if os.geteuid() != 0: + if self.which('sudo'): + command.insert(0, 'sudo') + else: + command = ['su', 'root', '-c', ' '.join(command)] + + print('Executing as root:', subprocess.list2cmdline(command)) + + subprocess.check_call(command, stdin=sys.stdin) + + def dnf_install(self, *packages): + if self.which('dnf'): + command = ['dnf', 'install'] + else: + command = ['yum', 'install'] + + if self.no_interactive: + command.append('-y') + command.extend(packages) + + self.run_as_root(command) + + def dnf_groupinstall(self, *packages): + if self.which('dnf'): + command = ['dnf', 'groupinstall'] + else: + command = ['yum', 'groupinstall'] + + if self.no_interactive: + command.append('-y') + command.extend(packages) + + self.run_as_root(command) + + def dnf_update(self, *packages): + if self.which('dnf'): + command = ['dnf', 'update'] + else: + command = ['yum', 'update'] + + if self.no_interactive: + command.append('-y') + command.extend(packages) + + self.run_as_root(command) + + def apt_install(self, *packages): + command = ['apt-get', 'install'] + if self.no_interactive: + command.append('-y') + command.extend(packages) + + self.run_as_root(command) + + def apt_update(self): + command = ['apt-get', 'update'] + if self.no_interactive: + command.append('-y') + + self.run_as_root(command) + + def apt_add_architecture(self, arch): + command = ['dpkg', '--add-architecture'] + command.extend(arch) + + self.run_as_root(command) + + def check_output(self, *args, **kwargs): + """Run subprocess.check_output even if Python doesn't provide it.""" + fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output) + + return fn(*args, **kwargs) + + @staticmethod + def _check_output(*args, **kwargs): + """Python 2.6 compatible implementation of subprocess.check_output.""" + proc = subprocess.Popen(stdout=subprocess.PIPE, *args, **kwargs) + output, unused_err = proc.communicate() + retcode = proc.poll() + if retcode: + cmd = kwargs.get('args', args[0]) + e = subprocess.CalledProcessError(retcode, cmd) + e.output = output + raise e + + return output + + def prompt_int(self, prompt, low, high, limit=5): + ''' Prompts the user with prompt and requires an integer between low and high. ''' + valid = False + while not valid and limit > 0: + try: + choice = int(raw_input(prompt)) + if not low <= choice <= high: + print("ERROR! Please enter a valid option!") + limit -= 1 + else: + valid = True + except ValueError: + print("ERROR! Please enter a valid option!") + limit -= 1 + + if limit > 0: + return choice + else: + raise Exception("Error! Reached max attempts of entering option.") + + def _ensure_package_manager_updated(self): + if self.package_manager_updated: + return + + self._update_package_manager() + self.package_manager_updated = True + + def _update_package_manager(self): + """Updates the package manager's manifests/package list. + + This should be defined in child classes. + """ + + def _hgplain_env(self): + """ Returns a copy of the current environment updated with the HGPLAIN + environment variable. + + HGPLAIN prevents Mercurial from applying locale variations to the output + making it suitable for use in scripts. + """ + env = os.environ.copy() + env[b'HGPLAIN'] = b'1' + + return env + + def is_mercurial_modern(self): + hg = self.which('hg') + if not hg: + print(NO_MERCURIAL) + return False, False, None + + info = self.check_output([hg, '--version'], env=self._hgplain_env()).splitlines()[0] + + match = re.search('version ([^\+\)]+)', info) + if not match: + print('ERROR: Unable to identify Mercurial version.') + return True, False, None + + our = LooseVersion(match.group(1)) + + return True, our >= MODERN_MERCURIAL_VERSION, our + + def ensure_mercurial_modern(self): + installed, modern, version = self.is_mercurial_modern() + + if modern: + print('Your version of Mercurial (%s) is sufficiently modern.' % + version) + return installed, modern + + self._ensure_package_manager_updated() + + if installed: + print('Your version of Mercurial (%s) is not modern enough.' % + version) + print('(Older versions of Mercurial have known security vulnerabilities. ' + 'Unless you are running a patched Mercurial version, you may be ' + 'vulnerable.') + else: + print('You do not have Mercurial installed') + + if self.upgrade_mercurial(version) is False: + return installed, modern + + installed, modern, after = self.is_mercurial_modern() + + if installed and not modern: + print(MERCURIAL_UPGRADE_FAILED % (MODERN_MERCURIAL_VERSION, after)) + + return installed, modern + + def upgrade_mercurial(self, current): + """Upgrade Mercurial. + + Child classes should reimplement this. + + Return False to not perform a version check after the upgrade is + performed. + """ + print(MERCURIAL_UNABLE_UPGRADE % (current, MODERN_MERCURIAL_VERSION)) + + def is_python_modern(self): + python = None + + for test in ['python2.7', 'python']: + python = self.which(test) + if python: + break + + assert python + + info = self.check_output([python, '--version'], + stderr=subprocess.STDOUT) + match = re.search('Python ([a-z0-9\.]+)', info) + if not match: + print('ERROR Unable to identify Python version.') + return False, None + + our = LooseVersion(match.group(1)) + + return our >= MODERN_PYTHON_VERSION, our + + def ensure_python_modern(self): + modern, version = self.is_python_modern() + + if modern: + print('Your version of Python (%s) is new enough.' % version) + return + + print('Your version of Python (%s) is too old. Will try to upgrade.' % + version) + + self._ensure_package_manager_updated() + self.upgrade_python(version) + + modern, after = self.is_python_modern() + + if not modern: + print(PYTHON_UPGRADE_FAILED % (MODERN_PYTHON_VERSION, after)) + sys.exit(1) + + def upgrade_python(self, current): + """Upgrade Python. + + Child classes should reimplement this. + """ + print(PYTHON_UNABLE_UPGRADE % (current, MODERN_PYTHON_VERSION)) + + def http_download_and_save(self, url, dest, sha256hexhash): + f = urllib2.urlopen(url) + h = hashlib.sha256() + with open(dest, 'wb') as out: + while True: + data = f.read(4096) + if data: + out.write(data) + h.update(data) + else: + break + if h.hexdigest() != sha256hexhash: + os.remove(dest) + raise ValueError('Hash of downloaded file does not match expected hash') diff --git a/python/mozboot/mozboot/bootstrap.py b/python/mozboot/mozboot/bootstrap.py new file mode 100644 index 000000000..40bb7cc86 --- /dev/null +++ b/python/mozboot/mozboot/bootstrap.py @@ -0,0 +1,437 @@ +# 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/. + +# If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks. +from __future__ import print_function + +import platform +import sys +import os +import subprocess + +# Don't forgot to add new mozboot modules to the bootstrap download +# list in bin/bootstrap.py! +from mozboot.centosfedora import CentOSFedoraBootstrapper +from mozboot.debian import DebianBootstrapper +from mozboot.freebsd import FreeBSDBootstrapper +from mozboot.gentoo import GentooBootstrapper +from mozboot.osx import OSXBootstrapper +from mozboot.openbsd import OpenBSDBootstrapper +from mozboot.archlinux import ArchlinuxBootstrapper +from mozboot.windows import WindowsBootstrapper +from mozboot.mozillabuild import MozillaBuildBootstrapper +from mozboot.util import ( + get_state_dir, +) + +APPLICATION_CHOICE = ''' +Please choose the version of Firefox you want to build: +%s + +Note on Artifact Mode: + +Firefox for Desktop and Android supports a fast build mode called +artifact mode. Artifact mode downloads pre-built C++ components rather +than building them locally, trading bandwidth for time. + +Artifact builds will be useful to many developers who are not working +with compiled code. If you want to work on look-and-feel of Firefox, +you want "Firefox for Desktop Artifact Mode". + +Similarly, if you want to work on the look-and-feel of Firefox for Android, +you want "Firefox for Android Artifact Mode". + +To work on the Gecko technology platform, you would need to opt to full, +non-artifact mode. Gecko is Mozilla's web rendering engine, similar to Edge, +Blink, and WebKit. Gecko is implemented in C++ and JavaScript. If you +want to work on web rendering, you want "Firefox for Desktop", or +"Firefox for Android". + +If you don't know what you want, start with just Artifact Mode of the desired +platform. Your builds will be much shorter than if you build Gecko as well. +But don't worry! You can always switch configurations later. + +You can learn more about Artifact mode builds at +https://developer.mozilla.org/en-US/docs/Artifact_builds. + +Your choice: +''' + +APPLICATIONS_LIST=[ + ('Firefox for Desktop Artifact Mode', 'browser_artifact_mode'), + ('Firefox for Desktop', 'browser'), + ('Firefox for Android Artifact Mode', 'mobile_android_artifact_mode'), + ('Firefox for Android', 'mobile_android'), +] + +# This is a workaround for the fact that we must support python2.6 (which has +# no OrderedDict) +APPLICATIONS = dict( + browser_artifact_mode=APPLICATIONS_LIST[0], + browser=APPLICATIONS_LIST[1], + mobile_android_artifact_mode=APPLICATIONS_LIST[2], + mobile_android=APPLICATIONS_LIST[3], +) + +STATE_DIR_INFO = ''' +The Firefox build system and related tools store shared, persistent state +in a common directory on the filesystem. On this machine, that directory +is: + + {statedir} + +If you would like to use a different directory, hit CTRL+c and set the +MOZBUILD_STATE_PATH environment variable to the directory you'd like to +use and re-run the bootstrapper. + +Would you like to create this directory? + + 1. Yes + 2. No + +Your choice: +''' + +FINISHED = ''' +Your system should be ready to build %s! +''' + +SOURCE_ADVERTISE = ''' +Source code can be obtained by running + + hg clone https://hg.mozilla.org/mozilla-unified + +Or, if you prefer Git, you should install git-cinnabar, and follow the +instruction here to clone from the Mercurial repository: + + https://github.com/glandium/git-cinnabar/wiki/Mozilla:-A-git-workflow-for-Gecko-development + +Or, if you really prefer vanilla flavor Git: + + git clone https://git.mozilla.org/integration/gecko-dev.git +''' + +CONFIGURE_MERCURIAL = ''' +Mozilla recommends a number of changes to Mercurial to enhance your +experience with it. + +Would you like to run a configuration wizard to ensure Mercurial is +optimally configured? + + 1. Yes + 2. No + +Please enter your reply: '''.lstrip() + +CLONE_MERCURIAL = ''' +If you would like to clone the canonical Mercurial repository, please +enter the destination path below. + +(If you prefer to use Git, leave this blank.) + +Destination directory for Mercurial clone (leave empty to not clone): '''.lstrip() + + +DEBIAN_DISTROS = ( + 'Debian', + 'debian', + 'Ubuntu', + # Most Linux Mint editions are based on Ubuntu. One is based on Debian. + # The difference is reported in dist_id from platform.linux_distribution. + # But it doesn't matter since we share a bootstrapper between Debian and + # Ubuntu. + 'Mint', + 'LinuxMint', + 'Elementary OS', + 'Elementary', + '"elementary OS"', +) + + +class Bootstrapper(object): + """Main class that performs system bootstrap.""" + + def __init__(self, finished=FINISHED, choice=None, no_interactive=False, + hg_configure=False): + self.instance = None + self.finished = finished + self.choice = choice + self.hg_configure = hg_configure + cls = None + args = {'no_interactive': no_interactive} + + if sys.platform.startswith('linux'): + distro, version, dist_id = platform.linux_distribution() + + if distro in ('CentOS', 'CentOS Linux', 'Fedora'): + cls = CentOSFedoraBootstrapper + args['distro'] = distro + elif distro in DEBIAN_DISTROS: + cls = DebianBootstrapper + elif distro == 'Gentoo Base System': + cls = GentooBootstrapper + elif os.path.exists('/etc/arch-release'): + # Even on archlinux, platform.linux_distribution() returns ['','',''] + cls = ArchlinuxBootstrapper + else: + raise NotImplementedError('Bootstrap support for this Linux ' + 'distro not yet available.') + + args['version'] = version + args['dist_id'] = dist_id + + elif sys.platform.startswith('darwin'): + # TODO Support Darwin platforms that aren't OS X. + osx_version = platform.mac_ver()[0] + + cls = OSXBootstrapper + args['version'] = osx_version + + elif sys.platform.startswith('openbsd'): + cls = OpenBSDBootstrapper + args['version'] = platform.uname()[2] + + elif sys.platform.startswith('dragonfly') or \ + sys.platform.startswith('freebsd'): + cls = FreeBSDBootstrapper + args['version'] = platform.release() + args['flavor'] = platform.system() + + elif sys.platform.startswith('win32') or sys.platform.startswith('msys'): + if 'MOZILLABUILD' in os.environ: + cls = MozillaBuildBootstrapper + else: + cls = WindowsBootstrapper + + if cls is None: + raise NotImplementedError('Bootstrap support is not yet available ' + 'for your OS.') + + self.instance = cls(**args) + + def bootstrap(self): + if self.choice is None: + # Like ['1. Firefox for Desktop', '2. Firefox for Android Artifact Mode', ...]. + labels = ['%s. %s' % (i + 1, name) for (i, (name, _)) in enumerate(APPLICATIONS_LIST)] + prompt = APPLICATION_CHOICE % '\n'.join(labels) + prompt_choice = self.instance.prompt_int(prompt=prompt, low=1, high=len(APPLICATIONS)) + name, application = APPLICATIONS_LIST[prompt_choice-1] + elif self.choice not in APPLICATIONS.keys(): + raise Exception('Please pick a valid application choice: (%s)' % '/'.join(APPLICATIONS.keys())) + else: + name, application = APPLICATIONS[self.choice] + + self.instance.install_system_packages() + + # Like 'install_browser_packages' or 'install_mobile_android_packages'. + getattr(self.instance, 'install_%s_packages' % application)() + + hg_installed, hg_modern = self.instance.ensure_mercurial_modern() + self.instance.ensure_python_modern() + + # The state directory code is largely duplicated from mach_bootstrap.py. + # We can't easily import mach_bootstrap.py because the bootstrapper may + # run in self-contained mode and only the files in this directory will + # be available. We /could/ refactor parts of mach_bootstrap.py to be + # part of this directory to avoid the code duplication. + state_dir, _ = get_state_dir() + + if not os.path.exists(state_dir): + if not self.instance.no_interactive: + choice = self.instance.prompt_int( + prompt=STATE_DIR_INFO.format(statedir=state_dir), + low=1, + high=2) + + if choice == 1: + print('Creating global state directory: %s' % state_dir) + os.makedirs(state_dir, mode=0o770) + + state_dir_available = os.path.exists(state_dir) + + # Possibly configure Mercurial if the user wants to. + # TODO offer to configure Git. + if hg_installed and state_dir_available: + configure_hg = False + if not self.instance.no_interactive: + choice = self.instance.prompt_int(prompt=CONFIGURE_MERCURIAL, + low=1, high=2) + if choice == 1: + configure_hg = True + else: + configure_hg = self.hg_configure + + if configure_hg: + configure_mercurial(self.instance.which('hg'), state_dir) + + # Offer to clone if we're not inside a clone. + checkout_type = current_firefox_checkout(check_output=self.instance.check_output, + hg=self.instance.which('hg')) + have_clone = False + + if checkout_type: + have_clone = True + elif hg_installed and not self.instance.no_interactive: + dest = raw_input(CLONE_MERCURIAL) + dest = dest.strip() + if dest: + dest = os.path.expanduser(dest) + have_clone = clone_firefox(self.instance.which('hg'), dest) + + if not have_clone: + print(SOURCE_ADVERTISE) + + print(self.finished % name) + + # Like 'suggest_browser_mozconfig' or 'suggest_mobile_android_mozconfig'. + getattr(self.instance, 'suggest_%s_mozconfig' % application)() + + +def update_vct(hg, root_state_dir): + """Ensure version-control-tools in the state directory is up to date.""" + vct_dir = os.path.join(root_state_dir, 'version-control-tools') + + # Ensure the latest revision of version-control-tools is present. + update_mercurial_repo(hg, 'https://hg.mozilla.org/hgcustom/version-control-tools', + vct_dir, '@') + + return vct_dir + + +def configure_mercurial(hg, root_state_dir): + """Run the Mercurial configuration wizard.""" + vct_dir = update_vct(hg, root_state_dir) + + # Run the config wizard from v-c-t. + args = [ + hg, + '--config', 'extensions.configwizard=%s/hgext/configwizard' % vct_dir, + 'configwizard', + ] + subprocess.call(args) + + +def update_mercurial_repo(hg, url, dest, revision): + """Perform a clone/pull + update of a Mercurial repository.""" + args = [hg] + + # Disable common extensions whose older versions may cause `hg` + # invocations to abort. + disable_exts = [ + 'bzexport', + 'bzpost', + 'firefoxtree', + 'hgwatchman', + 'mozext', + 'mqext', + 'qimportbz', + 'push-to-try', + 'reviewboard', + ] + for ext in disable_exts: + args.extend(['--config', 'extensions.%s=!' % ext]) + + if os.path.exists(dest): + args.extend(['pull', url]) + cwd = dest + else: + args.extend(['clone', '--noupdate', url, dest]) + cwd = '/' + + print('=' * 80) + print('Ensuring %s is up to date at %s' % (url, dest)) + + try: + subprocess.check_call(args, cwd=cwd) + subprocess.check_call([hg, 'update', '-r', revision], cwd=dest) + finally: + print('=' * 80) + + +def clone_firefox(hg, dest): + """Clone the Firefox repository to a specified destination.""" + print('Cloning Firefox Mercurial repository to %s' % dest) + + # We create an empty repo then modify the config before adding data. + # This is necessary to ensure storage settings are optimally + # configured. + args = [ + hg, + # The unified repo is generaldelta, so ensure the client is as + # well. + '--config', 'format.generaldelta=true', + 'init', + dest + ] + res = subprocess.call(args) + if res: + print('unable to create destination repo; please try cloning manually') + return False + + # Strictly speaking, this could overwrite a config based on a template + # the user has installed. Let's pretend this problem doesn't exist + # unless someone complains about it. + with open(os.path.join(dest, '.hg', 'hgrc'), 'ab') as fh: + fh.write('[paths]\n') + fh.write('default = https://hg.mozilla.org/mozilla-unified\n') + fh.write('\n') + + # The server uses aggressivemergedeltas which can blow up delta chain + # length. This can cause performance to tank due to delta chains being + # too long. Limit the delta chain length to something reasonable + # to bound revlog read time. + fh.write('[format]\n') + fh.write('# This is necessary to keep performance in check\n') + fh.write('maxchainlen = 10000\n') + + res = subprocess.call([hg, 'pull', 'https://hg.mozilla.org/mozilla-unified'], cwd=dest) + print('') + if res: + print('error pulling; try running `hg pull https://hg.mozilla.org/mozilla-unified` manually') + return False + + print('updating to "central" - the development head of Gecko and Firefox') + res = subprocess.call([hg, 'update', '-r', 'central'], cwd=dest) + if res: + print('error updating; you will need to `hg update` manually') + + print('Firefox source code available at %s' % dest) + return True + + +def current_firefox_checkout(check_output, hg=None): + """Determine whether we're in a Firefox checkout. + + Returns one of None, ``git``, or ``hg``. + """ + HG_ROOT_REVISIONS = set([ + # From mozilla-central. + '8ba995b74e18334ab3707f27e9eb8f4e37ba3d29', + ]) + + path = os.getcwd() + while path: + hg_dir = os.path.join(path, '.hg') + git_dir = os.path.join(path, '.git') + if hg and os.path.exists(hg_dir): + # Verify the hg repo is a Firefox repo by looking at rev 0. + try: + node = check_output([hg, 'log', '-r', '0', '--template', '{node}'], cwd=path) + if node in HG_ROOT_REVISIONS: + return 'hg' + # Else the root revision is different. There could be nested + # repos. So keep traversing the parents. + except subprocess.CalledProcessError: + pass + + # TODO check git remotes or `git rev-parse -q --verify $sha1^{commit}` + # for signs of Firefox. + elif os.path.exists(git_dir): + return 'git' + + path, child = os.path.split(path) + if child == '': + break + + return None diff --git a/python/mozboot/mozboot/centosfedora.py b/python/mozboot/mozboot/centosfedora.py new file mode 100644 index 000000000..111e5eb90 --- /dev/null +++ b/python/mozboot/mozboot/centosfedora.py @@ -0,0 +1,153 @@ +# 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/. + +import os +import platform + +from mozboot.base import BaseBootstrapper + + +class CentOSFedoraBootstrapper(BaseBootstrapper): + def __init__(self, distro, version, dist_id, **kwargs): + BaseBootstrapper.__init__(self, **kwargs) + + self.distro = distro + self.version = version + self.dist_id = dist_id + + self.group_packages = [] + + self.packages = [ + 'autoconf213', + 'mercurial', + ] + + self.browser_group_packages = [ + 'GNOME Software Development', + ] + + self.browser_packages = [ + 'alsa-lib-devel', + 'GConf2-devel', + 'glibc-static', + 'gtk2-devel', # It is optional in Fedora 20's GNOME Software + # Development group. + 'libstdc++-static', + 'libXt-devel', + 'mesa-libGL-devel', + 'pulseaudio-libs-devel', + 'wireless-tools-devel', + 'yasm', + ] + + self.mobile_android_packages = [] + + if self.distro in ('CentOS', 'CentOS Linux'): + self.group_packages += [ + 'Development Tools', + 'Development Libraries', + 'GNOME Software Development', + ] + + self.packages += [ + 'curl-devel', + ] + + self.browser_packages += [ + 'dbus-glib-devel', + 'gtk3-devel', + ] + + elif self.distro == 'Fedora': + self.group_packages += [ + 'C Development Tools and Libraries', + ] + + self.packages += [ + 'python2-devel', + ] + + self.browser_packages += [ + 'gcc-c++', + ] + + self.mobile_android_packages += [ + 'java-1.8.0-openjdk-devel', + 'ncurses-devel.i686', + 'libstdc++.i686', + 'zlib-devel.i686', + ] + + def install_system_packages(self): + self.dnf_groupinstall(*self.group_packages) + self.dnf_install(*self.packages) + + def install_browser_packages(self): + self.ensure_browser_packages() + + def install_browser_artifact_mode_packages(self): + self.ensure_browser_packages(artifact_mode=True) + + def install_mobile_android_packages(self): + if self.distro in ('CentOS', 'CentOS Linux'): + BaseBootstrapper.install_mobile_android_packages(self) + elif self.distro == 'Fedora': + self.install_fedora_mobile_android_packages() + + def install_mobile_android_artifact_mode_packages(self): + if self.distro in ('CentOS', 'CentOS Linux'): + BaseBootstrapper.install_mobile_android_artifact_mode_packages(self) + elif self.distro == 'Fedora': + self.install_fedora_mobile_android_packages(artifact_mode=True) + + def ensure_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + self.dnf_groupinstall(*self.browser_group_packages) + self.dnf_install(*self.browser_packages) + + if self.distro in ('CentOS', 'CentOS Linux'): + yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.i686.rpm' + if platform.architecture()[0] == '64bit': + yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.x86_64.rpm' + + self.run_as_root(['rpm', '-ivh', yasm]) + + def install_fedora_mobile_android_packages(self, artifact_mode=False): + import android + + # Install Android specific packages. + self.dnf_install(*self.mobile_android_packages) + + # Fetch Android SDK and NDK. + 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-linux')) + self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r11b')) + self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz' + self.ndk_url = android.android_ndk_url('linux') + + 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, + artifact_mode=artifact_mode) + + # Most recent version of build-tools appears to be 23.0.1 on Fedora + packages = [p for p in android.ANDROID_PACKAGES if not p.startswith('build-tools')] + packages.append('build-tools-23.0.1') + + # 3. We expect the |android| tool to be at + # ~/.mozbuild/android-sdk-linux/tools/android. + android_tool = os.path.join(self.sdk_path, 'tools', 'android') + android.ensure_android_packages(android_tool=android_tool, packages=packages) + + def suggest_mobile_android_mozconfig(self, artifact_mode=False): + import android + android.suggest_mozconfig(sdk_path=self.sdk_path, + ndk_path=self.ndk_path, + artifact_mode=artifact_mode) + + def suggest_mobile_android_artifact_mode_mozconfig(self): + self.suggest_mobile_android_mozconfig(artifact_mode=True) + + def upgrade_mercurial(self, current): + self.dnf_update('mercurial') diff --git a/python/mozboot/mozboot/debian.py b/python/mozboot/mozboot/debian.py new file mode 100644 index 000000000..6e33e9e5b --- /dev/null +++ b/python/mozboot/mozboot/debian.py @@ -0,0 +1,188 @@ +# 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/. + +import os +import sys + +from mozboot.base import BaseBootstrapper + + +MERCURIAL_INSTALL_PROMPT = ''' +Mercurial releases a new version every 3 months and your distro's package +may become out of date. This may cause incompatibility with some +Mercurial extensions that rely on new Mercurial features. As a result, +you may not have an optimal version control experience. + +To have the best Mercurial experience possible, we recommend installing +Mercurial via the "pip" Python packaging utility. This will likely result +in files being placed in /usr/local/bin and /usr/local/lib. + +How would you like to continue? + +1) Install a modern Mercurial via pip (recommended) +2) Install a legacy Mercurial via apt +3) Do not install Mercurial + +Choice: +'''.strip() + + +class DebianBootstrapper(BaseBootstrapper): + # These are common packages for all Debian-derived distros (such as + # Ubuntu). + COMMON_PACKAGES = [ + 'autoconf2.13', + 'build-essential', + 'ccache', + 'python-dev', + 'python-pip', + 'python-setuptools', + 'unzip', + 'uuid', + 'zip', + ] + + # Subclasses can add packages to this variable to have them installed. + DISTRO_PACKAGES = [] + + # These are common packages for building Firefox for Desktop + # (browser) for all Debian-derived distros (such as Ubuntu). + BROWSER_COMMON_PACKAGES = [ + 'libasound2-dev', + 'libcurl4-openssl-dev', + 'libdbus-1-dev', + 'libdbus-glib-1-dev', + 'libgconf2-dev', + 'libgtk-3-dev', + 'libgtk2.0-dev', + 'libiw-dev', + 'libnotify-dev', + 'libpulse-dev', + 'libx11-xcb-dev', + 'libxt-dev', + 'mesa-common-dev', + 'python-dbus', + 'xvfb', + 'yasm', + ] + + # Subclasses can add packages to this variable to have them installed. + BROWSER_DISTRO_PACKAGES = [] + + # These are common packages for building Firefox for Android + # (mobile/android) for all Debian-derived distros (such as Ubuntu). + MOBILE_ANDROID_COMMON_PACKAGES = [ + 'zlib1g-dev', # mobile/android requires system zlib. + 'openjdk-7-jdk', + 'wget', # For downloading the Android SDK and NDK. + 'libncurses5:i386', # See comments about i386 below. + 'libstdc++6:i386', + 'zlib1g:i386', + ] + + # Subclasses can add packages to this variable to have them installed. + MOBILE_ANDROID_DISTRO_PACKAGES = [] + + def __init__(self, version, dist_id, **kwargs): + BaseBootstrapper.__init__(self, **kwargs) + + self.version = version + self.dist_id = dist_id + + self.packages = self.COMMON_PACKAGES + self.DISTRO_PACKAGES + self.browser_packages = self.BROWSER_COMMON_PACKAGES + self.BROWSER_DISTRO_PACKAGES + self.mobile_android_packages = self.MOBILE_ANDROID_COMMON_PACKAGES + self.MOBILE_ANDROID_DISTRO_PACKAGES + + + def install_system_packages(self): + self.apt_install(*self.packages) + + def install_browser_packages(self): + self.ensure_browser_packages() + + def install_browser_artifact_mode_packages(self): + self.ensure_browser_packages(artifact_mode=True) + + def install_mobile_android_packages(self): + self.ensure_mobile_android_packages() + + def install_mobile_android_artifact_mode_packages(self): + self.ensure_mobile_android_packages(artifact_mode=True) + + def ensure_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + self.apt_install(*self.browser_packages) + + def ensure_mobile_android_packages(self, artifact_mode=False): + import android + + # Multi-part process: + # 1. System packages. + # 2. Android SDK. Android NDK only if we are not in artifact mode. + # 3. Android packages. + + # 1. This is hard to believe, but the Android SDK binaries are 32-bit + # and that conflicts with 64-bit Debian and Ubuntu installations out of + # the box. The solution is to add the i386 architecture. See + # "Troubleshooting Ubuntu" at + # http://developer.android.com/sdk/installing/index.html?pkg=tools. + self.run_as_root(['dpkg', '--add-architecture', 'i386']) + # After adding a new arch, the list of packages has to be updated + self.apt_update() + self.apt_install(*self.mobile_android_packages) + + # 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-r11b}. + 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-linux')) + self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r11b')) + self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz' + self.ndk_url = android.android_ndk_url('linux') + + 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, + artifact_mode=artifact_mode) + + # 3. We expect the |android| tool to at + # ~/.mozbuild/android-sdk-linux/tools/android. + android_tool = os.path.join(self.sdk_path, 'tools', 'android') + android.ensure_android_packages(android_tool=android_tool) + + def suggest_mobile_android_mozconfig(self, artifact_mode=False): + import android + android.suggest_mozconfig(sdk_path=self.sdk_path, + ndk_path=self.ndk_path, + artifact_mode=artifact_mode) + + def suggest_mobile_android_artifact_mode_mozconfig(self): + self.suggest_mobile_android_mozconfig(artifact_mode=True) + + def _update_package_manager(self): + self.apt_update() + + def upgrade_mercurial(self, current): + """Install Mercurial from pip because Debian packages typically lag.""" + if self.no_interactive: + # Install via Apt in non-interactive mode because it is the more + # conservative option and less likely to make people upset. + self.apt_install('mercurial') + return + + res = self.prompt_int(MERCURIAL_INSTALL_PROMPT, 1, 3) + + # Apt. + if res == 2: + self.apt_install('mercurial') + return False + + # No Mercurial. + if res == 3: + print('Not installing Mercurial.') + return False + + # pip. + assert res == 1 + self.run_as_root(['pip', 'install', '--upgrade', 'Mercurial']) diff --git a/python/mozboot/mozboot/freebsd.py b/python/mozboot/mozboot/freebsd.py new file mode 100644 index 000000000..c524d188c --- /dev/null +++ b/python/mozboot/mozboot/freebsd.py @@ -0,0 +1,63 @@ +# 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 mozboot.base import BaseBootstrapper + + +class FreeBSDBootstrapper(BaseBootstrapper): + def __init__(self, version, flavor, **kwargs): + BaseBootstrapper.__init__(self, **kwargs) + self.version = int(version.split('.')[0]) + self.flavor = flavor.lower() + + self.packages = [ + 'autoconf213', + 'gmake', + 'gtar', + 'mercurial', + 'pkgconf', + 'watchman', + 'zip', + ] + + self.browser_packages = [ + 'dbus-glib', + 'gconf2', + 'gtk2', + 'gtk3', + 'pulseaudio', + 'v4l_compat', + 'yasm', + ] + + if not self.which('unzip'): + self.packages.append('unzip') + + # GCC 4.2 or Clang 3.4 in base are too old + if self.flavor == 'freebsd' and self.version < 11: + self.browser_packages.append('gcc') + + def pkg_install(self, *packages): + command = ['pkg', 'install'] + if self.no_interactive: + command.append('-y') + + command.extend(packages) + self.run_as_root(command) + + def install_system_packages(self): + self.pkg_install(*self.packages) + + def install_browser_packages(self): + self.ensure_browser_packages() + + def install_browser_artifact_mode_packages(self): + self.ensure_browser_packages(artifact_mode=True) + + def ensure_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + self.pkg_install(*self.browser_packages) + + def upgrade_mercurial(self, current): + self.pkg_install('mercurial') diff --git a/python/mozboot/mozboot/gentoo.py b/python/mozboot/mozboot/gentoo.py new file mode 100644 index 000000000..085f03ae3 --- /dev/null +++ b/python/mozboot/mozboot/gentoo.py @@ -0,0 +1,33 @@ +# 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 mozboot.base import BaseBootstrapper + + +class GentooBootstrapper(BaseBootstrapper): + def __init__(self, version, dist_id, **kwargs): + BaseBootstrapper.__init__(self, **kwargs) + + self.version = version + self.dist_id = dist_id + + def install_system_packages(self): + self.run_as_root(['emerge', '--quiet', 'dev-vcs/git', 'mercurial']) + + def install_browser_packages(self): + self.ensure_browser_packages() + + def install_browser_artifact_mode_packages(self): + self.ensure_browser_packages(artifact_mode=True) + + def ensure_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + self.run_as_root(['emerge', '--onlydeps', '--quiet', 'firefox']) + self.run_as_root(['emerge', '--quiet', 'gtk+']) + + def _update_package_manager(self): + self.run_as_root(['emerge', '--sync']) + + def upgrade_mercurial(self, current): + self.run_as_root(['emerge', '--update', 'mercurial']) diff --git a/python/mozboot/mozboot/mach_commands.py b/python/mozboot/mozboot/mach_commands.py new file mode 100644 index 000000000..940ffabbb --- /dev/null +++ b/python/mozboot/mozboot/mach_commands.py @@ -0,0 +1,67 @@ +# 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 absolute_import, print_function, unicode_literals + +import sys + +from mach.decorators import ( + CommandArgument, + CommandProvider, + Command, +) + + +@CommandProvider +class Bootstrap(object): + """Bootstrap system and mach for optimal development experience.""" + + @Command('bootstrap', category='devenv', + description='Install required system packages for building.') + def bootstrap(self): + from mozboot.bootstrap import Bootstrapper + + bootstrapper = Bootstrapper() + bootstrapper.bootstrap() + + +@CommandProvider +class VersionControlCommands(object): + def __init__(self, context): + self._context = context + + @Command('mercurial-setup', category='devenv', + description='Help configure Mercurial for optimal development.') + @CommandArgument('-u', '--update-only', action='store_true', + help='Only update recommended extensions, don\'t run the wizard.') + def mercurial_setup(self, update_only=False): + """Ensure Mercurial is optimally configured. + + This command will inspect your Mercurial configuration and + guide you through an interactive wizard helping you configure + Mercurial for optimal use on Mozilla projects. + + User choice is respected: no changes are made without explicit + confirmation from you. + + If "--update-only" is used, the interactive wizard is disabled + and this command only ensures that remote repositories providing + Mercurial extensions are up to date. + """ + import which + import mozboot.bootstrap as bootstrap + + # "hg" is an executable script with a shebang, which will be found + # be which.which. We need to pass a win32 executable to the function + # because we spawn a process + # from it. + if sys.platform in ('win32', 'msys'): + hg = which.which('hg.exe') + else: + hg = which.which('hg') + + if update_only: + bootstrap.update_vct(hg, self._context.state_dir) + else: + bootstrap.configure_mercurial(hg, self._context.state_dir) diff --git a/python/mozboot/mozboot/mozillabuild.py b/python/mozboot/mozboot/mozillabuild.py new file mode 100644 index 000000000..6d4958812 --- /dev/null +++ b/python/mozboot/mozboot/mozillabuild.py @@ -0,0 +1,77 @@ +# 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/. + +import os +import sys +import subprocess +import tempfile + +from mozboot.base import BaseBootstrapper + +class MozillaBuildBootstrapper(BaseBootstrapper): + '''Bootstrapper for MozillaBuild to install rustup.''' + def __init__(self, no_interactive=False): + BaseBootstrapper.__init__(self, no_interactive=no_interactive) + print("mach bootstrap is not fully implemented in MozillaBuild") + + def which(self, name): + return BaseBootstrapper.which(self, name + '.exe') + + def install_system_packages(self): + self.install_rustup() + + def install_rustup(self): + try: + rustup_init = tempfile.gettempdir() + '/rustup-init.exe' + self.http_download_and_save( + 'https://static.rust-lang.org/rustup/archive/0.2.0/i686-pc-windows-msvc/rustup-init.exe', + rustup_init, + 'a45ab7462b567dacddaf6e9e48bb43a1b9c1db4404ba77868f7d6fc685282a46') + self.run([rustup_init, '--no-modify-path', '--default-host', + 'x86_64-pc-windows-msvc', '--default-toolchain', 'stable', '-y']) + mozillabuild_dir = os.environ['MOZILLABUILD'] + + with open(mozillabuild_dir + 'msys/etc/profile.d/profile-rustup.sh', 'wb') as f: + f.write('#!/bash/sh\n') + f.write('if test -n "$MOZILLABUILD"; then\n') + f.write(' WIN_HOME=$(cd "$HOME" && pwd)\n') + f.write(' PATH="$WIN_HOME/.cargo/bin:$PATH"\n') + f.write(' export PATH\n') + f.write('fi') + finally: + try: + os.remove(rustup_init) + except FileNotFoundError: + pass + + def upgrade_mercurial(self, current): + self.pip_install('mercurial') + + def upgrade_python(self, current): + pass + + def install_browser_packages(self): + pass + + def install_browser_artifact_mode_packages(self): + pass + + def install_mobile_android_packages(self): + pass + + def install_mobile_android_artifact_mode_packages(self): + pass + + def _update_package_manager(self): + pass + + def run(self, command): + subprocess.check_call(command, stdin=sys.stdin) + + def pip_install(self, *packages): + pip_dir = os.path.join(os.environ['MOZILLABUILD'], 'python', 'Scripts', 'pip.exe') + command = [pip_dir, 'install', '--upgrade'] + command.extend(packages) + self.run(command) + diff --git a/python/mozboot/mozboot/openbsd.py b/python/mozboot/mozboot/openbsd.py new file mode 100644 index 000000000..df6a195fd --- /dev/null +++ b/python/mozboot/mozboot/openbsd.py @@ -0,0 +1,45 @@ +# 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 mozboot.base import BaseBootstrapper + + +class OpenBSDBootstrapper(BaseBootstrapper): + def __init__(self, version, **kwargs): + BaseBootstrapper.__init__(self, **kwargs) + + self.packages = [ + 'mercurial', + 'autoconf-2.13', + 'gmake', + 'gtar', + 'wget', + 'unzip', + 'zip', + ] + + self.browser_packages = [ + 'llvm', + 'yasm', + 'gconf2', + 'gtk+2', + 'gtk+3', + 'dbus-glib', + 'pulseaudio', + ] + + def install_system_packages(self): + # we use -z because there's no other way to say "any autoconf-2.13" + self.run_as_root(['pkg_add', '-z'] + self.packages) + + def install_browser_packages(self): + self.ensure_browser_packages() + + def install_browser_artifact_mode_packages(self): + self.ensure_browser_packages(artifact_mode=True) + + def ensure_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + # we use -z because there's no other way to say "any autoconf-2.13" + self.run_as_root(['pkg_add', '-z'] + self.browser_packages) diff --git a/python/mozboot/mozboot/osx.py b/python/mozboot/mozboot/osx.py new file mode 100644 index 000000000..d66d66d6b --- /dev/null +++ b/python/mozboot/mozboot/osx.py @@ -0,0 +1,577 @@ +# 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 = {'11': 'https://distfiles.macports.org/MacPorts/MacPorts-2.3.4-10.11-ElCapitan.pkg', + '10': 'https://distfiles.macports.org/MacPorts/MacPorts-2.3.4-10.10-Yosemite.pkg', + '9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.3.4-10.9-Mavericks.pkg', + '8': 'https://distfiles.macports.org/MacPorts/MacPorts-2.3.4-10.8-MountainLion.pkg', + '7': 'https://distfiles.macports.org/MacPorts/MacPorts-2.3.4-10.7-Lion.pkg', + '6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.3.4-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_browser_artifact_mode_packages(self): + getattr(self, 'ensure_%s_browser_packages' % self.package_manager)(artifact_mode=True) + + def install_mobile_android_packages(self): + getattr(self, 'ensure_%s_mobile_android_packages' % self.package_manager)() + + def install_mobile_android_artifact_mode_packages(self): + getattr(self, 'ensure_%s_mobile_android_packages' % self.package_manager)(artifact_mode=True) + + def suggest_mobile_android_mozconfig(self): + getattr(self, 'suggest_%s_mobile_android_mozconfig' % self.package_manager)() + + def suggest_mobile_android_artifact_mode_mozconfig(self): + getattr(self, 'suggest_%s_mobile_android_mozconfig' % self.package_manager)(artifact_mode=True) + + 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',), + ('terminal-notifier', 'terminal-notifier') + ] + self._ensure_homebrew_packages(packages) + + def ensure_homebrew_browser_packages(self, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + 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, artifact_mode=False): + # Multi-part process: + # 1. System packages. + # 2. Android SDK. Android NDK only if we are not in artifact mode. + # 3. Android packages. + + import android + + # 1. System packages. + packages = [ + ('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-r11b}. + 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-r11b')) + 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, + artifact_mode=artifact_mode) + + # 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, artifact_mode=False): + import android + android.suggest_mozconfig(sdk_path=self.sdk_path, + ndk_path=self.ndk_path, + artifact_mode=artifact_mode) + + 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', + 'py27-readline', + '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, artifact_mode=False): + # TODO: Figure out what not to install for artifact mode + 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, artifact_mode=False): + # Multi-part process: + # 1. System packages. + # 2. Android SDK. Android NDK only if we are not in artifact mode. + # 3. Android packages. + + import android + + # 1. System packages. + packages = [ + 'wget', + ] + self._ensure_macports_packages(packages) + + # Verify the presence of java and javac. + if not self.which('java') or not self.which('javac'): + raise Exception('You need to have Java version 1.7 or later installed. Please visit http://www.java.com/en/download/mac_download.jsp to get the latest version.') + + # 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-r11b}. + 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-r11b')) + 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, + artifact_mode=artifact_mode) + + # 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_macports_mobile_android_mozconfig(self, artifact_mode=False): + import android + android.suggest_mozconfig(sdk_path=self.sdk_path, + ndk_path=self.ndk_path, + artifact_mode=artifact_mode) + + 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') diff --git a/python/mozboot/mozboot/util.py b/python/mozboot/mozboot/util.py new file mode 100644 index 000000000..f2bbb76db --- /dev/null +++ b/python/mozboot/mozboot/util.py @@ -0,0 +1,20 @@ +# 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/. + +import os + + +def get_state_dir(): + """Obtain path to a directory to hold state. + + Returns a tuple of the path and a bool indicating whether the + value came from an environment variable. + """ + state_user_dir = os.path.expanduser('~/.mozbuild') + state_env_dir = os.environ.get('MOZBUILD_STATE_PATH') + + if state_env_dir: + return state_env_dir, True + else: + return state_user_dir, False diff --git a/python/mozboot/mozboot/windows.py b/python/mozboot/mozboot/windows.py new file mode 100644 index 000000000..c072b1b91 --- /dev/null +++ b/python/mozboot/mozboot/windows.py @@ -0,0 +1,95 @@ +# 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/. + +import os +import sys +import subprocess + +from mozboot.base import BaseBootstrapper + +class WindowsBootstrapper(BaseBootstrapper): + '''Bootstrapper for msys2 based environments for building in Windows.''' + + SYSTEM_PACKAGES = [ + 'mingw-w64-x86_64-make', + 'mingw-w64-x86_64-python2-pip', + 'mingw-w64-x86_64-perl', + 'patch', + 'patchutils', + 'diffutils', + 'autoconf2.13', + 'tar', + 'zip', + 'unzip', + 'mingw-w64-x86_64-toolchain', # TODO: Should be removed when Mercurial is installable from a wheel. + 'mingw-w64-i686-toolchain' + ] + + BROWSER_PACKAGES = [ + 'mingw-w64-x86_64-yasm', + 'mingw-w64-i686-nsis' + ] + + MOBILE_ANDROID_COMMON_PACKAGES = [ + 'wget' + ] + + def __init__(self, **kwargs): + if 'MOZ_WINDOWS_BOOTSTRAP' not in os.environ or os.environ['MOZ_WINDOWS_BOOTSTRAP'] != '1': + raise NotImplementedError('Bootstrap support for Windows is under development. For now, use MozillaBuild ' + 'to set up a build environment on Windows. If you are testing Windows Bootstrap support, ' + 'try `export MOZ_WINDOWS_BOOTSTRAP=1`') + BaseBootstrapper.__init__(self, **kwargs) + if not self.which('pacman'): + raise NotImplementedError('The Windows bootstrapper only works with msys2 with pacman. Get msys2 at ' + 'http://msys2.github.io/') + print 'Using an experimental bootstrapper for Windows.' + + def which(self, name): + return BaseBootstrapper.which(self, name + '.exe') + + def install_system_packages(self): + self.pacman_install(*self.SYSTEM_PACKAGES) + + def upgrade_mercurial(self, current): + self.pip_install('mercurial') + + def upgrade_python(self, current): + self.pacman_install('mingw-w64-x86_64-python2') + + def install_browser_packages(self): + self.pacman_install(*self.BROWSER_PACKAGES) + + def install_mobile_android_packages(self): + raise NotImplementedError('We do not support building Android on Windows. Sorry!') + + def install_mobile_android_artifact_mode_packages(self): + raise NotImplementedError('We do not support building Android on Windows. Sorry!') + + def _update_package_manager(self): + self.pacman_update() + + def run(self, command): + subprocess.check_call(command, stdin=sys.stdin) + + def pacman_update(self): + command = ['pacman', '--sync', '--refresh'] + self.run(command) + + def pacman_upgrade(self): + command = ['pacman', '--sync', '--refresh', '--sysupgrade'] + self.run(command) + + def pacman_install(self, *packages): + command = ['pacman', '--sync', '--needed'] + if self.no_interactive: + command.append('--noconfirm') + + command.extend(packages) + self.run(command) + + def pip_install(self, *packages): + command = ['pip', 'install', '--upgrade'] + command.extend(packages) + self.run(command) diff --git a/python/mozboot/setup.py b/python/mozboot/setup.py new file mode 100644 index 000000000..2ad2c63ec --- /dev/null +++ b/python/mozboot/setup.py @@ -0,0 +1,16 @@ +# 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 distutils.core import setup + +VERSION = '0.1' + +setup( + name='mozboot', + description='System bootstrap for building Mozilla projects.', + license='MPL 2.0', + packages=['mozboot'], + version=VERSION, + scripts=['bin/bootstrap.py'], +) diff --git a/python/mozboot/support/ConEmu.xml b/python/mozboot/support/ConEmu.xml new file mode 100755 index 000000000..e2df514f1 --- /dev/null +++ b/python/mozboot/support/ConEmu.xml @@ -0,0 +1,897 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3