onnxruntime/tools/ci_build/github/android/build_aar_package.py
Guoyu Wang e05528a365
Update Android AAR packaging pipeline script (#7559)
* update android package pipeline

* update shell script

* update script

* add kMSExperimentalDomain to reduction
2021-05-04 11:13:33 -07:00

191 lines
7.7 KiB
Python

#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import argparse
import os
import pathlib
import json
import subprocess
import sys
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
REPO_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", "..", ".."))
BUILD_PY = os.path.join(REPO_DIR, "tools", "ci_build", "build.py")
JAVA_ROOT = os.path.join(REPO_DIR, "java")
sys.path.insert(0, os.path.join(REPO_DIR, "tools", "python"))
from util import is_windows # noqa: E402
# We by default will build all 4 ABIs
DEFAULT_BUILD_ABIS = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
# Onnx Runtime native library is built against NDK API 21 by default
# It is possible to build from source for Android API levels below 21, but it is not guaranteed
DEFAULT_ANDROID_MIN_SDK_VER = 21
# Android API 24 is the default target API version for Android builds, based on Microsoft 1CS requirements
# It is possible to build from source using API level 21 and higher as the target SDK version
DEFAULT_ANDROID_TARGET_SDK_VER = 24
def _parse_build_settings(args):
_setting_file = args.build_settings_file.resolve()
if not _setting_file.is_file():
raise FileNotFoundError('Build config file {} is not a file.'.format(_setting_file))
with open(_setting_file) as f:
_build_settings_data = json.load(f)
build_settings = {}
if 'build_abis' in _build_settings_data:
build_settings['build_abis'] = _build_settings_data['build_abis']
else:
build_settings['build_abis'] = DEFAULT_BUILD_ABIS
build_params = []
if 'build_params' in _build_settings_data:
build_params += _build_settings_data['build_params']
else:
raise ValueError('build_params is required in the build config file')
if 'android_min_sdk_version' in _build_settings_data:
build_settings['android_min_sdk_version'] = _build_settings_data['android_min_sdk_version']
else:
build_settings['android_min_sdk_version'] = DEFAULT_ANDROID_MIN_SDK_VER
build_params += ['--android_api=' + str(build_settings['android_min_sdk_version'])]
if 'android_target_sdk_version' in _build_settings_data:
build_settings['android_target_sdk_version'] = _build_settings_data['android_target_sdk_version']
else:
build_settings['android_target_sdk_version'] = DEFAULT_ANDROID_TARGET_SDK_VER
if build_settings['android_min_sdk_version'] > build_settings['android_target_sdk_version']:
raise ValueError(
'android_min_sdk_version {} cannot be larger than android_target_sdk_version {}'.format(
build_settings['android_min_sdk_version'], build_settings['android_target_sdk_version']
))
build_settings['build_params'] = build_params
return build_settings
def _build_aar(args):
build_settings = _parse_build_settings(args)
build_dir = os.path.abspath(args.build_dir)
# Setup temp environment for building
_env = os.environ.copy()
_env['ANDROID_HOME'] = os.path.abspath(args.android_sdk_path)
_env['ANDROID_NDK_HOME'] = os.path.abspath(args.android_ndk_path)
# Temp dirs to hold building results
_intermediates_dir = os.path.join(build_dir, 'intermediates')
_build_config = args.config
_aar_dir = os.path.join(_intermediates_dir, 'aar', _build_config)
_jnilibs_dir = os.path.join(_intermediates_dir, 'jnilibs', _build_config)
_base_build_command = [
sys.executable, BUILD_PY, '--config=' + _build_config
] + build_settings['build_params']
# Build binary for each ABI, one by one
for abi in build_settings['build_abis']:
_build_dir = os.path.join(_intermediates_dir, abi)
_build_command = _base_build_command + [
'--android_abi=' + abi,
'--build_dir=' + _build_dir
]
if args.include_ops_by_config is not None:
_build_command += ['--include_ops_by_config=' + args.include_ops_by_config]
subprocess.run(_build_command, env=_env, shell=False, check=True, cwd=REPO_DIR)
# create symbolic links for libonnxruntime.so and libonnxruntime4j_jni.so
# to jnilibs/[abi] for later compiling the aar package
_jnilibs_abi_dir = os.path.join(_jnilibs_dir, abi)
os.makedirs(_jnilibs_abi_dir, exist_ok=True)
for lib_name in ['libonnxruntime.so', 'libonnxruntime4j_jni.so']:
_target_lib_name = os.path.join(_jnilibs_abi_dir, lib_name)
# If the symbolic already exists, delete it first
# For some reason, os.path.exists will return false for a symbolic link in Linux,
# add double check with os.path.islink
if os.path.exists(_target_lib_name) or os.path.islink(_target_lib_name):
os.remove(_target_lib_name)
os.symlink(os.path.join(_build_dir, _build_config, lib_name), _target_lib_name)
# The directory to publish final AAR
_aar_publish_dir = os.path.join(build_dir, 'aar_out', _build_config)
os.makedirs(_aar_publish_dir, exist_ok=True)
# get the common gradle command args
_gradle_command = [
'gradle',
'--no-daemon',
'-b=build-android.gradle',
'-c=settings-android.gradle',
'-DjniLibsDir=' + _jnilibs_dir,
'-DbuildDir=' + _aar_dir,
'-DpublishDir=' + _aar_publish_dir,
'-DminSdkVer=' + str(build_settings['android_min_sdk_version']),
'-DtargetSdkVer=' + str(build_settings['android_target_sdk_version'])
]
# If not using shell on Window, will not be able to find gradle in path
_shell = True if is_windows() else False
# clean, build, and publish to a local directory
subprocess.run(_gradle_command + ['clean'], env=_env, shell=_shell, check=True, cwd=JAVA_ROOT)
subprocess.run(_gradle_command + ['build'], env=_env, shell=_shell, check=True, cwd=JAVA_ROOT)
subprocess.run(_gradle_command + ['publish'], env=_env, shell=_shell, check=True, cwd=JAVA_ROOT)
def parse_args():
parser = argparse.ArgumentParser(
os.path.basename(__file__),
description='''Create Android Archive (AAR) package for one or more Android ABI(s)
and building properties specified in the given build config file, see
tools/ci_build/github/android/default_mobile_aar_build_settings.json for details.
The output of the final AAR package can be found under [build_dir]/aar_out
'''
)
parser.add_argument("--android_sdk_path", type=str, default=os.environ.get("ANDROID_HOME", ""),
help="Path to the Android SDK")
parser.add_argument("--android_ndk_path", type=str, default=os.environ.get("ANDROID_NDK_HOME", ""),
help="Path to the Android NDK")
parser.add_argument('--build_dir', type=str, default=os.path.join(REPO_DIR, 'build/android_aar'),
help='Provide the root directory for build output')
parser.add_argument(
"--include_ops_by_config", type=str,
help="Include ops from config file. See /docs/Reduced_Operator_Kernel_build.md for more information.")
parser.add_argument("--config", type=str, default="Release",
choices=["Debug", "MinSizeRel", "Release", "RelWithDebInfo"],
help="Configuration to build.")
parser.add_argument('build_settings_file', type=pathlib.Path,
help='Provide the file contains settings for building AAR')
return parser.parse_args()
def main():
args = parse_args()
# Android SDK and NDK path are required
if not args.android_sdk_path:
raise ValueError('android_sdk_path is required')
if not args.android_ndk_path:
raise ValueError('android_ndk_path is required')
_build_aar(args)
if __name__ == '__main__':
main()