Benjamin Schubert pushed to branch bschubert/rework-sort at BuildStream / buildstream
Commits:
-
6d1a4608
by Benjamin Schubert at 2019-02-04T09:48:13Z
4 changed files:
- buildstream/_cachekey.py
- buildstream/_loader/loadelement.py
- buildstream/_loader/loader.py
- buildstream/_loader/metaelement.py
Changes:
| ... | ... | @@ -18,8 +18,10 @@ |
| 18 | 18 |
# Tristan Van Berkom <tristan vanberkom codethink co uk>
|
| 19 | 19 |
|
| 20 | 20 |
|
| 21 |
+import json
|
|
| 21 | 22 |
import hashlib
|
| 22 | 23 |
import pickle
|
| 24 |
+import pprint
|
|
| 23 | 25 |
|
| 24 | 26 |
from . import _yaml
|
| 25 | 27 |
|
| ... | ... | @@ -39,4 +41,12 @@ from . import _yaml |
| 39 | 41 |
def generate_key(value):
|
| 40 | 42 |
ordered = _yaml.node_sanitize(value)
|
| 41 | 43 |
string = pickle.dumps(ordered)
|
| 44 |
+ |
|
| 45 |
+ with open("data.ordered", "a") as fp:
|
|
| 46 |
+ ordered = json.loads(json.dumps(ordered))
|
|
| 47 |
+ ordered.pop("environment", None)
|
|
| 48 |
+ ordered.pop("public", None)
|
|
| 49 |
+ # ordered.pop("public")
|
|
| 50 |
+ fp.write(pprint.pformat(ordered, indent=1) + "\n\n")
|
|
| 51 |
+ |
|
| 42 | 52 |
return hashlib.sha256(string).hexdigest()
|
| ... | ... | @@ -64,7 +64,9 @@ class LoadElement(): |
| 64 | 64 |
self.name = filename # The element name
|
| 65 | 65 |
self.full_name = None # The element full name (with associated junction)
|
| 66 | 66 |
self.visited = False
|
| 67 |
- |
|
| 67 |
+ self.tried_visit = False
|
|
| 68 |
+ self.in_pipeline = False
|
|
| 69 |
+ self.__on_visit = []
|
|
| 68 | 70 |
#
|
| 69 | 71 |
# Private members
|
| 70 | 72 |
#
|
| ... | ... | @@ -96,6 +98,13 @@ class LoadElement(): |
| 96 | 98 |
def junction(self):
|
| 97 | 99 |
return self._loader.project.junction
|
| 98 | 100 |
|
| 101 |
+ def on_visit(self, func):
|
|
| 102 |
+ self.__on_visit.append(func)
|
|
| 103 |
+ |
|
| 104 |
+ def visit(self, meta_element):
|
|
| 105 |
+ for func in self.__on_visit:
|
|
| 106 |
+ func(meta_element)
|
|
| 107 |
+ |
|
| 99 | 108 |
# reverse_dependencies()
|
| 100 | 109 |
#
|
| 101 | 110 |
# Iterable over the Element's reverse dependencies
|
| ... | ... | @@ -116,41 +125,41 @@ class LoadElement(): |
| 116 | 125 |
# unbreakable circles of dependencies
|
| 117 | 126 |
self.__reverse_dependencies.append(weakref.proxy(element))
|
| 118 | 127 |
|
| 119 |
- # depends():
|
|
| 120 |
- #
|
|
| 121 |
- # Checks if this element depends on another element, directly
|
|
| 122 |
- # or indirectly.
|
|
| 123 |
- #
|
|
| 124 |
- # Args:
|
|
| 125 |
- # other (LoadElement): Another LoadElement
|
|
| 126 |
- #
|
|
| 127 |
- # Returns:
|
|
| 128 |
- # (bool): True if this LoadElement depends on 'other'
|
|
| 129 |
- #
|
|
| 130 |
- def depends(self, other):
|
|
| 131 |
- self._ensure_depends_cache()
|
|
| 132 |
- return self._dep_cache.get(other.full_name) is not None
|
|
| 133 |
- |
|
| 134 |
- ###########################################
|
|
| 135 |
- # Private Methods #
|
|
| 136 |
- ###########################################
|
|
| 137 |
- def _ensure_depends_cache(self):
|
|
| 138 |
- |
|
| 139 |
- if self._dep_cache:
|
|
| 140 |
- return
|
|
| 141 |
- |
|
| 142 |
- self._dep_cache = {}
|
|
| 143 |
- for dep in self.dependencies:
|
|
| 144 |
- elt = dep.element
|
|
| 145 |
- |
|
| 146 |
- # Ensure the cache of the element we depend on
|
|
| 147 |
- elt._ensure_depends_cache()
|
|
| 148 |
- |
|
| 149 |
- # We depend on this element
|
|
| 150 |
- self._dep_cache[elt.full_name] = True
|
|
| 151 |
- |
|
| 152 |
- # And we depend on everything this element depends on
|
|
| 153 |
- self._dep_cache.update(elt._dep_cache)
|
|
| 128 |
+ # # depends():
|
|
| 129 |
+ # #
|
|
| 130 |
+ # # Checks if this element depends on another element, directly
|
|
| 131 |
+ # # or indirectly.
|
|
| 132 |
+ # #
|
|
| 133 |
+ # # Args:
|
|
| 134 |
+ # # other (LoadElement): Another LoadElement
|
|
| 135 |
+ # #
|
|
| 136 |
+ # # Returns:
|
|
| 137 |
+ # # (bool): True if this LoadElement depends on 'other'
|
|
| 138 |
+ # #
|
|
| 139 |
+ # def depends(self, other):
|
|
| 140 |
+ # self._ensure_depends_cache()
|
|
| 141 |
+ # return self._dep_cache.get(other.full_name) is not None
|
|
| 142 |
+ |
|
| 143 |
+ # ###########################################
|
|
| 144 |
+ # # Private Methods #
|
|
| 145 |
+ # ###########################################
|
|
| 146 |
+ # def _ensure_depends_cache(self):
|
|
| 147 |
+ |
|
| 148 |
+ # if self._dep_cache:
|
|
| 149 |
+ # return
|
|
| 150 |
+ |
|
| 151 |
+ # self._dep_cache = {}
|
|
| 152 |
+ # for dep in self.dependencies:
|
|
| 153 |
+ # elt = dep.element
|
|
| 154 |
+ |
|
| 155 |
+ # # Ensure the cache of the element we depend on
|
|
| 156 |
+ # elt._ensure_depends_cache()
|
|
| 157 |
+ |
|
| 158 |
+ # # We depend on this element
|
|
| 159 |
+ # self._dep_cache[elt.full_name] = True
|
|
| 160 |
+ |
|
| 161 |
+ # # And we depend on everything this element depends on
|
|
| 162 |
+ # self._dep_cache.update(elt._dep_cache)
|
|
| 154 | 163 |
|
| 155 | 164 |
|
| 156 | 165 |
# _extract_depends_from_node():
|
| ... | ... | @@ -19,8 +19,8 @@ |
| 19 | 19 |
|
| 20 | 20 |
import os
|
| 21 | 21 |
from functools import cmp_to_key
|
| 22 |
-from collections import defaultdict, deque
|
|
| 23 |
-from collections.abc import Mapping
|
|
| 22 |
+from collections import defaultdict, deque, Mapping
|
|
| 23 |
+from operator import attrgetter
|
|
| 24 | 24 |
import tempfile
|
| 25 | 25 |
import shutil
|
| 26 | 26 |
|
| ... | ... | @@ -153,15 +153,17 @@ class Loader(): |
| 153 | 153 |
ret = loader._collect_elements(target_elements)
|
| 154 | 154 |
profile_end(Topics.COLLECT_ELEMENTS, key)
|
| 155 | 155 |
|
| 156 |
- print("#" * 60)
|
|
| 157 |
- for e in ret:
|
|
| 158 |
- print(e.name)
|
|
| 159 |
- print("dependencies:")
|
|
| 160 |
- for d in e.dependencies:
|
|
| 161 |
- print("\t", d.name)
|
|
| 162 |
- print("build deps:")
|
|
| 163 |
- for d in e.build_dependencies:
|
|
| 164 |
- print("\t", d.name)
|
|
| 156 |
+ # print("#" * 60)
|
|
| 157 |
+ # for element in self._meta_elements.values():
|
|
| 158 |
+ # print(element.name)
|
|
| 159 |
+ # for bdep in element.build_dependencies:
|
|
| 160 |
+ # print("\t", bdep.name)
|
|
| 161 |
+ # for dep in element.dependencies:
|
|
| 162 |
+ # print("\t", dep.name)
|
|
| 163 |
+ # print("RET")
|
|
| 164 |
+ # print([e.name for e in ret])
|
|
| 165 |
+ # print("#" * 60)
|
|
| 166 |
+ |
|
| 165 | 167 |
|
| 166 | 168 |
return ret
|
| 167 | 169 |
|
| ... | ... | @@ -333,75 +335,6 @@ class Loader(): |
| 333 | 335 |
# Eliminate duplicate paths
|
| 334 | 336 |
validated[element] = True
|
| 335 | 337 |
|
| 336 |
- # _sort_dependencies():
|
|
| 337 |
- #
|
|
| 338 |
- # Sort dependencies of each element by their dependencies,
|
|
| 339 |
- # so that direct dependencies which depend on other direct
|
|
| 340 |
- # dependencies (directly or indirectly) appear later in the
|
|
| 341 |
- # list.
|
|
| 342 |
- #
|
|
| 343 |
- # This avoids the need for performing multiple topological
|
|
| 344 |
- # sorts throughout the build process.
|
|
| 345 |
- #
|
|
| 346 |
- # Args:
|
|
| 347 |
- # element (LoadElement): The element to sort
|
|
| 348 |
- #
|
|
| 349 |
- def _sort_dependencies(self, element, visited=None):
|
|
| 350 |
- if visited is None:
|
|
| 351 |
- visited = set()
|
|
| 352 |
- |
|
| 353 |
- if element in visited:
|
|
| 354 |
- return
|
|
| 355 |
- |
|
| 356 |
- for dep in element.dependencies:
|
|
| 357 |
- dep.element._loader._sort_dependencies(dep.element, visited=visited)
|
|
| 358 |
- |
|
| 359 |
- def dependency_cmp(dep_a, dep_b):
|
|
| 360 |
- element_a = dep_a.element
|
|
| 361 |
- element_b = dep_b.element
|
|
| 362 |
- |
|
| 363 |
- # Sort on inter element dependency first
|
|
| 364 |
- if element_a.depends(element_b):
|
|
| 365 |
- return 1
|
|
| 366 |
- elif element_b.depends(element_a):
|
|
| 367 |
- return -1
|
|
| 368 |
- |
|
| 369 |
- # If there are no inter element dependencies, place
|
|
| 370 |
- # runtime only dependencies last
|
|
| 371 |
- if dep_a.dep_type != dep_b.dep_type:
|
|
| 372 |
- if dep_a.dep_type == Symbol.RUNTIME:
|
|
| 373 |
- return 1
|
|
| 374 |
- elif dep_b.dep_type == Symbol.RUNTIME:
|
|
| 375 |
- return -1
|
|
| 376 |
- |
|
| 377 |
- # All things being equal, string comparison.
|
|
| 378 |
- if element_a.name > element_b.name:
|
|
| 379 |
- return 1
|
|
| 380 |
- elif element_a.name < element_b.name:
|
|
| 381 |
- return -1
|
|
| 382 |
- |
|
| 383 |
- # Sort local elements before junction elements
|
|
| 384 |
- # and use string comparison between junction elements
|
|
| 385 |
- if element_a.junction and element_b.junction:
|
|
| 386 |
- if element_a.junction > element_b.junction:
|
|
| 387 |
- return 1
|
|
| 388 |
- elif element_a.junction < element_b.junction:
|
|
| 389 |
- return -1
|
|
| 390 |
- elif element_a.junction:
|
|
| 391 |
- return -1
|
|
| 392 |
- elif element_b.junction:
|
|
| 393 |
- return 1
|
|
| 394 |
- |
|
| 395 |
- # This wont ever happen
|
|
| 396 |
- return 0
|
|
| 397 |
- |
|
| 398 |
- # Now dependency sort, we ensure that if any direct dependency
|
|
| 399 |
- # directly or indirectly depends on another direct dependency,
|
|
| 400 |
- # it is found later in the list.
|
|
| 401 |
- element.dependencies.sort(key=cmp_to_key(dependency_cmp))
|
|
| 402 |
- |
|
| 403 |
- visited.add(element)
|
|
| 404 |
- |
|
| 405 | 338 |
# _collect_element()
|
| 406 | 339 |
#
|
| 407 | 340 |
# Collect the toplevel elements we have
|
| ... | ... | @@ -454,19 +387,10 @@ class Loader(): |
| 454 | 387 |
# Cache it now, make sure it's already there before recursing
|
| 455 | 388 |
self._meta_elements[element.name] = meta_element
|
| 456 | 389 |
|
| 457 |
- # # Descend
|
|
| 458 |
- # for dep in element.dependencies:
|
|
| 459 |
- # loader = dep.element._loader
|
|
| 460 |
- # meta_dep = loader._collect_element(dep.element)
|
|
| 461 |
- # if dep.dep_type != 'runtime':
|
|
| 462 |
- # meta_element.build_dependencies.append(meta_dep)
|
|
| 463 |
- # if dep.dep_type != 'build':
|
|
| 464 |
- # meta_element.dependencies.append(meta_dep)
|
|
| 465 |
- |
|
| 466 | 390 |
return meta_element
|
| 467 | 391 |
|
| 468 | 392 |
def _collect_elements(self, elements):
|
| 469 |
- elements_to_load = deque(elements)
|
|
| 393 |
+ elements_to_load = deque(reversed(elements))
|
|
| 470 | 394 |
|
| 471 | 395 |
def compare_unprocessed(dep_a, dep_b):
|
| 472 | 396 |
if dep_a.dep_type != dep_b.dep_type:
|
| ... | ... | @@ -498,150 +422,44 @@ class Loader(): |
| 498 | 422 |
|
| 499 | 423 |
return 0
|
| 500 | 424 |
|
| 501 |
- print("#" * 60)
|
|
| 425 |
+ def not_visited(element):
|
|
| 426 |
+ return not element.visited
|
|
| 502 | 427 |
|
| 503 | 428 |
while elements_to_load:
|
| 504 |
- print([e.name for e in elements_to_load])
|
|
| 505 | 429 |
element = elements_to_load.popleft()
|
| 506 | 430 |
|
| 507 |
- print("Treating", element.name)
|
|
| 508 |
- |
|
| 509 |
- if element.visited:
|
|
| 510 |
- print("\tAlready visited")
|
|
| 511 |
- continue
|
|
| 512 |
- |
|
| 513 |
- unprocessed_dependencies = [
|
|
| 514 |
- dep
|
|
| 515 |
- for dep in element.dependencies
|
|
| 516 |
- if dep.element.visited is False
|
|
| 517 |
- ]
|
|
| 518 |
- |
|
| 519 |
- if unprocessed_dependencies:
|
|
| 520 |
- print("\tUnprocessed deps:", [e.element.name for e in unprocessed_dependencies])
|
|
| 521 |
- elements_to_load.appendleft(element)
|
|
| 522 |
- elements_to_load.extendleft(
|
|
| 523 |
- dep.element
|
|
| 524 |
- for dep in sorted(unprocessed_dependencies, key=cmp_to_key(compare_unprocessed), reverse=True)
|
|
| 525 |
- )
|
|
| 431 |
+ if any(filter(not_visited, element.reverse_dependencies)):
|
|
| 432 |
+ # We will want to treat this item as soon as possible.
|
|
| 433 |
+ # Mark it as already seen
|
|
| 434 |
+ element.tried_visit = True
|
|
| 526 | 435 |
continue
|
| 527 | 436 |
|
| 528 | 437 |
element.visited = True
|
| 529 | 438 |
meta_element = element._loader._collect_element(element)
|
| 439 |
+ element.visit(meta_element)
|
|
| 530 | 440 |
|
| 531 |
- for dep in element.dependencies:
|
|
| 532 |
- print("\tElement has dependency:", dep.element.name)
|
|
| 533 |
- meta_dep = dep.element._loader._collect_element(dep.element)
|
|
| 534 |
- |
|
| 441 |
+ for dep in sorted(element.dependencies, key=cmp_to_key(compare_unprocessed), reverse=True):
|
|
| 535 | 442 |
if dep.dep_type != Symbol.RUNTIME:
|
| 536 |
- meta_element.build_dependencies.append(meta_dep)
|
|
| 443 |
+ dep.element.on_visit(meta_element.build_dependencies.append)
|
|
| 537 | 444 |
if dep.dep_type != Symbol.BUILD:
|
| 538 |
- meta_element.dependencies.append(meta_dep)
|
|
| 445 |
+ dep.element.on_visit(meta_element.dependencies.append)
|
|
| 539 | 446 |
|
| 540 |
- from operator import attrgetter
|
|
| 541 |
- meta_element.build_dependencies.sort(key=attrgetter("index"))
|
|
| 542 |
- meta_element.dependencies.sort(key=attrgetter("index"))
|
|
| 447 |
+ if not element.in_pipeline:
|
|
| 448 |
+ if dep.element.tried_visit:
|
|
| 449 |
+ # This element has already been requested, we should treat
|
|
| 450 |
+ # it as soon as possible
|
|
| 451 |
+ elements_to_load.appendleft(dep.element)
|
|
| 452 |
+ else:
|
|
| 453 |
+ elements_to_load.append(dep.element)
|
|
| 454 |
+ |
|
| 455 |
+ for element in self._meta_elements.values():
|
|
| 456 |
+ element.build_dependencies.sort(key=attrgetter("index"), reverse=True)
|
|
| 457 |
+ element.dependencies.sort(key=attrgetter("index"), reverse=True)
|
|
| 543 | 458 |
|
| 544 | 459 |
ret = [self._collect_element(e) for e in elements]
|
| 545 | 460 |
|
| 546 |
- print("RETURN:", [e.name for e in ret])
|
|
| 547 | 461 |
return ret
|
| 548 | 462 |
|
| 549 |
- # levels = defaultdict(list)
|
|
| 550 |
- |
|
| 551 |
- # while elements_to_load:
|
|
| 552 |
- # print("#" * 60)
|
|
| 553 |
- # print([e.name for e in elements_to_load])
|
|
| 554 |
- # element = elements_to_load.popleft()
|
|
| 555 |
- # print("Treating element", element.name)
|
|
| 556 |
- |
|
| 557 |
- # if element.level is not None: # This node has already been treated
|
|
| 558 |
- # print("Element", element.name, "already treated with level", element.level)
|
|
| 559 |
- # continue
|
|
| 560 |
- |
|
| 561 |
- # unprocessed_reverse_dependencies = [
|
|
| 562 |
- # rdep
|
|
| 563 |
- # for rdep in element.reverse_dependencies
|
|
| 564 |
- # if rdep.level is None
|
|
| 565 |
- # ]
|
|
| 566 |
- |
|
| 567 |
- # if unprocessed_reverse_dependencies:
|
|
| 568 |
- # print("Element has unprocessed reverse dependencies:", [e.name for e in unprocessed_reverse_dependencies])
|
|
| 569 |
- # elements_to_load.appendleft(element)
|
|
| 570 |
- # elements_to_load.extendleft(unprocessed_reverse_dependencies)
|
|
| 571 |
- # continue
|
|
| 572 |
- |
|
| 573 |
- # element.level = 1 + max((e.level for e in element.reverse_dependencies), default=0)
|
|
| 574 |
- # levels[element.level].append(element)
|
|
| 575 |
- # print("Element level is", element.level)
|
|
| 576 |
- # print("Levels: ", {
|
|
| 577 |
- # key: [e.name for e in value]
|
|
| 578 |
- # for key, value in levels.items()
|
|
| 579 |
- # })
|
|
| 580 |
- |
|
| 581 |
- # def sort_by_dep_type(dep_a, dep_b):
|
|
| 582 |
- # if dep_a.dep_type != dep_b.dep_type:
|
|
| 583 |
- # if dep_a.dep_type == Symbol.RUNTIME:
|
|
| 584 |
- # return 1
|
|
| 585 |
- # if dep_b.dep_type == Symbol.RUNTIME:
|
|
| 586 |
- # return -1
|
|
| 587 |
- |
|
| 588 |
- # element_a = dep_a.element
|
|
| 589 |
- # element_b = dep_b.element
|
|
| 590 |
- |
|
| 591 |
- # # All things being equal, string comparison.
|
|
| 592 |
- # if element_a.name > element_b.name:
|
|
| 593 |
- # return 1
|
|
| 594 |
- # elif element_a.name < element_b.name:
|
|
| 595 |
- # return -1
|
|
| 596 |
- |
|
| 597 |
- # # Sort local elements before junction elements
|
|
| 598 |
- # # and use string comparison between junction elements
|
|
| 599 |
- # if element_a.junction and element_b.junction:
|
|
| 600 |
- # if element_a.junction > element_b.junction:
|
|
| 601 |
- # return 1
|
|
| 602 |
- # elif element_a.junction < element_b.junction:
|
|
| 603 |
- # return -1
|
|
| 604 |
- # elif element_a.junction:
|
|
| 605 |
- # return -1
|
|
| 606 |
- # elif element_b.junction:
|
|
| 607 |
- # return 1
|
|
| 608 |
- |
|
| 609 |
- # return 0
|
|
| 610 |
- |
|
| 611 |
- # elements_to_load.extend(
|
|
| 612 |
- # dep.element
|
|
| 613 |
- # for dep in sorted(element.dependencies, key=cmp_to_key(sort_by_dep_type))
|
|
| 614 |
- # )
|
|
| 615 |
- |
|
| 616 |
- # ret = []
|
|
| 617 |
- |
|
| 618 |
- # from operator import attrgetter
|
|
| 619 |
- |
|
| 620 |
- # print("Sorting levels")
|
|
| 621 |
- |
|
| 622 |
- # for _level in sorted(levels.keys(), reverse=True):
|
|
| 623 |
- # print("## Treating level", _level)
|
|
| 624 |
- # for element in levels[_level]:
|
|
| 625 |
- # print("\t## Element", element.name)
|
|
| 626 |
- # meta_element = self._collect_element(element)
|
|
| 627 |
- |
|
| 628 |
- # for dep in element.dependencies:
|
|
| 629 |
- # meta_dep = self._collect_element(dep.element)
|
|
| 630 |
- |
|
| 631 |
- # if dep.dep_type != Symbol.RUNTIME:
|
|
| 632 |
- # meta_element.build_dependencies.append(meta_dep)
|
|
| 633 |
- # if dep.dep_type != Symbol.BUILD:
|
|
| 634 |
- # meta_element.dependencies.append(meta_dep)
|
|
| 635 |
- |
|
| 636 |
- # meta_element.build_dependencies.sort(key=attrgetter("index"))
|
|
| 637 |
- # meta_element.dependencies.sort(key=attrgetter("index"))
|
|
| 638 |
- |
|
| 639 |
- # if _level == 1:
|
|
| 640 |
- # ret.append(meta_element)
|
|
| 641 |
- |
|
| 642 |
- # return ret
|
|
| 643 |
- |
|
| 644 |
- |
|
| 645 | 463 |
# _get_loader():
|
| 646 | 464 |
#
|
| 647 | 465 |
# Return loader for specified junction
|
| ... | ... | @@ -17,6 +17,7 @@ |
| 17 | 17 |
# Authors:
|
| 18 | 18 |
# Tristan Van Berkom <tristan vanberkom codethink co uk>
|
| 19 | 19 |
|
| 20 |
+from collections import deque
|
|
| 20 | 21 |
import itertools
|
| 21 | 22 |
|
| 22 | 23 |
|
