SConstruct 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. #!/usr/bin/env python
  2. EnsureSConsVersion(0, 98, 1)
  3. # System
  4. import glob
  5. import os
  6. import string
  7. import sys
  8. # Local
  9. import methods
  10. # moved below to compensate with module version string
  11. # methods.update_version()
  12. # scan possible build platforms
  13. platform_list = [] # list of platforms
  14. platform_opts = {} # options for each platform
  15. platform_flags = {} # flags for each platform
  16. active_platforms = []
  17. active_platform_ids = []
  18. platform_exporters = []
  19. platform_apis = []
  20. global_defaults = []
  21. for x in glob.glob("platform/*"):
  22. if (not os.path.isdir(x) or not os.path.exists(x + "/detect.py")):
  23. continue
  24. tmppath = "./" + x
  25. sys.path.insert(0, tmppath)
  26. import detect
  27. if (os.path.exists(x + "/export/export.cpp")):
  28. platform_exporters.append(x[9:])
  29. if (os.path.exists(x + "/api/api.cpp")):
  30. platform_apis.append(x[9:])
  31. if (os.path.exists(x + "/globals/global_defaults.cpp")):
  32. global_defaults.append(x[9:])
  33. if (detect.is_active()):
  34. active_platforms.append(detect.get_name())
  35. active_platform_ids.append(x)
  36. if (detect.can_build()):
  37. x = x.replace("platform/", "") # rest of world
  38. x = x.replace("platform\\", "") # win32
  39. platform_list += [x]
  40. platform_opts[x] = detect.get_opts()
  41. platform_flags[x] = detect.get_flags()
  42. sys.path.remove(tmppath)
  43. sys.modules.pop('detect')
  44. module_list = methods.detect_modules()
  45. # print "Detected Platforms: "+str(platform_list)
  46. methods.save_active_platforms(active_platforms, active_platform_ids)
  47. custom_tools = ['default']
  48. platform_arg = ARGUMENTS.get("platform", ARGUMENTS.get("p", False))
  49. if (os.name == "posix"):
  50. pass
  51. elif (os.name == "nt"):
  52. if (os.getenv("VCINSTALLDIR") == None or platform_arg == "android" or platform_arg == "javascript"):
  53. custom_tools = ['mingw']
  54. env_base = Environment(tools=custom_tools)
  55. if 'TERM' in os.environ:
  56. env_base['ENV']['TERM'] = os.environ['TERM']
  57. env_base.AppendENVPath('PATH', os.getenv('PATH'))
  58. env_base.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
  59. env_base.global_defaults = global_defaults
  60. env_base.android_maven_repos = []
  61. env_base.android_flat_dirs = []
  62. env_base.android_dependencies = []
  63. env_base.android_gradle_plugins = []
  64. env_base.android_gradle_classpath = []
  65. env_base.android_java_dirs = []
  66. env_base.android_res_dirs = []
  67. env_base.android_asset_dirs = []
  68. env_base.android_aidl_dirs = []
  69. env_base.android_jni_dirs = []
  70. env_base.android_default_config = []
  71. env_base.android_manifest_chunk = ""
  72. env_base.android_permission_chunk = ""
  73. env_base.android_appattributes_chunk = ""
  74. env_base.disabled_modules = []
  75. env_base.use_ptrcall = False
  76. env_base.split_drivers = False
  77. env_base.split_modules = False
  78. env_base.module_version_string = ""
  79. # To decide whether to rebuild a file, use the MD5 sum only if the timestamp has changed.
  80. # http://scons.org/doc/production/HTML/scons-user/ch06.html#idm139837621851792
  81. env_base.Decider('MD5-timestamp')
  82. # Use cached implicit dependencies by default. Can be overridden by specifying `--implicit-deps-changed` in the command line.
  83. # http://scons.org/doc/production/HTML/scons-user/ch06s04.html
  84. env_base.SetOption('implicit_cache', 1)
  85. env_base.__class__.android_add_maven_repository = methods.android_add_maven_repository
  86. env_base.__class__.android_add_flat_dir = methods.android_add_flat_dir
  87. env_base.__class__.android_add_dependency = methods.android_add_dependency
  88. env_base.__class__.android_add_java_dir = methods.android_add_java_dir
  89. env_base.__class__.android_add_res_dir = methods.android_add_res_dir
  90. env_base.__class__.android_add_asset_dir = methods.android_add_asset_dir
  91. env_base.__class__.android_add_aidl_dir = methods.android_add_aidl_dir
  92. env_base.__class__.android_add_jni_dir = methods.android_add_jni_dir
  93. env_base.__class__.android_add_default_config = methods.android_add_default_config
  94. env_base.__class__.android_add_to_manifest = methods.android_add_to_manifest
  95. env_base.__class__.android_add_to_permissions = methods.android_add_to_permissions
  96. env_base.__class__.android_add_to_attributes = methods.android_add_to_attributes
  97. env_base.__class__.android_add_gradle_plugin = methods.android_add_gradle_plugin
  98. env_base.__class__.android_add_gradle_classpath = methods.android_add_gradle_classpath
  99. env_base.__class__.disable_module = methods.disable_module
  100. env_base.__class__.add_module_version_string = methods.add_module_version_string
  101. env_base.__class__.add_source_files = methods.add_source_files
  102. env_base.__class__.use_windows_spawn_fix = methods.use_windows_spawn_fix
  103. env_base.__class__.split_lib = methods.split_lib
  104. env_base.__class__.add_shared_library = methods.add_shared_library
  105. env_base.__class__.add_library = methods.add_library
  106. env_base.__class__.add_program = methods.add_program
  107. env_base.__class__.CommandNoCache = methods.CommandNoCache
  108. env_base["x86_libtheora_opt_gcc"] = False
  109. env_base["x86_libtheora_opt_vc"] = False
  110. # Build options
  111. customs = ['custom.py']
  112. profile = ARGUMENTS.get("profile", False)
  113. if profile:
  114. if os.path.isfile(profile):
  115. customs.append(profile)
  116. elif os.path.isfile(profile + ".py"):
  117. customs.append(profile + ".py")
  118. opts = Variables(customs, ARGUMENTS)
  119. # Target build options
  120. opts.Add('arch', "Platform-dependent architecture (arm/arm64/x86/x64/mips/etc)", '')
  121. opts.Add(EnumVariable('bits', "Target platform bits", 'default', ('default', '32', '64', 'fat')))
  122. opts.Add('p', "Platform (alias for 'platform')", '')
  123. opts.Add('platform', "Target platform (%s)" % ('|'.join(platform_list), ), '')
  124. opts.Add(EnumVariable('target', "Compilation target", 'debug', ('debug', 'release_debug', 'release')))
  125. opts.Add(BoolVariable('tools', "Build the tools a.k.a. the Godot editor", True))
  126. opts.Add(BoolVariable('use_lto', 'Use linking time optimization', False))
  127. # Components
  128. opts.Add(BoolVariable('deprecated', "Enable deprecated features", True))
  129. opts.Add(BoolVariable('gdscript', "Build GDSCript support", True))
  130. opts.Add(BoolVariable('minizip', "Build minizip archive support", True))
  131. opts.Add(BoolVariable('xaudio2', "XAudio2 audio driver", False))
  132. opts.Add(BoolVariable('xml', "XML format support for resources", True))
  133. # Advanced options
  134. opts.Add(BoolVariable('disable_3d', "Disable 3D nodes for smaller executable", False))
  135. opts.Add(BoolVariable('disable_advanced_gui', "Disable advanced 3D gui nodes and behaviors", False))
  136. opts.Add('extra_suffix', "Custom extra suffix added to the base filename of all generated binary files", '')
  137. opts.Add('unix_global_settings_path', "UNIX-specific path to system-wide settings. Currently only used for templates", '')
  138. opts.Add(BoolVariable('verbose', "Enable verbose output for the compilation", False))
  139. opts.Add(BoolVariable('vsproj', "Generate Visual Studio Project", False))
  140. opts.Add(EnumVariable('warnings', "Set the level of warnings emitted during compilation", 'no', ('extra', 'all', 'moderate', 'no')))
  141. opts.Add(BoolVariable('progress', "Show a progress indicator during build", True))
  142. opts.Add(BoolVariable('dev', "If yes, alias for verbose=yes warnings=all", False))
  143. opts.Add(EnumVariable('macports_clang', "Build using clang from MacPorts", 'no', ('no', '5.0', 'devel')))
  144. # Thirdparty libraries
  145. opts.Add(BoolVariable('builtin_bullet', "Use the builtin bullet library", True))
  146. opts.Add(BoolVariable('builtin_enet', "Use the builtin enet library", True))
  147. opts.Add(BoolVariable('builtin_freetype', "Use the builtin freetype library", True))
  148. opts.Add(BoolVariable('builtin_libogg', "Use the builtin libogg library", True))
  149. opts.Add(BoolVariable('builtin_libpng', "Use the builtin libpng library", True))
  150. opts.Add(BoolVariable('builtin_libtheora', "Use the builtin libtheora library", True))
  151. opts.Add(BoolVariable('builtin_libvorbis', "Use the builtin libvorbis library", True))
  152. opts.Add(BoolVariable('builtin_libvpx', "Use the builtin libvpx library", True))
  153. opts.Add(BoolVariable('builtin_libwebp', "Use the builtin libwebp library", True))
  154. opts.Add(BoolVariable('builtin_openssl', "Use the builtin openssl library", True))
  155. opts.Add(BoolVariable('builtin_opus', "Use the builtin opus library", True))
  156. opts.Add(BoolVariable('builtin_pcre2', "Use the builtin pcre2 library)", True))
  157. opts.Add(BoolVariable('builtin_recast', "Use the builtin recast library", True))
  158. opts.Add(BoolVariable('builtin_squish', "Use the builtin squish library", True))
  159. opts.Add(BoolVariable('builtin_thekla_atlas', "Use the builtin thekla_altas library", True))
  160. opts.Add(BoolVariable('builtin_zlib', "Use the builtin zlib library", True))
  161. opts.Add(BoolVariable('builtin_zstd', "Use the builtin zstd library", True))
  162. opts.Add(BoolVariable('no_editor_splash', "Don't use the custom splash screen for the editor", False))
  163. # Compilation environment setup
  164. opts.Add("CXX", "C++ compiler")
  165. opts.Add("CC", "C compiler")
  166. opts.Add("LINK", "Linker")
  167. opts.Add("CCFLAGS", "Custom flags for both the C and C++ compilers")
  168. opts.Add("CXXFLAGS", "Custom flags for the C++ compiler")
  169. opts.Add("CFLAGS", "Custom flags for the C compiler")
  170. opts.Add("LINKFLAGS", "Custom flags for the linker")
  171. # add platform specific options
  172. for k in platform_opts.keys():
  173. opt_list = platform_opts[k]
  174. for o in opt_list:
  175. opts.Add(o)
  176. for x in module_list:
  177. module_enabled = True
  178. tmppath = "./modules/" + x
  179. sys.path.insert(0, tmppath)
  180. import config
  181. enabled_attr = getattr(config, "is_enabled", None)
  182. if (callable(enabled_attr) and not config.is_enabled()):
  183. module_enabled = False
  184. sys.path.remove(tmppath)
  185. sys.modules.pop('config')
  186. opts.Add(BoolVariable('module_' + x + '_enabled', "Enable module '%s'" % (x, ), module_enabled))
  187. opts.Update(env_base) # update environment
  188. Help(opts.GenerateHelpText(env_base)) # generate help
  189. # add default include paths
  190. env_base.Append(CPPPATH=['#core', '#core/math', '#editor', '#drivers', '#'])
  191. # configure ENV for platform
  192. env_base.platform_exporters = platform_exporters
  193. env_base.platform_apis = platform_apis
  194. if (env_base['target'] == 'debug'):
  195. env_base.Append(CPPFLAGS=['-DDEBUG_MEMORY_ALLOC'])
  196. env_base.Append(CPPFLAGS=['-DSCI_NAMESPACE'])
  197. if (env_base['no_editor_splash']):
  198. env_base.Append(CPPFLAGS=['-DNO_EDITOR_SPLASH'])
  199. if not env_base['deprecated']:
  200. env_base.Append(CPPFLAGS=['-DDISABLE_DEPRECATED'])
  201. env_base.platforms = {}
  202. selected_platform = ""
  203. if env_base['platform'] != "":
  204. selected_platform = env_base['platform']
  205. elif env_base['p'] != "":
  206. selected_platform = env_base['p']
  207. env_base["platform"] = selected_platform
  208. if selected_platform in platform_list:
  209. tmppath = "./platform/" + selected_platform
  210. sys.path.insert(0, tmppath)
  211. import detect
  212. if "create" in dir(detect):
  213. env = detect.create(env_base)
  214. else:
  215. env = env_base.Clone()
  216. if env['dev']:
  217. env["warnings"] = "all"
  218. env['verbose'] = True
  219. if env['vsproj']:
  220. env.vs_incs = []
  221. env.vs_srcs = []
  222. def AddToVSProject(sources):
  223. for x in sources:
  224. if type(x) == type(""):
  225. fname = env.File(x).path
  226. else:
  227. fname = env.File(x)[0].path
  228. pieces = fname.split(".")
  229. if len(pieces) > 0:
  230. basename = pieces[0]
  231. basename = basename.replace('\\\\', '/')
  232. if os.path.isfile(basename + ".h"):
  233. env.vs_incs = env.vs_incs + [basename + ".h"]
  234. elif os.path.isfile(basename + ".hpp"):
  235. env.vs_incs = env.vs_incs + [basename + ".hpp"]
  236. if os.path.isfile(basename + ".c"):
  237. env.vs_srcs = env.vs_srcs + [basename + ".c"]
  238. elif os.path.isfile(basename + ".cpp"):
  239. env.vs_srcs = env.vs_srcs + [basename + ".cpp"]
  240. env.AddToVSProject = AddToVSProject
  241. env.extra_suffix = ""
  242. if env["extra_suffix"] != '':
  243. env.extra_suffix += '.' + env["extra_suffix"]
  244. CCFLAGS = env.get('CCFLAGS', '')
  245. env['CCFLAGS'] = ''
  246. env.Append(CCFLAGS=str(CCFLAGS).split())
  247. CFLAGS = env.get('CFLAGS', '')
  248. env['CFLAGS'] = ''
  249. env.Append(CFLAGS=str(CFLAGS).split())
  250. LINKFLAGS = env.get('LINKFLAGS', '')
  251. env['LINKFLAGS'] = ''
  252. env.Append(LINKFLAGS=str(LINKFLAGS).split())
  253. flag_list = platform_flags[selected_platform]
  254. for f in flag_list:
  255. if not (f[0] in ARGUMENTS): # allow command line to override platform flags
  256. env[f[0]] = f[1]
  257. # must happen after the flags, so when flags are used by configure, stuff happens (ie, ssl on x11)
  258. detect.configure(env)
  259. if (env["warnings"] == 'yes'):
  260. print("WARNING: warnings=yes is deprecated; assuming warnings=all")
  261. env.msvc = 0
  262. if (os.name == "nt" and os.getenv("VCINSTALLDIR") and (platform_arg == "windows" or platform_arg == "uwp")): # MSVC, needs to stand out of course
  263. env.msvc = 1
  264. disable_nonessential_warnings = ['/wd4267', '/wd4244', '/wd4305', '/wd4800'] # Truncations, narrowing conversions...
  265. if (env["warnings"] == 'extra'):
  266. env.Append(CCFLAGS=['/Wall']) # Implies /W4
  267. elif (env["warnings"] == 'all' or env["warnings"] == 'yes'):
  268. env.Append(CCFLAGS=['/W3'] + disable_nonessential_warnings)
  269. elif (env["warnings"] == 'moderate'):
  270. # C4244 shouldn't be needed here being a level-3 warning, but it is
  271. env.Append(CCFLAGS=['/W2'] + disable_nonessential_warnings)
  272. else: # 'no'
  273. env.Append(CCFLAGS=['/w'])
  274. # Set exception handling model to avoid warnings caused by Windows system headers.
  275. env.Append(CCFLAGS=['/EHsc'])
  276. else: # Rest of the world
  277. if (env["warnings"] == 'extra'):
  278. env.Append(CCFLAGS=['-Wall', '-Wextra'])
  279. elif (env["warnings"] == 'all' or env["warnings"] == 'yes'):
  280. env.Append(CCFLAGS=['-Wall'])
  281. elif (env["warnings"] == 'moderate'):
  282. env.Append(CCFLAGS=['-Wall', '-Wno-unused'])
  283. else: # 'no'
  284. env.Append(CCFLAGS=['-w'])
  285. env.Append(CCFLAGS=['-Werror=return-type'])
  286. #env['platform_libsuffix'] = env['LIBSUFFIX']
  287. suffix = "." + selected_platform
  288. if (env["target"] == "release"):
  289. if env["tools"]:
  290. print("Tools can only be built with targets 'debug' and 'release_debug'.")
  291. sys.exit(255)
  292. suffix += ".opt"
  293. env.Append(CCFLAGS=['-DNDEBUG'])
  294. elif (env["target"] == "release_debug"):
  295. if env["tools"]:
  296. suffix += ".opt.tools"
  297. else:
  298. suffix += ".opt.debug"
  299. else:
  300. if env["tools"]:
  301. suffix += ".tools"
  302. else:
  303. suffix += ".debug"
  304. if env["arch"] != "":
  305. suffix += "." + env["arch"]
  306. elif (env["bits"] == "32"):
  307. suffix += ".32"
  308. elif (env["bits"] == "64"):
  309. suffix += ".64"
  310. elif (env["bits"] == "fat"):
  311. suffix += ".fat"
  312. suffix += env.extra_suffix
  313. sys.path.remove(tmppath)
  314. sys.modules.pop('detect')
  315. env.module_list = []
  316. env.doc_class_path = {}
  317. for x in module_list:
  318. if not env['module_' + x + '_enabled']:
  319. continue
  320. tmppath = "./modules/" + x
  321. sys.path.insert(0, tmppath)
  322. env.current_module = x
  323. import config
  324. if (config.can_build(selected_platform)):
  325. config.configure(env)
  326. env.module_list.append(x)
  327. try:
  328. doc_classes = config.get_doc_classes()
  329. doc_path = config.get_doc_path()
  330. for c in doc_classes:
  331. env.doc_class_path[c] = "modules/" + x + "/" + doc_path
  332. except:
  333. pass
  334. sys.path.remove(tmppath)
  335. sys.modules.pop('config')
  336. methods.update_version(env.module_version_string)
  337. env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"]
  338. env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]
  339. env["LIBSUFFIX"] = suffix + env["LIBSUFFIX"]
  340. env["SHLIBSUFFIX"] = suffix + env["SHLIBSUFFIX"]
  341. if (env.use_ptrcall):
  342. env.Append(CPPFLAGS=['-DPTRCALL_ENABLED'])
  343. # to test 64 bits compiltion
  344. # env.Append(CPPFLAGS=['-m64'])
  345. if env['tools']:
  346. env.Append(CPPFLAGS=['-DTOOLS_ENABLED'])
  347. if env['disable_3d']:
  348. env.Append(CPPFLAGS=['-D_3D_DISABLED'])
  349. if env['gdscript']:
  350. env.Append(CPPFLAGS=['-DGDSCRIPT_ENABLED'])
  351. if env['disable_advanced_gui']:
  352. env.Append(CPPFLAGS=['-DADVANCED_GUI_DISABLED'])
  353. if env['minizip']:
  354. env.Append(CPPFLAGS=['-DMINIZIP_ENABLED'])
  355. if env['xml']:
  356. env.Append(CPPFLAGS=['-DXML_ENABLED'])
  357. if not env['verbose']:
  358. methods.no_verbose(sys, env)
  359. if (not env["platform"] == "server"): # FIXME: detect GLES3
  360. env.Append( BUILDERS = { 'GLES3_GLSL' : env.Builder(action = methods.build_gles3_headers, suffix = 'glsl.gen.h',src_suffix = '.glsl') } )
  361. scons_cache_path = os.environ.get("SCONS_CACHE")
  362. if scons_cache_path != None:
  363. CacheDir(scons_cache_path)
  364. print("Scons cache enabled... (path: '" + scons_cache_path + "')")
  365. Export('env')
  366. # build subdirs, the build order is dependent on link order.
  367. SConscript("core/SCsub")
  368. SConscript("servers/SCsub")
  369. SConscript("scene/SCsub")
  370. SConscript("editor/SCsub")
  371. SConscript("drivers/SCsub")
  372. SConscript("platform/SCsub")
  373. SConscript("modules/SCsub")
  374. SConscript("main/SCsub")
  375. SConscript("platform/" + selected_platform + "/SCsub") # build selected platform
  376. # Microsoft Visual Studio Project Generation
  377. if env['vsproj']:
  378. env['CPPPATH'] = [Dir(path) for path in env['CPPPATH']]
  379. methods.generate_vs_project(env, GetOption("num_jobs"))
  380. methods.generate_cpp_hint_file("cpp.hint")
  381. # Check for the existence of headers
  382. conf = Configure(env)
  383. if ("check_c_headers" in env):
  384. for header in env["check_c_headers"]:
  385. if (conf.CheckCHeader(header[0])):
  386. if (env.msvc):
  387. env.Append(CCFLAGS=['/D' + header[1]])
  388. else:
  389. env.Append(CCFLAGS=['-D' + header[1]])
  390. else:
  391. print("No valid target platform selected.")
  392. print("The following were detected:")
  393. for x in platform_list:
  394. print("\t" + x)
  395. print("\nPlease run scons again with argument: platform=<string>")
  396. # The following only makes sense when the env is defined, and assumes it is
  397. if 'env' in locals():
  398. screen = sys.stdout
  399. # Progress reporting is not available in non-TTY environments since it
  400. # messes with the output (for example, when writing to a file)
  401. show_progress = (env['progress'] and sys.stdout.isatty())
  402. node_count = 0
  403. node_count_max = 0
  404. node_count_interval = 1
  405. node_count_fname = str(env.Dir('#')) + '/.scons_node_count'
  406. import time, math
  407. class cache_progress:
  408. # The default is 1 GB cache and 12 hours half life
  409. def __init__(self, path = None, limit = 1073741824, half_life = 43200):
  410. self.path = path
  411. self.limit = limit
  412. self.exponent_scale = math.log(2) / half_life
  413. if env['verbose'] and path != None:
  414. screen.write('Current cache limit is ' + self.convert_size(limit) + ' (used: ' + self.convert_size(self.get_size(path)) + ')\n')
  415. self.delete(self.file_list())
  416. def __call__(self, node, *args, **kw):
  417. global node_count, node_count_max, node_count_interval, node_count_fname, show_progress
  418. if show_progress:
  419. # Print the progress percentage
  420. node_count += node_count_interval
  421. if (node_count_max > 0 and node_count <= node_count_max):
  422. screen.write('\r[%3d%%] ' % (node_count * 100 / node_count_max))
  423. screen.flush()
  424. elif (node_count_max > 0 and node_count > node_count_max):
  425. screen.write('\r[100%] ')
  426. screen.flush()
  427. else:
  428. screen.write('\r[Initial build] ')
  429. screen.flush()
  430. def delete(self, files):
  431. if len(files) == 0:
  432. return
  433. if env['verbose']:
  434. # Utter something
  435. screen.write('\rPurging %d %s from cache...\n' % (len(files), len(files) > 1 and 'files' or 'file'))
  436. [os.remove(f) for f in files]
  437. def file_list(self):
  438. if self.path == None:
  439. # Nothing to do
  440. return []
  441. # Gather a list of (filename, (size, atime)) within the
  442. # cache directory
  443. file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, '*', '*'))]
  444. if file_stat == []:
  445. # Nothing to do
  446. return []
  447. # Weight the cache files by size (assumed to be roughly
  448. # proportional to the recompilation time) times an exponential
  449. # decay since the ctime, and return a list with the entries
  450. # (filename, size, weight).
  451. current_time = time.time()
  452. file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat]
  453. # Sort by the most resently accessed files (most sensible to keep) first
  454. file_stat.sort(key=lambda x: x[2])
  455. # Search for the first entry where the storage limit is
  456. # reached
  457. sum, mark = 0, None
  458. for i,x in enumerate(file_stat):
  459. sum += x[1]
  460. if sum > self.limit:
  461. mark = i
  462. break
  463. if mark == None:
  464. return []
  465. else:
  466. return [x[0] for x in file_stat[mark:]]
  467. def convert_size(self, size_bytes):
  468. if size_bytes == 0:
  469. return "0 bytes"
  470. size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
  471. i = int(math.floor(math.log(size_bytes, 1024)))
  472. p = math.pow(1024, i)
  473. s = round(size_bytes / p, 2)
  474. return "%s %s" % (int(s) if i == 0 else s, size_name[i])
  475. def get_size(self, start_path = '.'):
  476. total_size = 0
  477. for dirpath, dirnames, filenames in os.walk(start_path):
  478. for f in filenames:
  479. fp = os.path.join(dirpath, f)
  480. total_size += os.path.getsize(fp)
  481. return total_size
  482. def progress_finish(target, source, env):
  483. global node_count, progressor
  484. with open(node_count_fname, 'w') as f:
  485. f.write('%d\n' % node_count)
  486. progressor.delete(progressor.file_list())
  487. try:
  488. with open(node_count_fname) as f:
  489. node_count_max = int(f.readline())
  490. except:
  491. pass
  492. cache_directory = os.environ.get("SCONS_CACHE")
  493. # Simple cache pruning, attached to SCons' progress callback. Trim the
  494. # cache directory to a size not larger than cache_limit.
  495. cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
  496. progressor = cache_progress(cache_directory, cache_limit)
  497. Progress(progressor, interval = node_count_interval)
  498. progress_finish_command = Command('progress_finish', [], progress_finish)
  499. AlwaysBuild(progress_finish_command)