SConstruct 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 == 'darwin':
  51. host_platform = 'osx'
  52. elif sys.platform == 'win32' or sys.platform == 'msys':
  53. host_platform = 'windows'
  54. else:
  55. raise ValueError(
  56. 'Could not detect platform automatically, please specify with '
  57. 'platform=<platform>'
  58. )
  59. env = Environment(ENV = os.environ)
  60. is64 = sys.maxsize > 2**32
  61. if (
  62. env['TARGET_ARCH'] == 'amd64' or
  63. env['TARGET_ARCH'] == 'emt64' or
  64. env['TARGET_ARCH'] == 'x86_64' or
  65. env['TARGET_ARCH'] == 'arm64-v8a'
  66. ):
  67. is64 = True
  68. opts = Variables([], ARGUMENTS)
  69. opts.Add(EnumVariable(
  70. 'platform',
  71. 'Target platform',
  72. host_platform,
  73. allowed_values=('linux', 'osx', 'windows', 'android', 'ios'),
  74. ignorecase=2
  75. ))
  76. opts.Add(EnumVariable(
  77. 'bits',
  78. 'Target platform bits',
  79. '64' if is64 else '32',
  80. ('32', '64')
  81. ))
  82. opts.Add(BoolVariable(
  83. 'use_llvm',
  84. 'Use the LLVM compiler - only effective when targeting Linux',
  85. False
  86. ))
  87. opts.Add(BoolVariable(
  88. 'use_mingw',
  89. 'Use the MinGW compiler instead of MSVC - only effective on Windows',
  90. False
  91. ))
  92. # Must be the same setting as used for cpp_bindings
  93. opts.Add(EnumVariable(
  94. 'target',
  95. 'Compilation target',
  96. 'debug',
  97. allowed_values=('debug', 'release'),
  98. ignorecase=2
  99. ))
  100. opts.Add(PathVariable(
  101. 'headers_dir',
  102. 'Path to the directory containing Godot headers',
  103. 'godot-headers',
  104. PathVariable.PathIsDir
  105. ))
  106. opts.Add(PathVariable(
  107. 'custom_api_file',
  108. 'Path to a custom JSON API file',
  109. None,
  110. PathVariable.PathIsFile
  111. ))
  112. opts.Add(BoolVariable(
  113. 'generate_bindings',
  114. 'Generate GDNative API bindings',
  115. False
  116. ))
  117. opts.Add(EnumVariable(
  118. 'android_arch',
  119. 'Target Android architecture',
  120. 'armv7',
  121. ['armv7','arm64v8','x86','x86_64']
  122. ))
  123. opts.Add(
  124. 'macos_deployment_target',
  125. 'macOS deployment target',
  126. 'default'
  127. )
  128. opts.Add(EnumVariable(
  129. 'ios_arch',
  130. 'Target iOS architecture',
  131. 'arm64',
  132. ['armv7', 'arm64', 'x86_64']
  133. ))
  134. opts.Add(BoolVariable(
  135. 'ios_simulator',
  136. 'Target iOS Simulator',
  137. False
  138. ))
  139. opts.Add(
  140. 'IPHONEPATH',
  141. 'Path to iPhone toolchain',
  142. '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain',
  143. )
  144. opts.Add(
  145. 'android_api_level',
  146. 'Target Android API level',
  147. '18' if ARGUMENTS.get("android_arch", 'armv7') in ['armv7', 'x86'] else '21'
  148. )
  149. opts.Add(
  150. 'ANDROID_NDK_ROOT',
  151. 'Path to your Android NDK installation. By default, uses ANDROID_NDK_ROOT from your defined environment variables.',
  152. os.environ.get("ANDROID_NDK_ROOT", None)
  153. )
  154. opts.Add(BoolVariable(
  155. 'generate_template_get_node',
  156. "Generate a template version of the Node class's get_node.",
  157. True
  158. ))
  159. opts.Update(env)
  160. Help(opts.GenerateHelpText(env))
  161. # This makes sure to keep the session environment variables on Windows.
  162. # This way, you can run SCons in a Visual Studio 2017 prompt and it will find
  163. # all the required tools
  164. if host_platform == 'windows' and env['platform'] != 'android':
  165. if env['bits'] == '64':
  166. env = Environment(TARGET_ARCH='amd64')
  167. elif env['bits'] == '32':
  168. env = Environment(TARGET_ARCH='x86')
  169. opts.Update(env)
  170. if env['platform'] == 'linux':
  171. if env['use_llvm']:
  172. env['CXX'] = 'clang++'
  173. env.Append(CCFLAGS=['-fPIC', '-std=c++14', '-Wwrite-strings'])
  174. env.Append(LINKFLAGS=["-Wl,-R,'$$ORIGIN'"])
  175. if env['target'] == 'debug':
  176. env.Append(CCFLAGS=['-Og', '-g'])
  177. elif env['target'] == 'release':
  178. env.Append(CCFLAGS=['-O3'])
  179. if env['bits'] == '64':
  180. env.Append(CCFLAGS=['-m64'])
  181. env.Append(LINKFLAGS=['-m64'])
  182. elif env['bits'] == '32':
  183. env.Append(CCFLAGS=['-m32'])
  184. env.Append(LINKFLAGS=['-m32'])
  185. elif env['platform'] == 'osx':
  186. # Use Clang on macOS by default
  187. env['CXX'] = 'clang++'
  188. if env['bits'] == '32':
  189. raise ValueError(
  190. 'Only 64-bit builds are supported for the macOS target.'
  191. )
  192. env.Append(CCFLAGS=['-std=c++14', '-arch', 'x86_64'])
  193. if env['macos_deployment_target'] != 'default':
  194. env.Append(CCFLAGS=['-mmacosx-version-min=' + env['macos_deployment_target']])
  195. env.Append(LINKFLAGS=[
  196. '-arch',
  197. 'x86_64',
  198. '-framework',
  199. 'Cocoa',
  200. '-Wl,-undefined,dynamic_lookup',
  201. ])
  202. if env['target'] == 'debug':
  203. env.Append(CCFLAGS=['-Og', '-g'])
  204. elif env['target'] == 'release':
  205. env.Append(CCFLAGS=['-O3'])
  206. elif env['platform'] == 'ios':
  207. if env['ios_simulator']:
  208. sdk_name = 'iphonesimulator'
  209. env.Append(CCFLAGS=['-mios-simulator-version-min=10.0'])
  210. env['LIBSUFFIX'] = ".simulator" + env['LIBSUFFIX']
  211. else:
  212. sdk_name = 'iphoneos'
  213. env.Append(CCFLAGS=['-miphoneos-version-min=10.0'])
  214. try:
  215. sdk_path = decode_utf8(subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).strip())
  216. except (subprocess.CalledProcessError, OSError):
  217. raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
  218. compiler_path = env['IPHONEPATH'] + '/usr/bin/'
  219. env['ENV']['PATH'] = env['IPHONEPATH'] + "/Developer/usr/bin/:" + env['ENV']['PATH']
  220. env['CC'] = compiler_path + 'clang'
  221. env['CXX'] = compiler_path + 'clang++'
  222. env['AR'] = compiler_path + 'ar'
  223. env['RANLIB'] = compiler_path + 'ranlib'
  224. env.Append(CCFLAGS=['-std=c++14', '-arch', env['ios_arch'], '-isysroot', sdk_path])
  225. env.Append(LINKFLAGS=[
  226. '-arch',
  227. env['ios_arch'],
  228. '-framework',
  229. 'Cocoa',
  230. '-Wl,-undefined,dynamic_lookup',
  231. '-isysroot', sdk_path,
  232. '-F' + sdk_path
  233. ])
  234. if env['target'] == 'debug':
  235. env.Append(CCFLAGS=['-Og', '-g'])
  236. elif env['target'] == 'release':
  237. env.Append(CCFLAGS=['-O3'])
  238. elif env['platform'] == 'windows':
  239. if host_platform == 'windows' and not env['use_mingw']:
  240. # MSVC
  241. env.Append(LINKFLAGS=['/WX'])
  242. if env['target'] == 'debug':
  243. env.Append(CCFLAGS=['/Z7', '/Od', '/EHsc', '/D_DEBUG', '/MDd'])
  244. elif env['target'] == 'release':
  245. env.Append(CCFLAGS=['/O2', '/EHsc', '/DNDEBUG', '/MD'])
  246. elif host_platform == 'linux' or host_platform == 'osx':
  247. # Cross-compilation using MinGW
  248. if env['bits'] == '64':
  249. env['CXX'] = 'x86_64-w64-mingw32-g++'
  250. env['AR'] = "x86_64-w64-mingw32-ar"
  251. env['RANLIB'] = "x86_64-w64-mingw32-ranlib"
  252. env['LINK'] = "x86_64-w64-mingw32-g++"
  253. elif env['bits'] == '32':
  254. env['CXX'] = 'i686-w64-mingw32-g++'
  255. env['AR'] = "i686-w64-mingw32-ar"
  256. env['RANLIB'] = "i686-w64-mingw32-ranlib"
  257. env['LINK'] = "i686-w64-mingw32-g++"
  258. elif host_platform == 'windows' and env['use_mingw']:
  259. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  260. env = Environment(ENV = os.environ, tools=["mingw"])
  261. opts.Update(env)
  262. #env = env.Clone(tools=['mingw'])
  263. env["SPAWN"] = mySpawn
  264. # Native or cross-compilation using MinGW
  265. if host_platform == 'linux' or host_platform == 'osx' or env['use_mingw']:
  266. # These options are for a release build even using target=debug
  267. env.Append(CCFLAGS=['-O3', '-std=c++14', '-Wwrite-strings'])
  268. env.Append(LINKFLAGS=[
  269. '--static',
  270. '-Wl,--no-undefined',
  271. '-static-libgcc',
  272. '-static-libstdc++',
  273. ])
  274. elif env['platform'] == 'android':
  275. if host_platform == 'windows':
  276. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  277. env = Environment(ENV = os.environ, tools=["mingw"])
  278. opts.Update(env)
  279. #env = env.Clone(tools=['mingw'])
  280. env["SPAWN"] = mySpawn
  281. # Verify NDK root
  282. if not 'ANDROID_NDK_ROOT' in env:
  283. 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.")
  284. # Validate API level
  285. api_level = int(env['android_api_level'])
  286. if env['android_arch'] in ['x86_64', 'arm64v8'] and api_level < 21:
  287. print("WARN: 64-bit Android architectures require an API level of at least 21; setting android_api_level=21")
  288. env['android_api_level'] = '21'
  289. api_level = 21
  290. # Setup toolchain
  291. toolchain = env['ANDROID_NDK_ROOT'] + "/toolchains/llvm/prebuilt/"
  292. if host_platform == "windows":
  293. toolchain += "windows"
  294. import platform as pltfm
  295. if pltfm.machine().endswith("64"):
  296. toolchain += "-x86_64"
  297. elif host_platform == "linux":
  298. toolchain += "linux-x86_64"
  299. elif host_platform == "osx":
  300. toolchain += "darwin-x86_64"
  301. env.PrependENVPath('PATH', toolchain + "/bin") # This does nothing half of the time, but we'll put it here anyways
  302. # Get architecture info
  303. arch_info_table = {
  304. "armv7" : {
  305. "march":"armv7-a", "target":"armv7a-linux-androideabi", "tool_path":"arm-linux-androideabi", "compiler_path":"armv7a-linux-androideabi",
  306. "ccflags" : ['-mfpu=neon']
  307. },
  308. "arm64v8" : {
  309. "march":"armv8-a", "target":"aarch64-linux-android", "tool_path":"aarch64-linux-android", "compiler_path":"aarch64-linux-android",
  310. "ccflags" : []
  311. },
  312. "x86" : {
  313. "march":"i686", "target":"i686-linux-android", "tool_path":"i686-linux-android", "compiler_path":"i686-linux-android",
  314. "ccflags" : ['-mstackrealign']
  315. },
  316. "x86_64" : {"march":"x86-64", "target":"x86_64-linux-android", "tool_path":"x86_64-linux-android", "compiler_path":"x86_64-linux-android",
  317. "ccflags" : []
  318. }
  319. }
  320. arch_info = arch_info_table[env['android_arch']]
  321. # Setup tools
  322. env['CC'] = toolchain + "/bin/clang"
  323. env['CXX'] = toolchain + "/bin/clang++"
  324. env['AR'] = toolchain + "/bin/" + arch_info['tool_path'] + "-ar"
  325. env.Append(CCFLAGS=['--target=' + arch_info['target'] + env['android_api_level'], '-march=' + arch_info['march'], '-fPIC'])#, '-fPIE', '-fno-addrsig', '-Oz'])
  326. env.Append(CCFLAGS=arch_info['ccflags'])
  327. env.Append(CPPPATH=[
  328. '.',
  329. env['headers_dir'],
  330. 'include',
  331. 'include/gen',
  332. 'include/core',
  333. ])
  334. # Generate bindings?
  335. json_api_file = ''
  336. if 'custom_api_file' in env:
  337. json_api_file = env['custom_api_file']
  338. else:
  339. json_api_file = os.path.join(os.getcwd(), env['headers_dir'], 'api.json')
  340. if env['generate_bindings']:
  341. # Actually create the bindings here
  342. import binding_generator
  343. binding_generator.generate_bindings(json_api_file, env['generate_template_get_node'])
  344. # Sources to compile
  345. sources = []
  346. add_sources(sources, 'src/core', 'cpp')
  347. add_sources(sources, 'src/gen', 'cpp')
  348. arch_suffix = env['bits']
  349. if env['platform'] == 'android':
  350. arch_suffix = env['android_arch']
  351. if env['platform'] == 'ios':
  352. arch_suffix = env['ios_arch']
  353. library = env.StaticLibrary(
  354. target='bin/' + 'libgodot-cpp.{}.{}.{}{}'.format(
  355. env['platform'],
  356. env['target'],
  357. arch_suffix,
  358. env['LIBSUFFIX']
  359. ), source=sources
  360. )
  361. Default(library)