diff --git a/cmake/onnxruntime_java.cmake b/cmake/onnxruntime_java.cmake index dfe81df048..8d60966c5b 100644 --- a/cmake/onnxruntime_java.cmake +++ b/cmake/onnxruntime_java.cmake @@ -41,6 +41,11 @@ set(JAVA_OUTPUT_JAR ${JAVA_ROOT}/build/libs/onnxruntime.jar) set(GRADLE_ARGS clean jar) if(WIN32) set(GRADLE_ARGS ${GRADLE_ARGS} -Dorg.gradle.daemon=false) +elseif (CMAKE_SYSTEM_NAME STREQUAL "Android") + # For Android build, we may run gradle multiple times in same build, + # sometimes gradle JVM will run out of memory if we keep the daemon running + # it is better to not keep a daemon running + set(GRADLE_ARGS ${GRADLE_ARGS} --no-daemon) endif() if(onnxruntime_USE_CUDA) set(GRADLE_ARGS ${GRADLE_ARGS} -DUSE_CUDA=1) @@ -188,6 +193,11 @@ endif() set(GRADLE_ARGS cmakeBuild -DcmakeBuildDir=${CMAKE_CURRENT_BINARY_DIR}) if(WIN32) set(GRADLE_ARGS ${GRADLE_ARGS} -Dorg.gradle.daemon=false) +elseif (CMAKE_SYSTEM_NAME STREQUAL "Android") + # For Android build, we may run gradle multiple times in same build, + # sometimes gradle JVM will run out of memory if we keep the daemon running + # it is better to not keep a daemon running + set(GRADLE_ARGS ${GRADLE_ARGS} --no-daemon) endif() if(onnxruntime_USE_CUDA) set(GRADLE_ARGS ${GRADLE_ARGS} -DUSE_CUDA=1) diff --git a/java/build-android.gradle b/java/build-android.gradle index de19d85f76..8e6762602d 100644 --- a/java/build-android.gradle +++ b/java/build-android.gradle @@ -1,9 +1,29 @@ apply plugin: 'com.android.library' +apply plugin: 'maven-publish' def jniLibsDir = System.properties['jniLibsDir'] def buildDir = System.properties['buildDir'] +def publishDir = System.properties['publishDir'] +def minSdkVer = System.properties['minSdkVer'] +def targetSdkVer = System.properties['targetSdkVer'] + +// Since Android requires a higher numbers indicating more recent versions +// This function assume ORT version number will be in formart of A.B.C such as 1.7.0 +// We generate version code A[0{0,1}]B[0{0,1}]C, +// for example '1.7.0' -> 10700, '1.6.15' -> 10615 +def getVersionCode(String version){ + String[] codes = version.split('\\.'); + // This will have problem if we have 3 digit [sub]version number, such as 1.7.199 + // but it is highly unlikely to happen + String versionCodeStr = String.format("%d%02d%02d", codes[0] as int, codes[1] as int, codes[2] as int); + return versionCodeStr as int; +} project.buildDir = buildDir +project.version = rootProject.file('../VERSION_NUMBER').text.trim() +project.group = "com.microsoft.onnxruntime" + +def mavenArtifactId = project.name + '-mobile' buildscript { repositories { @@ -30,13 +50,12 @@ android { buildToolsVersion "29.0.2" defaultConfig { - minSdkVersion 24 - targetSdkVersion 28 - versionCode 1 - versionName "1.0" + minSdkVersion minSdkVer + targetSdkVersion targetSdkVer + versionCode = getVersionCode(project.version) + versionName = project.version testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } android { @@ -44,15 +63,18 @@ android { abortOnError false } } + buildTypes { release { minifyEnabled false + debuggable false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + compileOptions { - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } sourceSets { @@ -60,6 +82,27 @@ android { jniLibs.srcDirs = [jniLibsDir] } } + +} + +task sourcesJar(type: Jar) { + classifier "sources" + from android.sourceSets.main.java.srcDirs +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar } dependencies { @@ -67,3 +110,53 @@ dependencies { testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' testImplementation 'com.google.protobuf:protobuf-java:3.10.0' } + +publishing { + publications { + maven(MavenPublication) { + groupId = project.group + artifactId = mavenArtifactId + version = project.version + + // Three artifacts, the `aar`, the sources and the javadoc + artifact("$buildDir/outputs/aar/${project.name}-release.aar") + artifact javadocJar + artifact sourcesJar + + pom { + name = 'onnx-runtime' + description = 'ONNX Runtime is a performance-focused inference engine for ONNX (Open Neural Network Exchange) models.' + url = 'https://microsoft.github.io/onnxruntime/' + licenses { + license { + name = 'MIT License' + url = 'https://opensource.org/licenses/MIT' + } + } + organization { + name = 'Microsoft' + url = 'http://www.microsoft.com' + } + scm { + connection = 'scm:git:git://github.com:microsoft/onnxruntime.git' + developerConnection = 'scm:git:ssh://github.com/microsoft/onnxruntime.git' + url = 'http://github.com/microsoft/onnxruntime' + } + developers { + developer { + id = 'onnxruntime' + name = 'ONNX Runtime' + email = 'onnxruntime@microsoft.com' + } + } + } + } + } + + //publish to filesystem repo + repositories{ + maven { + url "$publishDir" + } + } +} diff --git a/tools/ci_build/build.py b/tools/ci_build/build.py index 997cdb6aee..9409b67116 100644 --- a/tools/ci_build/build.py +++ b/tools/ci_build/build.py @@ -280,8 +280,12 @@ def parse_arguments(): choices=["armeabi-v7a", "arm64-v8a", "x86", "x86_64"], help="Specify the target Android Application Binary Interface (ABI)") parser.add_argument("--android_api", type=int, default=27, help='Android API Level, e.g. 21') - parser.add_argument("--android_sdk_path", type=str, help='Path to the Android SDK') - parser.add_argument("--android_ndk_path", default="", help="Path to the Android NDK") + 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("--android_cpp_shared", action="store_true", help="Build with shared libc++ instead of the default static libc++.") parser.add_argument("--android_run_emulator", action="store_true", @@ -797,8 +801,13 @@ def generate_build_tree(cmake_path, source_dir, build_dir, cuda_home, cudnn_home cmake_args += ["-Donnxruntime_NNAPI_MIN_API=" + str(args.nnapi_min_api)] if args.android: + if not args.android_ndk_path: + raise BuildError("android_ndk_path required to build for Android") + if not args.android_sdk_path: + raise BuildError("android_sdk_path required to build for Android") cmake_args += [ - "-DCMAKE_TOOLCHAIN_FILE=" + args.android_ndk_path + "/build/cmake/android.toolchain.cmake", + "-DCMAKE_TOOLCHAIN_FILE=" + os.path.join( + args.android_ndk_path, 'build', 'cmake', 'android.toolchain.cmake'), "-DANDROID_PLATFORM=android-" + str(args.android_api), "-DANDROID_ABI=" + str(args.android_abi) ] diff --git a/tools/ci_build/github/android/build_aar_package.py b/tools/ci_build/github/android/build_aar_package.py new file mode 100644 index 0000000000..097bc90729 --- /dev/null +++ b/tools/ci_build/github/android/build_aar_package.py @@ -0,0 +1,180 @@ +#!/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 + +SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) +REPO_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", "..", "..")) +BUILD_PY = os.path.normpath(os.path.join(REPO_DIR, "tools", "ci_build", "build.py")) +JAVA_ROOT = os.path.normpath(os.path.join(REPO_DIR, "java")) + +# We by default will build all 4 ABIs +DEFAULT_BUILD_ABIS = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"] +# Android API 21 is the lowest API version we support +DEFAULT_ANDROID_MIN_SDK_VER = 21 +# Android API 28 is the default target API version for Android builds +DEFAULT_ANDROID_TARGET_SDK_VER = 28 + + +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 = {} + build_settings['android_sdk_path'] = args.android_sdk_path + build_settings['android_ndk_path'] = args.android_ndk_path + + if 'build_flavor' in _build_settings_data: + build_settings['build_flavor'] = _build_settings_data['build_flavor'] + else: + raise ValueError('build_flavor is required in the build config file') + + 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 = args.build_dir + + # Setup temp environment for building + my_env = os.environ.copy() + my_env['ANDROID_HOME'] = build_settings['android_sdk_path'] + my_env['ANDROID_NDK_HOME'] = build_settings['android_ndk_path'] + + # Temp dirs to hold building results + _intermediates_dir = os.path.join(build_dir, 'intermediates') + _build_flavor = build_settings['build_flavor'] + _aar_dir = os.path.join(_intermediates_dir, 'aar', _build_flavor) + _jnilibs_dir = os.path.join(_intermediates_dir, 'jnilibs', _build_flavor) + _base_build_command = [ + 'python3', BUILD_PY, '--config=' + _build_flavor + ] + 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=my_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 + if os.path.exists(_target_lib_name): + os.remove(_target_lib_name) + os.symlink(os.path.join(_build_dir, _build_flavor, lib_name), _target_lib_name) + + # The directory to publish final AAR + _aar_publish_dir = os.path.join(build_dir, 'aar_out', _build_flavor) + 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']) + ] + + # clean, build, and publish to a local directory + subprocess.run(_gradle_command + ['clean'], env=my_env, shell=False, check=True, cwd=JAVA_ROOT) + subprocess.run(_gradle_command + ['build'], env=my_env, shell=False, check=True, cwd=JAVA_ROOT) + subprocess.run(_gradle_command + ['publish'], env=my_env, shell=False, 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 + ''' + ) + + 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=pathlib.Path, 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('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() diff --git a/tools/ci_build/github/android/default_mobile_aar_build_settings.json b/tools/ci_build/github/android/default_mobile_aar_build_settings.json new file mode 100644 index 0000000000..250fd91a49 --- /dev/null +++ b/tools/ci_build/github/android/default_mobile_aar_build_settings.json @@ -0,0 +1,24 @@ +{ + "build_abis": [ + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64" + ], + "build_flavor": "Release", + "android_min_sdk_version": 21, + "android_target_sdk_version": 28, + "build_params": [ + "--android", + "--parallel", + "--cmake_generator=Ninja", + "--build_java", + "--build_shared_lib", + "--minimal_build=extended", + "--disable_rtti", + "--disable_ml_ops", + "--disable_exceptions", + "--use_nnapi", + "--skip_tests" + ] +} \ No newline at end of file