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