OLD | NEW |
(Empty) | |
| 1 """SCons.SConsign |
| 2 |
| 3 Writing and reading information to the .sconsign file or files. |
| 4 |
| 5 """ |
| 6 |
| 7 # |
| 8 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The S
Cons Foundation |
| 9 # |
| 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: |
| 17 # |
| 18 # The above copyright notice and this permission notice shall be included |
| 19 # in all copies or substantial portions of the Software. |
| 20 # |
| 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. |
| 28 # |
| 29 |
| 30 __revision__ = "src/engine/SCons/SConsign.py 5134 2010/08/16 23:02:40 bdeegan" |
| 31 |
| 32 import SCons.compat |
| 33 |
| 34 import os |
| 35 # compat layer imports "cPickle" for us if it's available. |
| 36 import pickle |
| 37 |
| 38 import SCons.dblite |
| 39 import SCons.Warnings |
| 40 |
| 41 def corrupt_dblite_warning(filename): |
| 42 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, |
| 43 "Ignoring corrupt .sconsign file: %s"%filename) |
| 44 |
| 45 SCons.dblite.ignore_corrupt_dbfiles = 1 |
| 46 SCons.dblite.corruption_warning = corrupt_dblite_warning |
| 47 |
| 48 #XXX Get rid of the global array so this becomes re-entrant. |
| 49 sig_files = [] |
| 50 |
| 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). |
| 57 DataBase = {} |
| 58 DB_Module = SCons.dblite |
| 59 DB_Name = ".sconsign" |
| 60 DB_sync_list = [] |
| 61 |
| 62 def Get_DataBase(dir): |
| 63 global DataBase, DB_Module, DB_Name |
| 64 top = dir.fs.Top |
| 65 if not os.path.isabs(DB_Name) and top.repositories: |
| 66 mode = "c" |
| 67 for d in [top] + top.repositories: |
| 68 if dir.is_under(d): |
| 69 try: |
| 70 return DataBase[d], mode |
| 71 except KeyError: |
| 72 path = d.entry_abspath(DB_Name) |
| 73 try: db = DataBase[d] = DB_Module.open(path, mode) |
| 74 except (IOError, OSError): pass |
| 75 else: |
| 76 if mode != "r": |
| 77 DB_sync_list.append(db) |
| 78 return db, mode |
| 79 mode = "r" |
| 80 try: |
| 81 return DataBase[top], "c" |
| 82 except KeyError: |
| 83 db = DataBase[top] = DB_Module.open(DB_Name, "c") |
| 84 DB_sync_list.append(db) |
| 85 return db, "c" |
| 86 except TypeError: |
| 87 print "DataBase =", DataBase |
| 88 raise |
| 89 |
| 90 def Reset(): |
| 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 |
| 94 sig_files = [] |
| 95 DB_sync_list = [] |
| 96 |
| 97 normcase = os.path.normcase |
| 98 |
| 99 def write(): |
| 100 global sig_files |
| 101 for sig_file in sig_files: |
| 102 sig_file.write(sync=0) |
| 103 for db in DB_sync_list: |
| 104 try: |
| 105 syncmethod = db.sync |
| 106 except AttributeError: |
| 107 pass # Not all anydbm modules have sync() methods. |
| 108 else: |
| 109 syncmethod() |
| 110 |
| 111 class SConsignEntry(object): |
| 112 """ |
| 113 Wrapper class for the generic entry in a .sconsign file. |
| 114 The Node subclass populates it with attributes as it pleases. |
| 115 |
| 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. |
| 118 """ |
| 119 current_version_id = 1 |
| 120 def __init__(self): |
| 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) |
| 128 |
| 129 class Base(object): |
| 130 """ |
| 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. |
| 137 """ |
| 138 def __init__(self): |
| 139 self.entries = {} |
| 140 self.dirty = False |
| 141 self.to_be_merged = {} |
| 142 |
| 143 def get_entry(self, filename): |
| 144 """ |
| 145 Fetch the specified entry attribute. |
| 146 """ |
| 147 return self.entries[filename] |
| 148 |
| 149 def set_entry(self, filename, obj): |
| 150 """ |
| 151 Set the entry. |
| 152 """ |
| 153 self.entries[filename] = obj |
| 154 self.dirty = True |
| 155 |
| 156 def do_not_set_entry(self, filename, obj): |
| 157 pass |
| 158 |
| 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 |
| 163 self.dirty = True |
| 164 |
| 165 def do_not_store_info(self, filename, node): |
| 166 pass |
| 167 |
| 168 def merge(self): |
| 169 for key, node in self.to_be_merged.items(): |
| 170 entry = node.get_stored_info() |
| 171 try: |
| 172 ninfo = entry.ninfo |
| 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. |
| 177 pass |
| 178 else: |
| 179 ninfo.merge(node.get_ninfo()) |
| 180 self.entries[key] = entry |
| 181 self.to_be_merged = {} |
| 182 |
| 183 class DB(Base): |
| 184 """ |
| 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. |
| 188 """ |
| 189 def __init__(self, dir): |
| 190 Base.__init__(self) |
| 191 |
| 192 self.dir = dir |
| 193 |
| 194 db, mode = Get_DataBase(dir) |
| 195 |
| 196 # Read using the path relative to the top of the Repository |
| 197 # (self.dir.tpath) from which we're fetching the signature |
| 198 # information. |
| 199 path = normcase(dir.tpath) |
| 200 try: |
| 201 rawentries = db[path] |
| 202 except KeyError: |
| 203 pass |
| 204 else: |
| 205 try: |
| 206 self.entries = pickle.loads(rawentries) |
| 207 if not isinstance(self.entries, dict): |
| 208 self.entries = {} |
| 209 raise TypeError |
| 210 except KeyboardInterrupt: |
| 211 raise |
| 212 except Exception, e: |
| 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) |
| 217 |
| 218 if mode == "r": |
| 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 |
| 225 |
| 226 global sig_files |
| 227 sig_files.append(self) |
| 228 |
| 229 def write(self, sync=1): |
| 230 if not self.dirty: |
| 231 return |
| 232 |
| 233 self.merge() |
| 234 |
| 235 db, mode = Get_DataBase(self.dir) |
| 236 |
| 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) |
| 245 |
| 246 if sync: |
| 247 try: |
| 248 syncmethod = db.sync |
| 249 except AttributeError: |
| 250 # Not all anydbm modules have sync() methods. |
| 251 pass |
| 252 else: |
| 253 syncmethod() |
| 254 |
| 255 class Dir(Base): |
| 256 def __init__(self, fp=None, dir=None): |
| 257 """ |
| 258 fp - file pointer to read entries from |
| 259 """ |
| 260 Base.__init__(self) |
| 261 |
| 262 if not fp: |
| 263 return |
| 264 |
| 265 self.entries = pickle.load(fp) |
| 266 if not isinstance(self.entries, dict): |
| 267 self.entries = {} |
| 268 raise TypeError |
| 269 |
| 270 if dir: |
| 271 for key, entry in self.entries.items(): |
| 272 entry.convert_from_sconsign(dir, key) |
| 273 |
| 274 class DirFile(Dir): |
| 275 """ |
| 276 Encapsulates reading and writing a per-directory .sconsign file. |
| 277 """ |
| 278 def __init__(self, dir): |
| 279 """ |
| 280 dir - the directory for the file |
| 281 """ |
| 282 |
| 283 self.dir = dir |
| 284 self.sconsign = os.path.join(dir.path, '.sconsign') |
| 285 |
| 286 try: |
| 287 fp = open(self.sconsign, 'rb') |
| 288 except IOError: |
| 289 fp = None |
| 290 |
| 291 try: |
| 292 Dir.__init__(self, fp, dir) |
| 293 except KeyboardInterrupt: |
| 294 raise |
| 295 except: |
| 296 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, |
| 297 "Ignoring corrupt .sconsign file: %s"%self.scons
ign) |
| 298 |
| 299 global sig_files |
| 300 sig_files.append(self) |
| 301 |
| 302 def write(self, sync=1): |
| 303 """ |
| 304 Write the .sconsign file to disk. |
| 305 |
| 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. |
| 314 """ |
| 315 if not self.dirty: |
| 316 return |
| 317 |
| 318 self.merge() |
| 319 |
| 320 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid()) |
| 321 try: |
| 322 file = open(temp, 'wb') |
| 323 fname = temp |
| 324 except IOError: |
| 325 try: |
| 326 file = open(self.sconsign, 'wb') |
| 327 fname = self.sconsign |
| 328 except IOError: |
| 329 return |
| 330 for key, entry in self.entries.items(): |
| 331 entry.convert_to_sconsign() |
| 332 pickle.dump(self.entries, file, 1) |
| 333 file.close() |
| 334 if fname != self.sconsign: |
| 335 try: |
| 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. |
| 344 pass |
| 345 try: |
| 346 os.rename(fname, self.sconsign) |
| 347 except OSError: |
| 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) |
| 357 try: |
| 358 os.unlink(temp) |
| 359 except (IOError, OSError): |
| 360 pass |
| 361 |
| 362 ForDirectory = DB |
| 363 |
| 364 def File(name, dbm_module=None): |
| 365 """ |
| 366 Arrange for all signatures to be stored in a global .sconsign.db* |
| 367 file. |
| 368 """ |
| 369 global ForDirectory, DB_Name, DB_Module |
| 370 if name is None: |
| 371 ForDirectory = DirFile |
| 372 DB_Module = None |
| 373 else: |
| 374 ForDirectory = DB |
| 375 DB_Name = name |
| 376 if not dbm_module is None: |
| 377 DB_Module = dbm_module |
| 378 |
| 379 # Local Variables: |
| 380 # tab-width:4 |
| 381 # indent-tabs-mode:nil |
| 382 # End: |
| 383 # vim: set expandtab tabstop=4 shiftwidth=4: |
OLD | NEW |