3 Writing and reading information to the .sconsign file or files.
8 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
10 # Permission is hereby granted, free of charge, to any person obtaining
11 # a copy of this software and associated documentation files (the
12 # "Software"), to deal in the Software without restriction, including
13 # without limitation the rights to use, copy, modify, merge, publish,
14 # distribute, sublicense, and/or sell copies of the Software, and to
15 # permit persons to whom the Software is furnished to do so, subject to
16 # the following conditions:
18 # The above copyright notice and this permission notice shall be included
19 # in all copies or substantial portions of the Software.
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
22 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
23 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 __revision__ = "src/engine/SCons/SConsign.py 3842 2008/12/20 22:59:52 scons"
39 def corrupt_dblite_warning(filename):
40 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
41 "Ignoring corrupt .sconsign file: %s"%filename)
43 SCons.dblite.ignore_corrupt_dbfiles = 1
44 SCons.dblite.corruption_warning = corrupt_dblite_warning
46 #XXX Get rid of the global array so this becomes re-entrant.
49 # Info for the database SConsign implementation (now the default):
50 # "DataBase" is a dictionary that maps top-level SConstruct directories
51 # to open database handles.
52 # "DB_Module" is the Python database module to create the handles.
53 # "DB_Name" is the base name of the database file (minus any
54 # extension the underlying DB module will add).
56 DB_Module = SCons.dblite
60 def Get_DataBase(dir):
61 global DataBase, DB_Module, DB_Name
63 if not os.path.isabs(DB_Name) and top.repositories:
65 for d in [top] + top.repositories:
68 return DataBase[d], mode
70 path = d.entry_abspath(DB_Name)
71 try: db = DataBase[d] = DB_Module.open(path, mode)
72 except (IOError, OSError): pass
75 DB_sync_list.append(db)
79 return DataBase[top], "c"
81 db = DataBase[top] = DB_Module.open(DB_Name, "c")
82 DB_sync_list.append(db)
85 print "DataBase =", DataBase
89 """Reset global state. Used by unit tests that end up using
90 SConsign multiple times to get a clean slate for each test."""
91 global sig_files, DB_sync_list
95 normcase = os.path.normcase
99 for sig_file in sig_files:
100 sig_file.write(sync=0)
101 for db in DB_sync_list:
104 except AttributeError:
105 pass # Not all anydbm modules have sync() methods.
111 Wrapper class for the generic entry in a .sconsign file.
112 The Node subclass populates it with attributes as it pleases.
114 XXX As coded below, we do expect a '.binfo' attribute to be added,
115 but we'll probably generalize this in the next refactorings.
117 current_version_id = 1
119 # Create an object attribute from the class attribute so it ends up
120 # in the pickled data in the .sconsign file.
121 _version_id = self.current_version_id
122 def convert_to_sconsign(self):
123 self.binfo.convert_to_sconsign()
124 def convert_from_sconsign(self, dir, name):
125 self.binfo.convert_from_sconsign(dir, name)
129 This is the controlling class for the signatures for the collection of
130 entries associated with a specific directory. The actual directory
131 association will be maintained by a subclass that is specific to
132 the underlying storage method. This class provides a common set of
133 methods for fetching and storing the individual bits of information
134 that make up signature entry.
139 self.to_be_merged = {}
141 def get_entry(self, filename):
143 Fetch the specified entry attribute.
145 return self.entries[filename]
147 def set_entry(self, filename, obj):
151 self.entries[filename] = obj
154 def do_not_set_entry(self, filename, obj):
157 def store_info(self, filename, node):
158 entry = node.get_stored_info()
159 entry.binfo.merge(node.get_binfo())
160 self.to_be_merged[filename] = node
163 def do_not_store_info(self, filename, node):
167 for key, node in self.to_be_merged.items():
168 entry = node.get_stored_info()
171 except AttributeError:
172 # This happens with SConf Nodes, because the configuration
173 # subsystem takes direct control over how the build decision
174 # is made and its information stored.
177 ninfo.merge(node.get_ninfo())
178 self.entries[key] = entry
179 self.to_be_merged = {}
183 A Base subclass that reads and writes signature information
184 from a global .sconsign.db* file--the actual file suffix is
185 determined by the database module.
187 def __init__(self, dir):
192 db, mode = Get_DataBase(dir)
194 # Read using the path relative to the top of the Repository
195 # (self.dir.tpath) from which we're fetching the signature
197 path = normcase(dir.tpath)
199 rawentries = db[path]
204 self.entries = cPickle.loads(rawentries)
205 if type(self.entries) is not type({}):
208 except KeyboardInterrupt:
211 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
212 "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e))
213 for key, entry in self.entries.items():
214 entry.convert_from_sconsign(dir, key)
217 # This directory is actually under a repository, which means
218 # likely they're reaching in directly for a dependency on
219 # a file there. Don't actually set any entry info, so we
220 # won't try to write to that .sconsign.dblite file.
221 self.set_entry = self.do_not_set_entry
222 self.store_info = self.do_not_store_info
225 sig_files.append(self)
227 def write(self, sync=1):
233 db, mode = Get_DataBase(self.dir)
235 # Write using the path relative to the top of the SConstruct
236 # directory (self.dir.path), not relative to the top of
237 # the Repository; we only write to our own .sconsign file,
238 # not to .sconsign files in Repositories.
239 path = normcase(self.dir.path)
240 for key, entry in self.entries.items():
241 entry.convert_to_sconsign()
242 db[path] = cPickle.dumps(self.entries, 1)
247 except AttributeError:
248 # Not all anydbm modules have sync() methods.
254 def __init__(self, fp=None, dir=None):
256 fp - file pointer to read entries from
263 self.entries = cPickle.load(fp)
264 if type(self.entries) is not type({}):
269 for key, entry in self.entries.items():
270 entry.convert_from_sconsign(dir, key)
274 Encapsulates reading and writing a per-directory .sconsign file.
276 def __init__(self, dir):
278 dir - the directory for the file
282 self.sconsign = os.path.join(dir.path, '.sconsign')
285 fp = open(self.sconsign, 'rb')
290 Dir.__init__(self, fp, dir)
291 except KeyboardInterrupt:
294 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
295 "Ignoring corrupt .sconsign file: %s"%self.sconsign)
298 sig_files.append(self)
300 def write(self, sync=1):
302 Write the .sconsign file to disk.
304 Try to write to a temporary file first, and rename it if we
305 succeed. If we can't write to the temporary file, it's
306 probably because the directory isn't writable (and if so,
307 how did we build anything in this directory, anyway?), so
308 try to write directly to the .sconsign file as a backup.
309 If we can't rename, try to copy the temporary contents back
310 to the .sconsign file. Either way, always try to remove
311 the temporary file at the end.
318 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
320 file = open(temp, 'wb')
324 file = open(self.sconsign, 'wb')
325 fname = self.sconsign
328 for key, entry in self.entries.items():
329 entry.convert_to_sconsign()
330 cPickle.dump(self.entries, file, 1)
332 if fname != self.sconsign:
334 mode = os.stat(self.sconsign)[0]
335 os.chmod(self.sconsign, 0666)
336 os.unlink(self.sconsign)
337 except (IOError, OSError):
338 # Try to carry on in the face of either OSError
339 # (things like permission issues) or IOError (disk
340 # or network issues). If there's a really dangerous
341 # issue, it should get re-raised by the calls below.
344 os.rename(fname, self.sconsign)
346 # An OSError failure to rename may indicate something
347 # like the directory has no write permission, but
348 # the .sconsign file itself might still be writable,
349 # so try writing on top of it directly. An IOError
350 # here, or in any of the following calls, would get
351 # raised, indicating something like a potentially
352 # serious disk or network issue.
353 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
354 os.chmod(self.sconsign, mode)
357 except (IOError, OSError):
362 def File(name, dbm_module=None):
364 Arrange for all signatures to be stored in a global .sconsign.db*
367 global ForDirectory, DB_Name, DB_Module
369 ForDirectory = DirFile
374 if not dbm_module is None:
375 DB_Module = dbm_module