text: #!/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
  
  
  # Add ORT C and C++ API headers to the AAR package (in fact a zip file)
  # Such that developers using ORT native API can extract libraries and header from AAR package without building ORT
  # TODO, see if we can use Gradle to add headers to AAR package directly, which is necessary if we want to
  # publish the packagee directly using Gradle in the pipeline
  def _add_headers_to_aar(aar_file_path, header_files_path):
      import shutil
      import tempfile
      with tempfile.TemporaryDirectory() as temp_dir:
          aar_content = os.path.join(temp_dir, 'aar_content')
          shutil.unpack_archive(aar_file_path, aar_content, 'zip')
  
          # copy necessary header files
          shutil.copytree(header_files_path, os.path.join(aar_content, 'headers'))
  
          # create the zip archive
          zip_base_filename = os.path.join(temp_dir, 'aar_with_headers')
          zip_filename = zip_base_filename + '.zip'
          shutil.make_archive(zip_base_filename, 'zip', root_dir=aar_content)
  
          # overwrite the existing AAR package
          shutil.move(zip_filename, aar_file_path)
  
  
  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']
      header_files_path = ''
  
      # 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)
  
          # we only need to define the header files path once
          if not header_files_path:
              header_files_path = os.path.join(_build_dir, _build_config, 'android', 'headers')
  
      # 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)
  
      # add C and C++ API headers to the intermediate aar package
      aar_file_path = os.path.join(_aar_dir, 'outputs', 'aar', 'onnxruntime-release.aar')
      _add_headers_to_aar(aar_file_path, header_files_path)
  
      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()
  