build_static_aar.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/env python
  2. import argparse
  3. import json
  4. from os import path
  5. import os
  6. import shutil
  7. import subprocess
  8. from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version, get_ndk_version
  9. ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template")
  10. TEMP_DIR = "build_static"
  11. ANDROID_PROJECT_DIR = path.join(TEMP_DIR, "AndroidProject")
  12. COMPILED_AAR_PATH_1 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/OpenCV-release.aar") # original package name
  13. COMPILED_AAR_PATH_2 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/opencv-release.aar") # lower case package name
  14. AAR_UNZIPPED_DIR = path.join(TEMP_DIR, "aar_unzipped")
  15. FINAL_AAR_PATH_TEMPLATE = "outputs/opencv_static_<OPENCV_VERSION>.aar"
  16. FINAL_REPO_PATH = "outputs/maven_repo"
  17. MAVEN_PACKAGE_NAME = "opencv-static"
  18. def get_list_of_opencv_libs(sdk_dir):
  19. files = os.listdir(path.join(sdk_dir, "sdk/native/staticlibs/arm64-v8a"))
  20. libs = [f[3:-2] for f in files if f[:3] == "lib" and f[-2:] == ".a"]
  21. return libs
  22. def get_list_of_3rdparty_libs(sdk_dir, abis):
  23. libs = []
  24. for abi in abis:
  25. files = os.listdir(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi))
  26. cur_libs = [f[3:-2] for f in files if f[:3] == "lib" and f[-2:] == ".a"]
  27. for lib in cur_libs:
  28. if lib not in libs:
  29. libs.append(lib)
  30. return libs
  31. def add_printing_linked_libs(sdk_dir, opencv_libs):
  32. """
  33. Modifies CMakeLists.txt file in Android project, so it prints linked libraries for each OpenCV library"
  34. """
  35. sdk_jni_dir = sdk_dir + "/sdk/native/jni"
  36. with open(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"), "a") as f:
  37. f.write('\nset(OpenCV_DIR "' + sdk_jni_dir + '")\n')
  38. f.write('find_package(OpenCV REQUIRED)\n')
  39. for lib_name in opencv_libs:
  40. output_filename_prefix = "linkedlibs." + lib_name + "."
  41. f.write('get_target_property(OUT "' + lib_name + '" INTERFACE_LINK_LIBRARIES)\n')
  42. f.write('file(WRITE "' + output_filename_prefix + '${ANDROID_ABI}.txt" "${OUT}")\n')
  43. def read_linked_libs(lib_name, abis):
  44. """
  45. Reads linked libs for each OpenCV library from files, that was generated by gradle. See add_printing_linked_libs()
  46. """
  47. deps_lists = []
  48. for abi in abis:
  49. with open(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp", f"linkedlibs.{lib_name}.{abi}.txt")) as f:
  50. text = f.read()
  51. linked_libs = text.split(";")
  52. linked_libs = [x.replace("$<LINK_ONLY:", "").replace(">", "") for x in linked_libs]
  53. deps_lists.append(linked_libs)
  54. return merge_dependencies_lists(deps_lists)
  55. def merge_dependencies_lists(deps_lists):
  56. """
  57. One library may have different dependencies for different ABIS.
  58. We need to merge them into one list with all the dependencies preserving the order.
  59. """
  60. result = []
  61. for d_list in deps_lists:
  62. for i in range(len(d_list)):
  63. if d_list[i] not in result:
  64. if i == 0:
  65. result.append(d_list[i])
  66. else:
  67. index = result.index(d_list[i-1])
  68. result = result[:index + 1] + [d_list[i]] + result[index + 1:]
  69. return result
  70. def convert_deps_list_to_prefab(linked_libs, opencv_libs, external_libs):
  71. """
  72. Converting list of dependencies into prefab format.
  73. """
  74. prefab_linked_libs = []
  75. for lib in linked_libs:
  76. if (lib in opencv_libs) or (lib in external_libs):
  77. prefab_linked_libs.append(":" + lib)
  78. elif (lib[:3] == "lib" and lib[3:] in external_libs):
  79. prefab_linked_libs.append(":" + lib[3:])
  80. elif lib == "ocv.3rdparty.android_mediandk":
  81. prefab_linked_libs += ["-landroid", "-llog", "-lmediandk"]
  82. print("Warning: manualy handled ocv.3rdparty.android_mediandk dependency")
  83. elif lib == "ocv.3rdparty.flatbuffers":
  84. print("Warning: manualy handled ocv.3rdparty.flatbuffers dependency")
  85. elif lib.startswith("ocv.3rdparty"):
  86. raise Exception("Unknown lib " + lib)
  87. else:
  88. prefab_linked_libs.append("-l" + lib)
  89. return prefab_linked_libs
  90. def main(args):
  91. opencv_version = get_opencv_version(args.opencv_sdk_path)
  92. ndk_version = get_ndk_version(args.ndk_location)
  93. print("Detected ndk_version:", ndk_version)
  94. abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs"))
  95. final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("<OPENCV_VERSION>", opencv_version)
  96. sdk_dir = args.opencv_sdk_path
  97. print("Removing data from previous runs...")
  98. cleanup([TEMP_DIR, final_aar_path, path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)])
  99. print("Preparing Android project...")
  100. # ANDROID_PROJECT_TEMPLATE_DIR contains an Android project template that creates AAR
  101. shutil.copytree(ANDROID_PROJECT_TEMPLATE_DIR, ANDROID_PROJECT_DIR)
  102. # Configuring the Android project to static C++ libs version
  103. fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle.template"),
  104. path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle"),
  105. {"LIB_NAME": "templib",
  106. "LIB_TYPE": "c++_static",
  107. "PACKAGE_NAME": MAVEN_PACKAGE_NAME,
  108. "OPENCV_VERSION": opencv_version,
  109. "NDK_VERSION": ndk_version,
  110. "COMPILE_SDK": args.android_compile_sdk,
  111. "MIN_SDK": args.android_min_sdk,
  112. "TARGET_SDK": args.android_target_sdk,
  113. "ABI_FILTERS": ", ".join(['"' + x + '"' for x in abis]),
  114. "JAVA_VERSION": args.java_version,
  115. })
  116. fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt.template"),
  117. path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"),
  118. {"LIB_NAME": "templib", "LIB_TYPE": "STATIC"})
  119. local_props = ""
  120. if args.ndk_location:
  121. local_props += "ndk.dir=" + args.ndk_location + "\n"
  122. if args.cmake_location:
  123. local_props += "cmake.dir=" + args.cmake_location + "\n"
  124. if local_props:
  125. with open(path.join(ANDROID_PROJECT_DIR, "local.properties"), "wt") as f:
  126. f.write(local_props)
  127. opencv_libs = get_list_of_opencv_libs(sdk_dir)
  128. external_libs = get_list_of_3rdparty_libs(sdk_dir, abis)
  129. add_printing_linked_libs(sdk_dir, opencv_libs)
  130. print("Running gradle assembleRelease...")
  131. cmd = ["./gradlew", "assembleRelease"]
  132. if args.offline:
  133. cmd = cmd + ["--offline"]
  134. # Running gradle to build the Android project
  135. subprocess.run(cmd, shell=False, cwd=ANDROID_PROJECT_DIR, check=True)
  136. # The created AAR package contains only one empty libtemplib.a library.
  137. # We need to add OpenCV libraries manually.
  138. # AAR package is just a zip archive
  139. complied_aar_path = get_compiled_aar_path(COMPILED_AAR_PATH_1, COMPILED_AAR_PATH_2) # two possible paths
  140. shutil.unpack_archive(complied_aar_path, AAR_UNZIPPED_DIR, "zip")
  141. print("Adding libs to AAR...")
  142. # Copying 3rdparty libs from SDK into the AAR
  143. for lib in external_libs:
  144. for abi in abis:
  145. os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi))
  146. if path.exists(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi, "lib" + lib + ".a")):
  147. shutil.copy(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi, "lib" + lib + ".a"),
  148. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a"))
  149. else:
  150. # One OpenCV library may have different dependency lists for different ABIs, but we can write only one
  151. # full dependency list for all ABIs. So we just add empty .a library if this ABI doesn't have this dependency.
  152. shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi, "libtemplib.a"),
  153. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a"))
  154. shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi + "/abi.json"),
  155. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi + "/abi.json"))
  156. shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/module.json"),
  157. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/module.json"))
  158. # Copying OpenV libs from SDK into the AAR
  159. for lib in opencv_libs:
  160. for abi in abis:
  161. os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi))
  162. shutil.copy(path.join(sdk_dir, "sdk/native/staticlibs/" + abi, "lib" + lib + ".a"),
  163. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a"))
  164. shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi + "/abi.json"),
  165. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi + "/abi.json"))
  166. os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2"))
  167. shutil.copy(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + lib.replace("opencv_", "") + ".hpp"),
  168. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2/" + lib.replace("opencv_", "") + ".hpp"))
  169. shutil.copytree(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + lib.replace("opencv_", "")),
  170. path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2/" + lib.replace("opencv_", "")))
  171. # Adding dependencies list
  172. module_json_text = {
  173. "export_libraries": convert_deps_list_to_prefab(read_linked_libs(lib, abis), opencv_libs, external_libs),
  174. "android": {},
  175. }
  176. with open(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/module.json"), "w") as f:
  177. json.dump(module_json_text, f)
  178. for h_file in ("cvconfig.h", "opencv.hpp", "opencv_modules.hpp"):
  179. shutil.copy(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + h_file),
  180. path.join(AAR_UNZIPPED_DIR, "prefab/modules/opencv_core/include/opencv2/" + h_file))
  181. shutil.rmtree(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib"))
  182. # Creating final AAR zip archive
  183. os.makedirs("outputs", exist_ok=True)
  184. shutil.make_archive(final_aar_path, "zip", AAR_UNZIPPED_DIR, ".")
  185. os.rename(final_aar_path + ".zip", final_aar_path)
  186. print("Creating local maven repo...")
  187. shutil.copy(final_aar_path, path.join(ANDROID_PROJECT_DIR, "OpenCV/opencv-release.aar"))
  188. print("Creating a maven repo from project sources (with sources jar and javadoc jar)...")
  189. cmd = ["./gradlew", "publishReleasePublicationToMyrepoRepository"]
  190. if args.offline:
  191. cmd = cmd + ["--offline"]
  192. subprocess.run(cmd, shell=False, cwd=ANDROID_PROJECT_DIR, check=True)
  193. os.makedirs(path.join(FINAL_REPO_PATH, "org/opencv"), exist_ok=True)
  194. shutil.move(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME),
  195. path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME))
  196. print("Creating a maven repo from modified AAR (with cpp libraries)...")
  197. cmd = ["./gradlew", "publishModifiedPublicationToMyrepoRepository"]
  198. if args.offline:
  199. cmd = cmd + ["--offline"]
  200. subprocess.run(cmd, shell=False, cwd=ANDROID_PROJECT_DIR, check=True)
  201. # Replacing AAR from the first maven repo with modified AAR from the second maven repo
  202. shutil.copytree(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME),
  203. path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME),
  204. dirs_exist_ok=True)
  205. print("Done")
  206. if __name__ == "__main__":
  207. parser = argparse.ArgumentParser(description="Builds AAR with static C++ libs from OpenCV SDK")
  208. parser.add_argument('opencv_sdk_path')
  209. parser.add_argument('--android_compile_sdk', default="34")
  210. parser.add_argument('--android_min_sdk', default="21")
  211. parser.add_argument('--android_target_sdk', default="34")
  212. parser.add_argument('--java_version', default="1_8")
  213. parser.add_argument('--ndk_location', default="")
  214. parser.add_argument('--cmake_location', default="")
  215. parser.add_argument('--offline', action="store_true", help="Force Gradle use offline mode")
  216. args = parser.parse_args()
  217. main(args)