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__"
35 # compat layer imports "cPickle" for us if it's available.
41 def corrupt_dblite_warning(filename):
42 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
43 "Ignoring corrupt .sconsign file: %s"%filename)
45 SCons.dblite.ignore_corrupt_dbfiles = 1
46 SCons.dblite.corruption_warning = corrupt_dblite_warning
48 #XXX Get rid of the global array so this becomes re-entrant.
51 # Info for the database SConsign implementation (now the default):
52 # "DataBase" is a dictionary that maps top-level SConstruct directories
53 # to open database handles.
54 # "DB_Module" is the Python database module to create the handles.
55 # "DB_Name" is the base name of the database file (minus any
56 # extension the underlying DB module will add).
58 DB_Module = SCons.dblite
62 def Get_DataBase(dir):
63 global DataBase, DB_Module, DB_Name
65 if not os.path.isabs(DB_Name) and top.repositories:
67 for d in [top] + top.repositories:
70 return DataBase[d], mode
72 path = d.entry_abspath(DB_Name)
73 try: db = DataBase[d] = DB_Module.open(path, mode)
74 except (IOError, OSError): pass
77 DB_sync_list.append(db)
81 return DataBase[top], "c"
83 db = DataBase[top] = DB_Module.open(DB_Name, "c")
84 DB_sync_list.append(db)
87 print "DataBase =", DataBase
91 """Reset global state. Used by unit tests that end up using
92 SConsign multiple times to get a clean slate for each test."""
93 global sig_files, DB_sync_list
97 normcase = os.path.normcase
101 for sig_file in sig_files:
102 sig_file.write(sync=0)
103 for db in DB_sync_list:
106 except AttributeError:
107 pass # Not all anydbm modules have sync() methods.
113 Wrapper class for the generic entry in a .sconsign file.
114 The Node subclass populates it with attributes as it pleases.
116 XXX As coded below, we do expect a '.binfo' attribute to be added,
117 but we'll probably generalize this in the next refactorings.
119 current_version_id = 1
121 # Create an object attribute from the class attribute so it ends up
122 # in the pickled data in the .sconsign file.
123 _version_id = self.current_version_id
124 def convert_to_sconsign(self):
125 self.binfo.convert_to_sconsign()
126 def convert_from_sconsign(self, dir, name):
127 self.binfo.convert_from_sconsign(dir, name)
131 This is the controlling class for the signatures for the collection of
132 entries associated with a specific directory. The actual directory
133 association will be maintained by a subclass that is specific to
134 the underlying storage method. This class provides a common set of
135 methods for fetching and storing the individual bits of information
136 that make up signature entry.
141 self.to_be_merged = {}
143 def get_entry(self, filename):
145 Fetch the specified entry attribute.
147 return self.entries[filename]
149 def set_entry(self, filename, obj):
153 self.entries[filename] = obj
156 def do_not_set_entry(self, filename, obj):
159 def store_info(self, filename, node):
160 entry = node.get_stored_info()
161 entry.binfo.merge(node.get_binfo())
162 self.to_be_merged[filename] = node
165 def do_not_store_info(self, filename, node):
169 for key, node in self.to_be_merged.items():
170 entry = node.get_stored_info()
173 except AttributeError:
174 # This happens with SConf Nodes, because the configuration
175 # subsystem takes direct control over how the build decision
176 # is made and its information stored.
179 ninfo.merge(node.get_ninfo())
180 self.entries[key] = entry
181 self.to_be_merged = {}
185 A Base subclass that reads and writes signature information
186 from a global .sconsign.db* file--the actual file suffix is
187 determined by the database module.
189 def __init__(self, dir):
194 db, mode = Get_DataBase(dir)
196 # Read using the path relative to the top of the Repository
197 # (self.dir.tpath) from which we're fetching the signature
199 path = normcase(dir.tpath)
201 rawentries = db[path]
206 self.entries = pickle.loads(rawentries)
207 if not isinstance(self.entries, dict):
210 except KeyboardInterrupt:
213 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
214 "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e))
215 for key, entry in self.entries.items():
216 entry.convert_from_sconsign(dir, key)
219 # This directory is actually under a repository, which means
220 # likely they're reaching in directly for a dependency on
221 # a file there. Don't actually set any entry info, so we
222 # won't try to write to that .sconsign.dblite file.
223 self.set_entry = self.do_not_set_entry
224 self.store_info = self.do_not_store_info
227 sig_files.append(self)
229 def write(self, sync=1):
235 db, mode = Get_DataBase(self.dir)
237 # Write using the path relative to the top of the SConstruct
238 # directory (self.dir.path), not relative to the top of
239 # the Repository; we only write to our own .sconsign file,
240 # not to .sconsign files in Repositories.
241 path = normcase(self.dir.path)
242 for key, entry in self.entries.items():
243 entry.convert_to_sconsign()
244 db[path] = pickle.dumps(self.entries, 1)
249 except AttributeError:
250 # Not all anydbm modules have sync() methods.
256 def __init__(self, fp=None, dir=None):
258 fp - file pointer to read entries from
265 self.entries = pickle.load(fp)
266 if not isinstance(self.entries, dict):
271 for key, entry in self.entries.items():
272 entry.convert_from_sconsign(dir, key)
276 Encapsulates reading and writing a per-directory .sconsign file.
278 def __init__(self, dir):
280 dir - the directory for the file
284 self.sconsign = os.path.join(dir.path, '.sconsign')
287 fp = open(self.sconsign, 'rb')
292 Dir.__init__(self, fp, dir)
293 except KeyboardInterrupt:
296 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning,
297 "Ignoring corrupt .sconsign file: %s"%self.sconsign)
300 sig_files.append(self)
302 def write(self, sync=1):
304 Write the .sconsign file to disk.
306 Try to write to a temporary file first, and rename it if we
307 succeed. If we can't write to the temporary file, it's
308 probably because the directory isn't writable (and if so,
309 how did we build anything in this directory, anyway?), so
310 try to write directly to the .sconsign file as a backup.
311 If we can't rename, try to copy the temporary contents back
312 to the .sconsign file. Either way, always try to remove
313 the temporary file at the end.
320 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid())
322 file = open(temp, 'wb')
326 file = open(self.sconsign, 'wb')
327 fname = self.sconsign
330 for key, entry in self.entries.items():
331 entry.convert_to_sconsign()
332 pickle.dump(self.entries, file, 1)
334 if fname != self.sconsign:
336 mode = os.stat(self.sconsign)[0]
337 os.chmod(self.sconsign, 0666)
338 os.unlink(self.sconsign)
339 except (IOError, OSError):
340 # Try to carry on in the face of either OSError
341 # (things like permission issues) or IOError (disk
342 # or network issues). If there's a really dangerous
343 # issue, it should get re-raised by the calls below.
346 os.rename(fname, self.sconsign)
348 # An OSError failure to rename may indicate something
349 # like the directory has no write permission, but
350 # the .sconsign file itself might still be writable,
351 # so try writing on top of it directly. An IOError
352 # here, or in any of the following calls, would get
353 # raised, indicating something like a potentially
354 # serious disk or network issue.
355 open(self.sconsign, 'wb').write(open(fname, 'rb').read())
356 os.chmod(self.sconsign, mode)
359 except (IOError, OSError):
364 def File(name, dbm_module=None):
366 Arrange for all signatures to be stored in a global .sconsign.db*
369 global ForDirectory, DB_Name, DB_Module
371 ForDirectory = DirFile
376 if not dbm_module is None:
377 DB_Module = dbm_module
381 # indent-tabs-mode:nil
383 # vim: set expandtab tabstop=4 shiftwidth=4: