SConstruct 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. #!/usr/bin/env python
  2. import os
  3. import sys
  4. import subprocess
  5. from binding_generator import scons_generate_bindings, scons_emit_files
  6. if sys.version_info < (3,):
  7. def decode_utf8(x):
  8. return x
  9. else:
  10. import codecs
  11. def decode_utf8(x):
  12. return codecs.utf_8_decode(x)[0]
  13. # Workaround for MinGW. See:
  14. # http://www.scons.org/wiki/LongCmdLinesOnWin32
  15. if os.name == "nt":
  16. import subprocess
  17. def mySubProcess(cmdline, env):
  18. # print "SPAWNED : " + cmdline
  19. startupinfo = subprocess.STARTUPINFO()
  20. startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  21. proc = subprocess.Popen(
  22. cmdline,
  23. stdin=subprocess.PIPE,
  24. stdout=subprocess.PIPE,
  25. stderr=subprocess.PIPE,
  26. startupinfo=startupinfo,
  27. shell=False,
  28. env=env,
  29. )
  30. data, err = proc.communicate()
  31. rv = proc.wait()
  32. if rv:
  33. print("=====")
  34. print(err.decode("utf-8"))
  35. print("=====")
  36. return rv
  37. def mySpawn(sh, escape, cmd, args, env):
  38. newargs = " ".join(args[1:])
  39. cmdline = cmd + " " + newargs
  40. rv = 0
  41. if len(cmdline) > 32000 and cmd.endswith("ar"):
  42. cmdline = cmd + " " + args[1] + " " + args[2] + " "
  43. for i in range(3, len(args)):
  44. rv = mySubProcess(cmdline + args[i], env)
  45. if rv:
  46. break
  47. else:
  48. rv = mySubProcess(cmdline, env)
  49. return rv
  50. def add_sources(sources, dir, extension):
  51. for f in os.listdir(dir):
  52. if f.endswith("." + extension):
  53. sources.append(dir + "/" + f)
  54. # Try to detect the host platform automatically.
  55. # This is used if no `platform` argument is passed
  56. if sys.platform.startswith("linux"):
  57. host_platform = "linux"
  58. elif sys.platform.startswith("freebsd"):
  59. host_platform = "freebsd"
  60. elif sys.platform == "darwin":
  61. host_platform = "osx"
  62. elif sys.platform == "win32" or sys.platform == "msys":
  63. host_platform = "windows"
  64. else:
  65. raise ValueError("Could not detect platform automatically, please specify with platform=<platform>")
  66. env = Environment(ENV=os.environ)
  67. is64 = sys.maxsize > 2**32
  68. if (
  69. env["TARGET_ARCH"] == "amd64"
  70. or env["TARGET_ARCH"] == "emt64"
  71. or env["TARGET_ARCH"] == "x86_64"
  72. or env["TARGET_ARCH"] == "arm64-v8a"
  73. ):
  74. is64 = True
  75. opts = Variables([], ARGUMENTS)
  76. opts.Add(
  77. EnumVariable(
  78. "platform",
  79. "Target platform",
  80. host_platform,
  81. allowed_values=("linux", "freebsd", "osx", "windows", "android", "ios", "javascript"),
  82. ignorecase=2,
  83. )
  84. )
  85. opts.Add(EnumVariable("bits", "Target platform bits", "64" if is64 else "32", ("32", "64")))
  86. opts.Add(BoolVariable("use_llvm", "Use the LLVM compiler - only effective when targeting Linux or FreeBSD", False))
  87. opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False))
  88. # Must be the same setting as used for cpp_bindings
  89. opts.Add(EnumVariable("target", "Compilation target", "debug", allowed_values=("debug", "release"), ignorecase=2))
  90. opts.Add(
  91. PathVariable(
  92. "headers_dir", "Path to the directory containing Godot headers", "godot-headers", PathVariable.PathIsDir
  93. )
  94. )
  95. opts.Add(PathVariable("custom_api_file", "Path to a custom JSON API file", None, PathVariable.PathIsFile))
  96. opts.Add(
  97. BoolVariable("generate_bindings", "Force GDExtension API bindings generation. Auto-detected by default.", False)
  98. )
  99. opts.Add(EnumVariable("android_arch", "Target Android architecture", "armv7", ["armv7", "arm64v8", "x86", "x86_64"]))
  100. opts.Add("macos_deployment_target", "macOS deployment target", "default")
  101. opts.Add("macos_sdk_path", "macOS SDK path", "")
  102. opts.Add(EnumVariable("macos_arch", "Target macOS architecture", "universal", ["universal", "x86_64", "arm64"]))
  103. opts.Add(EnumVariable("ios_arch", "Target iOS architecture", "arm64", ["universal", "arm64", "x86_64"]))
  104. opts.Add(BoolVariable("ios_simulator", "Target iOS Simulator", False))
  105. opts.Add(
  106. "IPHONEPATH",
  107. "Path to iPhone toolchain",
  108. "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
  109. )
  110. opts.Add(
  111. "android_api_level",
  112. "Target Android API level",
  113. "18" if ARGUMENTS.get("android_arch", "armv7") in ["armv7", "x86"] else "21",
  114. )
  115. opts.Add(
  116. "ANDROID_NDK_ROOT",
  117. "Path to your Android NDK installation. By default, uses ANDROID_NDK_ROOT from your defined environment variables.",
  118. os.environ.get("ANDROID_NDK_ROOT", None),
  119. )
  120. opts.Add(BoolVariable("generate_template_get_node", "Generate a template version of the Node class's get_node.", True))
  121. opts.Add(BoolVariable("build_library", "Build the godot-cpp library.", True))
  122. opts.Add(EnumVariable("float", "Floating-point precision", "32", ("32", "64")))
  123. opts.Update(env)
  124. Help(opts.GenerateHelpText(env))
  125. # Detect and print a warning listing unknown SCons variables to ease troubleshooting.
  126. unknown = opts.UnknownVariables()
  127. if unknown:
  128. print("WARNING: Unknown SCons variables were passed and will be ignored:")
  129. for item in unknown.items():
  130. print(" " + item[0] + "=" + item[1])
  131. # This makes sure to keep the session environment variables on Windows.
  132. # This way, you can run SCons in a Visual Studio 2017 prompt and it will find
  133. # all the required tools
  134. if host_platform == "windows" and env["platform"] != "android":
  135. if env["bits"] == "64":
  136. env = Environment(TARGET_ARCH="amd64")
  137. elif env["bits"] == "32":
  138. env = Environment(TARGET_ARCH="x86")
  139. opts.Update(env)
  140. # Require C++17
  141. if host_platform == "windows" and env["platform"] == "windows" and not env["use_mingw"]:
  142. # MSVC
  143. env.Append(CXXFLAGS=["/std:c++17"])
  144. else:
  145. env.Append(CXXFLAGS=["-std=c++17"])
  146. if env["target"] == "debug":
  147. env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_METHODS_ENABLED"])
  148. if env["float"] == "64":
  149. env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"])
  150. if env["platform"] == "linux" or env["platform"] == "freebsd":
  151. if env["use_llvm"]:
  152. env["CXX"] = "clang++"
  153. env.Append(CCFLAGS=["-fPIC", "-Wwrite-strings"])
  154. env.Append(LINKFLAGS=["-Wl,-R,'$$ORIGIN'"])
  155. if env["target"] == "debug":
  156. env.Append(CCFLAGS=["-Og", "-g"])
  157. elif env["target"] == "release":
  158. env.Append(CCFLAGS=["-O3"])
  159. if env["bits"] == "64":
  160. env.Append(CCFLAGS=["-m64"])
  161. env.Append(LINKFLAGS=["-m64"])
  162. elif env["bits"] == "32":
  163. env.Append(CCFLAGS=["-m32"])
  164. env.Append(LINKFLAGS=["-m32"])
  165. elif env["platform"] == "osx":
  166. # Use Clang on macOS by default
  167. env["CXX"] = "clang++"
  168. if env["bits"] == "32":
  169. raise ValueError("Only 64-bit builds are supported for the macOS target.")
  170. if env["macos_arch"] == "universal":
  171. env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"])
  172. env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"])
  173. else:
  174. env.Append(LINKFLAGS=["-arch", env["macos_arch"]])
  175. env.Append(CCFLAGS=["-arch", env["macos_arch"]])
  176. if env["macos_deployment_target"] != "default":
  177. env.Append(CCFLAGS=["-mmacosx-version-min=" + env["macos_deployment_target"]])
  178. env.Append(LINKFLAGS=["-mmacosx-version-min=" + env["macos_deployment_target"]])
  179. if env["macos_sdk_path"]:
  180. env.Append(CCFLAGS=["-isysroot", env["macos_sdk_path"]])
  181. env.Append(LINKFLAGS=["-isysroot", env["macos_sdk_path"]])
  182. env.Append(
  183. LINKFLAGS=[
  184. "-framework",
  185. "Cocoa",
  186. "-Wl,-undefined,dynamic_lookup",
  187. ]
  188. )
  189. if env["target"] == "debug":
  190. env.Append(CCFLAGS=["-Og", "-g"])
  191. elif env["target"] == "release":
  192. env.Append(CCFLAGS=["-O3"])
  193. elif env["platform"] == "ios":
  194. if env["ios_simulator"]:
  195. sdk_name = "iphonesimulator"
  196. env.Append(CCFLAGS=["-mios-simulator-version-min=10.0"])
  197. else:
  198. sdk_name = "iphoneos"
  199. env.Append(CCFLAGS=["-miphoneos-version-min=10.0"])
  200. try:
  201. sdk_path = decode_utf8(subprocess.check_output(["xcrun", "--sdk", sdk_name, "--show-sdk-path"]).strip())
  202. except (subprocess.CalledProcessError, OSError):
  203. raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
  204. compiler_path = env["IPHONEPATH"] + "/usr/bin/"
  205. env["ENV"]["PATH"] = env["IPHONEPATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
  206. env["CC"] = compiler_path + "clang"
  207. env["CXX"] = compiler_path + "clang++"
  208. env["AR"] = compiler_path + "ar"
  209. env["RANLIB"] = compiler_path + "ranlib"
  210. env["SHLIBSUFFIX"] = ".dylib"
  211. if env["ios_arch"] == "universal":
  212. if env["ios_simulator"]:
  213. env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"])
  214. env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"])
  215. else:
  216. env.Append(LINKFLAGS=["-arch", "arm64"])
  217. env.Append(CCFLAGS=["-arch", "arm64"])
  218. else:
  219. env.Append(LINKFLAGS=["-arch", env["ios_arch"]])
  220. env.Append(CCFLAGS=["-arch", env["ios_arch"]])
  221. env.Append(CCFLAGS=["-isysroot", sdk_path])
  222. env.Append(LINKFLAGS=["-isysroot", sdk_path, "-F" + sdk_path])
  223. if env["target"] == "debug":
  224. env.Append(CCFLAGS=["-Og", "-g"])
  225. elif env["target"] == "release":
  226. env.Append(CCFLAGS=["-O3"])
  227. elif env["platform"] == "windows":
  228. if host_platform == "windows" and not env["use_mingw"]:
  229. # MSVC
  230. env.Append(CPPDEFINES=["TYPED_METHOD_BIND"])
  231. env.Append(LINKFLAGS=["/WX"])
  232. if env["target"] == "debug":
  233. env.Append(CCFLAGS=["/Z7", "/Od", "/EHsc", "/D_DEBUG", "/MDd"])
  234. elif env["target"] == "release":
  235. env.Append(CCFLAGS=["/O2", "/EHsc", "/DNDEBUG", "/MD"])
  236. elif host_platform == "linux" or host_platform == "freebsd" or host_platform == "osx":
  237. # Cross-compilation using MinGW
  238. if env["bits"] == "64":
  239. env["CXX"] = "x86_64-w64-mingw32-g++"
  240. env["AR"] = "x86_64-w64-mingw32-ar"
  241. env["RANLIB"] = "x86_64-w64-mingw32-ranlib"
  242. env["LINK"] = "x86_64-w64-mingw32-g++"
  243. elif env["bits"] == "32":
  244. env["CXX"] = "i686-w64-mingw32-g++"
  245. env["AR"] = "i686-w64-mingw32-ar"
  246. env["RANLIB"] = "i686-w64-mingw32-ranlib"
  247. env["LINK"] = "i686-w64-mingw32-g++"
  248. elif host_platform == "windows" and env["use_mingw"]:
  249. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  250. env = Environment(ENV=os.environ, tools=["mingw"])
  251. opts.Update(env)
  252. # Still need to use C++17.
  253. env.Append(CCFLAGS=["-std=c++17"])
  254. # Don't want lib prefixes
  255. env["IMPLIBPREFIX"] = ""
  256. env["SHLIBPREFIX"] = ""
  257. # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
  258. env["SPAWN"] = mySpawn
  259. env.Replace(ARFLAGS=["q"])
  260. # Native or cross-compilation using MinGW
  261. if host_platform == "linux" or host_platform == "freebsd" or host_platform == "osx" or env["use_mingw"]:
  262. # These options are for a release build even using target=debug
  263. env.Append(CCFLAGS=["-O3", "-Wwrite-strings"])
  264. env.Append(
  265. LINKFLAGS=[
  266. "--static",
  267. "-Wl,--no-undefined",
  268. "-static-libgcc",
  269. "-static-libstdc++",
  270. ]
  271. )
  272. elif env["platform"] == "android":
  273. if host_platform == "windows":
  274. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  275. env = Environment(ENV=os.environ, tools=["mingw"])
  276. opts.Update(env)
  277. # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
  278. env["SPAWN"] = mySpawn
  279. env.Replace(ARFLAGS=["q"])
  280. # Verify NDK root
  281. if not "ANDROID_NDK_ROOT" in env:
  282. raise ValueError(
  283. "To build for Android, ANDROID_NDK_ROOT must be defined. Please set ANDROID_NDK_ROOT to the root folder of your Android NDK installation."
  284. )
  285. # Validate API level
  286. api_level = int(env["android_api_level"])
  287. if env["android_arch"] in ["x86_64", "arm64v8"] and api_level < 21:
  288. print("WARN: 64-bit Android architectures require an API level of at least 21; setting android_api_level=21")
  289. env["android_api_level"] = "21"
  290. api_level = 21
  291. # Setup toolchain
  292. toolchain = env["ANDROID_NDK_ROOT"] + "/toolchains/llvm/prebuilt/"
  293. if host_platform == "windows":
  294. toolchain += "windows"
  295. import platform as pltfm
  296. if pltfm.machine().endswith("64"):
  297. toolchain += "-x86_64"
  298. elif host_platform == "linux":
  299. toolchain += "linux-x86_64"
  300. elif host_platform == "osx":
  301. toolchain += "darwin-x86_64"
  302. env.PrependENVPath("PATH", toolchain + "/bin") # This does nothing half of the time, but we'll put it here anyways
  303. # Get architecture info
  304. arch_info_table = {
  305. "armv7": {
  306. "march": "armv7-a",
  307. "target": "armv7a-linux-androideabi",
  308. "tool_path": "arm-linux-androideabi",
  309. "compiler_path": "armv7a-linux-androideabi",
  310. "ccflags": ["-mfpu=neon"],
  311. },
  312. "arm64v8": {
  313. "march": "armv8-a",
  314. "target": "aarch64-linux-android",
  315. "tool_path": "aarch64-linux-android",
  316. "compiler_path": "aarch64-linux-android",
  317. "ccflags": [],
  318. },
  319. "x86": {
  320. "march": "i686",
  321. "target": "i686-linux-android",
  322. "tool_path": "i686-linux-android",
  323. "compiler_path": "i686-linux-android",
  324. "ccflags": ["-mstackrealign"],
  325. },
  326. "x86_64": {
  327. "march": "x86-64",
  328. "target": "x86_64-linux-android",
  329. "tool_path": "x86_64-linux-android",
  330. "compiler_path": "x86_64-linux-android",
  331. "ccflags": [],
  332. },
  333. }
  334. arch_info = arch_info_table[env["android_arch"]]
  335. # Setup tools
  336. env["CC"] = toolchain + "/bin/clang"
  337. env["CXX"] = toolchain + "/bin/clang++"
  338. env["AR"] = toolchain + "/bin/" + arch_info["tool_path"] + "-ar"
  339. env["SHLIBSUFFIX"] = ".so"
  340. env.Append(
  341. CCFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"], "-fPIC"]
  342. ) # , '-fPIE', '-fno-addrsig', '-Oz'])
  343. env.Append(CCFLAGS=arch_info["ccflags"])
  344. env.Append(LINKFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"]])
  345. if env["target"] == "debug":
  346. env.Append(CCFLAGS=["-Og", "-g"])
  347. elif env["target"] == "release":
  348. env.Append(CCFLAGS=["-O3"])
  349. elif env["platform"] == "javascript":
  350. if host_platform == "windows":
  351. env = Environment(ENV=os.environ, tools=["cc", "c++", "ar", "link", "textfile", "zip"])
  352. opts.Update(env)
  353. else:
  354. env["ENV"] = os.environ
  355. env["CC"] = "emcc"
  356. env["CXX"] = "em++"
  357. env["AR"] = "emar"
  358. env["RANLIB"] = "emranlib"
  359. env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
  360. env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
  361. env["SHOBJSUFFIX"] = ".bc"
  362. env["SHLIBSUFFIX"] = ".wasm"
  363. # Use TempFileMunge since some AR invocations are too long for cmd.exe.
  364. # Use POSIX-style paths, required with TempFileMunge.
  365. env["ARCOM_POSIX"] = env["ARCOM"].replace("$TARGET", "$TARGET.posix").replace("$SOURCES", "$SOURCES.posix")
  366. env["ARCOM"] = "${TEMPFILE(ARCOM_POSIX)}"
  367. # All intermediate files are just LLVM bitcode.
  368. env["OBJPREFIX"] = ""
  369. env["OBJSUFFIX"] = ".bc"
  370. env["PROGPREFIX"] = ""
  371. # Program() output consists of multiple files, so specify suffixes manually at builder.
  372. env["PROGSUFFIX"] = ""
  373. env["LIBPREFIX"] = "lib"
  374. env["LIBSUFFIX"] = ".a"
  375. env["LIBPREFIXES"] = ["$LIBPREFIX"]
  376. env["LIBSUFFIXES"] = ["$LIBSUFFIX"]
  377. env.Replace(SHLINKFLAGS="$LINKFLAGS")
  378. env.Replace(SHLINKFLAGS="$LINKFLAGS")
  379. if env["target"] == "debug":
  380. env.Append(CCFLAGS=["-O0", "-g"])
  381. elif env["target"] == "release":
  382. env.Append(CCFLAGS=["-O3"])
  383. # Generate bindings
  384. env.Append(BUILDERS={"GenerateBindings": Builder(action=scons_generate_bindings, emitter=scons_emit_files)})
  385. json_api_file = ""
  386. if "custom_api_file" in env:
  387. json_api_file = env["custom_api_file"]
  388. else:
  389. json_api_file = os.path.join(os.getcwd(), env["headers_dir"], "extension_api.json")
  390. bindings = env.GenerateBindings(
  391. env.Dir("."), [json_api_file, os.path.join(env["headers_dir"], "godot", "gdnative_interface.h")]
  392. )
  393. # Forces bindings regeneration.
  394. if env["generate_bindings"]:
  395. AlwaysBuild(bindings)
  396. # Includes
  397. env.Append(CPPPATH=[[env.Dir(d) for d in [env["headers_dir"], "include", os.path.join("gen", "include")]]])
  398. # Sources to compile
  399. sources = []
  400. add_sources(sources, "src", "cpp")
  401. add_sources(sources, "src/classes", "cpp")
  402. add_sources(sources, "src/core", "cpp")
  403. add_sources(sources, "src/variant", "cpp")
  404. sources.extend([f for f in bindings if str(f).endswith(".cpp")])
  405. env["arch_suffix"] = env["bits"]
  406. if env["platform"] == "android":
  407. env["arch_suffix"] = env["android_arch"]
  408. elif env["platform"] == "ios":
  409. env["arch_suffix"] = env["ios_arch"]
  410. if env["ios_simulator"]:
  411. env["arch_suffix"] += ".simulator"
  412. elif env["platform"] == "javascript":
  413. env["arch_suffix"] = "wasm"
  414. elif env["platform"] == "osx":
  415. env["arch_suffix"] = env["macos_arch"]
  416. library = None
  417. env["OBJSUFFIX"] = ".{}.{}.{}{}".format(env["platform"], env["target"], env["arch_suffix"], env["OBJSUFFIX"])
  418. library_name = "libgodot-cpp.{}.{}.{}{}".format(env["platform"], env["target"], env["arch_suffix"], env["LIBSUFFIX"])
  419. if env["build_library"]:
  420. library = env.StaticLibrary(target=env.File("bin/%s" % library_name), source=sources)
  421. Default(library)
  422. env.Append(LIBPATH=[env.Dir("bin")])
  423. env.Append(LIBS=library_name)
  424. Return("env")