You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
11 KiB

6 years ago
  1. #
  2. # Copyright (c) 2001 - 2017 The SCons Foundation
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included
  13. # in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  16. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  17. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. #
  23. __revision__ = "src/engine/SCons/CacheDir.py rel_3.0.0:4395:8972f6a2f699 2017/09/18 12:59:24 bdbaddog"
  24. __doc__ = """
  25. CacheDir support
  26. """
  27. import json
  28. import os
  29. import stat
  30. import sys
  31. import SCons.Action
  32. import SCons.Warnings
  33. cache_enabled = True
  34. cache_debug = False
  35. cache_force = False
  36. cache_show = False
  37. cache_readonly = False
  38. def CacheRetrieveFunc(target, source, env):
  39. t = target[0]
  40. fs = t.fs
  41. cd = env.get_CacheDir()
  42. cachedir, cachefile = cd.cachepath(t)
  43. if not fs.exists(cachefile):
  44. cd.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
  45. return 1
  46. cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
  47. if SCons.Action.execute_actions:
  48. if fs.islink(cachefile):
  49. fs.symlink(fs.readlink(cachefile), t.get_internal_path())
  50. else:
  51. env.copy_from_cache(cachefile, t.get_internal_path())
  52. st = fs.stat(cachefile)
  53. fs.chmod(t.get_internal_path(), stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
  54. return 0
  55. def CacheRetrieveString(target, source, env):
  56. t = target[0]
  57. fs = t.fs
  58. cd = env.get_CacheDir()
  59. cachedir, cachefile = cd.cachepath(t)
  60. if t.fs.exists(cachefile):
  61. return "Retrieved `%s' from cache" % t.get_internal_path()
  62. return None
  63. CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
  64. CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
  65. def CachePushFunc(target, source, env):
  66. if cache_readonly:
  67. return
  68. t = target[0]
  69. if t.nocache:
  70. return
  71. fs = t.fs
  72. cd = env.get_CacheDir()
  73. cachedir, cachefile = cd.cachepath(t)
  74. if fs.exists(cachefile):
  75. # Don't bother copying it if it's already there. Note that
  76. # usually this "shouldn't happen" because if the file already
  77. # existed in cache, we'd have retrieved the file from there,
  78. # not built it. This can happen, though, in a race, if some
  79. # other person running the same build pushes their copy to
  80. # the cache after we decide we need to build it but before our
  81. # build completes.
  82. cd.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
  83. return
  84. cd.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
  85. tempfile = cachefile+'.tmp'+str(os.getpid())
  86. errfmt = "Unable to copy %s to cache. Cache file is %s"
  87. if not fs.isdir(cachedir):
  88. try:
  89. fs.makedirs(cachedir)
  90. except EnvironmentError:
  91. # We may have received an exception because another process
  92. # has beaten us creating the directory.
  93. if not fs.isdir(cachedir):
  94. msg = errfmt % (str(target), cachefile)
  95. raise SCons.Errors.EnvironmentError(msg)
  96. try:
  97. if fs.islink(t.get_internal_path()):
  98. fs.symlink(fs.readlink(t.get_internal_path()), tempfile)
  99. else:
  100. fs.copy2(t.get_internal_path(), tempfile)
  101. fs.rename(tempfile, cachefile)
  102. st = fs.stat(t.get_internal_path())
  103. fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
  104. except EnvironmentError:
  105. # It's possible someone else tried writing the file at the
  106. # same time we did, or else that there was some problem like
  107. # the CacheDir being on a separate file system that's full.
  108. # In any case, inability to push a file to cache doesn't affect
  109. # the correctness of the build, so just print a warning.
  110. msg = errfmt % (str(target), cachefile)
  111. SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg)
  112. CachePush = SCons.Action.Action(CachePushFunc, None)
  113. # Nasty hack to cut down to one warning for each cachedir path that needs
  114. # upgrading.
  115. warned = dict()
  116. class CacheDir(object):
  117. def __init__(self, path):
  118. try:
  119. import hashlib
  120. except ImportError:
  121. msg = "No hashlib or MD5 module available, CacheDir() not supported"
  122. SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
  123. path = None
  124. self.path = path
  125. self.current_cache_debug = None
  126. self.debugFP = None
  127. self.config = dict()
  128. if path is None:
  129. return
  130. # See if there's a config file in the cache directory. If there is,
  131. # use it. If there isn't, and the directory exists and isn't empty,
  132. # produce a warning. If the directory doesn't exist or is empty,
  133. # write a config file.
  134. config_file = os.path.join(path, 'config')
  135. if not os.path.exists(config_file):
  136. # A note: There is a race hazard here, if two processes start and
  137. # attempt to create the cache directory at the same time. However,
  138. # python doesn't really give you the option to do exclusive file
  139. # creation (it doesn't even give you the option to error on opening
  140. # an existing file for writing...). The ordering of events here
  141. # as an attempt to alleviate this, on the basis that it's a pretty
  142. # unlikely occurence (it'd require two builds with a brand new cache
  143. # directory)
  144. if os.path.isdir(path) and len(os.listdir(path)) != 0:
  145. self.config['prefix_len'] = 1
  146. # When building the project I was testing this on, the warning
  147. # was output over 20 times. That seems excessive
  148. global warned
  149. if self.path not in warned:
  150. msg = "Please upgrade your cache by running " +\
  151. " scons-configure-cache.py " + self.path
  152. SCons.Warnings.warn(SCons.Warnings.CacheVersionWarning, msg)
  153. warned[self.path] = True
  154. else:
  155. if not os.path.isdir(path):
  156. try:
  157. os.makedirs(path)
  158. except OSError:
  159. # If someone else is trying to create the directory at
  160. # the same time as me, bad things will happen
  161. msg = "Failed to create cache directory " + path
  162. raise SCons.Errors.EnvironmentError(msg)
  163. self.config['prefix_len'] = 2
  164. if not os.path.exists(config_file):
  165. try:
  166. with open(config_file, 'w') as config:
  167. json.dump(self.config, config)
  168. except:
  169. msg = "Failed to write cache configuration for " + path
  170. raise SCons.Errors.EnvironmentError(msg)
  171. else:
  172. try:
  173. with open(config_file) as config:
  174. self.config = json.load(config)
  175. except ValueError:
  176. msg = "Failed to read cache configuration for " + path
  177. raise SCons.Errors.EnvironmentError(msg)
  178. def CacheDebug(self, fmt, target, cachefile):
  179. if cache_debug != self.current_cache_debug:
  180. if cache_debug == '-':
  181. self.debugFP = sys.stdout
  182. elif cache_debug:
  183. self.debugFP = open(cache_debug, 'w')
  184. else:
  185. self.debugFP = None
  186. self.current_cache_debug = cache_debug
  187. if self.debugFP:
  188. self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
  189. def is_enabled(self):
  190. return cache_enabled and not self.path is None
  191. def is_readonly(self):
  192. return cache_readonly
  193. def cachepath(self, node):
  194. """
  195. """
  196. if not self.is_enabled():
  197. return None, None
  198. sig = node.get_cachedir_bsig()
  199. subdir = sig[:self.config['prefix_len']].upper()
  200. dir = os.path.join(self.path, subdir)
  201. return dir, os.path.join(dir, sig)
  202. def retrieve(self, node):
  203. """
  204. This method is called from multiple threads in a parallel build,
  205. so only do thread safe stuff here. Do thread unsafe stuff in
  206. built().
  207. Note that there's a special trick here with the execute flag
  208. (one that's not normally done for other actions). Basically
  209. if the user requested a no_exec (-n) build, then
  210. SCons.Action.execute_actions is set to 0 and when any action
  211. is called, it does its showing but then just returns zero
  212. instead of actually calling the action execution operation.
  213. The problem for caching is that if the file does NOT exist in
  214. cache then the CacheRetrieveString won't return anything to
  215. show for the task, but the Action.__call__ won't call
  216. CacheRetrieveFunc; instead it just returns zero, which makes
  217. the code below think that the file *was* successfully
  218. retrieved from the cache, therefore it doesn't do any
  219. subsequent building. However, the CacheRetrieveString didn't
  220. print anything because it didn't actually exist in the cache,
  221. and no more build actions will be performed, so the user just
  222. sees nothing. The fix is to tell Action.__call__ to always
  223. execute the CacheRetrieveFunc and then have the latter
  224. explicitly check SCons.Action.execute_actions itself.
  225. """
  226. if not self.is_enabled():
  227. return False
  228. env = node.get_build_env()
  229. if cache_show:
  230. if CacheRetrieveSilent(node, [], env, execute=1) == 0:
  231. node.build(presub=0, execute=0)
  232. return True
  233. else:
  234. if CacheRetrieve(node, [], env, execute=1) == 0:
  235. return True
  236. return False
  237. def push(self, node):
  238. if self.is_readonly() or not self.is_enabled():
  239. return
  240. return CachePush(node, [], node.get_build_env())
  241. def push_if_forced(self, node):
  242. if cache_force:
  243. return self.push(node)
  244. # Local Variables:
  245. # tab-width:4
  246. # indent-tabs-mode:nil
  247. # End:
  248. # vim: set expandtab tabstop=4 shiftwidth=4: