1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 __revision__ = "src/engine/SCons/CacheDir.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"
25
26 __doc__ = """
27 CacheDir support
28 """
29
30 import hashlib
31 import json
32 import os
33 import stat
34 import sys
35
36 import SCons.Action
37 import SCons.Warnings
38 from SCons.Util import PY3
39
40 cache_enabled = True
41 cache_debug = False
42 cache_force = False
43 cache_show = False
44 cache_readonly = False
69
78
79 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
80
81 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
84 if cache_readonly:
85 return
86
87 t = target[0]
88 if t.nocache:
89 return
90 fs = t.fs
91 cd = env.get_CacheDir()
92 cachedir, cachefile = cd.cachepath(t)
93 if fs.exists(cachefile):
94
95
96
97
98
99
100
101 cd.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
102 return
103
104 cd.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
105
106 tempfile = cachefile+'.tmp'+str(os.getpid())
107 errfmt = "Unable to copy %s to cache. Cache file is %s"
108
109 if not fs.isdir(cachedir):
110 try:
111 fs.makedirs(cachedir)
112 except EnvironmentError:
113
114
115 if not fs.isdir(cachedir):
116 msg = errfmt % (str(target), cachefile)
117 raise SCons.Errors.SConsEnvironmentError(msg)
118
119 try:
120 if fs.islink(t.get_internal_path()):
121 fs.symlink(fs.readlink(t.get_internal_path()), tempfile)
122 else:
123 fs.copy2(t.get_internal_path(), tempfile)
124 fs.rename(tempfile, cachefile)
125 st = fs.stat(t.get_internal_path())
126 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
127 except EnvironmentError:
128
129
130
131
132
133 msg = errfmt % (str(target), cachefile)
134 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg)
135
136 CachePush = SCons.Action.Action(CachePushFunc, None)
137
138
139
140 warned = dict()
143
145 """
146 Initialize a CacheDir object.
147
148 The cache configuration is stored in the object. It
149 is read from the config file in the supplied path if
150 one exists, if not the config file is created and
151 the default config is written, as well as saved in the object.
152 """
153 self.requests = 0
154 self.hits = 0
155 self.path = path
156 self.current_cache_debug = None
157 self.debugFP = None
158 self.config = dict()
159 if path is None:
160 return
161
162 if PY3:
163 self._readconfig3(path)
164 else:
165 self._readconfig2(path)
166
167
169 """
170 Python3 version of reading the cache config.
171
172 If directory or config file do not exist, create. Take advantage
173 of Py3 capability in os.makedirs() and in file open(): just try
174 the operation and handle failure appropriately.
175
176 Omit the check for old cache format, assume that's old enough
177 there will be none of those left to worry about.
178
179 :param path: path to the cache directory
180 """
181 config_file = os.path.join(path, 'config')
182 try:
183 os.makedirs(path, exist_ok=True)
184 except FileExistsError:
185 pass
186 except OSError:
187 msg = "Failed to create cache directory " + path
188 raise SCons.Errors.EnvironmentError(msg)
189
190 try:
191 with open(config_file, 'x') as config:
192 self.config['prefix_len'] = 2
193 try:
194 json.dump(self.config, config)
195 except Exception:
196 msg = "Failed to write cache configuration for " + path
197 raise SCons.Errors.EnvironmentError(msg)
198 except FileExistsError:
199 try:
200 with open(config_file) as config:
201 self.config = json.load(config)
202 except ValueError:
203 msg = "Failed to read cache configuration for " + path
204 raise SCons.Errors.EnvironmentError(msg)
205
206
208 """
209 Python2 version of reading cache config.
210
211 See if there is a config file in the cache directory. If there is,
212 use it. If there isn't, and the directory exists and isn't empty,
213 produce a warning. If the directory does not exist or is empty,
214 write a config file.
215
216 :param path: path to the cache directory
217 """
218 config_file = os.path.join(path, 'config')
219 if not os.path.exists(config_file):
220
221
222
223
224
225
226
227
228 if os.path.isdir(path) and any(f != "config" for f in os.listdir(path)):
229 self.config['prefix_len'] = 1
230
231
232 global warned
233 if self.path not in warned:
234 msg = "Please upgrade your cache by running " +\
235 "scons-configure-cache.py " + self.path
236 SCons.Warnings.warn(SCons.Warnings.CacheVersionWarning, msg)
237 warned[self.path] = True
238 else:
239 if not os.path.isdir(path):
240 try:
241 os.makedirs(path)
242 except OSError:
243
244
245 msg = "Failed to create cache directory " + path
246 raise SCons.Errors.SConsEnvironmentError(msg)
247
248 self.config['prefix_len'] = 2
249 if not os.path.exists(config_file):
250 try:
251 with open(config_file, 'w') as config:
252 json.dump(self.config, config)
253 except Exception:
254 msg = "Failed to write cache configuration for " + path
255 raise SCons.Errors.SConsEnvironmentError(msg)
256 else:
257 try:
258 with open(config_file) as config:
259 self.config = json.load(config)
260 except ValueError:
261 msg = "Failed to read cache configuration for " + path
262 raise SCons.Errors.SConsEnvironmentError(msg)
263
264
266 if cache_debug != self.current_cache_debug:
267 if cache_debug == '-':
268 self.debugFP = sys.stdout
269 elif cache_debug:
270 self.debugFP = open(cache_debug, 'w')
271 else:
272 self.debugFP = None
273 self.current_cache_debug = cache_debug
274 if self.debugFP:
275 self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
276 self.debugFP.write("requests: %d, hits: %d, misses: %d, hit rate: %.2f%%\n" %
277 (self.requests, self.hits, self.misses, self.hit_ratio))
278
279 @property
281 return (100.0 * self.hits / self.requests if self.requests > 0 else 100)
282
283 @property
285 return self.requests - self.hits
286
289
292
294 """
295 """
296 if not self.is_enabled():
297 return None, None
298
299 sig = node.get_cachedir_bsig()
300
301 subdir = sig[:self.config['prefix_len']].upper()
302
303 dir = os.path.join(self.path, subdir)
304 return dir, os.path.join(dir, sig)
305
307 """
308 This method is called from multiple threads in a parallel build,
309 so only do thread safe stuff here. Do thread unsafe stuff in
310 built().
311
312 Note that there's a special trick here with the execute flag
313 (one that's not normally done for other actions). Basically
314 if the user requested a no_exec (-n) build, then
315 SCons.Action.execute_actions is set to 0 and when any action
316 is called, it does its showing but then just returns zero
317 instead of actually calling the action execution operation.
318 The problem for caching is that if the file does NOT exist in
319 cache then the CacheRetrieveString won't return anything to
320 show for the task, but the Action.__call__ won't call
321 CacheRetrieveFunc; instead it just returns zero, which makes
322 the code below think that the file *was* successfully
323 retrieved from the cache, therefore it doesn't do any
324 subsequent building. However, the CacheRetrieveString didn't
325 print anything because it didn't actually exist in the cache,
326 and no more build actions will be performed, so the user just
327 sees nothing. The fix is to tell Action.__call__ to always
328 execute the CacheRetrieveFunc and then have the latter
329 explicitly check SCons.Action.execute_actions itself.
330 """
331 if not self.is_enabled():
332 return False
333
334 env = node.get_build_env()
335 if cache_show:
336 if CacheRetrieveSilent(node, [], env, execute=1) == 0:
337 node.build(presub=0, execute=0)
338 return True
339 else:
340 if CacheRetrieve(node, [], env, execute=1) == 0:
341 return True
342
343 return False
344
345 - def push(self, node):
349
353
354
355
356
357
358
359