detect.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import os
  2. import platform
  3. import subprocess
  4. import sys
  5. from typing import TYPE_CHECKING
  6. from methods import print_error, print_warning
  7. from platform_methods import validate_arch
  8. if TYPE_CHECKING:
  9. from SCons.Script.SConscript import SConsEnvironment
  10. def get_name():
  11. return "Android"
  12. def can_build():
  13. return os.path.exists(get_env_android_sdk_root())
  14. def get_tools(env: "SConsEnvironment"):
  15. return ["clang", "clang++", "as", "ar", "link"]
  16. def get_opts():
  17. from SCons.Variables import BoolVariable
  18. return [
  19. ("ANDROID_HOME", "Path to the Android SDK", get_env_android_sdk_root()),
  20. (
  21. "ndk_platform",
  22. 'Target platform (android-<api>, e.g. "android-' + str(get_min_target_api()) + '")',
  23. "android-" + str(get_min_target_api()),
  24. ),
  25. BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False),
  26. BoolVariable(
  27. ("generate_android_binaries", "generate_apk"),
  28. "Generate APK, AAB & AAR binaries after building Android library by calling Gradle",
  29. False,
  30. ),
  31. BoolVariable("swappy", "Use Swappy Frame Pacing library", False),
  32. ]
  33. def get_doc_classes():
  34. return [
  35. "EditorExportPlatformAndroid",
  36. ]
  37. def get_doc_path():
  38. return "doc_classes"
  39. # Return the ANDROID_HOME environment variable.
  40. def get_env_android_sdk_root():
  41. return os.environ.get("ANDROID_HOME", os.environ.get("ANDROID_SDK_ROOT", ""))
  42. def get_min_sdk_version(platform):
  43. return int(platform.split("-")[1])
  44. def get_android_ndk_root(env: "SConsEnvironment"):
  45. return os.path.join(env["ANDROID_HOME"], "ndk", get_ndk_version())
  46. # This is kept in sync with the value in 'platform/android/java/app/config.gradle'.
  47. def get_ndk_version():
  48. return "28.1.13356709"
  49. # This is kept in sync with the value in 'platform/android/java/app/config.gradle'.
  50. def get_min_target_api():
  51. return 24
  52. def get_flags():
  53. return {
  54. "arch": "arm64",
  55. "target": "template_debug",
  56. "supported": ["mono"],
  57. }
  58. # Check if Android NDK version is installed
  59. # If not, install it.
  60. def install_ndk_if_needed(env: "SConsEnvironment"):
  61. sdk_root = env["ANDROID_HOME"]
  62. if not os.path.exists(get_android_ndk_root(env)):
  63. extension = ".bat" if os.name == "nt" else ""
  64. sdkmanager = os.path.join(sdk_root, "cmdline-tools", "latest", "bin", "sdkmanager" + extension)
  65. if os.path.exists(sdkmanager):
  66. # Install the Android NDK
  67. print("Installing Android NDK...")
  68. ndk_download_args = "ndk;" + get_ndk_version()
  69. subprocess.check_call([sdkmanager, ndk_download_args])
  70. else:
  71. print_error(
  72. f'Cannot find "{sdkmanager}". Please ensure ANDROID_HOME is correct and cmdline-tools'
  73. f' are installed, or install NDK version "{get_ndk_version()}" manually.'
  74. )
  75. sys.exit(255)
  76. env["ANDROID_NDK_ROOT"] = get_android_ndk_root(env)
  77. def detect_swappy():
  78. archs = ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"]
  79. has_swappy = True
  80. for arch in archs:
  81. if not os.path.isfile(f"thirdparty/swappy-frame-pacing/{arch}/libswappy_static.a"):
  82. has_swappy = False
  83. return has_swappy
  84. def configure(env: "SConsEnvironment"):
  85. # Validate arch.
  86. supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
  87. validate_arch(env["arch"], get_name(), supported_arches)
  88. if get_min_sdk_version(env["ndk_platform"]) < get_min_target_api():
  89. print_warning(
  90. "Minimum supported Android target api is %d. Forcing target api %d."
  91. % (get_min_target_api(), get_min_target_api())
  92. )
  93. env["ndk_platform"] = "android-" + str(get_min_target_api())
  94. install_ndk_if_needed(env)
  95. ndk_root = env["ANDROID_NDK_ROOT"]
  96. # Architecture
  97. if env["arch"] == "arm32":
  98. target_triple = "armv7a-linux-androideabi"
  99. elif env["arch"] == "arm64":
  100. target_triple = "aarch64-linux-android"
  101. elif env["arch"] == "x86_32":
  102. target_triple = "i686-linux-android"
  103. elif env["arch"] == "x86_64":
  104. target_triple = "x86_64-linux-android"
  105. target_option = ["-target", target_triple + str(get_min_sdk_version(env["ndk_platform"]))]
  106. env.Append(ASFLAGS=[target_option, "-c"])
  107. env.Append(CCFLAGS=target_option)
  108. env.Append(LINKFLAGS=target_option)
  109. # LTO
  110. if env["lto"] == "auto": # LTO benefits for Android (size, performance) haven't been clearly established yet.
  111. env["lto"] = "none"
  112. if env["lto"] != "none":
  113. if env["lto"] == "thin":
  114. env.Append(CCFLAGS=["-flto=thin"])
  115. env.Append(LINKFLAGS=["-flto=thin"])
  116. else:
  117. env.Append(CCFLAGS=["-flto"])
  118. env.Append(LINKFLAGS=["-flto"])
  119. # Compiler configuration
  120. env["SHLIBSUFFIX"] = ".so"
  121. if env["PLATFORM"] == "win32":
  122. env.use_windows_spawn_fix()
  123. if sys.platform.startswith("linux"):
  124. host_subpath = "linux-x86_64"
  125. elif sys.platform.startswith("darwin"):
  126. host_subpath = "darwin-x86_64"
  127. elif sys.platform.startswith("win"):
  128. if platform.machine().endswith("64"):
  129. host_subpath = "windows-x86_64"
  130. else:
  131. host_subpath = "windows"
  132. toolchain_path = os.path.join(ndk_root, "toolchains", "llvm", "prebuilt", host_subpath)
  133. compiler_path = os.path.join(toolchain_path, "bin")
  134. env["CC"] = os.path.join(compiler_path, "clang")
  135. env["CXX"] = os.path.join(compiler_path, "clang++")
  136. env["AR"] = os.path.join(compiler_path, "llvm-ar")
  137. env["RANLIB"] = os.path.join(compiler_path, "llvm-ranlib")
  138. env["AS"] = os.path.join(compiler_path, "clang")
  139. env.Append(
  140. CCFLAGS=(["-fpic", "-ffunction-sections", "-funwind-tables", "-fstack-protector-strong", "-fvisibility=hidden"])
  141. )
  142. has_swappy = detect_swappy()
  143. if not has_swappy:
  144. print_warning(
  145. "Swappy Frame Pacing not detected! It is strongly recommended you run `python misc/scripts/install_swappy_android.py` to download and install Swappy before compiling.\n"
  146. + "Without Swappy, Godot apps on Android will inevitably suffer stutter and struggle to keep a consistent framerate. Although Swappy cannot guarantee your app will be stutter-free, not having Swappy will guarantee there will be stutter even on the best phones and the most simple of scenes."
  147. )
  148. if env["swappy"]:
  149. print_error("Use build option `swappy=no` to ignore missing Swappy dependency and build without it.")
  150. sys.exit(255)
  151. if get_min_sdk_version(env["ndk_platform"]) >= 24:
  152. env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)])
  153. if env["arch"] == "x86_32":
  154. if has_swappy:
  155. env.Append(LIBPATH=["#thirdparty/swappy-frame-pacing/x86"])
  156. elif env["arch"] == "x86_64":
  157. if has_swappy:
  158. env.Append(LIBPATH=["#thirdparty/swappy-frame-pacing/x86_64"])
  159. elif env["arch"] == "arm32":
  160. env.Append(CCFLAGS=["-march=armv7-a", "-mfloat-abi=softfp"])
  161. env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"])
  162. env.Append(CPPDEFINES=["__ARM_NEON__"])
  163. if has_swappy:
  164. env.Append(LIBPATH=["#thirdparty/swappy-frame-pacing/armeabi-v7a"])
  165. elif env["arch"] == "arm64":
  166. env.Append(CCFLAGS=["-mfix-cortex-a53-835769"])
  167. env.Append(CPPDEFINES=["__ARM_ARCH_8A__"])
  168. if has_swappy:
  169. env.Append(LIBPATH=["#thirdparty/swappy-frame-pacing/arm64-v8a"])
  170. env.Append(CCFLAGS=["-ffp-contract=off"])
  171. # Link flags
  172. env.Append(LINKFLAGS=["-Wl,--gc-sections", "-Wl,--no-undefined", "-Wl,-z,now"])
  173. env.Append(LINKFLAGS=["-Wl,--build-id"])
  174. env.Append(LINKFLAGS=["-Wl,-soname,libgodot_android.so"])
  175. env.Prepend(CPPPATH=["#platform/android"])
  176. env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"])
  177. env.Append(LIBS=["OpenSLES", "EGL", "android", "log", "z", "dl"])
  178. if env["vulkan"]:
  179. env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
  180. if has_swappy:
  181. env.Append(CPPDEFINES=["SWAPPY_FRAME_PACING_ENABLED"])
  182. env.Append(LIBS=["swappy_static"])
  183. if not env["use_volk"]:
  184. env.Append(LIBS=["vulkan"])
  185. if env["opengl3"]:
  186. env.Append(CPPDEFINES=["GLES3_ENABLED"])
  187. env.Append(LIBS=["GLESv3"])