# 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)