SConstruct 16 KB

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