SConstruct 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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(
  133. 'macos_sdk_path',
  134. 'macOS SDK path',
  135. ''
  136. )
  137. opts.Add(EnumVariable(
  138. 'macos_arch',
  139. 'Target macOS architecture',
  140. 'x86_64',
  141. ['x86_64', 'arm64']
  142. ))
  143. opts.Add(EnumVariable(
  144. 'ios_arch',
  145. 'Target iOS architecture',
  146. 'arm64',
  147. ['armv7', 'arm64', 'x86_64']
  148. ))
  149. opts.Add(BoolVariable(
  150. 'ios_simulator',
  151. 'Target iOS Simulator',
  152. False
  153. ))
  154. opts.Add(
  155. 'IPHONEPATH',
  156. 'Path to iPhone toolchain',
  157. '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain',
  158. )
  159. opts.Add(
  160. 'android_api_level',
  161. 'Target Android API level',
  162. '18' if ARGUMENTS.get("android_arch", 'armv7') in ['armv7', 'x86'] else '21'
  163. )
  164. opts.Add(
  165. 'ANDROID_NDK_ROOT',
  166. 'Path to your Android NDK installation. By default, uses ANDROID_NDK_ROOT from your defined environment variables.',
  167. os.environ.get("ANDROID_NDK_ROOT", None)
  168. )
  169. opts.Add(BoolVariable(
  170. 'generate_template_get_node',
  171. "Generate a template version of the Node class's get_node.",
  172. True
  173. ))
  174. opts.Update(env)
  175. Help(opts.GenerateHelpText(env))
  176. # This makes sure to keep the session environment variables on Windows.
  177. # This way, you can run SCons in a Visual Studio 2017 prompt and it will find
  178. # all the required tools
  179. if host_platform == 'windows' and env['platform'] != 'android':
  180. if env['bits'] == '64':
  181. env = Environment(TARGET_ARCH='amd64')
  182. elif env['bits'] == '32':
  183. env = Environment(TARGET_ARCH='x86')
  184. opts.Update(env)
  185. if env['platform'] == 'linux' or env['platform'] == 'freebsd':
  186. if env['use_llvm']:
  187. env['CXX'] = 'clang++'
  188. env.Append(CCFLAGS=['-fPIC', '-std=c++14', '-Wwrite-strings'])
  189. env.Append(LINKFLAGS=["-Wl,-R,'$$ORIGIN'"])
  190. if env['target'] == 'debug':
  191. env.Append(CCFLAGS=['-Og', '-g'])
  192. elif env['target'] == 'release':
  193. env.Append(CCFLAGS=['-O3'])
  194. if env['bits'] == '64':
  195. env.Append(CCFLAGS=['-m64'])
  196. env.Append(LINKFLAGS=['-m64'])
  197. elif env['bits'] == '32':
  198. env.Append(CCFLAGS=['-m32'])
  199. env.Append(LINKFLAGS=['-m32'])
  200. elif env['platform'] == 'osx':
  201. # Use Clang on macOS by default
  202. env['CXX'] = 'clang++'
  203. if env['bits'] == '32':
  204. raise ValueError(
  205. 'Only 64-bit builds are supported for the macOS target.'
  206. )
  207. env.Append(CCFLAGS=['-std=c++14', '-arch', env['macos_arch']])
  208. if env['macos_deployment_target'] != 'default':
  209. env.Append(CCFLAGS=['-mmacosx-version-min=' + env['macos_deployment_target']])
  210. env.Append(LINKFLAGS=['-mmacosx-version-min=' + env['macos_deployment_target']])
  211. if env['macos_sdk_path']:
  212. env.Append(CCFLAGS=['-isysroot', env['macos_sdk_path']])
  213. env.Append(LINKFLAGS=['-isysroot', env['macos_sdk_path']])
  214. env.Append(LINKFLAGS=[
  215. '-arch',
  216. env['macos_arch'],
  217. '-framework',
  218. 'Cocoa',
  219. '-Wl,-undefined,dynamic_lookup',
  220. ])
  221. if env['target'] == 'debug':
  222. env.Append(CCFLAGS=['-Og', '-g'])
  223. elif env['target'] == 'release':
  224. env.Append(CCFLAGS=['-O3'])
  225. elif env['platform'] == 'ios':
  226. if env['ios_simulator']:
  227. sdk_name = 'iphonesimulator'
  228. env.Append(CCFLAGS=['-mios-simulator-version-min=10.0'])
  229. env['LIBSUFFIX'] = ".simulator" + env['LIBSUFFIX']
  230. else:
  231. sdk_name = 'iphoneos'
  232. env.Append(CCFLAGS=['-miphoneos-version-min=10.0'])
  233. try:
  234. sdk_path = decode_utf8(subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).strip())
  235. except (subprocess.CalledProcessError, OSError):
  236. raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
  237. compiler_path = env['IPHONEPATH'] + '/usr/bin/'
  238. env['ENV']['PATH'] = env['IPHONEPATH'] + "/Developer/usr/bin/:" + env['ENV']['PATH']
  239. env['CC'] = compiler_path + 'clang'
  240. env['CXX'] = compiler_path + 'clang++'
  241. env['AR'] = compiler_path + 'ar'
  242. env['RANLIB'] = compiler_path + 'ranlib'
  243. env.Append(CCFLAGS=['-std=c++14', '-arch', env['ios_arch'], '-isysroot', sdk_path])
  244. env.Append(LINKFLAGS=[
  245. '-arch',
  246. env['ios_arch'],
  247. '-framework',
  248. 'Cocoa',
  249. '-Wl,-undefined,dynamic_lookup',
  250. '-isysroot', sdk_path,
  251. '-F' + sdk_path
  252. ])
  253. if env['target'] == 'debug':
  254. env.Append(CCFLAGS=['-Og', '-g'])
  255. elif env['target'] == 'release':
  256. env.Append(CCFLAGS=['-O3'])
  257. elif env['platform'] == 'windows':
  258. if host_platform == 'windows' and not env['use_mingw']:
  259. # MSVC
  260. env.Append(LINKFLAGS=['/WX'])
  261. if env['target'] == 'debug':
  262. env.Append(CCFLAGS=['/Z7', '/Od', '/EHsc', '/D_DEBUG', '/MDd'])
  263. elif env['target'] == 'release':
  264. env.Append(CCFLAGS=['/O2', '/EHsc', '/DNDEBUG', '/MD'])
  265. elif host_platform == 'linux' or host_platform == 'freebsd' or host_platform == 'osx':
  266. # Cross-compilation using MinGW
  267. if env['bits'] == '64':
  268. env['CXX'] = 'x86_64-w64-mingw32-g++'
  269. env['AR'] = "x86_64-w64-mingw32-ar"
  270. env['RANLIB'] = "x86_64-w64-mingw32-ranlib"
  271. env['LINK'] = "x86_64-w64-mingw32-g++"
  272. elif env['bits'] == '32':
  273. env['CXX'] = 'i686-w64-mingw32-g++'
  274. env['AR'] = "i686-w64-mingw32-ar"
  275. env['RANLIB'] = "i686-w64-mingw32-ranlib"
  276. env['LINK'] = "i686-w64-mingw32-g++"
  277. elif host_platform == 'windows' and env['use_mingw']:
  278. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  279. env = Environment(ENV = os.environ, tools=["mingw"])
  280. opts.Update(env)
  281. #env = env.Clone(tools=['mingw'])
  282. env["SPAWN"] = mySpawn
  283. # Native or cross-compilation using MinGW
  284. if host_platform == 'linux' or host_platform == 'freebsd' or host_platform == 'osx' or env['use_mingw']:
  285. # These options are for a release build even using target=debug
  286. env.Append(CCFLAGS=['-O3', '-std=c++14', '-Wwrite-strings'])
  287. env.Append(LINKFLAGS=[
  288. '--static',
  289. '-Wl,--no-undefined',
  290. '-static-libgcc',
  291. '-static-libstdc++',
  292. ])
  293. elif env['platform'] == 'android':
  294. if host_platform == 'windows':
  295. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  296. env = Environment(ENV = os.environ, tools=["mingw"])
  297. opts.Update(env)
  298. #env = env.Clone(tools=['mingw'])
  299. env["SPAWN"] = mySpawn
  300. # Verify NDK root
  301. if not 'ANDROID_NDK_ROOT' in env:
  302. 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.")
  303. # Validate API level
  304. api_level = int(env['android_api_level'])
  305. if env['android_arch'] in ['x86_64', 'arm64v8'] and api_level < 21:
  306. print("WARN: 64-bit Android architectures require an API level of at least 21; setting android_api_level=21")
  307. env['android_api_level'] = '21'
  308. api_level = 21
  309. # Setup toolchain
  310. toolchain = env['ANDROID_NDK_ROOT'] + "/toolchains/llvm/prebuilt/"
  311. if host_platform == "windows":
  312. toolchain += "windows"
  313. import platform as pltfm
  314. if pltfm.machine().endswith("64"):
  315. toolchain += "-x86_64"
  316. elif host_platform == "linux":
  317. toolchain += "linux-x86_64"
  318. elif host_platform == "osx":
  319. toolchain += "darwin-x86_64"
  320. env.PrependENVPath('PATH', toolchain + "/bin") # This does nothing half of the time, but we'll put it here anyways
  321. # Get architecture info
  322. arch_info_table = {
  323. "armv7" : {
  324. "march":"armv7-a", "target":"armv7a-linux-androideabi", "tool_path":"arm-linux-androideabi", "compiler_path":"armv7a-linux-androideabi",
  325. "ccflags" : ['-mfpu=neon']
  326. },
  327. "arm64v8" : {
  328. "march":"armv8-a", "target":"aarch64-linux-android", "tool_path":"aarch64-linux-android", "compiler_path":"aarch64-linux-android",
  329. "ccflags" : []
  330. },
  331. "x86" : {
  332. "march":"i686", "target":"i686-linux-android", "tool_path":"i686-linux-android", "compiler_path":"i686-linux-android",
  333. "ccflags" : ['-mstackrealign']
  334. },
  335. "x86_64" : {"march":"x86-64", "target":"x86_64-linux-android", "tool_path":"x86_64-linux-android", "compiler_path":"x86_64-linux-android",
  336. "ccflags" : []
  337. }
  338. }
  339. arch_info = arch_info_table[env['android_arch']]
  340. # Setup tools
  341. env['CC'] = toolchain + "/bin/clang"
  342. env['CXX'] = toolchain + "/bin/clang++"
  343. env['AR'] = toolchain + "/bin/" + arch_info['tool_path'] + "-ar"
  344. env.Append(CCFLAGS=['--target=' + arch_info['target'] + env['android_api_level'], '-march=' + arch_info['march'], '-fPIC'])#, '-fPIE', '-fno-addrsig', '-Oz'])
  345. env.Append(CCFLAGS=arch_info['ccflags'])
  346. if env['target'] == 'debug':
  347. env.Append(CCFLAGS=['-Og', '-g'])
  348. elif env['target'] == 'release':
  349. env.Append(CCFLAGS=['-O3'])
  350. elif env["platform"] == "javascript":
  351. env["ENV"] = os.environ
  352. env["CC"] = "emcc"
  353. env["CXX"] = "em++"
  354. env["AR"] = "emar"
  355. env["RANLIB"] = "emranlib"
  356. env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
  357. env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
  358. env["SHOBJSUFFIX"] = ".bc"
  359. env["SHLIBSUFFIX"] = ".wasm"
  360. # Use TempFileMunge since some AR invocations are too long for cmd.exe.
  361. # Use POSIX-style paths, required with TempFileMunge.
  362. env["ARCOM_POSIX"] = env["ARCOM"].replace("$TARGET", "$TARGET.posix").replace("$SOURCES", "$SOURCES.posix")
  363. env["ARCOM"] = "${TEMPFILE(ARCOM_POSIX)}"
  364. # All intermediate files are just LLVM bitcode.
  365. env["OBJPREFIX"] = ""
  366. env["OBJSUFFIX"] = ".bc"
  367. env["PROGPREFIX"] = ""
  368. # Program() output consists of multiple files, so specify suffixes manually at builder.
  369. env["PROGSUFFIX"] = ""
  370. env["LIBPREFIX"] = "lib"
  371. env["LIBSUFFIX"] = ".a"
  372. env["LIBPREFIXES"] = ["$LIBPREFIX"]
  373. env["LIBSUFFIXES"] = ["$LIBSUFFIX"]
  374. env.Replace(SHLINKFLAGS='$LINKFLAGS')
  375. env.Replace(SHLINKFLAGS='$LINKFLAGS')
  376. if env['target'] == 'debug':
  377. env.Append(CCFLAGS=['-O0', '-g'])
  378. elif env['target'] == 'release':
  379. env.Append(CCFLAGS=['-O3'])
  380. env.Append(CPPPATH=[
  381. '.',
  382. env['headers_dir'],
  383. 'include',
  384. 'include/gen',
  385. 'include/core',
  386. ])
  387. # Generate bindings?
  388. json_api_file = ''
  389. if 'custom_api_file' in env:
  390. json_api_file = env['custom_api_file']
  391. else:
  392. json_api_file = os.path.join(os.getcwd(), env['headers_dir'], 'api.json')
  393. if env['generate_bindings'] == 'auto':
  394. # Check if generated files exist
  395. should_generate_bindings = not os.path.isfile(os.path.join(os.getcwd(), 'src', 'gen', 'Object.cpp'))
  396. else:
  397. should_generate_bindings = env['generate_bindings'] in ['yes', 'true']
  398. if should_generate_bindings:
  399. # Actually create the bindings here
  400. import binding_generator
  401. binding_generator.generate_bindings(json_api_file, env['generate_template_get_node'])
  402. # Sources to compile
  403. sources = []
  404. add_sources(sources, 'src/core', 'cpp')
  405. add_sources(sources, 'src/gen', 'cpp')
  406. arch_suffix = env['bits']
  407. if env['platform'] == 'android':
  408. arch_suffix = env['android_arch']
  409. if env['platform'] == 'ios':
  410. arch_suffix = env['ios_arch']
  411. if env['platform'] == 'javascript':
  412. arch_suffix = 'wasm'
  413. library = env.StaticLibrary(
  414. target='bin/' + 'libgodot-cpp.{}.{}.{}{}'.format(
  415. env['platform'],
  416. env['target'],
  417. arch_suffix,
  418. env['LIBSUFFIX']
  419. ), source=sources
  420. )
  421. Default(library)