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