1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this, 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5# If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks. 6from __future__ import absolute_import, print_function 7 8import errno 9import os 10import stat 11import subprocess 12import sys 13 14# We need the NDK version in multiple different places, and it's inconvenient 15# to pass down the NDK version to all relevant places, so we have this global 16# variable. 17NDK_VERSION = 'r15c' 18 19ANDROID_NDK_EXISTS = ''' 20Looks like you have the Android NDK installed at: 21%s 22''' 23 24ANDROID_SDK_EXISTS = ''' 25Looks like you have the Android SDK installed at: 26%s 27We will install all required Android packages. 28''' 29 30ANDROID_SDK_TOO_OLD = ''' 31Looks like you have an outdated Android SDK installed at: 32%s 33I can't update outdated Android SDKs to have the required 'sdkmanager' 34tool. Move it out of the way (or remove it entirely) and then run 35bootstrap again. 36''' 37 38INSTALLING_ANDROID_PACKAGES = ''' 39We are now installing the following Android packages: 40%s 41You may be prompted to agree to the Android license. You may see some of 42output as packages are downloaded and installed. 43''' 44 45MOBILE_ANDROID_MOZCONFIG_TEMPLATE = ''' 46Paste the lines between the chevrons (>>> and <<<) into your mozconfig file: 47 48<<< 49# Build Firefox for Android: 50ac_add_options --enable-application=mobile/android 51ac_add_options --target=arm-linux-androideabi 52 53{extra_lines} 54# With the following Android SDK and NDK: 55ac_add_options --with-android-sdk="{sdk_path}" 56ac_add_options --with-android-ndk="{ndk_path}" 57>>> 58''' 59 60MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE = ''' 61Paste the lines between the chevrons (>>> and <<<) into your mozconfig file: 62 63<<< 64# Build Firefox for Android Artifact Mode: 65ac_add_options --enable-application=mobile/android 66ac_add_options --target=arm-linux-androideabi 67ac_add_options --enable-artifact-builds 68 69{extra_lines} 70# With the following Android SDK: 71ac_add_options --with-android-sdk="{sdk_path}" 72 73# Write build artifacts to: 74mk_add_options MOZ_OBJDIR=./objdir-frontend 75>>> 76''' 77 78 79def install_mobile_android_sdk_or_ndk(url, path): 80 ''' 81 Fetch an Android SDK or NDK from |url| and unpack it into 82 the given |path|. 83 84 We expect wget to be installed and found on the system path. 85 86 We use, and wget respects, https. We could also include SHAs for a 87 small improvement in the integrity guarantee we give. But this script is 88 bootstrapped over https anyway, so it's a really minor improvement. 89 90 We use |wget --continue| as a cheap cache of the downloaded artifacts, 91 writing into |path|/mozboot. We don't yet clean the cache; it's better 92 to waste disk and not require a long re-download than to wipe the cache 93 prematurely. 94 ''' 95 96 old_path = os.getcwd() 97 try: 98 download_path = os.path.join(path, 'mozboot') 99 try: 100 os.makedirs(download_path) 101 except OSError as e: 102 if e.errno == errno.EEXIST and os.path.isdir(download_path): 103 pass 104 else: 105 raise 106 107 os.chdir(download_path) 108 subprocess.check_call(['wget', '--continue', url]) 109 file = url.split('/')[-1] 110 111 os.chdir(path) 112 abspath = os.path.join(download_path, file) 113 if file.endswith('.tar.gz') or file.endswith('.tgz'): 114 cmd = ['tar', 'zxf', abspath] 115 elif file.endswith('.tar.bz2'): 116 cmd = ['tar', 'jxf', abspath] 117 elif file.endswith('.zip'): 118 cmd = ['unzip', '-q', abspath] 119 elif file.endswith('.bin'): 120 # Execute the .bin file, which unpacks the content. 121 mode = os.stat(path).st_mode 122 os.chmod(abspath, mode | stat.S_IXUSR) 123 cmd = [abspath] 124 else: 125 raise NotImplementedError("Don't know how to unpack file: %s" % file) 126 127 print('Unpacking %s...' % abspath) 128 129 with open(os.devnull, "w") as stdout: 130 # These unpack commands produce a ton of output; ignore it. The 131 # .bin files are 7z archives; there's no command line flag to quiet 132 # output, so we use this hammer. 133 subprocess.check_call(cmd, stdout=stdout) 134 135 print('Unpacking %s... DONE' % abspath) 136 137 finally: 138 os.chdir(old_path) 139 140 141def get_paths(os_name): 142 mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', 143 os.path.expanduser(os.path.join('~', '.mozbuild'))) 144 sdk_path = os.environ.get('ANDROID_SDK_HOME', 145 os.path.join(mozbuild_path, 'android-sdk-{0}'.format(os_name))) 146 ndk_path = os.environ.get('ANDROID_NDK_HOME', 147 os.path.join(mozbuild_path, 'android-ndk-{0}'.format(NDK_VERSION))) 148 return (mozbuild_path, sdk_path, ndk_path) 149 150 151def ensure_dir(dir): 152 '''Ensures the given directory exists''' 153 if dir and not os.path.exists(dir): 154 try: 155 os.makedirs(dir) 156 except OSError as error: 157 if error.errno != errno.EEXIST: 158 raise 159 160 161def ensure_android(os_name, artifact_mode=False, ndk_only=False, no_interactive=False): 162 ''' 163 Ensure the Android SDK (and NDK, if `artifact_mode` is falsy) are 164 installed. If not, fetch and unpack the SDK and/or NDK from the 165 given URLs. Ensure the required Android SDK packages are 166 installed. 167 168 `os_name` can be 'linux' or 'macosx'. 169 ''' 170 # The user may have an external Android SDK (in which case we 171 # save them a lengthy download), or they may have already 172 # completed the download. We unpack to 173 # ~/.mozbuild/{android-sdk-$OS_NAME, android-ndk-$VER}. 174 mozbuild_path, sdk_path, ndk_path = get_paths(os_name) 175 os_tag = 'darwin' if os_name == 'macosx' else os_name 176 sdk_url = 'https://dl.google.com/android/repository/sdk-tools-{0}-3859397.zip'.format(os_tag) 177 ndk_url = android_ndk_url(os_name) 178 179 ensure_android_sdk_and_ndk(mozbuild_path, os_name, 180 sdk_path=sdk_path, sdk_url=sdk_url, 181 ndk_path=ndk_path, ndk_url=ndk_url, 182 artifact_mode=artifact_mode, 183 ndk_only=ndk_only) 184 185 if ndk_only: 186 return 187 188 # We expect the |sdkmanager| tool to be at 189 # ~/.mozbuild/android-sdk-$OS_NAME/tools/bin/sdkmanager. 190 sdkmanager_tool = os.path.join(sdk_path, 'tools', 'bin', 'sdkmanager') 191 ensure_android_packages(sdkmanager_tool=sdkmanager_tool, no_interactive=no_interactive) 192 193 194def ensure_android_sdk_and_ndk(mozbuild_path, os_name, sdk_path, sdk_url, ndk_path, ndk_url, 195 artifact_mode, ndk_only): 196 ''' 197 Ensure the Android SDK and NDK are found at the given paths. If not, fetch 198 and unpack the SDK and/or NDK from the given URLs into 199 |mozbuild_path/{android-sdk-$OS_NAME,android-ndk-$VER}|. 200 ''' 201 202 # It's not particularly bad to overwrite the NDK toolchain, but it does take 203 # a while to unpack, so let's avoid the disk activity if possible. The SDK 204 # may prompt about licensing, so we do this first. 205 # Check for Android NDK only if we are not in artifact mode. 206 if not artifact_mode: 207 if os.path.isdir(ndk_path): 208 print(ANDROID_NDK_EXISTS % ndk_path) 209 else: 210 # The NDK archive unpacks into a top-level android-ndk-$VER directory. 211 install_mobile_android_sdk_or_ndk(ndk_url, mozbuild_path) 212 213 if ndk_only: 214 return 215 216 # We don't want to blindly overwrite, since we use the 217 # |sdkmanager| tool to install additional parts of the Android 218 # toolchain. If we overwrite, we lose whatever Android packages 219 # the user may have already installed. 220 if os.path.isfile(os.path.join(sdk_path, 'tools', 'bin', 'sdkmanager')): 221 print(ANDROID_SDK_EXISTS % sdk_path) 222 elif os.path.isdir(sdk_path): 223 raise NotImplementedError(ANDROID_SDK_TOO_OLD % sdk_path) 224 else: 225 # The SDK archive used to include a top-level 226 # android-sdk-$OS_NAME directory; it no longer does so. We 227 # preserve the old convention to smooth detecting existing SDK 228 # installations. 229 install_mobile_android_sdk_or_ndk(sdk_url, os.path.join(mozbuild_path, 230 'android-sdk-{0}'.format(os_name))) 231 232 233def ensure_android_packages(sdkmanager_tool, packages=None, no_interactive=False): 234 ''' 235 Use the given sdkmanager tool (like 'sdkmanager') to install required 236 Android packages. 237 ''' 238 239 # This tries to install all the required Android packages. The user 240 # may be prompted to agree to the Android license. 241 package_file_name = os.path.abspath(os.path.join(os.path.dirname(__file__), 242 'android-packages.txt')) 243 print(INSTALLING_ANDROID_PACKAGES % open(package_file_name, 'rt').read()) 244 245 args = [sdkmanager_tool, '--package_file={0}'.format(package_file_name)] 246 if not no_interactive: 247 subprocess.check_call(args) 248 return 249 250 # Emulate yes. For a discussion of passing input to check_output, 251 # see https://stackoverflow.com/q/10103551. 252 yes = '\n'.join(['y']*100) 253 proc = subprocess.Popen(args, 254 stdout=subprocess.PIPE, 255 stderr=subprocess.STDOUT, 256 stdin=subprocess.PIPE) 257 output, unused_err = proc.communicate(yes) 258 259 retcode = proc.poll() 260 if retcode: 261 cmd = args[0] 262 e = subprocess.CalledProcessError(retcode, cmd) 263 e.output = output 264 raise e 265 266 print(output) 267 268 269def suggest_mozconfig(os_name, artifact_mode=False, java_bin_path=None): 270 _mozbuild_path, sdk_path, ndk_path = get_paths(os_name) 271 272 extra_lines = [] 273 if java_bin_path: 274 extra_lines += [ 275 '# With the following java and javac:', 276 'ac_add_options --with-java-bin-path="{}"'.format(java_bin_path), 277 ] 278 if extra_lines: 279 extra_lines.append('') 280 281 if artifact_mode: 282 template = MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE 283 else: 284 template = MOBILE_ANDROID_MOZCONFIG_TEMPLATE 285 286 kwargs = dict( 287 sdk_path=sdk_path, 288 ndk_path=ndk_path, 289 extra_lines='\n'.join(extra_lines), 290 ) 291 print(template.format(**kwargs)) 292 293 294def android_ndk_url(os_name, ver=NDK_VERSION): 295 # Produce a URL like 296 # 'https://dl.google.com/android/repository/android-ndk-$VER-linux-x86_64.zip 297 base_url = 'https://dl.google.com/android/repository/android-ndk' 298 299 if os_name == 'macosx': 300 # |mach bootstrap| uses 'macosx', but Google uses 'darwin'. 301 os_name = 'darwin' 302 303 if sys.maxsize > 2**32: 304 arch = 'x86_64' 305 else: 306 arch = 'x86' 307 308 return '%s-%s-%s-%s.zip' % (base_url, ver, os_name, arch) 309 310 311def main(argv): 312 import optparse # No argparse, which is new in Python 2.7. 313 import platform 314 315 parser = optparse.OptionParser() 316 parser.add_option('-a', '--artifact-mode', dest='artifact_mode', action='store_true', 317 help='If true, install only the Android SDK (and not the Android NDK).') 318 parser.add_option('--ndk-only', dest='ndk_only', action='store_true', 319 help='If true, install only the Android NDK (and not the Android SDK).') 320 parser.add_option('--no-interactive', dest='no_interactive', action='store_true', 321 help='Accept the Android SDK licenses without user interaction.') 322 323 options, _ = parser.parse_args(argv) 324 325 if options.artifact_mode and options.ndk_only: 326 raise NotImplementedError('Use no options to install the NDK and the SDK.') 327 328 os_name = None 329 if platform.system() == 'Darwin': 330 os_name = 'macosx' 331 elif platform.system() == 'Linux': 332 os_name = 'linux' 333 elif platform.system() == 'Windows': 334 os_name = 'windows' 335 else: 336 raise NotImplementedError("We don't support bootstrapping the Android SDK (or Android " 337 "NDK) on {0} yet!".format(platform.system())) 338 339 ensure_android(os_name, artifact_mode=options.artifact_mode, 340 ndk_only=options.ndk_only, 341 no_interactive=options.no_interactive) 342 suggest_mozconfig(os_name, options.artifact_mode) 343 344 return 0 345 346 347if __name__ == '__main__': 348 sys.exit(main(sys.argv)) 349