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:
12 # The above copyright notice and this permission notice shall be included
13 # in all copies or substantial portions of the Software.
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.
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
41 def CacheRetrieveFunc(target, source, env):
44 cd = env.get_CacheDir()
45 cachedir, cachefile = cd.cachepath(t)
46 if not fs.exists(cachefile):
47 cd.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile)
49 cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile)
50 if SCons.Action.execute_actions:
51 if fs.islink(cachefile):
52 fs.symlink(fs.readlink(cachefile), t.path)
54 fs.copy2(cachefile, t.path)
55 st = fs.stat(cachefile)
56 fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
59 def CacheRetrieveString(target, source, env):
62 cd = env.get_CacheDir()
63 cachedir, cachefile = cd.cachepath(t)
64 if t.fs.exists(cachefile):
65 return "Retrieved `%s' from cache" % t.path
68 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
70 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
72 def CachePushFunc(target, source, env):
77 cd = env.get_CacheDir()
78 cachedir, cachefile = cd.cachepath(t)
79 if fs.exists(cachefile):
80 # Don't bother copying it if it's already there. Note that
81 # usually this "shouldn't happen" because if the file already
82 # existed in cache, we'd have retrieved the file from there,
83 # not built it. This can happen, though, in a race, if some
84 # other person running the same build pushes their copy to
85 # the cache after we decide we need to build it but before our
87 cd.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile)
90 cd.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile)
92 tempfile = cachefile+'.tmp'+str(os.getpid())
93 errfmt = "Unable to copy %s to cache. Cache file is %s"
95 if not fs.isdir(cachedir):
98 except EnvironmentError:
99 # We may have received an exception because another process
100 # has beaten us creating the directory.
101 if not fs.isdir(cachedir):
102 msg = errfmt % (str(target), cachefile)
103 raise SCons.Errors.EnvironmentError, msg
106 if fs.islink(t.path):
107 fs.symlink(fs.readlink(t.path), tempfile)
109 fs.copy2(t.path, tempfile)
110 fs.rename(tempfile, cachefile)
112 fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
113 except EnvironmentError:
114 # It's possible someone else tried writing the file at the
115 # same time we did, or else that there was some problem like
116 # the CacheDir being on a separate file system that's full.
117 # In any case, inability to push a file to cache doesn't affect
118 # the correctness of the build, so just print a warning.
119 msg = errfmt % (str(target), cachefile)
120 SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg)
122 CachePush = SCons.Action.Action(CachePushFunc, None)
126 def __init__(self, path):
130 msg = "No hashlib or MD5 module available, CacheDir() not supported"
131 SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
135 def CacheDebugWrite(self, fmt, target, cachefile):
136 self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
138 def CacheDebugQuiet(self, fmt, target, cachefile):
141 def CacheDebugInit(self, fmt, target, cachefile):
143 if cache_debug == '-':
144 self.debugFP = sys.stdout
146 self.debugFP = open(cache_debug, 'w')
147 self.CacheDebug = self.CacheDebugWrite
148 self.CacheDebug(fmt, target, cachefile)
150 self.CacheDebug = self.CacheDebugQuiet
152 CacheDebug = CacheDebugInit
154 def cachepath(self, node):
157 sig = node.get_cachedir_bsig()
158 subdir = string.upper(sig[0])
159 dir = os.path.join(self.path, subdir)
160 return dir, os.path.join(dir, sig)
162 def retrieve(self, node):
164 This method is called from multiple threads in a parallel build,
165 so only do thread safe stuff here. Do thread unsafe stuff in
168 Note that there's a special trick here with the execute flag
169 (one that's not normally done for other actions). Basically
170 if the user requested a no_exec (-n) build, then
171 SCons.Action.execute_actions is set to 0 and when any action
172 is called, it does its showing but then just returns zero
173 instead of actually calling the action execution operation.
174 The problem for caching is that if the file does NOT exist in
175 cache then the CacheRetrieveString won't return anything to
176 show for the task, but the Action.__call__ won't call
177 CacheRetrieveFunc; instead it just returns zero, which makes
178 the code below think that the file *was* successfully
179 retrieved from the cache, therefore it doesn't do any
180 subsequent building. However, the CacheRetrieveString didn't
181 print anything because it didn't actually exist in the cache,
182 and no more build actions will be performed, so the user just
183 sees nothing. The fix is to tell Action.__call__ to always
184 execute the CacheRetrieveFunc and then have the latter
185 explicitly check SCons.Action.execute_actions itself.
190 if CacheRetrieveSilent(node, [], node.get_build_env(), execute=1) == 0:
191 node.build(presub=0, execute=0)
194 if CacheRetrieve(node, [], node.get_build_env(), execute=1) == 0:
197 # Record build signature information, but don't
198 # push it out to cache. (We just got it from there!)
199 node.set_state(SCons.Node.executed)
200 SCons.Node.Node.built(node)
204 def push(self, node):
205 return CachePush(node, [], node.get_build_env())
207 def push_if_forced(self, node):
209 return self.push(node)
211 class Null(SCons.Util.Null):
213 return 'CacheDir.Null()'
214 def cachepath(self, node):
216 def retrieve(self, node):