diff options
| -rw-r--r-- | bitbake/lib/bb/siggen.py | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/bitbake/lib/bb/siggen.py b/bitbake/lib/bb/siggen.py index 6a729f3b1e..0105fa7251 100644 --- a/bitbake/lib/bb/siggen.py +++ b/bitbake/lib/bb/siggen.py | |||
| @@ -354,6 +354,169 @@ class SignatureGeneratorBasicHash(SignatureGeneratorBasic): | |||
| 354 | bb.note("Tainting hash to force rebuild of task %s, %s" % (fn, task)) | 354 | bb.note("Tainting hash to force rebuild of task %s, %s" % (fn, task)) |
| 355 | bb.build.write_taint(task, d, fn) | 355 | bb.build.write_taint(task, d, fn) |
| 356 | 356 | ||
| 357 | class SignatureGeneratorUniHashMixIn(object): | ||
| 358 | def get_taskdata(self): | ||
| 359 | return (self.server, self.method) + super().get_taskdata() | ||
| 360 | |||
| 361 | def set_taskdata(self, data): | ||
| 362 | self.server, self.method = data[:2] | ||
| 363 | super().set_taskdata(data[2:]) | ||
| 364 | |||
| 365 | def __get_task_unihash_key(self, task): | ||
| 366 | # TODO: The key only *needs* to be the taskhash, the task is just | ||
| 367 | # convenient | ||
| 368 | return '%s:%s' % (task, self.taskhash[task]) | ||
| 369 | |||
| 370 | def get_stampfile_hash(self, task): | ||
| 371 | if task in self.taskhash: | ||
| 372 | # If a unique hash is reported, use it as the stampfile hash. This | ||
| 373 | # ensures that if a task won't be re-run if the taskhash changes, | ||
| 374 | # but it would result in the same output hash | ||
| 375 | unihash = self.unihashes.get(self.__get_task_unihash_key(task)) | ||
| 376 | if unihash is not None: | ||
| 377 | return unihash | ||
| 378 | |||
| 379 | return super().get_stampfile_hash(task) | ||
| 380 | |||
| 381 | def get_unihash(self, task): | ||
| 382 | import urllib | ||
| 383 | import json | ||
| 384 | |||
| 385 | taskhash = self.taskhash[task] | ||
| 386 | |||
| 387 | key = self.__get_task_unihash_key(task) | ||
| 388 | |||
| 389 | # TODO: This cache can grow unbounded. It probably only needs to keep | ||
| 390 | # for each task | ||
| 391 | unihash = self.unihashes.get(key) | ||
| 392 | if unihash is not None: | ||
| 393 | return unihash | ||
| 394 | |||
| 395 | # In the absence of being able to discover a unique hash from the | ||
| 396 | # server, make it be equivalent to the taskhash. The unique "hash" only | ||
| 397 | # really needs to be a unique string (not even necessarily a hash), but | ||
| 398 | # making it match the taskhash has a few advantages: | ||
| 399 | # | ||
| 400 | # 1) All of the sstate code that assumes hashes can be the same | ||
| 401 | # 2) It provides maximal compatibility with builders that don't use | ||
| 402 | # an equivalency server | ||
| 403 | # 3) The value is easy for multiple independent builders to derive the | ||
| 404 | # same unique hash from the same input. This means that if the | ||
| 405 | # independent builders find the same taskhash, but it isn't reported | ||
| 406 | # to the server, there is a better chance that they will agree on | ||
| 407 | # the unique hash. | ||
| 408 | unihash = taskhash | ||
| 409 | |||
| 410 | try: | ||
| 411 | url = '%s/v1/equivalent?%s' % (self.server, | ||
| 412 | urllib.parse.urlencode({'method': self.method, 'taskhash': self.taskhash[task]})) | ||
| 413 | |||
| 414 | request = urllib.request.Request(url) | ||
| 415 | response = urllib.request.urlopen(request) | ||
| 416 | data = response.read().decode('utf-8') | ||
| 417 | |||
| 418 | json_data = json.loads(data) | ||
| 419 | |||
| 420 | if json_data: | ||
| 421 | unihash = json_data['unihash'] | ||
| 422 | # A unique hash equal to the taskhash is not very interesting, | ||
| 423 | # so it is reported it at debug level 2. If they differ, that | ||
| 424 | # is much more interesting, so it is reported at debug level 1 | ||
| 425 | bb.debug((1, 2)[unihash == taskhash], 'Found unihash %s in place of %s for %s from %s' % (unihash, taskhash, task, self.server)) | ||
| 426 | else: | ||
| 427 | bb.debug(2, 'No reported unihash for %s:%s from %s' % (task, taskhash, self.server)) | ||
| 428 | except urllib.error.URLError as e: | ||
| 429 | bb.warn('Failure contacting Hash Equivalence Server %s: %s' % (self.server, str(e))) | ||
| 430 | except (KeyError, json.JSONDecodeError) as e: | ||
| 431 | bb.warn('Poorly formatted response from %s: %s' % (self.server, str(e))) | ||
| 432 | |||
| 433 | self.unihashes[key] = unihash | ||
| 434 | return unihash | ||
| 435 | |||
| 436 | def report_unihash(self, path, task, d): | ||
| 437 | import urllib | ||
| 438 | import json | ||
| 439 | import tempfile | ||
| 440 | import base64 | ||
| 441 | import importlib | ||
| 442 | |||
| 443 | taskhash = d.getVar('BB_TASKHASH') | ||
| 444 | unihash = d.getVar('BB_UNIHASH') | ||
| 445 | report_taskdata = d.getVar('SSTATE_HASHEQUIV_REPORT_TASKDATA') == '1' | ||
| 446 | tempdir = d.getVar('T') | ||
| 447 | fn = d.getVar('BB_FILENAME') | ||
| 448 | key = fn + '.do_' + task + ':' + taskhash | ||
| 449 | |||
| 450 | # Sanity checks | ||
| 451 | cache_unihash = self.unihashes.get(key) | ||
| 452 | if cache_unihash is None: | ||
| 453 | bb.fatal('%s not in unihash cache. Please report this error' % key) | ||
| 454 | |||
| 455 | if cache_unihash != unihash: | ||
| 456 | bb.fatal("Cache unihash %s doesn't match BB_UNIHASH %s" % (cache_unihash, unihash)) | ||
| 457 | |||
| 458 | sigfile = None | ||
| 459 | sigfile_name = "depsig.do_%s.%d" % (task, os.getpid()) | ||
| 460 | sigfile_link = "depsig.do_%s" % task | ||
| 461 | |||
| 462 | try: | ||
| 463 | sigfile = open(os.path.join(tempdir, sigfile_name), 'w+b') | ||
| 464 | |||
| 465 | locs = {'path': path, 'sigfile': sigfile, 'task': task, 'd': d} | ||
| 466 | |||
| 467 | (module, method) = self.method.rsplit('.', 1) | ||
| 468 | locs['method'] = getattr(importlib.import_module(module), method) | ||
| 469 | |||
| 470 | outhash = bb.utils.better_eval('method(path, sigfile, task, d)', locs) | ||
| 471 | |||
| 472 | try: | ||
| 473 | url = '%s/v1/equivalent' % self.server | ||
| 474 | task_data = { | ||
| 475 | 'taskhash': taskhash, | ||
| 476 | 'method': self.method, | ||
| 477 | 'outhash': outhash, | ||
| 478 | 'unihash': unihash, | ||
| 479 | 'owner': d.getVar('SSTATE_HASHEQUIV_OWNER') | ||
| 480 | } | ||
| 481 | |||
| 482 | if report_taskdata: | ||
| 483 | sigfile.seek(0) | ||
| 484 | |||
| 485 | task_data['PN'] = d.getVar('PN') | ||
| 486 | task_data['PV'] = d.getVar('PV') | ||
| 487 | task_data['PR'] = d.getVar('PR') | ||
| 488 | task_data['task'] = task | ||
| 489 | task_data['outhash_siginfo'] = sigfile.read().decode('utf-8') | ||
| 490 | |||
| 491 | headers = {'content-type': 'application/json'} | ||
| 492 | |||
| 493 | request = urllib.request.Request(url, json.dumps(task_data).encode('utf-8'), headers) | ||
| 494 | response = urllib.request.urlopen(request) | ||
| 495 | data = response.read().decode('utf-8') | ||
| 496 | |||
| 497 | json_data = json.loads(data) | ||
| 498 | new_unihash = json_data['unihash'] | ||
| 499 | |||
| 500 | if new_unihash != unihash: | ||
| 501 | bb.debug(1, 'Task %s unihash changed %s -> %s by server %s' % (taskhash, unihash, new_unihash, self.server)) | ||
| 502 | else: | ||
| 503 | bb.debug(1, 'Reported task %s as unihash %s to %s' % (taskhash, unihash, self.server)) | ||
| 504 | except urllib.error.URLError as e: | ||
| 505 | bb.warn('Failure contacting Hash Equivalence Server %s: %s' % (self.server, str(e))) | ||
| 506 | except (KeyError, json.JSONDecodeError) as e: | ||
| 507 | bb.warn('Poorly formatted response from %s: %s' % (self.server, str(e))) | ||
| 508 | finally: | ||
| 509 | if sigfile: | ||
| 510 | sigfile.close() | ||
| 511 | |||
| 512 | sigfile_link_path = os.path.join(tempdir, sigfile_link) | ||
| 513 | bb.utils.remove(sigfile_link_path) | ||
| 514 | |||
| 515 | try: | ||
| 516 | os.symlink(sigfile_name, sigfile_link_path) | ||
| 517 | except OSError: | ||
| 518 | pass | ||
| 519 | |||
| 357 | def dump_this_task(outfile, d): | 520 | def dump_this_task(outfile, d): |
| 358 | import bb.parse | 521 | import bb.parse |
| 359 | fn = d.getVar("BB_FILENAME") | 522 | fn = d.getVar("BB_FILENAME") |
