SConstruct 14 KB

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