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 - 2016 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 rel_2.5.0:3543:937e55cd78f7 2016/04/09 11:29:54 bdbaddog" 
 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 dbm modules have sync() methods. 108 else: 109 syncmethod() 110 try: 111 closemethod = db.close 112 except AttributeError: 113 pass # Not all dbm modules have close() methods. 114 else: 115 closemethod()
116
117 -class SConsignEntry(object):
118 """ 119 Wrapper class for the generic entry in a .sconsign file. 120 The Node subclass populates it with attributes as it pleases. 121 122 XXX As coded below, we do expect a '.binfo' attribute to be added, 123 but we'll probably generalize this in the next refactorings. 124 """ 125 __slots__ = ("binfo", "ninfo", "__weakref__") 126 current_version_id = 2 127
128 - def __init__(self):
129 # Create an object attribute from the class attribute so it ends up 130 # in the pickled data in the .sconsign file. 131 #_version_id = self.current_version_id 132 pass
133
134 - def convert_to_sconsign(self):
136
137 - def convert_from_sconsign(self, dir, name):
139
140 - def __getstate__(self):
141 state = getattr(self, '__dict__', {}).copy() 142 for obj in type(self).mro(): 143 for name in getattr(obj,'__slots__',()): 144 if hasattr(self, name): 145 state[name] = getattr(self, name) 146 147 state['_version_id'] = self.current_version_id 148 try: 149 del state['__weakref__'] 150 except KeyError: 151 pass 152 return state
153
154 - def __setstate__(self, state):
155 for key, value in state.items(): 156 if key not in ('_version_id','__weakref__'): 157 setattr(self, key, value)
158
159 -class Base(object):
160 """ 161 This is the controlling class for the signatures for the collection of 162 entries associated with a specific directory. The actual directory 163 association will be maintained by a subclass that is specific to 164 the underlying storage method. This class provides a common set of 165 methods for fetching and storing the individual bits of information 166 that make up signature entry. 167 """
168 - def __init__(self):
169 self.entries = {} 170 self.dirty = False 171 self.to_be_merged = {}
172
173 - def get_entry(self, filename):
174 """ 175 Fetch the specified entry attribute. 176 """ 177 return self.entries[filename]
178
179 - def set_entry(self, filename, obj):
180 """ 181 Set the entry. 182 """ 183 self.entries[filename] = obj 184 self.dirty = True
185
186 - def do_not_set_entry(self, filename, obj):
187 pass
188
189 - def store_info(self, filename, node):
190 entry = node.get_stored_info() 191 entry.binfo.merge(node.get_binfo()) 192 self.to_be_merged[filename] = node 193 self.dirty = True
194
195 - def do_not_store_info(self, filename, node):
196 pass
197
198 - def merge(self):
199 for key, node in self.to_be_merged.items(): 200 entry = node.get_stored_info() 201 try: 202 ninfo = entry.ninfo 203 except AttributeError: 204 # This happens with SConf Nodes, because the configuration 205 # subsystem takes direct control over how the build decision 206 # is made and its information stored. 207 pass 208 else: 209 ninfo.merge(node.get_ninfo()) 210 self.entries[key] = entry 211 self.to_be_merged = {}
212
213 -class DB(Base):
214 """ 215 A Base subclass that reads and writes signature information 216 from a global .sconsign.db* file--the actual file suffix is 217 determined by the database module. 218 """
219 - def __init__(self, dir):
220 Base.__init__(self) 221 222 self.dir = dir 223 224 db, mode = Get_DataBase(dir) 225 226 # Read using the path relative to the top of the Repository 227 # (self.dir.tpath) from which we're fetching the signature 228 # information. 229 path = normcase(dir.get_tpath()) 230 try: 231 rawentries = db[path] 232 except KeyError: 233 pass 234 else: 235 try: 236 self.entries = pickle.loads(rawentries) 237 if not isinstance(self.entries, dict): 238 self.entries = {} 239 raise TypeError 240 except KeyboardInterrupt: 241 raise 242 except Exception, e: 243 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, 244 "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.get_tpath(), e)) 245 for key, entry in self.entries.items(): 246 entry.convert_from_sconsign(dir, key) 247 248 if mode == "r": 249 # This directory is actually under a repository, which means 250 # likely they're reaching in directly for a dependency on 251 # a file there. Don't actually set any entry info, so we 252 # won't try to write to that .sconsign.dblite file. 253 self.set_entry = self.do_not_set_entry 254 self.store_info = self.do_not_store_info 255 256 global sig_files 257 sig_files.append(self)
258
259 - def write(self, sync=1):
260 if not self.dirty: 261 return 262 263 self.merge() 264 265 db, mode = Get_DataBase(self.dir) 266 267 # Write using the path relative to the top of the SConstruct 268 # directory (self.dir.path), not relative to the top of 269 # the Repository; we only write to our own .sconsign file, 270 # not to .sconsign files in Repositories. 271 path = normcase(self.dir.get_internal_path()) 272 for key, entry in self.entries.items(): 273 entry.convert_to_sconsign() 274 db[path] = pickle.dumps(self.entries, 1) 275 276 if sync: 277 try: 278 syncmethod = db.sync 279 except AttributeError: 280 # Not all anydbm modules have sync() methods. 281 pass 282 else: 283 syncmethod()
284
285 -class Dir(Base):
286 - def __init__(self, fp=None, dir=None):
287 """ 288 fp - file pointer to read entries from 289 """ 290 Base.__init__(self) 291 292 if not fp: 293 return 294 295 self.entries = pickle.load(fp) 296 if not isinstance(self.entries, dict): 297 self.entries = {} 298 raise TypeError 299 300 if dir: 301 for key, entry in self.entries.items(): 302 entry.convert_from_sconsign(dir, key)
303
304 -class DirFile(Dir):
305 """ 306 Encapsulates reading and writing a per-directory .sconsign file. 307 """
308 - def __init__(self, dir):
309 """ 310 dir - the directory for the file 311 """ 312 313 self.dir = dir 314 self.sconsign = os.path.join(dir.get_internal_path(), '.sconsign') 315 316 try: 317 fp = open(self.sconsign, 'rb') 318 except IOError: 319 fp = None 320 321 try: 322 Dir.__init__(self, fp, dir) 323 except KeyboardInterrupt: 324 raise 325 except: 326 SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, 327 "Ignoring corrupt .sconsign file: %s"%self.sconsign) 328 329 global sig_files 330 sig_files.append(self)
331
332 - def write(self, sync=1):
333 """ 334 Write the .sconsign file to disk. 335 336 Try to write to a temporary file first, and rename it if we 337 succeed. If we can't write to the temporary file, it's 338 probably because the directory isn't writable (and if so, 339 how did we build anything in this directory, anyway?), so 340 try to write directly to the .sconsign file as a backup. 341 If we can't rename, try to copy the temporary contents back 342 to the .sconsign file. Either way, always try to remove 343 the temporary file at the end. 344 """ 345 if not self.dirty: 346 return 347 348 self.merge() 349 350 temp = os.path.join(self.dir.get_internal_path(), '.scons%d' % os.getpid()) 351 try: 352 file = open(temp, 'wb') 353 fname = temp 354 except IOError: 355 try: 356 file = open(self.sconsign, 'wb') 357 fname = self.sconsign 358 except IOError: 359 return 360 for key, entry in self.entries.items(): 361 entry.convert_to_sconsign() 362 pickle.dump(self.entries, file, 1) 363 file.close() 364 if fname != self.sconsign: 365 try: 366 mode = os.stat(self.sconsign)[0] 367 os.chmod(self.sconsign, 0666) 368 os.unlink(self.sconsign) 369 except (IOError, OSError): 370 # Try to carry on in the face of either OSError 371 # (things like permission issues) or IOError (disk 372 # or network issues). If there's a really dangerous 373 # issue, it should get re-raised by the calls below. 374 pass 375 try: 376 os.rename(fname, self.sconsign) 377 except OSError: 378 # An OSError failure to rename may indicate something 379 # like the directory has no write permission, but 380 # the .sconsign file itself might still be writable, 381 # so try writing on top of it directly. An IOError 382 # here, or in any of the following calls, would get 383 # raised, indicating something like a potentially 384 # serious disk or network issue. 385 open(self.sconsign, 'wb').write(open(fname, 'rb').read()) 386 os.chmod(self.sconsign, mode) 387 try: 388 os.unlink(temp) 389 except (IOError, OSError): 390 pass
391 392 ForDirectory = DB 393
394 -def File(name, dbm_module=None):
395 """ 396 Arrange for all signatures to be stored in a global .sconsign.db* 397 file. 398 """ 399 global ForDirectory, DB_Name, DB_Module 400 if name is None: 401 ForDirectory = DirFile 402 DB_Module = None 403 else: 404 ForDirectory = DB 405 DB_Name = name 406 if not dbm_module is None: 407 DB_Module = dbm_module
408 409 # Local Variables: 410 # tab-width:4 411 # indent-tabs-mode:nil 412 # End: 413 # vim: set expandtab tabstop=4 shiftwidth=4: 414