build_java_shared_aar.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python
  2. import argparse
  3. from os import path
  4. import os
  5. import re
  6. import shutil
  7. import string
  8. import subprocess
  9. COPY_FROM_SDK_TO_ANDROID_PROJECT = [
  10. ["sdk/native/jni/include", "OpenCV/src/main/cpp/include"],
  11. ["sdk/java/src/org", "OpenCV/src/main/java/org"],
  12. ["sdk/java/res", "OpenCV/src/main/res"]
  13. ]
  14. COPY_FROM_SDK_TO_APK = [
  15. ["sdk/native/libs/<ABI>/lib<LIB_NAME>.so", "jni/<ABI>/lib<LIB_NAME>.so"],
  16. ["sdk/native/libs/<ABI>/lib<LIB_NAME>.so", "prefab/modules/<LIB_NAME>/libs/android.<ABI>/lib<LIB_NAME>.so"],
  17. ]
  18. ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template")
  19. TEMP_DIR = "build_java_shared"
  20. ANDROID_PROJECT_DIR = path.join(TEMP_DIR, "AndroidProject")
  21. COMPILED_AAR_PATH_1 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/OpenCV-release.aar") # original package name
  22. COMPILED_AAR_PATH_2 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/opencv-release.aar") # lower case package name
  23. AAR_UNZIPPED_DIR = path.join(TEMP_DIR, "aar_unzipped")
  24. FINAL_AAR_PATH_TEMPLATE = "outputs/opencv_java_shared_<OPENCV_VERSION>.aar"
  25. FINAL_REPO_PATH = "outputs/maven_repo"
  26. MAVEN_PACKAGE_NAME = "opencv"
  27. def fill_template(src_path, dst_path, args_dict):
  28. with open(src_path, "r") as f:
  29. template_text = f.read()
  30. template = string.Template(template_text)
  31. text = template.safe_substitute(args_dict)
  32. with open(dst_path, "w") as f:
  33. f.write(text)
  34. def get_opencv_version(opencv_sdk_path):
  35. version_hpp_path = path.join(opencv_sdk_path, "sdk/native/jni/include/opencv2/core/version.hpp")
  36. with open(version_hpp_path, "rt") as f:
  37. data = f.read()
  38. major = re.search(r'^#define\W+CV_VERSION_MAJOR\W+(\d+)$', data, re.MULTILINE).group(1)
  39. minor = re.search(r'^#define\W+CV_VERSION_MINOR\W+(\d+)$', data, re.MULTILINE).group(1)
  40. revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1)
  41. return "%(major)s.%(minor)s.%(revision)s" % locals()
  42. def get_ndk_version(ndk_path):
  43. props_path = path.join(ndk_path, "source.properties")
  44. with open(props_path, "rt") as f:
  45. data = f.read()
  46. version = re.search(r'Pkg\.Revision\W+=\W+(\d+\.\d+\.\d+)', data).group(1)
  47. return version.strip()
  48. def get_compiled_aar_path(path1, path2):
  49. if path.exists(path1):
  50. return path1
  51. elif path.exists(path2):
  52. return path2
  53. else:
  54. raise Exception("Can't find compiled AAR path in [" + path1 + ", " + path2 + "]")
  55. def cleanup(paths_to_remove):
  56. exists = False
  57. for p in paths_to_remove:
  58. if path.exists(p):
  59. exists = True
  60. if path.isdir(p):
  61. shutil.rmtree(p)
  62. else:
  63. os.remove(p)
  64. print("Removed", p)
  65. if not exists:
  66. print("Nothing to remove")
  67. def main(args):
  68. opencv_version = get_opencv_version(args.opencv_sdk_path)
  69. ndk_version = get_ndk_version(args.ndk_location)
  70. print("Detected ndk_version:", ndk_version)
  71. abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs"))
  72. lib_name = "opencv_java" + opencv_version.split(".")[0]
  73. final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("<OPENCV_VERSION>", opencv_version)
  74. print("Removing data from previous runs...")
  75. cleanup([TEMP_DIR, final_aar_path, path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)])
  76. print("Preparing Android project...")
  77. # ANDROID_PROJECT_TEMPLATE_DIR contains an Android project template that creates AAR
  78. shutil.copytree(ANDROID_PROJECT_TEMPLATE_DIR, ANDROID_PROJECT_DIR)
  79. # Configuring the Android project to Java + shared C++ lib version
  80. shutil.rmtree(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/include"))
  81. fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle.template"),
  82. path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle"),
  83. {"LIB_NAME": lib_name,
  84. "LIB_TYPE": "c++_shared",
  85. "PACKAGE_NAME": MAVEN_PACKAGE_NAME,
  86. "OPENCV_VERSION": opencv_version,
  87. "NDK_VERSION": ndk_version,
  88. "COMPILE_SDK": args.android_compile_sdk,
  89. "MIN_SDK": args.android_min_sdk,
  90. "TARGET_SDK": args.android_target_sdk,
  91. "ABI_FILTERS": ", ".join(['"' + x + '"' for x in abis]),
  92. "JAVA_VERSION": args.java_version,
  93. })
  94. fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt.template"),
  95. path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"),
  96. {"LIB_NAME": lib_name, "LIB_TYPE": "SHARED"})
  97. local_props = ""
  98. if args.ndk_location:
  99. local_props += "ndk.dir=" + args.ndk_location + "\n"
  100. if args.cmake_location:
  101. local_props += "cmake.dir=" + args.cmake_location + "\n"
  102. if local_props:
  103. with open(path.join(ANDROID_PROJECT_DIR, "local.properties"), "wt") as f:
  104. f.write(local_props)
  105. # Copying Java code and C++ public headers from SDK to the Android project
  106. for src, dst in COPY_FROM_SDK_TO_ANDROID_PROJECT:
  107. shutil.copytree(path.join(args.opencv_sdk_path, src),
  108. path.join(ANDROID_PROJECT_DIR, dst))
  109. print("Running gradle assembleRelease...")
  110. # Running gradle to build the Android project
  111. cmd = ["./gradlew", "assembleRelease"]
  112. if args.offline:
  113. cmd = cmd + ["--offline"]
  114. subprocess.run(cmd, shell=False, cwd=ANDROID_PROJECT_DIR, check=True)
  115. print("Adding libs to AAR...")
  116. # The created AAR package doesn't contain C++ shared libs.
  117. # We need to add them manually.
  118. # AAR package is just a zip archive.
  119. complied_aar_path = get_compiled_aar_path(COMPILED_AAR_PATH_1, COMPILED_AAR_PATH_2) # two possible paths
  120. shutil.unpack_archive(complied_aar_path, AAR_UNZIPPED_DIR, "zip")
  121. for abi in abis:
  122. for src, dst in COPY_FROM_SDK_TO_APK:
  123. src = src.replace("<ABI>", abi).replace("<LIB_NAME>", lib_name)
  124. dst = dst.replace("<ABI>", abi).replace("<LIB_NAME>", lib_name)
  125. shutil.copy(path.join(args.opencv_sdk_path, src),
  126. path.join(AAR_UNZIPPED_DIR, dst))
  127. # Creating final AAR zip archive
  128. os.makedirs("outputs", exist_ok=True)
  129. shutil.make_archive(final_aar_path, "zip", AAR_UNZIPPED_DIR, ".")
  130. os.rename(final_aar_path + ".zip", final_aar_path)
  131. print("Creating local maven repo...")
  132. shutil.copy(final_aar_path, path.join(ANDROID_PROJECT_DIR, "OpenCV/opencv-release.aar"))
  133. print("Creating a maven repo from project sources (with sources jar and javadoc jar)...")
  134. cmd = ["./gradlew", "publishReleasePublicationToMyrepoRepository"]
  135. if args.offline:
  136. cmd = cmd + ["--offline"]
  137. subprocess.run(cmd, shell=False, cwd=ANDROID_PROJECT_DIR, check=True)
  138. os.makedirs(path.join(FINAL_REPO_PATH, "org/opencv"), exist_ok=True)
  139. shutil.move(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME),
  140. path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME))
  141. print("Creating a maven repo from modified AAR (with cpp libraries)...")
  142. cmd = ["./gradlew", "publishModifiedPublicationToMyrepoRepository"]
  143. if args.offline:
  144. cmd = cmd + ["--offline"]
  145. subprocess.run(cmd, shell=False, cwd=ANDROID_PROJECT_DIR, check=True)
  146. # Replacing AAR from the first maven repo with modified AAR from the second maven repo
  147. shutil.copytree(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME),
  148. path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME),
  149. dirs_exist_ok=True)
  150. if __name__ == "__main__":
  151. parser = argparse.ArgumentParser(description="Builds AAR with Java and shared C++ libs from OpenCV SDK")
  152. parser.add_argument('opencv_sdk_path')
  153. parser.add_argument('--android_compile_sdk', default="34")
  154. parser.add_argument('--android_min_sdk', default="21")
  155. parser.add_argument('--android_target_sdk', default="34")
  156. parser.add_argument('--java_version', default="17")
  157. parser.add_argument('--ndk_location', default="")
  158. parser.add_argument('--cmake_location', default="")
  159. parser.add_argument('--offline', action="store_true", help="Force Gradle use offline mode")
  160. args = parser.parse_args()
  161. main(args)