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
99 norm_entry = lambda s: s
102 return string.replace(str, os.sep, '/')
106 for sig_file in sig_files:
107 sig_file.write(sync=0)
108 for db in DB_sync_list:
111 except AttributeError:
112 pass # Not all anydbm modules have sync() methods.
118 This is the controlling class for the signatures for the collection of
119 entries associated with a specific directory. The actual directory
120 association will be maintained by a subclass that is specific to
121 the underlying storage method. This class provides a common set of
122 methods for fetching and storing the individual bits of information
123 that make up signature entry.
125 def __init__(self, module=None):
127 module - the signature module being used
130 self.module = module or SCons.Sig.default_calc.module
134 def get_entry(self, filename):
136 Fetch the specified entry attribute.
138 return self.entries[filename]
140 def set_entry(self, filename, obj):
144 self.entries[filename] = obj
147 def do_not_set_entry(self, filename, obj):
152 A Base subclass that reads and writes signature information
153 from a global .sconsign.db* file--the actual file suffix is
154 determined by the specified database module.
156 def __init__(self, dir, module=None):
157 Base.__init__(self, module)
161 db, mode = Get_DataBase(dir)
163 # Read using the path relative to the top of the Repository
164 # (self.dir.tpath) from which we're fetching the signature
166 path = norm_entry(dir.tpath)
168 rawentries = db[path]
173 self.entries = cPickle.loads(rawentries)
174 if type(self.entries) is not type({}):
177 except KeyboardInterrupt:
180 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
181 "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e))
182 for key, entry in self.entries.items():
183 entry.convert_from_sconsign(dir, key)
186 # This directory is actually under a repository, which means
187 # likely they're reaching in directly for a dependency on
188 # a file there. Don't actually set any entry info, so we
189 # won't try to write to that .sconsign.dblite file.
190 self.set_entry = self.do_not_set_entry
193 sig_files.append(self)
195 def write(self, sync=1):
199 db, mode = Get_DataBase(self.dir)
201 # Write using the path relative to the top of the SConstruct
202 # directory (self.dir.path), not relative to the top of
203 # the Repository; we only write to our own .sconsign file,
204 # not to .sconsign files in Repositories.
205 path = norm_entry(self.dir.path)
206 for key, entry in self.entries.items():
207 entry.convert_to_sconsign()
208 db[path] = cPickle.dumps(self.entries, 1)
213 except AttributeError:
214 # Not all anydbm modules have sync() methods.
220 def __init__(self, fp=None, module=None):
222 fp - file pointer to read entries from
223 module - the signature module being used
225 Base.__init__(self, module)
228 self.entries = cPickle.load(fp)
229 if type(self.entries) is not type({}):
235 Encapsulates reading and writing a per-directory .sconsign file.
237 def __init__(self, dir, module=None):
239 dir - the directory for the file
240 module - the signature module being used
244 self.sconsign = os.path.join(dir.path, '.sconsign')
247 fp = open(self.sconsign, 'rb')
252 Dir.__init__(self, fp, module)
253 except KeyboardInterrupt:
256 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
257 "Ignoring corrupt .sconsign file: %s"%self.sconsign)
260 sig_files.append(self)
262 def get_entry(self, filename):
264 Fetch the specified entry attribute, converting from .sconsign
265 format to in-memory format.
267 entry = Dir.get_entry(self, filename)
268 entry.convert_from_sconsign(self.dir, filename)
271 def write(self, sync=1):
273 Write the .sconsign file to disk.
275 Try to write to a temporary file first, and rename it if we
276 succeed. If we can't write to the temporary file, it's
277 probably because the directory isn't writable (and if so,
278 how did we build anything in this directory, anyway?), so
279 try to write directly to the .sconsign file as a backup.
280 If we can't rename, try to copy the temporary contents back
281 to the .sconsign file. Either way, always try to remove
282 the temporary file at the end.
285 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
287 file = open(temp, 'wb')
291 file = open(self.sconsign, 'wb')
292 fname = self.sconsign
295 for key, entry in self.entries.items():
296 entry.convert_to_sconsign()
297 cPickle.dump(self.entries, file, 1)
299 if fname != self.sconsign:
301 mode = os.stat(self.sconsign)[0]
302 os.chmod(self.sconsign, 0666)
303 os.unlink(self.sconsign)
307 os.rename(fname, self.sconsign)
309 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
310 os.chmod(self.sconsign, mode)
318 def File(name, dbm_module=None):
320 Arrange for all signatures to be stored in a global .sconsign.db*
323 global ForDirectory, DB_Name, DB_Module
325 ForDirectory = DirFile
330 if not dbm_module is None:
331 DB_Module = dbm_module