3 Writing and reading information to the .sconsign file or files.
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__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
42 def corrupt_dblite_warning(filename):
43 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
44 "Ignoring corrupt .sconsign file: %s"%filename)
46 SCons.dblite.ignore_corrupt_dbfiles = 1
47 SCons.dblite.corruption_warning = corrupt_dblite_warning
49 #XXX Get rid of the global array so this becomes re-entrant.
52 # Info for the database SConsign implementation (now the default):
53 # "DataBase" is a dictionary that maps top-level SConstruct directories
54 # to open database handles.
55 # "DB_Module" is the Python database module to create the handles.
56 # "DB_Name" is the base name of the database file (minus any
57 # extension the underlying DB module will add).
59 DB_Module = SCons.dblite
63 def Get_DataBase(dir):
64 global DataBase, DB_Module, DB_Name
66 if not os.path.isabs(DB_Name) and top.repositories:
68 for d in [top] + top.repositories:
71 return DataBase[d], mode
73 path = d.entry_abspath(DB_Name)
74 try: db = DataBase[d] = DB_Module.open(path, mode)
75 except (IOError, OSError): pass
78 DB_sync_list.append(db)
82 return DataBase[top], "c"
84 db = DataBase[top] = DB_Module.open(DB_Name, "c")
85 DB_sync_list.append(db)
88 print "DataBase =", DataBase
92 """Reset global state. Used by unit tests that end up using
93 SConsign multiple times to get a clean slate for each test."""
94 global sig_files, DB_sync_list
98 normcase = os.path.normcase
102 for sig_file in sig_files:
103 sig_file.write(sync=0)
104 for db in DB_sync_list:
107 except AttributeError:
108 pass # Not all anydbm modules have sync() methods.
114 This is the controlling class for the signatures for the collection of
115 entries associated with a specific directory. The actual directory
116 association will be maintained by a subclass that is specific to
117 the underlying storage method. This class provides a common set of
118 methods for fetching and storing the individual bits of information
119 that make up signature entry.
121 def __init__(self, module=None):
123 module - the signature module being used
126 self.module = module or SCons.Sig.default_calc.module
130 def get_entry(self, filename):
132 Fetch the specified entry attribute.
134 return self.entries[filename]
136 def set_entry(self, filename, obj):
140 self.entries[filename] = obj
143 def do_not_set_entry(self, filename, obj):
148 A Base subclass that reads and writes signature information
149 from a global .sconsign.db* file--the actual file suffix is
150 determined by the specified database module.
152 def __init__(self, dir, module=None):
153 Base.__init__(self, module)
157 db, mode = Get_DataBase(dir)
159 # Read using the path relative to the top of the Repository
160 # (self.dir.tpath) from which we're fetching the signature
162 path = normcase(dir.tpath)
164 rawentries = db[path]
169 self.entries = cPickle.loads(rawentries)
170 if type(self.entries) is not type({}):
173 except KeyboardInterrupt:
176 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
177 "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e))
178 for key, entry in self.entries.items():
179 entry.convert_from_sconsign(dir, key)
182 # This directory is actually under a repository, which means
183 # likely they're reaching in directly for a dependency on
184 # a file there. Don't actually set any entry info, so we
185 # won't try to write to that .sconsign.dblite file.
186 self.set_entry = self.do_not_set_entry
189 sig_files.append(self)
191 def write(self, sync=1):
195 db, mode = Get_DataBase(self.dir)
197 # Write using the path relative to the top of the SConstruct
198 # directory (self.dir.path), not relative to the top of
199 # the Repository; we only write to our own .sconsign file,
200 # not to .sconsign files in Repositories.
201 path = normcase(self.dir.path)
202 for key, entry in self.entries.items():
203 entry.convert_to_sconsign()
204 db[path] = cPickle.dumps(self.entries, 1)
209 except AttributeError:
210 # Not all anydbm modules have sync() methods.
216 def __init__(self, fp=None, module=None):
218 fp - file pointer to read entries from
219 module - the signature module being used
221 Base.__init__(self, module)
224 self.entries = cPickle.load(fp)
225 if type(self.entries) is not type({}):
231 Encapsulates reading and writing a per-directory .sconsign file.
233 def __init__(self, dir, module=None):
235 dir - the directory for the file
236 module - the signature module being used
240 self.sconsign = os.path.join(dir.path, '.sconsign')
243 fp = open(self.sconsign, 'rb')
248 Dir.__init__(self, fp, module)
249 except KeyboardInterrupt:
252 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
253 "Ignoring corrupt .sconsign file: %s"%self.sconsign)
256 sig_files.append(self)
258 def get_entry(self, filename):
260 Fetch the specified entry attribute, converting from .sconsign
261 format to in-memory format.
263 entry = Dir.get_entry(self, filename)
264 entry.convert_from_sconsign(self.dir, filename)
267 def write(self, sync=1):
269 Write the .sconsign file to disk.
271 Try to write to a temporary file first, and rename it if we
272 succeed. If we can't write to the temporary file, it's
273 probably because the directory isn't writable (and if so,
274 how did we build anything in this directory, anyway?), so
275 try to write directly to the .sconsign file as a backup.
276 If we can't rename, try to copy the temporary contents back
277 to the .sconsign file. Either way, always try to remove
278 the temporary file at the end.
281 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
283 file = open(temp, 'wb')
287 file = open(self.sconsign, 'wb')
288 fname = self.sconsign
291 for key, entry in self.entries.items():
292 entry.convert_to_sconsign()
293 cPickle.dump(self.entries, file, 1)
295 if fname != self.sconsign:
297 mode = os.stat(self.sconsign)[0]
298 os.chmod(self.sconsign, 0666)
299 os.unlink(self.sconsign)
300 except (IOError, OSError):
301 # Try to carry on in the face of either OSError
302 # (things like permission issues) or IOError (disk
303 # or network issues). If there's a really dangerous
304 # issue, it should get re-raised by the calls below.
307 os.rename(fname, self.sconsign)
309 # An OSError failure to rename may indicate something
310 # like the directory has no write permission, but
311 # the .sconsign file itself might still be writable,
312 # so try writing on top of it directly. An IOError
313 # here, or in any of the following calls, would get
314 # raised, indicating something like a potentially
315 # serious disk or network issue.
316 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
317 os.chmod(self.sconsign, mode)
320 except (IOError, OSError):
325 def File(name, dbm_module=None):
327 Arrange for all signatures to be stored in a global .sconsign.db*
330 global ForDirectory, DB_Name, DB_Module
332 ForDirectory = DirFile
337 if not dbm_module is None:
338 DB_Module = dbm_module