siggen.py 25 KB

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