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 5110 2010/07/25 16:14:38 bdeegan" 
 31   
 32  import cPickle 
 33  import os 
 34  import os.path 
 35   
 36  import SCons.dblite 
 37  import SCons.Warnings 
 38   
39 -def corrupt_dblite_warning(filename):
40 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, 41 "Ignoring corrupt .sconsign file: %s"%filename)
42 43 SCons.dblite.ignore_corrupt_dbfiles = 1 44 SCons.dblite.corruption_warning = corrupt_dblite_warning 45 46 #XXX Get rid of the global array so this becomes re-entrant. 47 sig_files = [] 48 49 # Info for the database SConsign implementation (now the default): 50 # "DataBase" is a dictionary that maps top-level SConstruct directories 51 # to open database handles. 52 # "DB_Module" is the Python database module to create the handles. 53 # "DB_Name" is the base name of the database file (minus any 54 # extension the underlying DB module will add). 55 DataBase = {} 56 DB_Module = SCons.dblite 57 DB_Name = ".sconsign" 58 DB_sync_list = [] 59
60 -def Get_DataBase(dir):
61 global DataBase, DB_Module, DB_Name 62 top = dir.fs.Top 63 if not os.path.isabs(DB_Name) and top.repositories: 64 mode = "c" 65 for d in [top] + top.repositories: 66 if dir.is_under(d): 67 try: 68 return DataBase[d], mode 69 except KeyError: 70 path = d.entry_abspath(DB_Name) 71 try: db = DataBase[d] = DB_Module.open(path, mode) 72 except (IOError, OSError): pass 73 else: 74 if mode != "r": 75 DB_sync_list.append(db) 76 return db, mode 77 mode = "r" 78 try: 79 return DataBase[top], "c" 80 except KeyError: 81 db = DataBase[top] = DB_Module.open(DB_Name, "c") 82 DB_sync_list.append(db) 83 return db, "c" 84 except TypeError: 85 print "DataBase =", DataBase 86 raise
87
88 -def Reset():
89 """Reset global state. Used by unit tests that end up using 90 SConsign multiple times to get a clean slate for each test.""" 91 global sig_files, DB_sync_list 92 sig_files = [] 93 DB_sync_list = []
94 95 normcase = os.path.normcase 96
97 -def write():
98 global sig_files 99 for sig_file in sig_files: 100 sig_file.write(sync=0) 101 for db in DB_sync_list: 102 try: 103 syncmethod = db.sync 104 except AttributeError: 105 pass # Not all anydbm modules have sync() methods. 106 else: 107 syncmethod()
108
109 -class SConsignEntry:
110 """ 111 Wrapper class for the generic entry in a .sconsign file. 112 The Node subclass populates it with attributes as it pleases. 113 114 XXX As coded below, we do expect a '.binfo' attribute to be added, 115 but we'll probably generalize this in the next refactorings. 116 """ 117 current_version_id = 1
118 - def __init__(self):
119 # Create an object attribute from the class attribute so it ends up 120 # in the pickled data in the .sconsign file. 121 _version_id = self.current_version_id
122 - def convert_to_sconsign(self):
123 self.binfo.convert_to_sconsign()
124 - def convert_from_sconsign(self, dir, name):
125 self.binfo.convert_from_sconsign(dir, name)
126
127 -class Base:
128 """ 129 This is the controlling class for the signatures for the collection of 130 entries associated with a specific directory. The actual directory 131 association will be maintained by a subclass that is specific to 132 the underlying storage method. This class provides a common set of 133 methods for fetching and storing the individual bits of information 134 that make up signature entry. 135 """
136 - def __init__(self):
137 self.entries = {} 138 self.dirty = False 139 self.to_be_merged = {}
140
141 - def get_entry(self, filename):
142 """ 143 Fetch the specified entry attribute. 144 """ 145 return self.entries[filename]
146
147 - def set_entry(self, filename, obj):
148 """ 149 Set the entry. 150 """ 151 self.entries[filename] = obj 152 self.dirty = True
153
154 - def do_not_set_entry(self, filename, obj):
155 pass
156
157 - def store_info(self, filename, node):
158 entry = node.get_stored_info() 159 entry.binfo.merge(node.get_binfo()) 160 self.to_be_merged[filename] = node 161 self.dirty = True
162
163 - def do_not_store_info(self, filename, node):
164 pass
165
166 - def merge(self):
167 for key, node in self.to_be_merged.items(): 168 entry = node.get_stored_info() 169 try: 170 ninfo = entry.ninfo 171 except AttributeError: 172 # This happens with SConf Nodes, because the configuration 173 # subsystem takes direct control over how the build decision 174 # is made and its information stored. 175 pass 176 else: 177 ninfo.merge(node.get_ninfo()) 178 self.entries[key] = entry 179 self.to_be_merged = {}
180
181 -class DB(Base):
182 """ 183 A Base subclass that reads and writes signature information 184 from a global .sconsign.db* file--the actual file suffix is 185 determined by the database module. 186 """
187 - def __init__(self, dir):
188 Base.__init__(self) 189 190 self.dir = dir 191 192 db, mode = Get_DataBase(dir) 193 194 # Read using the path relative to the top of the Repository 195 # (self.dir.tpath) from which we're fetching the signature 196 # information. 197 path = normcase(dir.tpath) 198 try: 199 rawentries = db[path] 200 except KeyError: 201 pass 202 else: 203 try: 204 self.entries = cPickle.loads(rawentries) 205 if type(self.entries) is not type({}): 206 self.entries = {} 207 raise TypeError 208 except KeyboardInterrupt: 209 raise 210 except Exception, e: 211 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, 212 "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e)) 213 for key, entry in self.entries.items(): 214 entry.convert_from_sconsign(dir, key) 215 216 if mode == "r": 217 # This directory is actually under a repository, which means 218 # likely they're reaching in directly for a dependency on 219 # a file there. Don't actually set any entry info, so we 220 # won't try to write to that .sconsign.dblite file. 221 self.set_entry = self.do_not_set_entry 222 self.store_info = self.do_not_store_info 223 224 global sig_files 225 sig_files.append(self)
226
227 - def write(self, sync=1):
228 if not self.dirty: 229 return 230 231 self.merge() 232 233 db, mode = Get_DataBase(self.dir) 234 235 # Write using the path relative to the top of the SConstruct 236 # directory (self.dir.path), not relative to the top of 237 # the Repository; we only write to our own .sconsign file, 238 # not to .sconsign files in Repositories. 239 path = normcase(self.dir.path) 240 for key, entry in self.entries.items(): 241 entry.convert_to_sconsign() 242 db[path] = cPickle.dumps(self.entries, 1) 243 244 if sync: 245 try: 246 syncmethod = db.sync 247 except AttributeError: 248 # Not all anydbm modules have sync() methods. 249 pass 250 else: 251 syncmethod()
252
253 -class Dir(Base):
254 - def __init__(self, fp=None, dir=None):
255 """ 256 fp - file pointer to read entries from 257 """ 258 Base.__init__(self) 259 260 if not fp: 261 return 262 263 self.entries = cPickle.load(fp) 264 if type(self.entries) is not type({}): 265 self.entries = {} 266 raise TypeError 267 268 if dir: 269 for key, entry in self.entries.items(): 270 entry.convert_from_sconsign(dir, key)
271
272 -class DirFile(Dir):
273 """ 274 Encapsulates reading and writing a per-directory .sconsign file. 275 """
276 - def __init__(self, dir):
277 """ 278 dir - the directory for the file 279 """ 280 281 self.dir = dir 282 self.sconsign = os.path.join(dir.path, '.sconsign') 283 284 try: 285 fp = open(self.sconsign, 'rb') 286 except IOError: 287 fp = None 288 289 try: 290 Dir.__init__(self, fp, dir) 291 except KeyboardInterrupt: 292 raise 293 except: 294 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, 295 "Ignoring corrupt .sconsign file: %s"%self.sconsign) 296 297 global sig_files 298 sig_files.append(self)
299
300 - def write(self, sync=1):
301 """ 302 Write the .sconsign file to disk. 303 304 Try to write to a temporary file first, and rename it if we 305 succeed. If we can't write to the temporary file, it's 306 probably because the directory isn't writable (and if so, 307 how did we build anything in this directory, anyway?), so 308 try to write directly to the .sconsign file as a backup. 309 If we can't rename, try to copy the temporary contents back 310 to the .sconsign file. Either way, always try to remove 311 the temporary file at the end. 312 """ 313 if not self.dirty: 314 return 315 316 self.merge() 317 318 temp = os.path.join(self.dir.path, '.scons%d' % os.getpid()) 319 try: 320 file = open(temp, 'wb') 321 fname = temp 322 except IOError: 323 try: 324 file = open(self.sconsign, 'wb') 325 fname = self.sconsign 326 except IOError: 327 return 328 for key, entry in self.entries.items(): 329 entry.convert_to_sconsign() 330 cPickle.dump(self.entries, file, 1) 331 file.close() 332 if fname != self.sconsign: 333 try: 334 mode = os.stat(self.sconsign)[0] 335 os.chmod(self.sconsign, 0666) 336 os.unlink(self.sconsign) 337 except (IOError, OSError): 338 # Try to carry on in the face of either OSError 339 # (things like permission issues) or IOError (disk 340 # or network issues). If there's a really dangerous 341 # issue, it should get re-raised by the calls below. 342 pass 343 try: 344 os.rename(fname, self.sconsign) 345 except OSError: 346 # An OSError failure to rename may indicate something 347 # like the directory has no write permission, but 348 # the .sconsign file itself might still be writable, 349 # so try writing on top of it directly. An IOError 350 # here, or in any of the following calls, would get 351 # raised, indicating something like a potentially 352 # serious disk or network issue. 353 open(self.sconsign, 'wb').write(open(fname, 'rb').read()) 354 os.chmod(self.sconsign, mode) 355 try: 356 os.unlink(temp) 357 except (IOError, OSError): 358 pass
359 360 ForDirectory = DB 361
362 -def File(name, dbm_module=None):
363 """ 364 Arrange for all signatures to be stored in a global .sconsign.db* 365 file. 366 """ 367 global ForDirectory, DB_Name, DB_Module 368 if name is None: 369 ForDirectory = DirFile 370 DB_Module = None 371 else: 372 ForDirectory = DB 373 DB_Name = name 374 if not dbm_module is None: 375 DB_Module = dbm_module
376 377 # Local Variables: 378 # tab-width:4 379 # indent-tabs-mode:nil 380 # End: 381 # vim: set expandtab tabstop=4 shiftwidth=4: 382