ringrtc/bin/build-aar.py
2021-01-19 17:19:23 -08:00

437 lines
16 KiB
Python
Executable file

#!/usr/bin/env python3
#
# Copyright 2019-2021 Signal Messenger, LLC
# SPDX-License-Identifier: AGPL-3.0-only
#
"""
This script generates libringrtc.aar for distribution
"""
# ------------------------------------------------------------------------------
#
# Imports
#
try:
import argparse
import logging
import subprocess
import sys
import os
import zipfile
import shutil
except ImportError as e:
raise ImportError(str(e) + "- required module not found")
DEFAULT_ARCHS = ['arm', 'arm64', 'x86', 'x64']
NINJA_TARGETS = ['ringrtc']
JAR_FILES = [
'lib.java/ringrtc/libringrtc.jar',
'lib.java/sdk/android/libwebrtc.jar',
]
SO_LIBS = [
'libringrtc_rffi.so',
'libringrtc.so',
]
# ------------------------------------------------------------------------------
#
# Main
#
def ParseArgs():
parser = argparse.ArgumentParser(
description='Build and package libringrtc.aar')
parser.add_argument('-v', '--verbose',
action='store_true',
help='Verbose output')
parser.add_argument('-q', '--quiet',
action='store_true',
help='Quiet output')
parser.add_argument('-b', '--build-dir',
required=True,
help='Build directory')
parser.add_argument('-w', '--webrtc-src-dir',
required=True,
help='WebRTC source root directory')
parser.add_argument('-o', '--output',
default = 'libringrtc.aar',
help='Output AAR file name')
parser.add_argument('-d', '--debug-build',
action='store_true',
help='Build a debug version of the AAR. Default is both')
parser.add_argument('-r', '--release-build',
action='store_true',
help='Build a release version of the AAR. Default is both')
parser.add_argument('-a', '--arch',
default=DEFAULT_ARCHS,
choices=DEFAULT_ARCHS,
nargs='*',
help='CPU architectures to build. Defaults: %(default)s.')
parser.add_argument('-g', '--extra-gn-args',
nargs='*', default=[],
help='''Additional GN arguments, passed via `gn --args` switch.
These args override anything set internally by this script.''')
parser.add_argument('-n', '--extra-ninja-flags',
nargs='*', default=[],
help='''Additional Ninja flags, overriding anything set internally
by this script.''')
parser.add_argument('-f', '--extra-gn-flags',
nargs='*', default=[],
help='''Additional GN flags, overriding anything set internally
by this script.''')
parser.add_argument('-j', '--jobs',
default=32,
help='Number of parallel ninja jobs to run.')
parser.add_argument('--gradle-dir',
required=True,
help='Android gradle directory')
parser.add_argument('--publish-version',
required=True,
help='Library version to publish')
parser.add_argument('--extra-gradle-args',
nargs='*', default=[],
help='Additional gradle arguments')
parser.add_argument('--install-local',
action='store_true',
help='Install to local maven repo')
parser.add_argument('--install-dir',
help='Install to local directory')
parser.add_argument('--upload-sonatype-repo',
help='Upload to remote sonatype repo')
parser.add_argument('--upload-sonatype-user',
help='Upload to remote sonatype repo as user')
parser.add_argument('--upload-sonatype-password',
help='Upload to remote sonatype repo using password')
parser.add_argument('--signing-keyid',
help='''GPG keyId for signing key (8 character short form).
See https://docs.gradle.org/current/userguide/signing_plugin.html''')
parser.add_argument('--signing-password',
help='''GPG passphrase for signing key.
See https://docs.gradle.org/current/userguide/signing_plugin.html''')
parser.add_argument('--signing-secret-keyring',
help='''Absolute path to the secret key ring file containing signing key.
See https://docs.gradle.org/current/userguide/signing_plugin.html''')
parser.add_argument('--dry-run',
action='store_true',
help='Dry Run: print what would happen, but do not actually do anything')
parser.add_argument('-u', '--unstripped',
action='store_true',
help='Store the unstripped libraries in the .aar. Default is false')
parser.add_argument('-c', '--compile-only',
action='store_true',
help='Only compile the code, do not build the .aar. Default is false')
parser.add_argument('--clean',
action='store_true',
help='Remove all the build products. Default is false')
return parser.parse_args()
def RunGn(dry_run, args):
cmd = [ 'gn' ] + args
logging.debug('Running: {}'.format(cmd))
if dry_run is False:
subprocess.check_call(cmd)
def RunNinja(dry_run, args):
cmd = [ 'ninja' ] + args
logging.debug('Running: {}'.format(cmd))
if dry_run is False:
subprocess.check_call(cmd)
def GetArchBuildRoot(build_dir, arch):
return os.path.join(build_dir, 'android-{}'.format(arch))
def GetArchBuildDir(build_dir, arch, debug_build):
if debug_build is True:
build_type = 'debug'
else:
build_type = 'release'
return os.path.join(GetArchBuildRoot(build_dir, arch), '{}'.format(build_type))
def GetOutputDir(build_dir, debug_build):
if debug_build is True:
build_type = 'debug'
else:
build_type = 'release'
return os.path.join(build_dir, '{}'.format(build_type))
def GetGradleBuildDir(build_dir):
return os.path.join(build_dir, 'gradle')
def BuildArch(dry_run, build_dir, arch, debug_build, extra_gn_args,
extra_gn_flags, extra_ninja_flags, jobs):
logging.info('Building: {} ...'.format(arch))
output_dir = GetArchBuildDir(build_dir, arch, debug_build)
gn_args = {
'target_os' : '"android"',
'target_cpu' : '"{}"'.format(arch),
'is_debug' : 'false',
'rtc_include_tests' : 'false',
'rtc_build_examples': 'false',
}
if debug_build is True:
gn_args['is_debug'] = 'true'
gn_args['symbol_level'] = '2'
gn_args_string = '--args=' + ' '.join(
[k + '=' + v for k, v in gn_args.items()] + extra_gn_args)
gn_total_args = [ 'gen', output_dir, gn_args_string ] + extra_gn_flags
RunGn(dry_run, gn_total_args)
ninja_args = [ '-C', output_dir ] + NINJA_TARGETS + [ '-j', jobs ] + extra_ninja_flags
RunNinja(dry_run, ninja_args)
def GetABI(arch):
if arch == 'arm':
return 'armeabi-v7a'
elif arch == 'arm64':
return 'arm64-v8a'
elif arch == 'x86':
return 'x86'
elif arch == 'x64':
return 'x86_64'
else:
raise Exception('Unknown architecture: ' + arch)
def CreateLibs(dry_run, build_dir, archs, output, debug_build, unstripped,
extra_gn_args, extra_gn_flags, extra_ninja_flags, jobs,
compile_only):
for arch in archs:
BuildArch(dry_run, build_dir, arch, debug_build, extra_gn_args,
extra_gn_flags, extra_ninja_flags, jobs)
if compile_only is True:
return
output_dir = os.path.join(GetOutputDir(build_dir, debug_build),
'libs')
output_file = os.path.join(output_dir, output)
if dry_run is True:
return
shutil.rmtree(GetOutputDir(build_dir, debug_build), ignore_errors=True)
os.makedirs(output_dir)
for jar in JAR_FILES:
logging.debug(' Adding jar: {} ...'.format(jar))
output_arch_dir = GetArchBuildDir(build_dir, archs[0], debug_build)
shutil.copyfile(os.path.join(output_arch_dir, jar),
os.path.join(output_dir, os.path.basename(jar)))
for arch in archs:
for lib in SO_LIBS:
output_arch_dir = GetArchBuildDir(build_dir, arch, debug_build)
if unstripped is True:
# package the unstripped libraries
lib_file = os.path.join("lib.unstripped", lib)
else:
lib_file = lib
target_dir = os.path.join(output_dir, GetABI(arch))
logging.debug(' Adding lib: {}/{} to {}...'.format(GetABI(arch), lib_file, target_dir))
os.makedirs(target_dir, exist_ok=True)
shutil.copyfile(os.path.join(output_arch_dir, lib_file),
os.path.join(target_dir,
os.path.basename(lib)))
def RunGradle(dry_run, args):
cmd = [ './gradlew' ] + args
logging.debug('Running: {}'.format(cmd))
if dry_run is False:
subprocess.check_call(cmd)
def CreateAar(dry_run, extra_gradle_args, version, gradle_dir,
sonatype_repo, sonatype_user, sonatype_password,
signing_keyid, signing_password, signing_secret_keyring,
compile_only,
install_local, install_dir, build_dir, archs,
output, debug_build, release_build, unstripped,
extra_gn_args, extra_gn_flags, extra_ninja_flags, jobs):
build_types = []
if not (debug_build or release_build):
# build both
build_types = ['debug', 'release']
else:
if debug_build:
build_types = ['debug']
if release_build:
build_types = build_types + ['release']
gradle_build_dir = GetGradleBuildDir(build_dir)
shutil.rmtree(gradle_build_dir, ignore_errors=True)
gradle_args = [
'-PringrtcVersion={}'.format(version),
'-PbuildDir={}'.format(gradle_build_dir),
]
if sonatype_repo is not None:
sonatype_args = [
'-PsonatypeRepo={}'.format(sonatype_repo),
'-PsignalSonatypeUsername={}'.format(sonatype_user),
'-PsignalSonatypePassword={}'.format(sonatype_password),
]
gradle_args.extend(sonatype_args)
if signing_keyid is not None:
gradle_args.append(
'-Psigning.keyId={}'.format(signing_keyid))
if signing_password is not None:
gradle_args.append(
'-Psigning.password={}'.format(signing_password))
if signing_secret_keyring is not None:
gradle_args.append(
'-Psigning.secretKeyRingFile={}'.format(signing_secret_keyring))
for build_type in build_types:
if build_type == 'debug':
build_debug = True
output_dir = GetOutputDir(build_dir, build_debug)
lib_dir = os.path.join(output_dir, 'libs')
gradle_args = gradle_args + [
"-PdebugRingrtcLibDirs=['{}']".format(lib_dir),
"-PdebugOutputDir={}".format(output_dir),
]
else:
build_debug = False
output_dir = GetOutputDir(build_dir, build_debug)
lib_dir = os.path.join(output_dir, 'libs')
gradle_args = gradle_args + [
"-PreleaseRingrtcLibDirs=['{}']".format(lib_dir),
"-PreleaseOutputDir={}".format(output_dir),
]
CreateLibs(dry_run, build_dir, archs, output, build_debug, unstripped,
extra_gn_args, extra_gn_flags, extra_ninja_flags, jobs,
compile_only)
if compile_only is True:
return
gradle_args.append('assemble')
if install_local is True:
gradle_args.append('installArchives')
if sonatype_repo is not None:
gradle_args.append('uploadArchives')
gradle_args.extend(extra_gradle_args)
# Run gradle
os.chdir(os.path.abspath(gradle_dir))
RunGradle(dry_run, gradle_args)
if install_dir is not None:
for build_type in build_types:
if build_type == 'debug':
build_debug = True
output_dir = GetOutputDir(build_dir, build_debug)
dest_dir = os.path.join(install_dir, version, 'android', 'debug')
else:
build_debug = False
output_dir = GetOutputDir(build_dir, build_debug)
dest_dir = os.path.join(install_dir, version, 'android', 'release')
logging.info('Installing locally to: {}'.format(dest_dir))
if dry_run is False:
shutil.rmtree(dest_dir, ignore_errors=True)
os.makedirs(os.path.dirname(dest_dir), exist_ok=True)
shutil.copytree(output_dir, dest_dir)
def clean_dir(directory, dry_run):
logging.info('Removing: {}'.format(directory))
if dry_run is False:
shutil.rmtree(directory, ignore_errors=True)
def main():
args = ParseArgs()
if args.dry_run is True:
args.verbose = True
if args.verbose is True:
log_level = logging.DEBUG
else:
log_level = logging.INFO
logging.basicConfig(level=log_level, format='%(levelname).1s:%(message)s')
if args.quiet is True:
logging.disable(logging.CRITICAL)
build_dir = os.path.abspath(args.build_dir)
logging.debug('Using build directory: {}'.format(build_dir))
if args.verbose is True:
args.extra_ninja_flags = args.extra_ninja_flags + ['-v']
gradle_dir = os.path.abspath(args.gradle_dir)
logging.debug('Using gradle directory: {}'.format(gradle_dir))
if args.clean is True:
for arch in DEFAULT_ARCHS:
rm_dir = GetArchBuildRoot(build_dir, arch)
clean_dir(GetArchBuildRoot(build_dir, arch), args.dry_run)
clean_dir(GetGradleBuildDir(build_dir), args.dry_run)
for dir in ('debug', 'release', 'javadoc', 'rustdoc', 'rust-lint'):
clean_dir(os.path.join(build_dir, dir), args.dry_run)
return 0
os.chdir(os.path.abspath(args.webrtc_src_dir))
if args.upload_sonatype_repo is not None:
if args.debug_build is True or args.release_build is True:
print('ERROR: When uploading, must upload complete release and debug builds')
print('ERROR: You cannot specifiy either --release or --debug while uploading')
return 1
if args.upload_sonatype_user is None or args.upload_sonatype_password is None:
print('ERROR: If --upload-sonatype-repo argument set, then both --upload-sonatype-user and --upload-sonatype-password must also be set.')
return 1
if args.signing_keyid is None or \
args.signing_password is None or \
args.signing_secret_keyring is None:
print('ERROR: If --upload-sonatype-repo argument set, then all of --signing-keyid, --signing-password, and --signing-secret-keyring must also be set.')
return 1
CreateAar(args.dry_run, args.extra_gradle_args, args.publish_version, args.gradle_dir,
args.upload_sonatype_repo, args.upload_sonatype_user, args.upload_sonatype_password,
args.signing_keyid, args.signing_password, args.signing_secret_keyring,
args.compile_only,
args.install_local, args.install_dir,
build_dir, args.arch, args.output,
args.debug_build, args.release_build, args.unstripped, args.extra_gn_args,
args.extra_gn_flags, args.extra_ninja_flags, str(args.jobs))
logging.info('''
Version : {}
Architectures : {}
Debug Build : {}
Release Build : {}
Build Directory : {}
Stripped Libraries: {}
'''.format(args.publish_version, args.arch, args.debug_build,
args.release_build, args.build_dir, not args.unstripped))
return 0
# --------------------
#
# execution check
#
if __name__ == '__main__':
exit(main())