Package SCons :: Module SConsign
[hide private]
[frames] | no frames]

Source Code for Module SCons.SConsign

  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 SCons 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 5023 2010/06/14 22:05:46 scons" 
 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.sconsign) 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: 384