summaryrefslogtreecommitdiff
path: root/morphlib/buildorder.py
blob: 39b53f053efb6e80ed7cafe796d588dc541f3dbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# Copyright (C) 2012  Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.


import cliapp
import collections


class CyclicDependencyChainError(cliapp.AppException):

    def __init__(self):
        cliapp.AppException.__init__(
            self, 'Cyclic dependency chain detected')


class BuildOrder:

    def __init__(self, artifacts):
        self.artifacts = artifacts

        if artifacts:
            sorting = self._compute_reverse_topological_sorting(artifacts)
            self.groups = self._create_build_groups(sorting)
        else:
            self.groups = []

    def _compute_reverse_topological_sorting(self, artifacts):
        '''Computes a reverse topological sorting of the build graph.

        A reverse topological sorting basically is the result of a series of
        breadth-first searches starting at each leaf node (artifacts with no
        dependents). Artifacts are added to the sorting as soon as all their
        dependents have been added.

        For more information, see
        http://en.wikipedia.org/wiki/Topological_sorting.

        '''

        # map artifacts to sets of satisfied dependents. this is to detect
        # when we can actually add artifacts to the BFS queue. rather than
        # dropping links between nodes, like most topological sorting
        # algorithms do, we simply remember all satisfied dependents and
        # check if all of them are met repeatedly
        satisfied_dependents = {}

        # create an empty sorting
        sorting = collections.deque()

        # create a set of leafs to start the DFS from
        leafs = collections.deque()
        for artifact in artifacts:
            satisfied_dependents[artifact] = set()
            if len(artifact.dependents) == 0:
                leafs.append(artifact)

        while len(leafs) > 0:
            # fetch a leaf artifact from the DFS queue
            artifact = leafs.popleft()

            # add it to the sorting
            sorting.append(artifact)

            # mark this dependency as resolved in dependent artifacts
            for dependency in artifact.dependencies:
                satisfied_dependents[dependency].add(artifact)

                # add the dependent blob as a leaf if all
                # its dependents have been resolved
                has = len(satisfied_dependents[dependency])
                needs = len(dependency.dependents)
                if has == needs:
                    leafs.append(dependency)

        # if not all dependencies were resolved on the way, we
        # have found at least one cyclic dependency
        if len(sorting) < len(artifacts):
            raise CyclicDependencyChainError()

        return sorting

    def _create_build_groups(self, sorting):
        groups = collections.deque()

        if sorting:
            # create the last group
            group = []
            groups.append(group)

            # traverse the build graph in reverse topological order
            for artifact in sorting:
                # add artifact to a group that comes as late in the build order
                # as possible; if the first group contains any dependents,
                # add the artifact to a new group at the beginning of the order
                for group in groups:
                    if not any([x in group for x in artifact.dependents]):
                        group.append(artifact)
                        break
                    else:
                        group = []
                        group.append(artifact)
                        groups.appendleft(group)
                        break

        return groups