1#!/usr/bin/env python 2 3# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. 4# 5# Use of this source code is governed by a BSD-style license 6# that can be found in the LICENSE file in the root of the source 7# tree. An additional intellectual property rights grant can be found 8# in the file PATENTS. All contributing project authors may 9# be found in the AUTHORS file in the root of the source tree. 10 11"""Script to generate libwebrtc.aar for distribution. 12 13The script has to be run from the root src folder. 14./tools_webrtc/android/build_aar.py 15 16.aar-file is just a zip-archive containing the files of the library. The file 17structure generated by this script looks like this: 18 - AndroidManifest.xml 19 - classes.jar 20 - libs/ 21 - armeabi-v7a/ 22 - libjingle_peerconnection_so.so 23 - x86/ 24 - libjingle_peerconnection_so.so 25""" 26 27import argparse 28import logging 29import os 30import shutil 31import subprocess 32import sys 33import tempfile 34import zipfile 35 36 37SCRIPT_DIR = os.path.dirname(os.path.realpath(sys.argv[0])) 38SRC_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir)) 39DEFAULT_ARCHS = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'] 40NEEDED_SO_FILES = ['libjingle_peerconnection_so.so'] 41JAR_FILE = 'lib.java/sdk/android/libwebrtc.jar' 42MANIFEST_FILE = 'sdk/android/AndroidManifest.xml' 43TARGETS = [ 44 'sdk/android:libwebrtc', 45 'sdk/android:libjingle_peerconnection_so', 46] 47 48sys.path.append(os.path.join(SCRIPT_DIR, '..', 'libs')) 49from generate_licenses import LicenseBuilder 50 51sys.path.append(os.path.join(SRC_DIR, 'build')) 52import find_depot_tools 53 54 55 56def _ParseArgs(): 57 parser = argparse.ArgumentParser(description='libwebrtc.aar generator.') 58 parser.add_argument('--build-dir', 59 help='Build dir. By default will create and use temporary dir.') 60 parser.add_argument('--output', default='libwebrtc.aar', 61 help='Output file of the script.') 62 parser.add_argument('--arch', default=DEFAULT_ARCHS, nargs='*', 63 help='Architectures to build. Defaults to %(default)s.') 64 parser.add_argument('--use-goma', action='store_true', default=False, 65 help='Use goma.') 66 parser.add_argument('--verbose', action='store_true', default=False, 67 help='Debug logging.') 68 parser.add_argument('--extra-gn-args', default=[], nargs='*', 69 help="""Additional GN arguments to be used during Ninja generation. 70 These are passed to gn inside `--args` switch and 71 applied after any other arguments and will 72 override any values defined by the script. 73 Example of building debug aar file: 74 build_aar.py --extra-gn-args='is_debug=true'""") 75 parser.add_argument('--extra-ninja-switches', default=[], nargs='*', 76 help="""Additional Ninja switches to be used during compilation. 77 These are applied after any other Ninja switches. 78 Example of enabling verbose Ninja output: 79 build_aar.py --extra-ninja-switches='-v'""") 80 parser.add_argument('--extra-gn-switches', default=[], nargs='*', 81 help="""Additional GN switches to be used during compilation. 82 These are applied after any other GN switches. 83 Example of enabling verbose GN output: 84 build_aar.py --extra-gn-switches='-v'""") 85 return parser.parse_args() 86 87 88def _RunGN(args): 89 cmd = [sys.executable, 90 os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'gn.py')] 91 cmd.extend(args) 92 logging.debug('Running: %r', cmd) 93 subprocess.check_call(cmd) 94 95 96def _RunNinja(output_directory, args): 97 cmd = [os.path.join(find_depot_tools.DEPOT_TOOLS_PATH, 'ninja'), 98 '-C', output_directory] 99 cmd.extend(args) 100 logging.debug('Running: %r', cmd) 101 subprocess.check_call(cmd) 102 103 104def _EncodeForGN(value): 105 """Encodes value as a GN literal.""" 106 if isinstance(value, str): 107 return '"' + value + '"' 108 elif isinstance(value, bool): 109 return repr(value).lower() 110 else: 111 return repr(value) 112 113 114def _GetOutputDirectory(build_dir, arch): 115 """Returns the GN output directory for the target architecture.""" 116 return os.path.join(build_dir, arch) 117 118 119def _GetTargetCpu(arch): 120 """Returns target_cpu for the GN build with the given architecture.""" 121 if arch in ['armeabi', 'armeabi-v7a']: 122 return 'arm' 123 elif arch == 'arm64-v8a': 124 return 'arm64' 125 elif arch == 'x86': 126 return 'x86' 127 elif arch == 'x86_64': 128 return 'x64' 129 else: 130 raise Exception('Unknown arch: ' + arch) 131 132 133def _GetArmVersion(arch): 134 """Returns arm_version for the GN build with the given architecture.""" 135 if arch == 'armeabi': 136 return 6 137 elif arch == 'armeabi-v7a': 138 return 7 139 elif arch in ['arm64-v8a', 'x86', 'x86_64']: 140 return None 141 else: 142 raise Exception('Unknown arch: ' + arch) 143 144 145def Build(build_dir, arch, use_goma, extra_gn_args, extra_gn_switches, 146 extra_ninja_switches): 147 """Generates target architecture using GN and builds it using ninja.""" 148 logging.info('Building: %s', arch) 149 output_directory = _GetOutputDirectory(build_dir, arch) 150 gn_args = { 151 'target_os': 'android', 152 'is_debug': False, 153 'is_component_build': False, 154 'rtc_include_tests': False, 155 'target_cpu': _GetTargetCpu(arch), 156 'use_goma': use_goma 157 } 158 arm_version = _GetArmVersion(arch) 159 if arm_version: 160 gn_args['arm_version'] = arm_version 161 gn_args_str = '--args=' + ' '.join([ 162 k + '=' + _EncodeForGN(v) for k, v in gn_args.items()] + extra_gn_args) 163 164 gn_args_list = ['gen', output_directory, gn_args_str] 165 gn_args_list.extend(extra_gn_switches) 166 _RunGN(gn_args_list) 167 168 ninja_args = TARGETS[:] 169 if use_goma: 170 ninja_args.extend(['-j', '200']) 171 ninja_args.extend(extra_ninja_switches) 172 _RunNinja(output_directory, ninja_args) 173 174 175def CollectCommon(aar_file, build_dir, arch): 176 """Collects architecture independent files into the .aar-archive.""" 177 logging.info('Collecting common files.') 178 output_directory = _GetOutputDirectory(build_dir, arch) 179 aar_file.write(MANIFEST_FILE, 'AndroidManifest.xml') 180 aar_file.write(os.path.join(output_directory, JAR_FILE), 'classes.jar') 181 182 183def Collect(aar_file, build_dir, arch): 184 """Collects architecture specific files into the .aar-archive.""" 185 logging.info('Collecting: %s', arch) 186 output_directory = _GetOutputDirectory(build_dir, arch) 187 188 abi_dir = os.path.join('jni', arch) 189 for so_file in NEEDED_SO_FILES: 190 aar_file.write(os.path.join(output_directory, so_file), 191 os.path.join(abi_dir, so_file)) 192 193 194def GenerateLicenses(output_dir, build_dir, archs): 195 builder = LicenseBuilder( 196 [_GetOutputDirectory(build_dir, arch) for arch in archs], TARGETS) 197 builder.GenerateLicenseText(output_dir) 198 199 200def BuildAar(archs, output_file, use_goma=False, extra_gn_args=None, 201 ext_build_dir=None, extra_gn_switches=None, 202 extra_ninja_switches=None): 203 extra_gn_args = extra_gn_args or [] 204 extra_gn_switches = extra_gn_switches or [] 205 extra_ninja_switches = extra_ninja_switches or [] 206 build_dir = ext_build_dir if ext_build_dir else tempfile.mkdtemp() 207 208 for arch in archs: 209 Build(build_dir, arch, use_goma, extra_gn_args, extra_gn_switches, 210 extra_ninja_switches) 211 212 with zipfile.ZipFile(output_file, 'w') as aar_file: 213 # Architecture doesn't matter here, arbitrarily using the first one. 214 CollectCommon(aar_file, build_dir, archs[0]) 215 for arch in archs: 216 Collect(aar_file, build_dir, arch) 217 218 license_dir = os.path.dirname(os.path.realpath(output_file)) 219 GenerateLicenses(license_dir, build_dir, archs) 220 221 if not ext_build_dir: 222 shutil.rmtree(build_dir, True) 223 224 225def main(): 226 args = _ParseArgs() 227 logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) 228 229 BuildAar(args.arch, args.output, args.use_goma, args.extra_gn_args, 230 args.build_dir, args.extra_gn_switches, args.extra_ninja_switches) 231 232 233if __name__ == '__main__': 234 sys.exit(main()) 235