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