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