1import hashlib 2import logging 3import os 4import re 5import tempfile 6import pickle 7import bb.data 8import difflib 9import simplediff 10from bb.checksum import FileChecksumCache 11 12logger = logging.getLogger('BitBake.SigGen') 13 14def init(d): 15 siggens = [obj for obj in globals().values() 16 if type(obj) is type and issubclass(obj, SignatureGenerator)] 17 18 desired = d.getVar("BB_SIGNATURE_HANDLER") or "noop" 19 for sg in siggens: 20 if desired == sg.name: 21 return sg(d) 22 break 23 else: 24 logger.error("Invalid signature generator '%s', using default 'noop'\n" 25 "Available generators: %s", desired, 26 ', '.join(obj.name for obj in siggens)) 27 return SignatureGenerator(d) 28 29class SignatureGenerator(object): 30 """ 31 """ 32 name = "noop" 33 34 def __init__(self, data): 35 self.basehash = {} 36 self.taskhash = {} 37 self.runtaskdeps = {} 38 self.file_checksum_values = {} 39 self.taints = {} 40 41 def finalise(self, fn, d, varient): 42 return 43 44 def get_taskhash(self, fn, task, deps, dataCache): 45 return "0" 46 47 def writeout_file_checksum_cache(self): 48 """Write/update the file checksum cache onto disk""" 49 return 50 51 def stampfile(self, stampbase, file_name, taskname, extrainfo): 52 return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.') 53 54 def stampcleanmask(self, stampbase, file_name, taskname, extrainfo): 55 return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.') 56 57 def dump_sigtask(self, fn, task, stampbase, runtime): 58 return 59 60 def invalidate_task(self, task, d, fn): 61 bb.build.del_stamp(task, d, fn) 62 63 def dump_sigs(self, dataCache, options): 64 return 65 66 def get_taskdata(self): 67 return (self.runtaskdeps, self.taskhash, self.file_checksum_values, self.taints, self.basehash) 68 69 def set_taskdata(self, data): 70 self.runtaskdeps, self.taskhash, self.file_checksum_values, self.taints, self.basehash = data 71 72 def reset(self, data): 73 self.__init__(data) 74 75 76class SignatureGeneratorBasic(SignatureGenerator): 77 """ 78 """ 79 name = "basic" 80 81 def __init__(self, data): 82 self.basehash = {} 83 self.taskhash = {} 84 self.taskdeps = {} 85 self.runtaskdeps = {} 86 self.file_checksum_values = {} 87 self.taints = {} 88 self.gendeps = {} 89 self.lookupcache = {} 90 self.pkgnameextract = re.compile("(?P<fn>.*)\..*") 91 self.basewhitelist = set((data.getVar("BB_HASHBASE_WHITELIST") or "").split()) 92 self.taskwhitelist = None 93 self.init_rundepcheck(data) 94 checksum_cache_file = data.getVar("BB_HASH_CHECKSUM_CACHE_FILE") 95 if checksum_cache_file: 96 self.checksum_cache = FileChecksumCache() 97 self.checksum_cache.init_cache(data, checksum_cache_file) 98 else: 99 self.checksum_cache = None 100 101 def init_rundepcheck(self, data): 102 self.taskwhitelist = data.getVar("BB_HASHTASK_WHITELIST") or None 103 if self.taskwhitelist: 104 self.twl = re.compile(self.taskwhitelist) 105 else: 106 self.twl = None 107 108 def _build_data(self, fn, d): 109 110 ignore_mismatch = ((d.getVar("BB_HASH_IGNORE_MISMATCH") or '') == '1') 111 tasklist, gendeps, lookupcache = bb.data.generate_dependencies(d) 112 113 taskdeps = {} 114 basehash = {} 115 116 for task in tasklist: 117 data = lookupcache[task] 118 119 if data is None: 120 bb.error("Task %s from %s seems to be empty?!" % (task, fn)) 121 data = '' 122 123 gendeps[task] -= self.basewhitelist 124 newdeps = gendeps[task] 125 seen = set() 126 while newdeps: 127 nextdeps = newdeps 128 seen |= nextdeps 129 newdeps = set() 130 for dep in nextdeps: 131 if dep in self.basewhitelist: 132 continue 133 gendeps[dep] -= self.basewhitelist 134 newdeps |= gendeps[dep] 135 newdeps -= seen 136 137 alldeps = sorted(seen) 138 for dep in alldeps: 139 data = data + dep 140 var = lookupcache[dep] 141 if var is not None: 142 data = data + str(var) 143 datahash = hashlib.md5(data.encode("utf-8")).hexdigest() 144 k = fn + "." + task 145 if not ignore_mismatch and k in self.basehash and self.basehash[k] != datahash: 146 bb.error("When reparsing %s, the basehash value changed from %s to %s. The metadata is not deterministic and this needs to be fixed." % (k, self.basehash[k], datahash)) 147 self.basehash[k] = datahash 148 taskdeps[task] = alldeps 149 150 self.taskdeps[fn] = taskdeps 151 self.gendeps[fn] = gendeps 152 self.lookupcache[fn] = lookupcache 153 154 return taskdeps 155 156 def finalise(self, fn, d, variant): 157 158 mc = d.getVar("__BBMULTICONFIG", False) or "" 159 if variant or mc: 160 fn = bb.cache.realfn2virtual(fn, variant, mc) 161 162 try: 163 taskdeps = self._build_data(fn, d) 164 except bb.parse.SkipRecipe: 165 raise 166 except: 167 bb.warn("Error during finalise of %s" % fn) 168 raise 169 170 #Slow but can be useful for debugging mismatched basehashes 171 #for task in self.taskdeps[fn]: 172 # self.dump_sigtask(fn, task, d.getVar("STAMP"), False) 173 174 for task in taskdeps: 175 d.setVar("BB_BASEHASH_task-%s" % task, self.basehash[fn + "." + task]) 176 177 def rundep_check(self, fn, recipename, task, dep, depname, dataCache): 178 # Return True if we should keep the dependency, False to drop it 179 # We only manipulate the dependencies for packages not in the whitelist 180 if self.twl and not self.twl.search(recipename): 181 # then process the actual dependencies 182 if self.twl.search(depname): 183 return False 184 return True 185 186 def read_taint(self, fn, task, stampbase): 187 taint = None 188 try: 189 with open(stampbase + '.' + task + '.taint', 'r') as taintf: 190 taint = taintf.read() 191 except IOError: 192 pass 193 return taint 194 195 def get_taskhash(self, fn, task, deps, dataCache): 196 k = fn + "." + task 197 data = dataCache.basetaskhash[k] 198 self.basehash[k] = data 199 self.runtaskdeps[k] = [] 200 self.file_checksum_values[k] = [] 201 recipename = dataCache.pkg_fn[fn] 202 203 for dep in sorted(deps, key=clean_basepath): 204 depname = dataCache.pkg_fn[self.pkgnameextract.search(dep).group('fn')] 205 if not self.rundep_check(fn, recipename, task, dep, depname, dataCache): 206 continue 207 if dep not in self.taskhash: 208 bb.fatal("%s is not in taskhash, caller isn't calling in dependency order?", dep) 209 data = data + self.taskhash[dep] 210 self.runtaskdeps[k].append(dep) 211 212 if task in dataCache.file_checksums[fn]: 213 if self.checksum_cache: 214 checksums = self.checksum_cache.get_checksums(dataCache.file_checksums[fn][task], recipename) 215 else: 216 checksums = bb.fetch2.get_file_checksums(dataCache.file_checksums[fn][task], recipename) 217 for (f,cs) in checksums: 218 self.file_checksum_values[k].append((f,cs)) 219 if cs: 220 data = data + cs 221 222 taskdep = dataCache.task_deps[fn] 223 if 'nostamp' in taskdep and task in taskdep['nostamp']: 224 # Nostamp tasks need an implicit taint so that they force any dependent tasks to run 225 import uuid 226 taint = str(uuid.uuid4()) 227 data = data + taint 228 self.taints[k] = "nostamp:" + taint 229 230 taint = self.read_taint(fn, task, dataCache.stamp[fn]) 231 if taint: 232 data = data + taint 233 self.taints[k] = taint 234 logger.warning("%s is tainted from a forced run" % k) 235 236 h = hashlib.md5(data.encode("utf-8")).hexdigest() 237 self.taskhash[k] = h 238 #d.setVar("BB_TASKHASH_task-%s" % task, taskhash[task]) 239 return h 240 241 def writeout_file_checksum_cache(self): 242 """Write/update the file checksum cache onto disk""" 243 if self.checksum_cache: 244 self.checksum_cache.save_extras() 245 self.checksum_cache.save_merge() 246 else: 247 bb.fetch2.fetcher_parse_save() 248 bb.fetch2.fetcher_parse_done() 249 250 def dump_sigtask(self, fn, task, stampbase, runtime): 251 252 k = fn + "." + task 253 referencestamp = stampbase 254 if isinstance(runtime, str) and runtime.startswith("customfile"): 255 sigfile = stampbase 256 referencestamp = runtime[11:] 257 elif runtime and k in self.taskhash: 258 sigfile = stampbase + "." + task + ".sigdata" + "." + self.taskhash[k] 259 else: 260 sigfile = stampbase + "." + task + ".sigbasedata" + "." + self.basehash[k] 261 262 bb.utils.mkdirhier(os.path.dirname(sigfile)) 263 264 data = {} 265 data['task'] = task 266 data['basewhitelist'] = self.basewhitelist 267 data['taskwhitelist'] = self.taskwhitelist 268 data['taskdeps'] = self.taskdeps[fn][task] 269 data['basehash'] = self.basehash[k] 270 data['gendeps'] = {} 271 data['varvals'] = {} 272 data['varvals'][task] = self.lookupcache[fn][task] 273 for dep in self.taskdeps[fn][task]: 274 if dep in self.basewhitelist: 275 continue 276 data['gendeps'][dep] = self.gendeps[fn][dep] 277 data['varvals'][dep] = self.lookupcache[fn][dep] 278 279 if runtime and k in self.taskhash: 280 data['runtaskdeps'] = self.runtaskdeps[k] 281 data['file_checksum_values'] = [(os.path.basename(f), cs) for f,cs in self.file_checksum_values[k]] 282 data['runtaskhashes'] = {} 283 for dep in data['runtaskdeps']: 284 data['runtaskhashes'][dep] = self.taskhash[dep] 285 data['taskhash'] = self.taskhash[k] 286 287 taint = self.read_taint(fn, task, referencestamp) 288 if taint: 289 data['taint'] = taint 290 291 if runtime and k in self.taints: 292 if 'nostamp:' in self.taints[k]: 293 data['taint'] = self.taints[k] 294 295 computed_basehash = calc_basehash(data) 296 if computed_basehash != self.basehash[k]: 297 bb.error("Basehash mismatch %s versus %s for %s" % (computed_basehash, self.basehash[k], k)) 298 if runtime and k in self.taskhash: 299 computed_taskhash = calc_taskhash(data) 300 if computed_taskhash != self.taskhash[k]: 301 bb.error("Taskhash mismatch %s versus %s for %s" % (computed_taskhash, self.taskhash[k], k)) 302 sigfile = sigfile.replace(self.taskhash[k], computed_taskhash) 303 304 fd, tmpfile = tempfile.mkstemp(dir=os.path.dirname(sigfile), prefix="sigtask.") 305 try: 306 with os.fdopen(fd, "wb") as stream: 307 p = pickle.dump(data, stream, -1) 308 stream.flush() 309 os.chmod(tmpfile, 0o664) 310 os.rename(tmpfile, sigfile) 311 except (OSError, IOError) as err: 312 try: 313 os.unlink(tmpfile) 314 except OSError: 315 pass 316 raise err 317 318 def dump_sigfn(self, fn, dataCaches, options): 319 if fn in self.taskdeps: 320 for task in self.taskdeps[fn]: 321 tid = fn + ":" + task 322 (mc, _, _) = bb.runqueue.split_tid(tid) 323 k = fn + "." + task 324 if k not in self.taskhash: 325 continue 326 if dataCaches[mc].basetaskhash[k] != self.basehash[k]: 327 bb.error("Bitbake's cached basehash does not match the one we just generated (%s)!" % k) 328 bb.error("The mismatched hashes were %s and %s" % (dataCaches[mc].basetaskhash[k], self.basehash[k])) 329 self.dump_sigtask(fn, task, dataCaches[mc].stamp[fn], True) 330 331class SignatureGeneratorBasicHash(SignatureGeneratorBasic): 332 name = "basichash" 333 334 def stampfile(self, stampbase, fn, taskname, extrainfo, clean=False): 335 if taskname != "do_setscene" and taskname.endswith("_setscene"): 336 k = fn + "." + taskname[:-9] 337 else: 338 k = fn + "." + taskname 339 if clean: 340 h = "*" 341 elif k in self.taskhash: 342 h = self.taskhash[k] 343 else: 344 # If k is not in basehash, then error 345 h = self.basehash[k] 346 return ("%s.%s.%s.%s" % (stampbase, taskname, h, extrainfo)).rstrip('.') 347 348 def stampcleanmask(self, stampbase, fn, taskname, extrainfo): 349 return self.stampfile(stampbase, fn, taskname, extrainfo, clean=True) 350 351 def invalidate_task(self, task, d, fn): 352 bb.note("Tainting hash to force rebuild of task %s, %s" % (fn, task)) 353 bb.build.write_taint(task, d, fn) 354 355def dump_this_task(outfile, d): 356 import bb.parse 357 fn = d.getVar("BB_FILENAME") 358 task = "do_" + d.getVar("BB_CURRENTTASK") 359 referencestamp = bb.build.stamp_internal(task, d, None, True) 360 bb.parse.siggen.dump_sigtask(fn, task, outfile, "customfile:" + referencestamp) 361 362def init_colors(enable_color): 363 """Initialise colour dict for passing to compare_sigfiles()""" 364 # First set up the colours 365 colors = {'color_title': '\033[1;37;40m', 366 'color_default': '\033[0;37;40m', 367 'color_add': '\033[1;32;40m', 368 'color_remove': '\033[1;31;40m', 369 } 370 # Leave all keys present but clear the values 371 if not enable_color: 372 for k in colors.keys(): 373 colors[k] = '' 374 return colors 375 376def worddiff_str(oldstr, newstr, colors=None): 377 if not colors: 378 colors = init_colors(False) 379 diff = simplediff.diff(oldstr.split(' '), newstr.split(' ')) 380 ret = [] 381 for change, value in diff: 382 value = ' '.join(value) 383 if change == '=': 384 ret.append(value) 385 elif change == '+': 386 item = '{color_add}{{+{value}+}}{color_default}'.format(value=value, **colors) 387 ret.append(item) 388 elif change == '-': 389 item = '{color_remove}[-{value}-]{color_default}'.format(value=value, **colors) 390 ret.append(item) 391 whitespace_note = '' 392 if oldstr != newstr and ' '.join(oldstr.split()) == ' '.join(newstr.split()): 393 whitespace_note = ' (whitespace changed)' 394 return '"%s"%s' % (' '.join(ret), whitespace_note) 395 396def list_inline_diff(oldlist, newlist, colors=None): 397 if not colors: 398 colors = init_colors(False) 399 diff = simplediff.diff(oldlist, newlist) 400 ret = [] 401 for change, value in diff: 402 value = ' '.join(value) 403 if change == '=': 404 ret.append("'%s'" % value) 405 elif change == '+': 406 item = '{color_add}+{value}{color_default}'.format(value=value, **colors) 407 ret.append(item) 408 elif change == '-': 409 item = '{color_remove}-{value}{color_default}'.format(value=value, **colors) 410 ret.append(item) 411 return '[%s]' % (', '.join(ret)) 412 413def clean_basepath(a): 414 mc = None 415 if a.startswith("multiconfig:"): 416 _, mc, a = a.split(":", 2) 417 b = a.rsplit("/", 2)[1] + '/' + a.rsplit("/", 2)[2] 418 if a.startswith("virtual:"): 419 b = b + ":" + a.rsplit(":", 1)[0] 420 if mc: 421 b = b + ":multiconfig:" + mc 422 return b 423 424def clean_basepaths(a): 425 b = {} 426 for x in a: 427 b[clean_basepath(x)] = a[x] 428 return b 429 430def clean_basepaths_list(a): 431 b = [] 432 for x in a: 433 b.append(clean_basepath(x)) 434 return b 435 436def compare_sigfiles(a, b, recursecb=None, color=False, collapsed=False): 437 output = [] 438 439 colors = init_colors(color) 440 def color_format(formatstr, **values): 441 """ 442 Return colour formatted string. 443 NOTE: call with the format string, not an already formatted string 444 containing values (otherwise you could have trouble with { and } 445 characters) 446 """ 447 if not formatstr.endswith('{color_default}'): 448 formatstr += '{color_default}' 449 # In newer python 3 versions you can pass both of these directly, 450 # but we only require 3.4 at the moment 451 formatparams = {} 452 formatparams.update(colors) 453 formatparams.update(values) 454 return formatstr.format(**formatparams) 455 456 with open(a, 'rb') as f: 457 p1 = pickle.Unpickler(f) 458 a_data = p1.load() 459 with open(b, 'rb') as f: 460 p2 = pickle.Unpickler(f) 461 b_data = p2.load() 462 463 def dict_diff(a, b, whitelist=set()): 464 sa = set(a.keys()) 465 sb = set(b.keys()) 466 common = sa & sb 467 changed = set() 468 for i in common: 469 if a[i] != b[i] and i not in whitelist: 470 changed.add(i) 471 added = sb - sa 472 removed = sa - sb 473 return changed, added, removed 474 475 def file_checksums_diff(a, b): 476 from collections import Counter 477 # Handle old siginfo format 478 if isinstance(a, dict): 479 a = [(os.path.basename(f), cs) for f, cs in a.items()] 480 if isinstance(b, dict): 481 b = [(os.path.basename(f), cs) for f, cs in b.items()] 482 # Compare lists, ensuring we can handle duplicate filenames if they exist 483 removedcount = Counter(a) 484 removedcount.subtract(b) 485 addedcount = Counter(b) 486 addedcount.subtract(a) 487 added = [] 488 for x in b: 489 if addedcount[x] > 0: 490 addedcount[x] -= 1 491 added.append(x) 492 removed = [] 493 changed = [] 494 for x in a: 495 if removedcount[x] > 0: 496 removedcount[x] -= 1 497 for y in added: 498 if y[0] == x[0]: 499 changed.append((x[0], x[1], y[1])) 500 added.remove(y) 501 break 502 else: 503 removed.append(x) 504 added = [x[0] for x in added] 505 removed = [x[0] for x in removed] 506 return changed, added, removed 507 508 if 'basewhitelist' in a_data and a_data['basewhitelist'] != b_data['basewhitelist']: 509 output.append(color_format("{color_title}basewhitelist changed{color_default} from '%s' to '%s'") % (a_data['basewhitelist'], b_data['basewhitelist'])) 510 if a_data['basewhitelist'] and b_data['basewhitelist']: 511 output.append("changed items: %s" % a_data['basewhitelist'].symmetric_difference(b_data['basewhitelist'])) 512 513 if 'taskwhitelist' in a_data and a_data['taskwhitelist'] != b_data['taskwhitelist']: 514 output.append(color_format("{color_title}taskwhitelist changed{color_default} from '%s' to '%s'") % (a_data['taskwhitelist'], b_data['taskwhitelist'])) 515 if a_data['taskwhitelist'] and b_data['taskwhitelist']: 516 output.append("changed items: %s" % a_data['taskwhitelist'].symmetric_difference(b_data['taskwhitelist'])) 517 518 if a_data['taskdeps'] != b_data['taskdeps']: 519 output.append(color_format("{color_title}Task dependencies changed{color_default} from:\n%s\nto:\n%s") % (sorted(a_data['taskdeps']), sorted(b_data['taskdeps']))) 520 521 if a_data['basehash'] != b_data['basehash'] and not collapsed: 522 output.append(color_format("{color_title}basehash changed{color_default} from %s to %s") % (a_data['basehash'], b_data['basehash'])) 523 524 changed, added, removed = dict_diff(a_data['gendeps'], b_data['gendeps'], a_data['basewhitelist'] & b_data['basewhitelist']) 525 if changed: 526 for dep in changed: 527 output.append(color_format("{color_title}List of dependencies for variable %s changed from '{color_default}%s{color_title}' to '{color_default}%s{color_title}'") % (dep, a_data['gendeps'][dep], b_data['gendeps'][dep])) 528 if a_data['gendeps'][dep] and b_data['gendeps'][dep]: 529 output.append("changed items: %s" % a_data['gendeps'][dep].symmetric_difference(b_data['gendeps'][dep])) 530 if added: 531 for dep in added: 532 output.append(color_format("{color_title}Dependency on variable %s was added") % (dep)) 533 if removed: 534 for dep in removed: 535 output.append(color_format("{color_title}Dependency on Variable %s was removed") % (dep)) 536 537 538 changed, added, removed = dict_diff(a_data['varvals'], b_data['varvals']) 539 if changed: 540 for dep in changed: 541 oldval = a_data['varvals'][dep] 542 newval = b_data['varvals'][dep] 543 if newval and oldval and ('\n' in oldval or '\n' in newval): 544 diff = difflib.unified_diff(oldval.splitlines(), newval.splitlines(), lineterm='') 545 # Cut off the first two lines, since we aren't interested in 546 # the old/new filename (they are blank anyway in this case) 547 difflines = list(diff)[2:] 548 if color: 549 # Add colour to diff output 550 for i, line in enumerate(difflines): 551 if line.startswith('+'): 552 line = color_format('{color_add}{line}', line=line) 553 difflines[i] = line 554 elif line.startswith('-'): 555 line = color_format('{color_remove}{line}', line=line) 556 difflines[i] = line 557 output.append(color_format("{color_title}Variable {var} value changed:{color_default}\n{diff}", var=dep, diff='\n'.join(difflines))) 558 elif newval and oldval and (' ' in oldval or ' ' in newval): 559 output.append(color_format("{color_title}Variable {var} value changed:{color_default}\n{diff}", var=dep, diff=worddiff_str(oldval, newval, colors))) 560 else: 561 output.append(color_format("{color_title}Variable {var} value changed from '{color_default}{oldval}{color_title}' to '{color_default}{newval}{color_title}'{color_default}", var=dep, oldval=oldval, newval=newval)) 562 563 if not 'file_checksum_values' in a_data: 564 a_data['file_checksum_values'] = {} 565 if not 'file_checksum_values' in b_data: 566 b_data['file_checksum_values'] = {} 567 568 changed, added, removed = file_checksums_diff(a_data['file_checksum_values'], b_data['file_checksum_values']) 569 if changed: 570 for f, old, new in changed: 571 output.append(color_format("{color_title}Checksum for file %s changed{color_default} from %s to %s") % (f, old, new)) 572 if added: 573 for f in added: 574 output.append(color_format("{color_title}Dependency on checksum of file %s was added") % (f)) 575 if removed: 576 for f in removed: 577 output.append(color_format("{color_title}Dependency on checksum of file %s was removed") % (f)) 578 579 if not 'runtaskdeps' in a_data: 580 a_data['runtaskdeps'] = {} 581 if not 'runtaskdeps' in b_data: 582 b_data['runtaskdeps'] = {} 583 584 if not collapsed: 585 if len(a_data['runtaskdeps']) != len(b_data['runtaskdeps']): 586 changed = ["Number of task dependencies changed"] 587 else: 588 changed = [] 589 for idx, task in enumerate(a_data['runtaskdeps']): 590 a = a_data['runtaskdeps'][idx] 591 b = b_data['runtaskdeps'][idx] 592 if a_data['runtaskhashes'][a] != b_data['runtaskhashes'][b] and not collapsed: 593 changed.append("%s with hash %s\n changed to\n%s with hash %s" % (clean_basepath(a), a_data['runtaskhashes'][a], clean_basepath(b), b_data['runtaskhashes'][b])) 594 595 if changed: 596 clean_a = clean_basepaths_list(a_data['runtaskdeps']) 597 clean_b = clean_basepaths_list(b_data['runtaskdeps']) 598 if clean_a != clean_b: 599 output.append(color_format("{color_title}runtaskdeps changed:{color_default}\n%s") % list_inline_diff(clean_a, clean_b, colors)) 600 else: 601 output.append(color_format("{color_title}runtaskdeps changed:")) 602 output.append("\n".join(changed)) 603 604 605 if 'runtaskhashes' in a_data and 'runtaskhashes' in b_data: 606 a = a_data['runtaskhashes'] 607 b = b_data['runtaskhashes'] 608 changed, added, removed = dict_diff(a, b) 609 if added: 610 for dep in added: 611 bdep_found = False 612 if removed: 613 for bdep in removed: 614 if b[dep] == a[bdep]: 615 #output.append("Dependency on task %s was replaced by %s with same hash" % (dep, bdep)) 616 bdep_found = True 617 if not bdep_found: 618 output.append(color_format("{color_title}Dependency on task %s was added{color_default} with hash %s") % (clean_basepath(dep), b[dep])) 619 if removed: 620 for dep in removed: 621 adep_found = False 622 if added: 623 for adep in added: 624 if b[adep] == a[dep]: 625 #output.append("Dependency on task %s was replaced by %s with same hash" % (adep, dep)) 626 adep_found = True 627 if not adep_found: 628 output.append(color_format("{color_title}Dependency on task %s was removed{color_default} with hash %s") % (clean_basepath(dep), a[dep])) 629 if changed: 630 for dep in changed: 631 if not collapsed: 632 output.append(color_format("{color_title}Hash for dependent task %s changed{color_default} from %s to %s") % (clean_basepath(dep), a[dep], b[dep])) 633 if callable(recursecb): 634 recout = recursecb(dep, a[dep], b[dep]) 635 if recout: 636 if collapsed: 637 output.extend(recout) 638 else: 639 # If a dependent hash changed, might as well print the line above and then defer to the changes in 640 # that hash since in all likelyhood, they're the same changes this task also saw. 641 output = [output[-1]] + recout 642 643 a_taint = a_data.get('taint', None) 644 b_taint = b_data.get('taint', None) 645 if a_taint != b_taint: 646 output.append(color_format("{color_title}Taint (by forced/invalidated task) changed{color_default} from %s to %s") % (a_taint, b_taint)) 647 648 return output 649 650 651def calc_basehash(sigdata): 652 task = sigdata['task'] 653 basedata = sigdata['varvals'][task] 654 655 if basedata is None: 656 basedata = '' 657 658 alldeps = sigdata['taskdeps'] 659 for dep in alldeps: 660 basedata = basedata + dep 661 val = sigdata['varvals'][dep] 662 if val is not None: 663 basedata = basedata + str(val) 664 665 return hashlib.md5(basedata.encode("utf-8")).hexdigest() 666 667def calc_taskhash(sigdata): 668 data = sigdata['basehash'] 669 670 for dep in sigdata['runtaskdeps']: 671 data = data + sigdata['runtaskhashes'][dep] 672 673 for c in sigdata['file_checksum_values']: 674 if c[1]: 675 data = data + c[1] 676 677 if 'taint' in sigdata: 678 if 'nostamp:' in sigdata['taint']: 679 data = data + sigdata['taint'][8:] 680 else: 681 data = data + sigdata['taint'] 682 683 return hashlib.md5(data.encode("utf-8")).hexdigest() 684 685 686def dump_sigfile(a): 687 output = [] 688 689 with open(a, 'rb') as f: 690 p1 = pickle.Unpickler(f) 691 a_data = p1.load() 692 693 output.append("basewhitelist: %s" % (a_data['basewhitelist'])) 694 695 output.append("taskwhitelist: %s" % (a_data['taskwhitelist'])) 696 697 output.append("Task dependencies: %s" % (sorted(a_data['taskdeps']))) 698 699 output.append("basehash: %s" % (a_data['basehash'])) 700 701 for dep in a_data['gendeps']: 702 output.append("List of dependencies for variable %s is %s" % (dep, a_data['gendeps'][dep])) 703 704 for dep in a_data['varvals']: 705 output.append("Variable %s value is %s" % (dep, a_data['varvals'][dep])) 706 707 if 'runtaskdeps' in a_data: 708 output.append("Tasks this task depends on: %s" % (a_data['runtaskdeps'])) 709 710 if 'file_checksum_values' in a_data: 711 output.append("This task depends on the checksums of files: %s" % (a_data['file_checksum_values'])) 712 713 if 'runtaskhashes' in a_data: 714 for dep in a_data['runtaskhashes']: 715 output.append("Hash for dependent task %s is %s" % (dep, a_data['runtaskhashes'][dep])) 716 717 if 'taint' in a_data: 718 output.append("Tainted (by forced/invalidated task): %s" % a_data['taint']) 719 720 if 'task' in a_data: 721 computed_basehash = calc_basehash(a_data) 722 output.append("Computed base hash is %s and from file %s" % (computed_basehash, a_data['basehash'])) 723 else: 724 output.append("Unable to compute base hash") 725 726 computed_taskhash = calc_taskhash(a_data) 727 output.append("Computed task hash is %s" % computed_taskhash) 728 729 return output 730