1# 2# Copyright BitBake Contributors 3# 4# SPDX-License-Identifier: GPL-2.0-only 5# 6 7import hashlib 8import logging 9import os 10import re 11import tempfile 12import pickle 13import bb.data 14import difflib 15import simplediff 16import json 17import types 18from contextlib import contextmanager 19import bb.compress.zstd 20from bb.checksum import FileChecksumCache 21from bb import runqueue 22import hashserv 23import hashserv.client 24 25logger = logging.getLogger('BitBake.SigGen') 26hashequiv_logger = logging.getLogger('BitBake.SigGen.HashEquiv') 27 28#find_siginfo and find_siginfo_version are set by the metadata siggen 29# The minimum version of the find_siginfo function we need 30find_siginfo_minversion = 2 31 32HASHSERV_ENVVARS = [ 33 "SSL_CERT_DIR", 34 "SSL_CERT_FILE", 35 "NO_PROXY", 36 "HTTPS_PROXY", 37 "HTTP_PROXY" 38] 39 40def check_siggen_version(siggen): 41 if not hasattr(siggen, "find_siginfo_version"): 42 bb.fatal("Siggen from metadata (OE-Core?) is too old, please update it (no version found)") 43 if siggen.find_siginfo_version < siggen.find_siginfo_minversion: 44 bb.fatal("Siggen from metadata (OE-Core?) is too old, please update it (%s vs %s)" % (siggen.find_siginfo_version, siggen.find_siginfo_minversion)) 45 46class SetEncoder(json.JSONEncoder): 47 def default(self, obj): 48 if isinstance(obj, set) or isinstance(obj, frozenset): 49 return dict(_set_object=list(sorted(obj))) 50 return json.JSONEncoder.default(self, obj) 51 52def SetDecoder(dct): 53 if '_set_object' in dct: 54 return frozenset(dct['_set_object']) 55 return dct 56 57def init(d): 58 siggens = [obj for obj in globals().values() 59 if type(obj) is type and issubclass(obj, SignatureGenerator)] 60 61 desired = d.getVar("BB_SIGNATURE_HANDLER") or "noop" 62 for sg in siggens: 63 if desired == sg.name: 64 return sg(d) 65 else: 66 logger.error("Invalid signature generator '%s', using default 'noop'\n" 67 "Available generators: %s", desired, 68 ', '.join(obj.name for obj in siggens)) 69 return SignatureGenerator(d) 70 71class SignatureGenerator(object): 72 """ 73 """ 74 name = "noop" 75 76 def __init__(self, data): 77 self.basehash = {} 78 self.taskhash = {} 79 self.unihash = {} 80 self.runtaskdeps = {} 81 self.file_checksum_values = {} 82 self.taints = {} 83 self.unitaskhashes = {} 84 self.tidtopn = {} 85 self.setscenetasks = set() 86 87 def finalise(self, fn, d, varient): 88 return 89 90 def postparsing_clean_cache(self): 91 return 92 93 def setup_datacache(self, datacaches): 94 self.datacaches = datacaches 95 96 def setup_datacache_from_datastore(self, mcfn, d): 97 # In task context we have no cache so setup internal data structures 98 # from the fully parsed data store provided 99 100 mc = d.getVar("__BBMULTICONFIG", False) or "" 101 tasks = d.getVar('__BBTASKS', False) 102 103 self.datacaches = {} 104 self.datacaches[mc] = types.SimpleNamespace() 105 setattr(self.datacaches[mc], "stamp", {}) 106 self.datacaches[mc].stamp[mcfn] = d.getVar('STAMP') 107 setattr(self.datacaches[mc], "stamp_extrainfo", {}) 108 self.datacaches[mc].stamp_extrainfo[mcfn] = {} 109 for t in tasks: 110 flag = d.getVarFlag(t, "stamp-extra-info") 111 if flag: 112 self.datacaches[mc].stamp_extrainfo[mcfn][t] = flag 113 114 def get_cached_unihash(self, tid): 115 return None 116 117 def get_unihash(self, tid): 118 unihash = self.get_cached_unihash(tid) 119 if unihash: 120 return unihash 121 return self.taskhash[tid] 122 123 def get_unihashes(self, tids): 124 return {tid: self.get_unihash(tid) for tid in tids} 125 126 def prep_taskhash(self, tid, deps, dataCaches): 127 return 128 129 def get_taskhash(self, tid, deps, dataCaches): 130 self.taskhash[tid] = hashlib.sha256(tid.encode("utf-8")).hexdigest() 131 return self.taskhash[tid] 132 133 def writeout_file_checksum_cache(self): 134 """Write/update the file checksum cache onto disk""" 135 return 136 137 def stampfile_base(self, mcfn): 138 mc = bb.runqueue.mc_from_tid(mcfn) 139 return self.datacaches[mc].stamp[mcfn] 140 141 def stampfile_mcfn(self, taskname, mcfn, extrainfo=True): 142 mc = bb.runqueue.mc_from_tid(mcfn) 143 stamp = self.datacaches[mc].stamp[mcfn] 144 if not stamp: 145 return 146 147 stamp_extrainfo = "" 148 if extrainfo: 149 taskflagname = taskname 150 if taskname.endswith("_setscene"): 151 taskflagname = taskname.replace("_setscene", "") 152 stamp_extrainfo = self.datacaches[mc].stamp_extrainfo[mcfn].get(taskflagname) or "" 153 154 return self.stampfile(stamp, mcfn, taskname, stamp_extrainfo) 155 156 def stampfile(self, stampbase, file_name, taskname, extrainfo): 157 return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.') 158 159 def stampcleanmask_mcfn(self, taskname, mcfn): 160 mc = bb.runqueue.mc_from_tid(mcfn) 161 stamp = self.datacaches[mc].stamp[mcfn] 162 if not stamp: 163 return [] 164 165 taskflagname = taskname 166 if taskname.endswith("_setscene"): 167 taskflagname = taskname.replace("_setscene", "") 168 stamp_extrainfo = self.datacaches[mc].stamp_extrainfo[mcfn].get(taskflagname) or "" 169 170 return self.stampcleanmask(stamp, mcfn, taskname, stamp_extrainfo) 171 172 def stampcleanmask(self, stampbase, file_name, taskname, extrainfo): 173 return ("%s.%s.%s" % (stampbase, taskname, extrainfo)).rstrip('.') 174 175 def dump_sigtask(self, mcfn, task, stampbase, runtime): 176 return 177 178 def invalidate_task(self, task, mcfn): 179 mc = bb.runqueue.mc_from_tid(mcfn) 180 stamp = self.datacaches[mc].stamp[mcfn] 181 bb.utils.remove(stamp) 182 183 def dump_sigs(self, dataCache, options): 184 return 185 186 def get_taskdata(self): 187 return (self.runtaskdeps, self.taskhash, self.unihash, self.file_checksum_values, self.taints, self.basehash, self.unitaskhashes, self.tidtopn, self.setscenetasks) 188 189 def set_taskdata(self, data): 190 self.runtaskdeps, self.taskhash, self.unihash, self.file_checksum_values, self.taints, self.basehash, self.unitaskhashes, self.tidtopn, self.setscenetasks = data 191 192 def reset(self, data): 193 self.__init__(data) 194 195 def get_taskhashes(self): 196 return self.taskhash, self.unihash, self.unitaskhashes, self.tidtopn 197 198 def set_taskhashes(self, hashes): 199 self.taskhash, self.unihash, self.unitaskhashes, self.tidtopn = hashes 200 201 def save_unitaskhashes(self): 202 return 203 204 def copy_unitaskhashes(self, targetdir): 205 return 206 207 def set_setscene_tasks(self, setscene_tasks): 208 return 209 210 def exit(self): 211 return 212 213def build_pnid(mc, pn, taskname): 214 if mc: 215 return "mc:" + mc + ":" + pn + ":" + taskname 216 return pn + ":" + taskname 217 218class SignatureGeneratorBasic(SignatureGenerator): 219 """ 220 """ 221 name = "basic" 222 223 def __init__(self, data): 224 self.basehash = {} 225 self.taskhash = {} 226 self.unihash = {} 227 self.runtaskdeps = {} 228 self.file_checksum_values = {} 229 self.taints = {} 230 self.setscenetasks = set() 231 self.basehash_ignore_vars = set((data.getVar("BB_BASEHASH_IGNORE_VARS") or "").split()) 232 self.taskhash_ignore_tasks = None 233 self.init_rundepcheck(data) 234 checksum_cache_file = data.getVar("BB_HASH_CHECKSUM_CACHE_FILE") 235 if checksum_cache_file: 236 self.checksum_cache = FileChecksumCache() 237 self.checksum_cache.init_cache(data, checksum_cache_file) 238 else: 239 self.checksum_cache = None 240 241 self.unihash_cache = bb.cache.SimpleCache("3") 242 self.unitaskhashes = self.unihash_cache.init_cache(data, "bb_unihashes.dat", {}) 243 self.localdirsexclude = (data.getVar("BB_SIGNATURE_LOCAL_DIRS_EXCLUDE") or "CVS .bzr .git .hg .osc .p4 .repo .svn").split() 244 self.tidtopn = {} 245 246 def init_rundepcheck(self, data): 247 self.taskhash_ignore_tasks = data.getVar("BB_TASKHASH_IGNORE_TASKS") or None 248 if self.taskhash_ignore_tasks: 249 self.twl = re.compile(self.taskhash_ignore_tasks) 250 else: 251 self.twl = None 252 253 def _build_data(self, mcfn, d): 254 255 ignore_mismatch = ((d.getVar("BB_HASH_IGNORE_MISMATCH") or '') == '1') 256 tasklist, gendeps, lookupcache = bb.data.generate_dependencies(d, self.basehash_ignore_vars) 257 258 taskdeps, basehash = bb.data.generate_dependency_hash(tasklist, gendeps, lookupcache, self.basehash_ignore_vars, mcfn) 259 260 for task in tasklist: 261 tid = mcfn + ":" + task 262 if not ignore_mismatch and tid in self.basehash and self.basehash[tid] != basehash[tid]: 263 bb.error("When reparsing %s, the basehash value changed from %s to %s. The metadata is not deterministic and this needs to be fixed." % (tid, self.basehash[tid], basehash[tid])) 264 bb.error("The following commands may help:") 265 cmd = "$ bitbake %s -c%s" % (d.getVar('PN'), task) 266 # Make sure sigdata is dumped before run printdiff 267 bb.error("%s -Snone" % cmd) 268 bb.error("Then:") 269 bb.error("%s -Sprintdiff\n" % cmd) 270 self.basehash[tid] = basehash[tid] 271 272 return taskdeps, gendeps, lookupcache 273 274 def set_setscene_tasks(self, setscene_tasks): 275 self.setscenetasks = set(setscene_tasks) 276 277 def finalise(self, fn, d, variant): 278 279 mc = d.getVar("__BBMULTICONFIG", False) or "" 280 mcfn = fn 281 if variant or mc: 282 mcfn = bb.cache.realfn2virtual(fn, variant, mc) 283 284 try: 285 taskdeps, gendeps, lookupcache = self._build_data(mcfn, d) 286 except bb.parse.SkipRecipe: 287 raise 288 except: 289 bb.warn("Error during finalise of %s" % mcfn) 290 raise 291 292 basehashes = {} 293 for task in taskdeps: 294 basehashes[task] = self.basehash[mcfn + ":" + task] 295 296 d.setVar("__siggen_basehashes", basehashes) 297 d.setVar("__siggen_gendeps", gendeps) 298 d.setVar("__siggen_varvals", lookupcache) 299 d.setVar("__siggen_taskdeps", taskdeps) 300 301 #Slow but can be useful for debugging mismatched basehashes 302 #self.setup_datacache_from_datastore(mcfn, d) 303 #for task in taskdeps: 304 # self.dump_sigtask(mcfn, task, d.getVar("STAMP"), False) 305 306 def setup_datacache_from_datastore(self, mcfn, d): 307 super().setup_datacache_from_datastore(mcfn, d) 308 309 mc = bb.runqueue.mc_from_tid(mcfn) 310 for attr in ["siggen_varvals", "siggen_taskdeps", "siggen_gendeps"]: 311 if not hasattr(self.datacaches[mc], attr): 312 setattr(self.datacaches[mc], attr, {}) 313 self.datacaches[mc].siggen_varvals[mcfn] = d.getVar("__siggen_varvals") 314 self.datacaches[mc].siggen_taskdeps[mcfn] = d.getVar("__siggen_taskdeps") 315 self.datacaches[mc].siggen_gendeps[mcfn] = d.getVar("__siggen_gendeps") 316 317 def rundep_check(self, fn, recipename, task, dep, depname, dataCaches): 318 # Return True if we should keep the dependency, False to drop it 319 # We only manipulate the dependencies for packages not in the ignore 320 # list 321 if self.twl and not self.twl.search(recipename): 322 # then process the actual dependencies 323 if self.twl.search(depname): 324 return False 325 return True 326 327 def read_taint(self, fn, task, stampbase): 328 taint = None 329 try: 330 with open(stampbase + '.' + task + '.taint', 'r') as taintf: 331 taint = taintf.read() 332 except IOError: 333 pass 334 return taint 335 336 def prep_taskhash(self, tid, deps, dataCaches): 337 338 (mc, _, task, mcfn) = bb.runqueue.split_tid_mcfn(tid) 339 340 self.basehash[tid] = dataCaches[mc].basetaskhash[tid] 341 self.runtaskdeps[tid] = [] 342 self.file_checksum_values[tid] = [] 343 recipename = dataCaches[mc].pkg_fn[mcfn] 344 345 self.tidtopn[tid] = recipename 346 # save hashfn for deps into siginfo? 347 for dep in deps: 348 (depmc, _, deptask, depmcfn) = bb.runqueue.split_tid_mcfn(dep) 349 dep_pn = dataCaches[depmc].pkg_fn[depmcfn] 350 351 if not self.rundep_check(mcfn, recipename, task, dep, dep_pn, dataCaches): 352 continue 353 354 if dep not in self.taskhash: 355 bb.fatal("%s is not in taskhash, caller isn't calling in dependency order?" % dep) 356 357 dep_pnid = build_pnid(depmc, dep_pn, deptask) 358 self.runtaskdeps[tid].append((dep_pnid, dep)) 359 360 if task in dataCaches[mc].file_checksums[mcfn]: 361 if self.checksum_cache: 362 checksums = self.checksum_cache.get_checksums(dataCaches[mc].file_checksums[mcfn][task], recipename, self.localdirsexclude) 363 else: 364 checksums = bb.fetch2.get_file_checksums(dataCaches[mc].file_checksums[mcfn][task], recipename, self.localdirsexclude) 365 for (f,cs) in checksums: 366 self.file_checksum_values[tid].append((f,cs)) 367 368 taskdep = dataCaches[mc].task_deps[mcfn] 369 if 'nostamp' in taskdep and task in taskdep['nostamp']: 370 # Nostamp tasks need an implicit taint so that they force any dependent tasks to run 371 if tid in self.taints and self.taints[tid].startswith("nostamp:"): 372 # Don't reset taint value upon every call 373 pass 374 else: 375 import uuid 376 taint = str(uuid.uuid4()) 377 self.taints[tid] = "nostamp:" + taint 378 379 taint = self.read_taint(mcfn, task, dataCaches[mc].stamp[mcfn]) 380 if taint: 381 self.taints[tid] = taint 382 logger.warning("%s is tainted from a forced run" % tid) 383 384 return 385 386 def get_taskhash(self, tid, deps, dataCaches): 387 388 data = self.basehash[tid] 389 for dep in sorted(self.runtaskdeps[tid]): 390 data += self.get_unihash(dep[1]) 391 392 for (f, cs) in sorted(self.file_checksum_values[tid], key=clean_checksum_file_path): 393 if cs: 394 if "/./" in f: 395 data += "./" + f.split("/./")[1] 396 data += cs 397 398 if tid in self.taints: 399 if self.taints[tid].startswith("nostamp:"): 400 data += self.taints[tid][8:] 401 else: 402 data += self.taints[tid] 403 404 h = hashlib.sha256(data.encode("utf-8")).hexdigest() 405 self.taskhash[tid] = h 406 #d.setVar("BB_TASKHASH:task-%s" % task, taskhash[task]) 407 return h 408 409 def writeout_file_checksum_cache(self): 410 """Write/update the file checksum cache onto disk""" 411 if self.checksum_cache: 412 self.checksum_cache.save_extras() 413 self.checksum_cache.save_merge() 414 else: 415 bb.fetch2.fetcher_parse_save() 416 bb.fetch2.fetcher_parse_done() 417 418 def save_unitaskhashes(self): 419 self.unihash_cache.save(self.unitaskhashes) 420 421 def copy_unitaskhashes(self, targetdir): 422 self.unihash_cache.copyfile(targetdir) 423 424 def dump_sigtask(self, mcfn, task, stampbase, runtime): 425 tid = mcfn + ":" + task 426 mc = bb.runqueue.mc_from_tid(mcfn) 427 referencestamp = stampbase 428 if isinstance(runtime, str) and runtime.startswith("customfile"): 429 sigfile = stampbase 430 referencestamp = runtime[11:] 431 elif runtime and tid in self.taskhash: 432 sigfile = stampbase + "." + task + ".sigdata" + "." + self.get_unihash(tid) 433 else: 434 sigfile = stampbase + "." + task + ".sigbasedata" + "." + self.basehash[tid] 435 436 with bb.utils.umask(0o002): 437 bb.utils.mkdirhier(os.path.dirname(sigfile)) 438 439 data = {} 440 data['task'] = task 441 data['basehash_ignore_vars'] = self.basehash_ignore_vars 442 data['taskhash_ignore_tasks'] = self.taskhash_ignore_tasks 443 data['taskdeps'] = self.datacaches[mc].siggen_taskdeps[mcfn][task] 444 data['basehash'] = self.basehash[tid] 445 data['gendeps'] = {} 446 data['varvals'] = {} 447 data['varvals'][task] = self.datacaches[mc].siggen_varvals[mcfn][task] 448 for dep in self.datacaches[mc].siggen_taskdeps[mcfn][task]: 449 if dep in self.basehash_ignore_vars: 450 continue 451 data['gendeps'][dep] = self.datacaches[mc].siggen_gendeps[mcfn][dep] 452 data['varvals'][dep] = self.datacaches[mc].siggen_varvals[mcfn][dep] 453 454 if runtime and tid in self.taskhash: 455 data['runtaskdeps'] = [dep[0] for dep in sorted(self.runtaskdeps[tid])] 456 data['file_checksum_values'] = [] 457 for f,cs in sorted(self.file_checksum_values[tid], key=clean_checksum_file_path): 458 if "/./" in f: 459 data['file_checksum_values'].append(("./" + f.split("/./")[1], cs)) 460 else: 461 data['file_checksum_values'].append((os.path.basename(f), cs)) 462 data['runtaskhashes'] = {} 463 for dep in self.runtaskdeps[tid]: 464 data['runtaskhashes'][dep[0]] = self.get_unihash(dep[1]) 465 data['taskhash'] = self.taskhash[tid] 466 data['unihash'] = self.get_unihash(tid) 467 468 taint = self.read_taint(mcfn, task, referencestamp) 469 if taint: 470 data['taint'] = taint 471 472 if runtime and tid in self.taints: 473 if 'nostamp:' in self.taints[tid]: 474 data['taint'] = self.taints[tid] 475 476 computed_basehash = calc_basehash(data) 477 if computed_basehash != self.basehash[tid]: 478 bb.error("Basehash mismatch %s versus %s for %s" % (computed_basehash, self.basehash[tid], tid)) 479 if runtime and tid in self.taskhash: 480 computed_taskhash = calc_taskhash(data) 481 if computed_taskhash != self.taskhash[tid]: 482 bb.error("Taskhash mismatch %s versus %s for %s" % (computed_taskhash, self.taskhash[tid], tid)) 483 sigfile = sigfile.replace(self.taskhash[tid], computed_taskhash) 484 485 fd, tmpfile = bb.utils.mkstemp(dir=os.path.dirname(sigfile), prefix="sigtask.") 486 try: 487 with bb.compress.zstd.open(fd, "wt", encoding="utf-8", num_threads=1) as f: 488 json.dump(data, f, sort_keys=True, separators=(",", ":"), cls=SetEncoder) 489 f.flush() 490 os.chmod(tmpfile, 0o664) 491 bb.utils.rename(tmpfile, sigfile) 492 except (OSError, IOError) as err: 493 try: 494 os.unlink(tmpfile) 495 except OSError: 496 pass 497 raise err 498 499class SignatureGeneratorBasicHash(SignatureGeneratorBasic): 500 name = "basichash" 501 502 def get_stampfile_hash(self, tid): 503 if tid in self.taskhash: 504 return self.taskhash[tid] 505 506 # If task is not in basehash, then error 507 return self.basehash[tid] 508 509 def stampfile(self, stampbase, mcfn, taskname, extrainfo, clean=False): 510 if taskname.endswith("_setscene"): 511 tid = mcfn + ":" + taskname[:-9] 512 else: 513 tid = mcfn + ":" + taskname 514 if clean: 515 h = "*" 516 else: 517 h = self.get_stampfile_hash(tid) 518 519 return ("%s.%s.%s.%s" % (stampbase, taskname, h, extrainfo)).rstrip('.') 520 521 def stampcleanmask(self, stampbase, mcfn, taskname, extrainfo): 522 return self.stampfile(stampbase, mcfn, taskname, extrainfo, clean=True) 523 524 def invalidate_task(self, task, mcfn): 525 bb.note("Tainting hash to force rebuild of task %s, %s" % (mcfn, task)) 526 527 mc = bb.runqueue.mc_from_tid(mcfn) 528 stamp = self.datacaches[mc].stamp[mcfn] 529 530 taintfn = stamp + '.' + task + '.taint' 531 532 import uuid 533 bb.utils.mkdirhier(os.path.dirname(taintfn)) 534 # The specific content of the taint file is not really important, 535 # we just need it to be random, so a random UUID is used 536 with open(taintfn, 'w') as taintf: 537 taintf.write(str(uuid.uuid4())) 538 539class SignatureGeneratorUniHashMixIn(object): 540 def __init__(self, data): 541 self.extramethod = {} 542 # NOTE: The cache only tracks hashes that exist. Hashes that don't 543 # exist are always queries from the server since it is possible for 544 # hashes to appear over time, but much less likely for them to 545 # disappear 546 self.unihash_exists_cache = set() 547 self.username = None 548 self.password = None 549 self.env = {} 550 551 origenv = data.getVar("BB_ORIGENV") 552 for e in HASHSERV_ENVVARS: 553 value = data.getVar(e) 554 if not value and origenv: 555 value = origenv.getVar(e) 556 if value: 557 self.env[e] = value 558 super().__init__(data) 559 560 def get_taskdata(self): 561 return (self.server, self.method, self.extramethod, self.max_parallel, self.username, self.password, self.env) + super().get_taskdata() 562 563 def set_taskdata(self, data): 564 self.server, self.method, self.extramethod, self.max_parallel, self.username, self.password, self.env = data[:7] 565 super().set_taskdata(data[7:]) 566 567 def get_hashserv_creds(self): 568 if self.username and self.password: 569 return { 570 "username": self.username, 571 "password": self.password, 572 } 573 574 return {} 575 576 @contextmanager 577 def _client_env(self): 578 orig_env = os.environ.copy() 579 try: 580 for k, v in self.env.items(): 581 os.environ[k] = v 582 583 yield 584 finally: 585 os.environ = orig_env 586 587 @contextmanager 588 def client(self): 589 with self._client_env(): 590 if getattr(self, '_client', None) is None: 591 self._client = hashserv.create_client(self.server, **self.get_hashserv_creds()) 592 yield self._client 593 594 @contextmanager 595 def client_pool(self): 596 with self._client_env(): 597 if getattr(self, '_client_pool', None) is None: 598 self._client_pool = hashserv.client.ClientPool(self.server, self.max_parallel, **self.get_hashserv_creds()) 599 yield self._client_pool 600 601 def reset(self, data): 602 self.__close_clients() 603 return super().reset(data) 604 605 def exit(self): 606 self.__close_clients() 607 return super().exit() 608 609 def __close_clients(self): 610 with self._client_env(): 611 if getattr(self, '_client', None) is not None: 612 self._client.close() 613 self._client = None 614 if getattr(self, '_client_pool', None) is not None: 615 self._client_pool.close() 616 self._client_pool = None 617 618 def get_stampfile_hash(self, tid): 619 if tid in self.taskhash: 620 # If a unique hash is reported, use it as the stampfile hash. This 621 # ensures that if a task won't be re-run if the taskhash changes, 622 # but it would result in the same output hash 623 unihash = self._get_unihash(tid) 624 if unihash is not None: 625 return unihash 626 627 return super().get_stampfile_hash(tid) 628 629 def set_unihash(self, tid, unihash): 630 (mc, fn, taskname, taskfn) = bb.runqueue.split_tid_mcfn(tid) 631 key = mc + ":" + self.tidtopn[tid] + ":" + taskname 632 self.unitaskhashes[key] = (self.taskhash[tid], unihash) 633 self.unihash[tid] = unihash 634 635 def _get_unihash(self, tid, checkkey=None): 636 if tid not in self.tidtopn: 637 return None 638 (mc, fn, taskname, taskfn) = bb.runqueue.split_tid_mcfn(tid) 639 key = mc + ":" + self.tidtopn[tid] + ":" + taskname 640 if key not in self.unitaskhashes: 641 return None 642 if not checkkey: 643 checkkey = self.taskhash[tid] 644 (key, unihash) = self.unitaskhashes[key] 645 if key != checkkey: 646 return None 647 return unihash 648 649 def get_cached_unihash(self, tid): 650 taskhash = self.taskhash[tid] 651 652 # If its not a setscene task we can return 653 if self.setscenetasks and tid not in self.setscenetasks: 654 self.unihash[tid] = None 655 return taskhash 656 657 # TODO: This cache can grow unbounded. It probably only needs to keep 658 # for each task 659 unihash = self._get_unihash(tid) 660 if unihash is not None: 661 self.unihash[tid] = unihash 662 return unihash 663 664 return None 665 666 def _get_method(self, tid): 667 method = self.method 668 if tid in self.extramethod: 669 method = method + self.extramethod[tid] 670 671 return method 672 673 def unihashes_exist(self, query): 674 if len(query) == 0: 675 return {} 676 677 uncached_query = {} 678 result = {} 679 for key, unihash in query.items(): 680 if unihash in self.unihash_exists_cache: 681 result[key] = True 682 else: 683 uncached_query[key] = unihash 684 685 if self.max_parallel <= 1 or len(uncached_query) <= 1: 686 # No parallelism required. Make the query serially with the single client 687 with self.client() as client: 688 uncached_result = { 689 key: client.unihash_exists(value) for key, value in uncached_query.items() 690 } 691 else: 692 with self.client_pool() as client_pool: 693 uncached_result = client_pool.unihashes_exist(uncached_query) 694 695 for key, exists in uncached_result.items(): 696 if exists: 697 self.unihash_exists_cache.add(query[key]) 698 result[key] = exists 699 700 return result 701 702 def get_unihash(self, tid): 703 return self.get_unihashes([tid])[tid] 704 705 def get_unihashes(self, tids): 706 """ 707 For a iterable of tids, returns a dictionary that maps each tid to a 708 unihash 709 """ 710 result = {} 711 queries = {} 712 query_result = {} 713 714 for tid in tids: 715 unihash = self.get_cached_unihash(tid) 716 if unihash: 717 result[tid] = unihash 718 else: 719 queries[tid] = (self._get_method(tid), self.taskhash[tid]) 720 721 if len(queries) == 0: 722 return result 723 724 if self.max_parallel <= 1 or len(queries) <= 1: 725 # No parallelism required. Make the query serially with the single client 726 with self.client() as client: 727 for tid, args in queries.items(): 728 query_result[tid] = client.get_unihash(*args) 729 else: 730 with self.client_pool() as client_pool: 731 query_result = client_pool.get_unihashes(queries) 732 733 for tid, unihash in query_result.items(): 734 # In the absence of being able to discover a unique hash from the 735 # server, make it be equivalent to the taskhash. The unique "hash" only 736 # really needs to be a unique string (not even necessarily a hash), but 737 # making it match the taskhash has a few advantages: 738 # 739 # 1) All of the sstate code that assumes hashes can be the same 740 # 2) It provides maximal compatibility with builders that don't use 741 # an equivalency server 742 # 3) The value is easy for multiple independent builders to derive the 743 # same unique hash from the same input. This means that if the 744 # independent builders find the same taskhash, but it isn't reported 745 # to the server, there is a better chance that they will agree on 746 # the unique hash. 747 taskhash = self.taskhash[tid] 748 if unihash: 749 # A unique hash equal to the taskhash is not very interesting, 750 # so it is reported it at debug level 2. If they differ, that 751 # is much more interesting, so it is reported at debug level 1 752 hashequiv_logger.bbdebug((1, 2)[unihash == taskhash], 'Found unihash %s in place of %s for %s from %s' % (unihash, taskhash, tid, self.server)) 753 else: 754 hashequiv_logger.debug2('No reported unihash for %s:%s from %s' % (tid, taskhash, self.server)) 755 unihash = taskhash 756 757 758 self.set_unihash(tid, unihash) 759 self.unihash[tid] = unihash 760 result[tid] = unihash 761 762 return result 763 764 def report_unihash(self, path, task, d): 765 import importlib 766 767 taskhash = d.getVar('BB_TASKHASH') 768 unihash = d.getVar('BB_UNIHASH') 769 report_taskdata = d.getVar('SSTATE_HASHEQUIV_REPORT_TASKDATA') == '1' 770 tempdir = d.getVar('T') 771 mcfn = d.getVar('BB_FILENAME') 772 tid = mcfn + ':do_' + task 773 key = tid + ':' + taskhash 774 775 if self.setscenetasks and tid not in self.setscenetasks: 776 return 777 778 # This can happen if locked sigs are in action. Detect and just exit 779 if taskhash != self.taskhash[tid]: 780 return 781 782 # Sanity checks 783 cache_unihash = self._get_unihash(tid, checkkey=taskhash) 784 if cache_unihash is None: 785 bb.fatal('%s not in unihash cache. Please report this error' % key) 786 787 if cache_unihash != unihash: 788 bb.fatal("Cache unihash %s doesn't match BB_UNIHASH %s" % (cache_unihash, unihash)) 789 790 sigfile = None 791 sigfile_name = "depsig.do_%s.%d" % (task, os.getpid()) 792 sigfile_link = "depsig.do_%s" % task 793 794 try: 795 sigfile = open(os.path.join(tempdir, sigfile_name), 'w+b') 796 797 locs = {'path': path, 'sigfile': sigfile, 'task': task, 'd': d} 798 799 if "." in self.method: 800 (module, method) = self.method.rsplit('.', 1) 801 locs['method'] = getattr(importlib.import_module(module), method) 802 outhash = bb.utils.better_eval('method(path, sigfile, task, d)', locs) 803 else: 804 outhash = bb.utils.better_eval(self.method + '(path, sigfile, task, d)', locs) 805 806 try: 807 extra_data = {} 808 809 owner = d.getVar('SSTATE_HASHEQUIV_OWNER') 810 if owner: 811 extra_data['owner'] = owner 812 813 if report_taskdata: 814 sigfile.seek(0) 815 816 extra_data['PN'] = d.getVar('PN') 817 extra_data['PV'] = d.getVar('PV') 818 extra_data['PR'] = d.getVar('PR') 819 extra_data['task'] = task 820 extra_data['outhash_siginfo'] = sigfile.read().decode('utf-8') 821 822 method = self.method 823 if tid in self.extramethod: 824 method = method + self.extramethod[tid] 825 826 with self.client() as client: 827 data = client.report_unihash(taskhash, method, outhash, unihash, extra_data) 828 829 new_unihash = data['unihash'] 830 831 if new_unihash != unihash: 832 hashequiv_logger.debug('Task %s unihash changed %s -> %s by server %s' % (taskhash, unihash, new_unihash, self.server)) 833 bb.event.fire(bb.runqueue.taskUniHashUpdate(mcfn + ':do_' + task, new_unihash), d) 834 self.set_unihash(tid, new_unihash) 835 d.setVar('BB_UNIHASH', new_unihash) 836 else: 837 hashequiv_logger.debug('Reported task %s as unihash %s to %s' % (taskhash, unihash, self.server)) 838 except ConnectionError as e: 839 bb.warn('Error contacting Hash Equivalence Server %s: %s' % (self.server, str(e))) 840 finally: 841 if sigfile: 842 sigfile.close() 843 844 sigfile_link_path = os.path.join(tempdir, sigfile_link) 845 bb.utils.remove(sigfile_link_path) 846 847 try: 848 os.symlink(sigfile_name, sigfile_link_path) 849 except OSError: 850 pass 851 852 def report_unihash_equiv(self, tid, taskhash, wanted_unihash, current_unihash, datacaches): 853 try: 854 extra_data = {} 855 method = self.method 856 if tid in self.extramethod: 857 method = method + self.extramethod[tid] 858 859 with self.client() as client: 860 data = client.report_unihash_equiv(taskhash, method, wanted_unihash, extra_data) 861 862 hashequiv_logger.verbose('Reported task %s as unihash %s to %s (%s)' % (tid, wanted_unihash, self.server, str(data))) 863 864 if data is None: 865 bb.warn("Server unable to handle unihash report") 866 return False 867 868 finalunihash = data['unihash'] 869 870 if finalunihash == current_unihash: 871 hashequiv_logger.verbose('Task %s unihash %s unchanged by server' % (tid, finalunihash)) 872 elif finalunihash == wanted_unihash: 873 hashequiv_logger.verbose('Task %s unihash changed %s -> %s as wanted' % (tid, current_unihash, finalunihash)) 874 self.set_unihash(tid, finalunihash) 875 return True 876 else: 877 # TODO: What to do here? 878 hashequiv_logger.verbose('Task %s unihash reported as unwanted hash %s' % (tid, finalunihash)) 879 880 except ConnectionError as e: 881 bb.warn('Error contacting Hash Equivalence Server %s: %s' % (self.server, str(e))) 882 883 return False 884 885# 886# Dummy class used for bitbake-selftest 887# 888class SignatureGeneratorTestEquivHash(SignatureGeneratorUniHashMixIn, SignatureGeneratorBasicHash): 889 name = "TestEquivHash" 890 def init_rundepcheck(self, data): 891 super().init_rundepcheck(data) 892 self.server = data.getVar('BB_HASHSERVE') 893 self.method = "sstate_output_hash" 894 self.max_parallel = 1 895 896def clean_checksum_file_path(file_checksum_tuple): 897 f, cs = file_checksum_tuple 898 if "/./" in f: 899 return "./" + f.split("/./")[1] 900 return f 901 902def dump_this_task(outfile, d): 903 import bb.parse 904 mcfn = d.getVar("BB_FILENAME") 905 task = "do_" + d.getVar("BB_CURRENTTASK") 906 referencestamp = bb.parse.siggen.stampfile_base(mcfn) 907 bb.parse.siggen.dump_sigtask(mcfn, task, outfile, "customfile:" + referencestamp) 908 909def init_colors(enable_color): 910 """Initialise colour dict for passing to compare_sigfiles()""" 911 # First set up the colours 912 colors = {'color_title': '\033[1m', 913 'color_default': '\033[0m', 914 'color_add': '\033[0;32m', 915 'color_remove': '\033[0;31m', 916 } 917 # Leave all keys present but clear the values 918 if not enable_color: 919 for k in colors.keys(): 920 colors[k] = '' 921 return colors 922 923def worddiff_str(oldstr, newstr, colors=None): 924 if not colors: 925 colors = init_colors(False) 926 diff = simplediff.diff(oldstr.split(' '), newstr.split(' ')) 927 ret = [] 928 for change, value in diff: 929 value = ' '.join(value) 930 if change == '=': 931 ret.append(value) 932 elif change == '+': 933 item = '{color_add}{{+{value}+}}{color_default}'.format(value=value, **colors) 934 ret.append(item) 935 elif change == '-': 936 item = '{color_remove}[-{value}-]{color_default}'.format(value=value, **colors) 937 ret.append(item) 938 whitespace_note = '' 939 if oldstr != newstr and ' '.join(oldstr.split()) == ' '.join(newstr.split()): 940 whitespace_note = ' (whitespace changed)' 941 return '"%s"%s' % (' '.join(ret), whitespace_note) 942 943def list_inline_diff(oldlist, newlist, colors=None): 944 if not colors: 945 colors = init_colors(False) 946 diff = simplediff.diff(oldlist, newlist) 947 ret = [] 948 for change, value in diff: 949 value = ' '.join(value) 950 if change == '=': 951 ret.append("'%s'" % value) 952 elif change == '+': 953 item = '{color_add}+{value}{color_default}'.format(value=value, **colors) 954 ret.append(item) 955 elif change == '-': 956 item = '{color_remove}-{value}{color_default}'.format(value=value, **colors) 957 ret.append(item) 958 return '[%s]' % (', '.join(ret)) 959 960# Handled renamed fields 961def handle_renames(data): 962 if 'basewhitelist' in data: 963 data['basehash_ignore_vars'] = data['basewhitelist'] 964 del data['basewhitelist'] 965 if 'taskwhitelist' in data: 966 data['taskhash_ignore_tasks'] = data['taskwhitelist'] 967 del data['taskwhitelist'] 968 969 970def compare_sigfiles(a, b, recursecb=None, color=False, collapsed=False): 971 output = [] 972 973 colors = init_colors(color) 974 def color_format(formatstr, **values): 975 """ 976 Return colour formatted string. 977 NOTE: call with the format string, not an already formatted string 978 containing values (otherwise you could have trouble with { and } 979 characters) 980 """ 981 if not formatstr.endswith('{color_default}'): 982 formatstr += '{color_default}' 983 # In newer python 3 versions you can pass both of these directly, 984 # but we only require 3.4 at the moment 985 formatparams = {} 986 formatparams.update(colors) 987 formatparams.update(values) 988 return formatstr.format(**formatparams) 989 990 try: 991 with bb.compress.zstd.open(a, "rt", encoding="utf-8", num_threads=1) as f: 992 a_data = json.load(f, object_hook=SetDecoder) 993 except (TypeError, OSError) as err: 994 bb.error("Failed to open sigdata file '%s': %s" % (a, str(err))) 995 raise err 996 try: 997 with bb.compress.zstd.open(b, "rt", encoding="utf-8", num_threads=1) as f: 998 b_data = json.load(f, object_hook=SetDecoder) 999 except (TypeError, OSError) as err: 1000 bb.error("Failed to open sigdata file '%s': %s" % (b, str(err))) 1001 raise err 1002 1003 for data in [a_data, b_data]: 1004 handle_renames(data) 1005 1006 def dict_diff(a, b, ignored_vars=set()): 1007 sa = set(a.keys()) 1008 sb = set(b.keys()) 1009 common = sa & sb 1010 changed = set() 1011 for i in common: 1012 if a[i] != b[i] and i not in ignored_vars: 1013 changed.add(i) 1014 added = sb - sa 1015 removed = sa - sb 1016 return changed, added, removed 1017 1018 def file_checksums_diff(a, b): 1019 from collections import Counter 1020 1021 # Convert lists back to tuples 1022 a = [(f[0], f[1]) for f in a] 1023 b = [(f[0], f[1]) for f in b] 1024 1025 # Compare lists, ensuring we can handle duplicate filenames if they exist 1026 removedcount = Counter(a) 1027 removedcount.subtract(b) 1028 addedcount = Counter(b) 1029 addedcount.subtract(a) 1030 added = [] 1031 for x in b: 1032 if addedcount[x] > 0: 1033 addedcount[x] -= 1 1034 added.append(x) 1035 removed = [] 1036 changed = [] 1037 for x in a: 1038 if removedcount[x] > 0: 1039 removedcount[x] -= 1 1040 for y in added: 1041 if y[0] == x[0]: 1042 changed.append((x[0], x[1], y[1])) 1043 added.remove(y) 1044 break 1045 else: 1046 removed.append(x) 1047 added = [x[0] for x in added] 1048 removed = [x[0] for x in removed] 1049 return changed, added, removed 1050 1051 if 'basehash_ignore_vars' in a_data and a_data['basehash_ignore_vars'] != b_data['basehash_ignore_vars']: 1052 output.append(color_format("{color_title}basehash_ignore_vars changed{color_default} from '%s' to '%s'") % (a_data['basehash_ignore_vars'], b_data['basehash_ignore_vars'])) 1053 if a_data['basehash_ignore_vars'] and b_data['basehash_ignore_vars']: 1054 output.append("changed items: %s" % a_data['basehash_ignore_vars'].symmetric_difference(b_data['basehash_ignore_vars'])) 1055 1056 if 'taskhash_ignore_tasks' in a_data and a_data['taskhash_ignore_tasks'] != b_data['taskhash_ignore_tasks']: 1057 output.append(color_format("{color_title}taskhash_ignore_tasks changed{color_default} from '%s' to '%s'") % (a_data['taskhash_ignore_tasks'], b_data['taskhash_ignore_tasks'])) 1058 if a_data['taskhash_ignore_tasks'] and b_data['taskhash_ignore_tasks']: 1059 output.append("changed items: %s" % a_data['taskhash_ignore_tasks'].symmetric_difference(b_data['taskhash_ignore_tasks'])) 1060 1061 if a_data['taskdeps'] != b_data['taskdeps']: 1062 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']))) 1063 1064 if a_data['basehash'] != b_data['basehash'] and not collapsed: 1065 output.append(color_format("{color_title}basehash changed{color_default} from %s to %s") % (a_data['basehash'], b_data['basehash'])) 1066 1067 changed, added, removed = dict_diff(a_data['gendeps'], b_data['gendeps'], a_data['basehash_ignore_vars'] & b_data['basehash_ignore_vars']) 1068 if changed: 1069 for dep in sorted(changed): 1070 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])) 1071 if a_data['gendeps'][dep] and b_data['gendeps'][dep]: 1072 output.append("changed items: %s" % a_data['gendeps'][dep].symmetric_difference(b_data['gendeps'][dep])) 1073 if added: 1074 for dep in sorted(added): 1075 output.append(color_format("{color_title}Dependency on variable %s was added") % (dep)) 1076 if removed: 1077 for dep in sorted(removed): 1078 output.append(color_format("{color_title}Dependency on Variable %s was removed") % (dep)) 1079 1080 1081 changed, added, removed = dict_diff(a_data['varvals'], b_data['varvals']) 1082 if changed: 1083 for dep in sorted(changed): 1084 oldval = a_data['varvals'][dep] 1085 newval = b_data['varvals'][dep] 1086 if newval and oldval and ('\n' in oldval or '\n' in newval): 1087 diff = difflib.unified_diff(oldval.splitlines(), newval.splitlines(), lineterm='') 1088 # Cut off the first two lines, since we aren't interested in 1089 # the old/new filename (they are blank anyway in this case) 1090 difflines = list(diff)[2:] 1091 if color: 1092 # Add colour to diff output 1093 for i, line in enumerate(difflines): 1094 if line.startswith('+'): 1095 line = color_format('{color_add}{line}', line=line) 1096 difflines[i] = line 1097 elif line.startswith('-'): 1098 line = color_format('{color_remove}{line}', line=line) 1099 difflines[i] = line 1100 output.append(color_format("{color_title}Variable {var} value changed:{color_default}\n{diff}", var=dep, diff='\n'.join(difflines))) 1101 elif newval and oldval and (' ' in oldval or ' ' in newval): 1102 output.append(color_format("{color_title}Variable {var} value changed:{color_default}\n{diff}", var=dep, diff=worddiff_str(oldval, newval, colors))) 1103 else: 1104 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)) 1105 1106 if not 'file_checksum_values' in a_data: 1107 a_data['file_checksum_values'] = [] 1108 if not 'file_checksum_values' in b_data: 1109 b_data['file_checksum_values'] = [] 1110 1111 changed, added, removed = file_checksums_diff(a_data['file_checksum_values'], b_data['file_checksum_values']) 1112 if changed: 1113 for f, old, new in changed: 1114 output.append(color_format("{color_title}Checksum for file %s changed{color_default} from %s to %s") % (f, old, new)) 1115 if added: 1116 for f in added: 1117 output.append(color_format("{color_title}Dependency on checksum of file %s was added") % (f)) 1118 if removed: 1119 for f in removed: 1120 output.append(color_format("{color_title}Dependency on checksum of file %s was removed") % (f)) 1121 1122 if not 'runtaskdeps' in a_data: 1123 a_data['runtaskdeps'] = {} 1124 if not 'runtaskdeps' in b_data: 1125 b_data['runtaskdeps'] = {} 1126 1127 if not collapsed: 1128 if len(a_data['runtaskdeps']) != len(b_data['runtaskdeps']): 1129 changed = ["Number of task dependencies changed"] 1130 else: 1131 changed = [] 1132 for idx, task in enumerate(a_data['runtaskdeps']): 1133 a = a_data['runtaskdeps'][idx] 1134 b = b_data['runtaskdeps'][idx] 1135 if a_data['runtaskhashes'][a] != b_data['runtaskhashes'][b] and not collapsed: 1136 changed.append("%s with hash %s\n changed to\n%s with hash %s" % (a, a_data['runtaskhashes'][a], b, b_data['runtaskhashes'][b])) 1137 1138 if changed: 1139 clean_a = a_data['runtaskdeps'] 1140 clean_b = b_data['runtaskdeps'] 1141 if clean_a != clean_b: 1142 output.append(color_format("{color_title}runtaskdeps changed:{color_default}\n%s") % list_inline_diff(clean_a, clean_b, colors)) 1143 else: 1144 output.append(color_format("{color_title}runtaskdeps changed:")) 1145 output.append("\n".join(changed)) 1146 1147 1148 if 'runtaskhashes' in a_data and 'runtaskhashes' in b_data: 1149 a = a_data['runtaskhashes'] 1150 b = b_data['runtaskhashes'] 1151 changed, added, removed = dict_diff(a, b) 1152 if added: 1153 for dep in sorted(added): 1154 bdep_found = False 1155 if removed: 1156 for bdep in removed: 1157 if b[dep] == a[bdep]: 1158 #output.append("Dependency on task %s was replaced by %s with same hash" % (dep, bdep)) 1159 bdep_found = True 1160 if not bdep_found: 1161 output.append(color_format("{color_title}Dependency on task %s was added{color_default} with hash %s") % (dep, b[dep])) 1162 if removed: 1163 for dep in sorted(removed): 1164 adep_found = False 1165 if added: 1166 for adep in added: 1167 if b[adep] == a[dep]: 1168 #output.append("Dependency on task %s was replaced by %s with same hash" % (adep, dep)) 1169 adep_found = True 1170 if not adep_found: 1171 output.append(color_format("{color_title}Dependency on task %s was removed{color_default} with hash %s") % (dep, a[dep])) 1172 if changed: 1173 for dep in sorted(changed): 1174 if not collapsed: 1175 output.append(color_format("{color_title}Hash for task dependency %s changed{color_default} from %s to %s") % (dep, a[dep], b[dep])) 1176 if callable(recursecb): 1177 recout = recursecb(dep, a[dep], b[dep]) 1178 if recout: 1179 if collapsed: 1180 output.extend(recout) 1181 else: 1182 # If a dependent hash changed, might as well print the line above and then defer to the changes in 1183 # that hash since in all likelyhood, they're the same changes this task also saw. 1184 output = [output[-1]] + recout 1185 break 1186 1187 a_taint = a_data.get('taint', None) 1188 b_taint = b_data.get('taint', None) 1189 if a_taint != b_taint: 1190 if a_taint and a_taint.startswith('nostamp:'): 1191 a_taint = a_taint.replace('nostamp:', 'nostamp(uuid4):') 1192 if b_taint and b_taint.startswith('nostamp:'): 1193 b_taint = b_taint.replace('nostamp:', 'nostamp(uuid4):') 1194 output.append(color_format("{color_title}Taint (by forced/invalidated task) changed{color_default} from %s to %s") % (a_taint, b_taint)) 1195 1196 return output 1197 1198 1199def calc_basehash(sigdata): 1200 task = sigdata['task'] 1201 basedata = sigdata['varvals'][task] 1202 1203 if basedata is None: 1204 basedata = '' 1205 1206 alldeps = sigdata['taskdeps'] 1207 for dep in sorted(alldeps): 1208 basedata = basedata + dep 1209 val = sigdata['varvals'][dep] 1210 if val is not None: 1211 basedata = basedata + str(val) 1212 1213 return hashlib.sha256(basedata.encode("utf-8")).hexdigest() 1214 1215def calc_taskhash(sigdata): 1216 data = sigdata['basehash'] 1217 1218 for dep in sigdata['runtaskdeps']: 1219 data = data + sigdata['runtaskhashes'][dep] 1220 1221 for c in sigdata['file_checksum_values']: 1222 if c[1]: 1223 if "./" in c[0]: 1224 data = data + c[0] 1225 data = data + c[1] 1226 1227 if 'taint' in sigdata: 1228 if 'nostamp:' in sigdata['taint']: 1229 data = data + sigdata['taint'][8:] 1230 else: 1231 data = data + sigdata['taint'] 1232 1233 return hashlib.sha256(data.encode("utf-8")).hexdigest() 1234 1235 1236def dump_sigfile(a): 1237 output = [] 1238 1239 try: 1240 with bb.compress.zstd.open(a, "rt", encoding="utf-8", num_threads=1) as f: 1241 a_data = json.load(f, object_hook=SetDecoder) 1242 except (TypeError, OSError) as err: 1243 bb.error("Failed to open sigdata file '%s': %s" % (a, str(err))) 1244 raise err 1245 1246 handle_renames(a_data) 1247 1248 output.append("basehash_ignore_vars: %s" % (sorted(a_data['basehash_ignore_vars']))) 1249 1250 output.append("taskhash_ignore_tasks: %s" % (sorted(a_data['taskhash_ignore_tasks'] or []))) 1251 1252 output.append("Task dependencies: %s" % (sorted(a_data['taskdeps']))) 1253 1254 output.append("basehash: %s" % (a_data['basehash'])) 1255 1256 for dep in sorted(a_data['gendeps']): 1257 output.append("List of dependencies for variable %s is %s" % (dep, sorted(a_data['gendeps'][dep]))) 1258 1259 for dep in sorted(a_data['varvals']): 1260 output.append("Variable %s value is %s" % (dep, a_data['varvals'][dep])) 1261 1262 if 'runtaskdeps' in a_data: 1263 output.append("Tasks this task depends on: %s" % (sorted(a_data['runtaskdeps']))) 1264 1265 if 'file_checksum_values' in a_data: 1266 output.append("This task depends on the checksums of files: %s" % (sorted(a_data['file_checksum_values']))) 1267 1268 if 'runtaskhashes' in a_data: 1269 for dep in sorted(a_data['runtaskhashes']): 1270 output.append("Hash for dependent task %s is %s" % (dep, a_data['runtaskhashes'][dep])) 1271 1272 if 'taint' in a_data: 1273 if a_data['taint'].startswith('nostamp:'): 1274 msg = a_data['taint'].replace('nostamp:', 'nostamp(uuid4):') 1275 else: 1276 msg = a_data['taint'] 1277 output.append("Tainted (by forced/invalidated task): %s" % msg) 1278 1279 if 'task' in a_data: 1280 computed_basehash = calc_basehash(a_data) 1281 output.append("Computed base hash is %s and from file %s" % (computed_basehash, a_data['basehash'])) 1282 else: 1283 output.append("Unable to compute base hash") 1284 1285 computed_taskhash = calc_taskhash(a_data) 1286 output.append("Computed task hash is %s" % computed_taskhash) 1287 1288 return output 1289