SConstruct 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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. 'universal',
  141. ['universal', '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. if env["macos_arch"] == "universal":
  208. env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"])
  209. env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"])
  210. else:
  211. env.Append(LINKFLAGS=["-arch", env["macos_arch"]])
  212. env.Append(CCFLAGS=["-arch", env["macos_arch"]])
  213. env.Append(CCFLAGS=['-std=c++14'])
  214. if env['macos_deployment_target'] != 'default':
  215. env.Append(CCFLAGS=['-mmacosx-version-min=' + env['macos_deployment_target']])
  216. env.Append(LINKFLAGS=['-mmacosx-version-min=' + env['macos_deployment_target']])
  217. if env['macos_sdk_path']:
  218. env.Append(CCFLAGS=['-isysroot', env['macos_sdk_path']])
  219. env.Append(LINKFLAGS=['-isysroot', env['macos_sdk_path']])
  220. env.Append(LINKFLAGS=[
  221. '-framework',
  222. 'Cocoa',
  223. '-Wl,-undefined,dynamic_lookup',
  224. ])
  225. if env['target'] == 'debug':
  226. env.Append(CCFLAGS=['-Og', '-g'])
  227. elif env['target'] == 'release':
  228. env.Append(CCFLAGS=['-O3'])
  229. elif env['platform'] == 'ios':
  230. if env['ios_simulator']:
  231. sdk_name = 'iphonesimulator'
  232. env.Append(CCFLAGS=['-mios-simulator-version-min=10.0'])
  233. env['LIBSUFFIX'] = ".simulator" + env['LIBSUFFIX']
  234. else:
  235. sdk_name = 'iphoneos'
  236. env.Append(CCFLAGS=['-miphoneos-version-min=10.0'])
  237. try:
  238. sdk_path = decode_utf8(subprocess.check_output(['xcrun', '--sdk', sdk_name, '--show-sdk-path']).strip())
  239. except (subprocess.CalledProcessError, OSError):
  240. raise ValueError("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
  241. compiler_path = env['IPHONEPATH'] + '/usr/bin/'
  242. env['ENV']['PATH'] = env['IPHONEPATH'] + "/Developer/usr/bin/:" + env['ENV']['PATH']
  243. env['CC'] = compiler_path + 'clang'
  244. env['CXX'] = compiler_path + 'clang++'
  245. env['AR'] = compiler_path + 'ar'
  246. env['RANLIB'] = compiler_path + 'ranlib'
  247. env.Append(CCFLAGS=['-std=c++14', '-arch', env['ios_arch'], '-isysroot', sdk_path])
  248. env.Append(LINKFLAGS=[
  249. '-arch',
  250. env['ios_arch'],
  251. '-framework',
  252. 'Cocoa',
  253. '-Wl,-undefined,dynamic_lookup',
  254. '-isysroot', sdk_path,
  255. '-F' + sdk_path
  256. ])
  257. if env['target'] == 'debug':
  258. env.Append(CCFLAGS=['-Og', '-g'])
  259. elif env['target'] == 'release':
  260. env.Append(CCFLAGS=['-O3'])
  261. elif env['platform'] == 'windows':
  262. if host_platform == 'windows' and not env['use_mingw']:
  263. # MSVC
  264. env.Append(LINKFLAGS=['/WX'])
  265. if env['target'] == 'debug':
  266. env.Append(CCFLAGS=['/Z7', '/Od', '/EHsc', '/D_DEBUG', '/MDd'])
  267. elif env['target'] == 'release':
  268. env.Append(CCFLAGS=['/O2', '/EHsc', '/DNDEBUG', '/MD'])
  269. elif host_platform == 'linux' or host_platform == 'freebsd' or host_platform == 'osx':
  270. # Cross-compilation using MinGW
  271. if env['bits'] == '64':
  272. env['CXX'] = 'x86_64-w64-mingw32-g++'
  273. env['AR'] = "x86_64-w64-mingw32-ar"
  274. env['RANLIB'] = "x86_64-w64-mingw32-ranlib"
  275. env['LINK'] = "x86_64-w64-mingw32-g++"
  276. elif env['bits'] == '32':
  277. env['CXX'] = 'i686-w64-mingw32-g++'
  278. env['AR'] = "i686-w64-mingw32-ar"
  279. env['RANLIB'] = "i686-w64-mingw32-ranlib"
  280. env['LINK'] = "i686-w64-mingw32-g++"
  281. elif host_platform == 'windows' and env['use_mingw']:
  282. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  283. env = Environment(ENV = os.environ, tools=["mingw"])
  284. opts.Update(env)
  285. #env = env.Clone(tools=['mingw'])
  286. env["SPAWN"] = mySpawn
  287. # Native or cross-compilation using MinGW
  288. if host_platform == 'linux' or host_platform == 'freebsd' or host_platform == 'osx' or env['use_mingw']:
  289. # These options are for a release build even using target=debug
  290. env.Append(CCFLAGS=['-O3', '-std=c++14', '-Wwrite-strings'])
  291. env.Append(LINKFLAGS=[
  292. '--static',
  293. '-Wl,--no-undefined',
  294. '-static-libgcc',
  295. '-static-libstdc++',
  296. ])
  297. elif env['platform'] == 'android':
  298. if host_platform == 'windows':
  299. # Don't Clone the environment. Because otherwise, SCons will pick up msvc stuff.
  300. env = Environment(ENV = os.environ, tools=["mingw"])
  301. opts.Update(env)
  302. #env = env.Clone(tools=['mingw'])
  303. env["SPAWN"] = mySpawn
  304. # Verify NDK root
  305. if not 'ANDROID_NDK_ROOT' in env:
  306. 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.")
  307. # Validate API level
  308. api_level = int(env['android_api_level'])
  309. if env['android_arch'] in ['x86_64', 'arm64v8'] and api_level < 21:
  310. print("WARN: 64-bit Android architectures require an API level of at least 21; setting android_api_level=21")
  311. env['android_api_level'] = '21'
  312. api_level = 21
  313. # Setup toolchain
  314. toolchain = env['ANDROID_NDK_ROOT'] + "/toolchains/llvm/prebuilt/"
  315. if host_platform == "windows":
  316. toolchain += "windows"
  317. import platform as pltfm
  318. if pltfm.machine().endswith("64"):
  319. toolchain += "-x86_64"
  320. elif host_platform == "linux":
  321. toolchain += "linux-x86_64"
  322. elif host_platform == "osx":
  323. toolchain += "darwin-x86_64"
  324. env.PrependENVPath('PATH', toolchain + "/bin") # This does nothing half of the time, but we'll put it here anyways
  325. # Get architecture info
  326. arch_info_table = {
  327. "armv7" : {
  328. "march":"armv7-a", "target":"armv7a-linux-androideabi", "tool_path":"arm-linux-androideabi", "compiler_path":"armv7a-linux-androideabi",
  329. "ccflags" : ['-mfpu=neon']
  330. },
  331. "arm64v8" : {
  332. "march":"armv8-a", "target":"aarch64-linux-android", "tool_path":"aarch64-linux-android", "compiler_path":"aarch64-linux-android",
  333. "ccflags" : []
  334. },
  335. "x86" : {
  336. "march":"i686", "target":"i686-linux-android", "tool_path":"i686-linux-android", "compiler_path":"i686-linux-android",
  337. "ccflags" : ['-mstackrealign']
  338. },
  339. "x86_64" : {"march":"x86-64", "target":"x86_64-linux-android", "tool_path":"x86_64-linux-android", "compiler_path":"x86_64-linux-android",
  340. "ccflags" : []
  341. }
  342. }
  343. arch_info = arch_info_table[env['android_arch']]
  344. # Setup tools
  345. env['CC'] = toolchain + "/bin/clang"
  346. env['CXX'] = toolchain + "/bin/clang++"
  347. env['AR'] = toolchain + "/bin/" + arch_info['tool_path'] + "-ar"
  348. env["AS"] = toolchain + "/bin/" + arch_info['tool_path'] + "-as"
  349. env["LD"] = toolchain + "/bin/" + arch_info['tool_path'] + "-ld"
  350. env["STRIP"] = toolchain + "/bin/" + arch_info['tool_path'] + "-strip"
  351. env["RANLIB"] = toolchain + "/bin/" + arch_info['tool_path'] + "-ranlib"
  352. env.Append(CCFLAGS=['--target=' + arch_info['target'] + env['android_api_level'], '-march=' + arch_info['march'], '-fPIC'])
  353. env.Append(CCFLAGS=arch_info['ccflags'])
  354. if env['target'] == 'debug':
  355. env.Append(CCFLAGS=['-Og', '-g'])
  356. elif env['target'] == 'release':
  357. env.Append(CCFLAGS=['-O3'])
  358. elif env["platform"] == "javascript":
  359. env["ENV"] = os.environ
  360. env["CC"] = "emcc"
  361. env["CXX"] = "em++"
  362. env["AR"] = "emar"
  363. env["RANLIB"] = "emranlib"
  364. env.Append(CPPFLAGS=["-s", "SIDE_MODULE=1"])
  365. env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"])
  366. env["SHOBJSUFFIX"] = ".bc"
  367. env["SHLIBSUFFIX"] = ".wasm"
  368. # Use TempFileMunge since some AR invocations are too long for cmd.exe.
  369. # Use POSIX-style paths, required with TempFileMunge.
  370. env["ARCOM_POSIX"] = env["ARCOM"].replace("$TARGET", "$TARGET.posix").replace("$SOURCES", "$SOURCES.posix")
  371. env["ARCOM"] = "${TEMPFILE(ARCOM_POSIX)}"
  372. # All intermediate files are just LLVM bitcode.
  373. env["OBJPREFIX"] = ""
  374. env["OBJSUFFIX"] = ".bc"
  375. env["PROGPREFIX"] = ""
  376. # Program() output consists of multiple files, so specify suffixes manually at builder.
  377. env["PROGSUFFIX"] = ""
  378. env["LIBPREFIX"] = "lib"
  379. env["LIBSUFFIX"] = ".a"
  380. env["LIBPREFIXES"] = ["$LIBPREFIX"]
  381. env["LIBSUFFIXES"] = ["$LIBSUFFIX"]
  382. env.Replace(SHLINKFLAGS='$LINKFLAGS')
  383. env.Replace(SHLINKFLAGS='$LINKFLAGS')
  384. if env['target'] == 'debug':
  385. env.Append(CCFLAGS=['-O0', '-g'])
  386. elif env['target'] == 'release':
  387. env.Append(CCFLAGS=['-O3'])
  388. env.Append(CPPPATH=[
  389. '.',
  390. env['headers_dir'],
  391. 'include',
  392. 'include/gen',
  393. 'include/core',
  394. ])
  395. # Generate bindings?
  396. json_api_file = ''
  397. if 'custom_api_file' in env:
  398. json_api_file = env['custom_api_file']
  399. else:
  400. json_api_file = os.path.join(os.getcwd(), env['headers_dir'], 'api.json')
  401. if env['generate_bindings'] == 'auto':
  402. # Check if generated files exist
  403. should_generate_bindings = not os.path.isfile(os.path.join(os.getcwd(), 'src', 'gen', 'Object.cpp'))
  404. else:
  405. should_generate_bindings = env['generate_bindings'] in ['yes', 'true']
  406. if should_generate_bindings:
  407. # Actually create the bindings here
  408. import binding_generator
  409. binding_generator.generate_bindings(json_api_file, env['generate_template_get_node'])
  410. # Sources to compile
  411. sources = []
  412. add_sources(sources, 'src/core', 'cpp')
  413. add_sources(sources, 'src/gen', 'cpp')
  414. arch_suffix = env['bits']
  415. if env['platform'] == 'android':
  416. arch_suffix = env['android_arch']
  417. elif env['platform'] == 'ios':
  418. arch_suffix = env['ios_arch']
  419. elif env['platform'] == 'osx':
  420. if env['macos_arch'] != 'universal':
  421. arch_suffix = env['macos_arch']
  422. elif env['platform'] == 'javascript':
  423. arch_suffix = 'wasm'
  424. library = env.StaticLibrary(
  425. target='bin/' + 'libgodot-cpp.{}.{}.{}{}'.format(
  426. env['platform'],
  427. env['target'],
  428. arch_suffix,
  429. env['LIBSUFFIX']
  430. ), source=sources
  431. )
  432. Default(library)