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