Update items for 0.97.0d20071212.
[scons.git] / src / engine / SCons / CacheDir.py
1 #
2 # __COPYRIGHT__
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
24 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
25
26 __doc__ = """
27 CacheDir support
28 """
29
30 import os.path
31 import stat
32 import string
33 import sys
34
35 import SCons.Action
36
37 cache_debug = False
38 cache_force = False
39 cache_show = False
40
41 def CacheRetrieveFunc(target, source, env):
42     t = target[0]
43     fs = t.fs
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)
48         return 1
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)
53         else:
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)
57     return 0
58
59 def CacheRetrieveString(target, source, env):
60     t = target[0]
61     fs = t.fs
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
66     return None
67
68 CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString)
69
70 CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None)
71
72 def CachePushFunc(target, source, env):
73     t = target[0]
74     if t.nocache:
75         return
76     fs = t.fs
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
86         # build completes.
87         cd.CacheDebug('CachePush(%s):  %s already exists in cache\n', t, cachefile)
88         return
89
90     cd.CacheDebug('CachePush(%s):  pushing to %s\n', t, cachefile)
91
92     tempfile = cachefile+'.tmp'+str(os.getpid())
93     errfmt = "Unable to copy %s to cache. Cache file is %s"
94
95     if not fs.isdir(cachedir):
96         try:
97             fs.makedirs(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
104
105     try:
106         if fs.islink(t.path):
107             fs.symlink(fs.readlink(t.path), tempfile)
108         else:
109             fs.copy2(t.path, tempfile)
110         fs.rename(tempfile, cachefile)
111         st = fs.stat(t.path)
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)
121
122 CachePush = SCons.Action.Action(CachePushFunc, None)
123
124 class CacheDir:
125
126     def __init__(self, path):
127         try:
128             import hashlib
129         except ImportError:
130             msg = "No hashlib or MD5 module available, CacheDir() not supported"
131             SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg)
132         else:
133             self.path = path
134
135     def CacheDebugWrite(self, fmt, target, cachefile):
136         self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
137
138     def CacheDebugQuiet(self, fmt, target, cachefile):
139         pass
140
141     def CacheDebugInit(self, fmt, target, cachefile):
142         if cache_debug:
143             if cache_debug == '-':
144                 self.debugFP = sys.stdout
145             else:
146                 self.debugFP = open(cache_debug, 'w')
147             self.CacheDebug = self.CacheDebugWrite
148             self.CacheDebug(fmt, target, cachefile)
149         else:
150             self.CacheDebug = self.CacheDebugQuiet
151
152     CacheDebug = CacheDebugInit
153
154     def cachepath(self, node):
155         """
156         """
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)
161
162     def retrieve(self, node):
163         """
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
166         built().
167
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.
186         """
187         retrieved = False
188
189         if cache_show:
190             if CacheRetrieveSilent(node, [], node.get_build_env(), execute=1) == 0:
191                 node.build(presub=0, execute=0)
192                 retrieved = 1
193         else:
194             if CacheRetrieve(node, [], node.get_build_env(), execute=1) == 0:
195                 retrieved = 1
196         if retrieved:
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)
201
202         return retrieved
203
204     def push(self, node):
205         return CachePush(node, [], node.get_build_env())
206
207     def push_if_forced(self, node):
208         if cache_force:
209             return self.push(node)
210
211 class Null(SCons.Util.Null):
212     def repr(self):
213         return 'CacheDir.Null()'
214     def cachepath(self, node):
215         return None, None
216     def retrieve(self, node):
217         return False