detect.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. if TYPE_CHECKING:
  8. from SCons.Script.SConscript import SConsEnvironment
  9. def get_name():
  10. return "Android"
  11. def can_build():
  12. return os.path.exists(get_env_android_sdk_root())
  13. def get_opts():
  14. from SCons.Variables import BoolVariable
  15. return [
  16. ("ANDROID_HOME", "Path to the Android SDK", get_env_android_sdk_root()),
  17. (
  18. "ndk_platform",
  19. 'Target platform (android-<api>, e.g. "android-' + str(get_min_target_api()) + '")',
  20. "android-" + str(get_min_target_api()),
  21. ),
  22. BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False),
  23. BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False),
  24. ]
  25. def get_doc_classes():
  26. return [
  27. "EditorExportPlatformAndroid",
  28. ]
  29. def get_doc_path():
  30. return "doc_classes"
  31. # Return the ANDROID_HOME environment variable.
  32. def get_env_android_sdk_root():
  33. return os.environ.get("ANDROID_HOME", os.environ.get("ANDROID_SDK_ROOT", ""))
  34. def get_min_sdk_version(platform):
  35. return int(platform.split("-")[1])
  36. def get_android_ndk_root(env: "SConsEnvironment"):
  37. return env["ANDROID_HOME"] + "/ndk/" + get_ndk_version()
  38. # This is kept in sync with the value in 'platform/android/java/app/config.gradle'.
  39. def get_ndk_version():
  40. return "23.2.8568313"
  41. # This is kept in sync with the value in 'platform/android/java/app/config.gradle'.
  42. def get_min_target_api():
  43. return 21
  44. def get_flags():
  45. return {
  46. "arch": "arm64",
  47. "target": "template_debug",
  48. "supported": ["mono"],
  49. }
  50. # Check if Android NDK version is installed
  51. # If not, install it.
  52. def install_ndk_if_needed(env: "SConsEnvironment"):
  53. sdk_root = env["ANDROID_HOME"]
  54. if not os.path.exists(get_android_ndk_root(env)):
  55. extension = ".bat" if os.name == "nt" else ""
  56. sdkmanager = sdk_root + "/cmdline-tools/latest/bin/sdkmanager" + extension
  57. if os.path.exists(sdkmanager):
  58. # Install the Android NDK
  59. print("Installing Android NDK...")
  60. ndk_download_args = "ndk;" + get_ndk_version()
  61. subprocess.check_call([sdkmanager, ndk_download_args])
  62. else:
  63. print_error(
  64. f'Cannot find "{sdkmanager}". Please ensure ANDROID_HOME is correct and cmdline-tools'
  65. f' are installed, or install NDK version "{get_ndk_version()}" manually.'
  66. )
  67. sys.exit(255)
  68. env["ANDROID_NDK_ROOT"] = get_android_ndk_root(env)
  69. def configure(env: "SConsEnvironment"):
  70. # Validate arch.
  71. supported_arches = ["x86_32", "x86_64", "arm32", "arm64"]
  72. if env["arch"] not in supported_arches:
  73. print_error(
  74. 'Unsupported CPU architecture "%s" for Android. Supported architectures are: %s.'
  75. % (env["arch"], ", ".join(supported_arches))
  76. )
  77. sys.exit(255)
  78. if get_min_sdk_version(env["ndk_platform"]) < get_min_target_api():
  79. print_warning(
  80. "Minimum supported Android target api is %d. Forcing target api %d."
  81. % (get_min_target_api(), get_min_target_api())
  82. )
  83. env["ndk_platform"] = "android-" + str(get_min_target_api())
  84. install_ndk_if_needed(env)
  85. ndk_root = env["ANDROID_NDK_ROOT"]
  86. # Architecture
  87. if env["arch"] == "arm32":
  88. target_triple = "armv7a-linux-androideabi"
  89. elif env["arch"] == "arm64":
  90. target_triple = "aarch64-linux-android"
  91. elif env["arch"] == "x86_32":
  92. target_triple = "i686-linux-android"
  93. elif env["arch"] == "x86_64":
  94. target_triple = "x86_64-linux-android"
  95. target_option = ["-target", target_triple + str(get_min_sdk_version(env["ndk_platform"]))]
  96. env.Append(ASFLAGS=[target_option, "-c"])
  97. env.Append(CCFLAGS=target_option)
  98. env.Append(LINKFLAGS=target_option)
  99. # LTO
  100. if env["lto"] == "auto": # LTO benefits for Android (size, performance) haven't been clearly established yet.
  101. env["lto"] = "none"
  102. if env["lto"] != "none":
  103. if env["lto"] == "thin":
  104. env.Append(CCFLAGS=["-flto=thin"])
  105. env.Append(LINKFLAGS=["-flto=thin"])
  106. else:
  107. env.Append(CCFLAGS=["-flto"])
  108. env.Append(LINKFLAGS=["-flto"])
  109. # Compiler configuration
  110. env["SHLIBSUFFIX"] = ".so"
  111. if env["PLATFORM"] == "win32":
  112. env.use_windows_spawn_fix()
  113. if sys.platform.startswith("linux"):
  114. host_subpath = "linux-x86_64"
  115. elif sys.platform.startswith("darwin"):
  116. host_subpath = "darwin-x86_64"
  117. elif sys.platform.startswith("win"):
  118. if platform.machine().endswith("64"):
  119. host_subpath = "windows-x86_64"
  120. else:
  121. host_subpath = "windows"
  122. toolchain_path = ndk_root + "/toolchains/llvm/prebuilt/" + host_subpath
  123. compiler_path = toolchain_path + "/bin"
  124. env["CC"] = compiler_path + "/clang"
  125. env["CXX"] = compiler_path + "/clang++"
  126. env["AR"] = compiler_path + "/llvm-ar"
  127. env["RANLIB"] = compiler_path + "/llvm-ranlib"
  128. env["AS"] = compiler_path + "/clang"
  129. env.Append(
  130. CCFLAGS=(
  131. "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
  132. )
  133. )
  134. if get_min_sdk_version(env["ndk_platform"]) >= 24:
  135. env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)])
  136. if env["arch"] == "x86_32":
  137. # The NDK adds this if targeting API < 24, so we can drop it when Godot targets it at least
  138. env.Append(CCFLAGS=["-mstackrealign"])
  139. elif env["arch"] == "arm32":
  140. env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split())
  141. env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"])
  142. env.Append(CPPDEFINES=["__ARM_NEON__"])
  143. elif env["arch"] == "arm64":
  144. env.Append(CCFLAGS=["-mfix-cortex-a53-835769"])
  145. env.Append(CPPDEFINES=["__ARM_ARCH_8A__"])
  146. env.Append(CCFLAGS=["-ffp-contract=off"])
  147. # Link flags
  148. env.Append(LINKFLAGS="-Wl,--gc-sections -Wl,--no-undefined -Wl,-z,now".split())
  149. env.Append(LINKFLAGS="-Wl,-soname,libgodot_android.so")
  150. env.Prepend(CPPPATH=["#platform/android"])
  151. env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"])
  152. env.Append(LIBS=["OpenSLES", "EGL", "android", "log", "z", "dl"])
  153. if env["vulkan"]:
  154. env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
  155. if not env["use_volk"]:
  156. env.Append(LIBS=["vulkan"])
  157. if env["opengl3"]:
  158. env.Append(CPPDEFINES=["GLES3_ENABLED"])
  159. env.Append(LIBS=["GLESv3"])