2
0

SConstruct 18 KB

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