summaryrefslogtreecommitdiff
path: root/morphlib/builddependencygraph.py
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/builddependencygraph.py')
-rw-r--r--morphlib/builddependencygraph.py314
1 files changed, 0 insertions, 314 deletions
diff --git a/morphlib/builddependencygraph.py b/morphlib/builddependencygraph.py
deleted file mode 100644
index 7fd93b66..00000000
--- a/morphlib/builddependencygraph.py
+++ /dev/null
@@ -1,314 +0,0 @@
-# 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 collections
-import os
-
-import morphlib
-
-
-class BuildDependencyGraph(object): # pragma: no cover
-
- '''This class constructs a build dependency graph from an input morphology.
-
- Given a chunk, stratum or system morphology, this class constructs a build
- dependency graph and provides ways to traverse it. It also provides a
- method to transform the dependency graph into groups of items that are
- independent and can be built in parallel.
-
- '''
-
- def __init__(self, source_manager, morph_loader, repo, ref, filename):
- self.source_manager = source_manager
- self.morph_loader = morph_loader
- self.root_repo = repo
- self.root_ref = ref
- self.root_filename = filename
- self.blobs = set()
-
- def create_blob(self, treeish, filename, chunk_name=None):
- '''Creates a blob from a morphology.'''
-
- morph = self.morph_loader.load(treeish, filename, chunk_name)
-
- if morph.kind == 'stratum':
- return morphlib.blobs.Stratum(morph)
- elif morph.kind == 'chunk':
- return morphlib.blobs.Chunk(morph)
- else:
- return morphlib.blobs.System(morph)
-
- def get_blob(self, treeish, filename, chunk_name=None):
- '''Takes a repo, ref, filename and looks up the blob for them.
-
- Loads the corresponding morphology and chunk/stratum/system object
- on-demand if it is not cached yet.
-
- '''
-
- key = (treeish, filename)
- blob = self.cached_blobs.get(key, None)
- if not blob:
- blob = self.create_blob(treeish, filename, chunk_name)
- self.cached_blobs[key] = blob
- return blob
-
- def resolve(self):
- '''Constructs the graph by resolving dependencies recursively.'''
-
- self.cached_blobs = {}
- self.blobs = set()
-
- root = self.resolve_root()
- self.resolve_strata()
- self.resolve_chunks()
- return root
-
- def resolve_root(self):
- # prepare the repo, load the morphology and blob information
- treeish = self.source_manager.get_treeish(self.root_repo,
- self.root_ref)
- root = self.get_blob(treeish, self.root_filename)
- self.blobs.add(root)
-
- # load all strata the morph depends on (only if it's a system image)
- if root.morph.kind == 'system':
- for stratum_name in root.morph.strata:
- filename = '%s.morph' % stratum_name
- stratum = self.get_blob(treeish, filename)
- root.add_dependency(stratum)
- stratum.add_parent(root)
- self.blobs.add(stratum)
-
- return root
-
- def resolve_strata(self):
- '''Generates a dependency graph of strata recursively.
-
- It starts with the input morphology and attempts to resolve all its
- build dependencies recursively using breadth-first search. Morphologies
- and blobs are loaded from disk on demand.
-
- '''
-
- # start the BFS at all input strata
- queue = collections.deque()
- strata = [x for x in self.blobs if x.morph.kind == 'stratum']
- queue.extend(strata)
-
- while len(queue) > 0:
- stratum = queue.popleft()
-
- # the DFS recursion ends whenever we have a stratum
- # that depends on nothing else
- if not stratum.morph.build_depends:
- continue
-
- # verify that the build-depends format is valid
- if isinstance(stratum.morph.build_depends, list):
- for depname in stratum.morph.build_depends:
- # load the dependency stratum on demand
- depstratum = self.get_blob(stratum.morph.treeish,
- '%s.morph' % depname)
- self.blobs.add(depstratum)
-
- # add the dependency stratum to the graph
- stratum.add_dependency(depstratum)
- queue.append(depstratum)
- else:
- raise Exception('%s uses an invalid "build-depends" format'
- % stratum)
-
- def resolve_chunks(self):
- '''Resolves all chunks of the strata and inserts them into the graph.
-
- Starting with a dependency graph of strata, this method fills the
- graph with all contained chunks and creates dependencies where
- appropriate. Chunk morphologies and blobs are loaded on demand.
-
- '''
-
- blobs = list(self.blobs)
- for blob in blobs:
- if isinstance(blob, morphlib.blobs.Stratum):
- self.resolve_stratum_chunks(blob)
-
- def resolve_stratum_chunks(self, stratum):
- # the set of chunks contained in the stratum
- stratum_chunks = set()
-
- # dictionary that maps chunk names to chunks
- name_to_chunk = {}
-
- # create objects for all chunks in the stratum
- for i in xrange(0, len(stratum.morph.sources)):
- source = stratum.morph.sources[i]
-
- # construct a tuple for loading the chunk
- repo = source['repo']
- ref = source['ref']
- filename = '%s.morph' % (source['morph']
- if 'morph' in source
- else source['name'])
-
- # load the chunk on demand
- treeish = self.source_manager.get_treeish(repo, ref)
- chunk = self.get_blob(treeish, filename, source['name'])
- chunk.add_parent(stratum)
-
- # store (name -> chunk) association to avoid loading the chunk
- # twice
- name_to_chunk[source['name']] = chunk
-
- # read the build-depends information
- build_depends = (source['build-depends']
- if 'build-depends' in source
- else None)
-
- # turn build-depends into proper dependencies in the graph
- if build_depends is None:
- # chunks with no build-depends implicitly depend on all
- # chunks listed earlier in the same stratum
- for dependency in stratum_chunks:
- if not dependency is chunk:
- chunk.add_dependency(dependency)
- elif isinstance(build_depends, list):
- for depname in build_depends:
- if depname in name_to_chunk:
- dependency = name_to_chunk[depname]
- if not dependency is chunk:
- chunk.add_dependency(dependency)
- else:
- raise Exception('%s: source %s references %s before '
- 'it is defined' %
- (stratum.morph.filename,
- source['name'],
- depname))
- else:
- raise Exception('%s: source %s uses an invalid build-depends '
- 'format' %
- (stratum.morph.filename, source['name']))
-
- # add the chunk to stratum and graph
- stratum_chunks.add(chunk)
- self.blobs.add(chunk)
-
- # make the chunks in this stratum depend on all
- # strata that need to be built first
- for chunk in stratum_chunks:
- for dependency in stratum.dependencies:
- chunk.add_dependency(dependency)
-
- ## clear the dependencies of the stratum
- #for dependency in set(stratum.dependencies):
- # stratum.remove_dependency(dependency)
-
- # make the stratum depend on all its chunks
- for chunk in stratum_chunks:
- stratum.add_dependency(chunk)
-
- def build_order(self):
- '''Returns a queue of build groups in a valid order.
-
- This function computes a topological sorting of the dependency graph
- and generates a deque of groups, each of which contains a set of items
- that are independent and can be built in parallel. The items in each
- group are guaranteed to depend only on items in previous groups.
-
- '''
-
- sorting = self.compute_topological_sorting()
- groups = collections.deque()
-
- # create the first group
- group = set()
- groups.append(group)
-
- # traverse the graph in topological order
- for blob in sorting:
- # add the current item to the current group, or a new group
- # if one of its dependencies is in the current one
- create_group = False
- for dependency in blob.dependencies:
- if dependency in group:
- create_group = True
- if create_group:
- group = set()
- groups.append(group)
- group.add(blob)
-
- # return the set of blobs and the build groups
- return set(self.blobs), groups
-
- def compute_topological_sorting(self):
- '''Computes a topological sorting of the dependency graph.
-
- A topological sorting basically is the result of a series of
- breadth-first searches starting at each leaf node (blobs with no
- dependencies). Blobs are added to the sorting as soon as all their
- dependencies have been added (which means that by then, all
- dependencies are satisfied).
-
- For more information, see
- http://en.wikipedia.org/wiki/Topological_sorting.
-
- '''
-
- # map blobs to sets of satisfied dependencies. this is to detect when
- # we can actually add blobs to the BFS queue. rather than dropping
- # links between nodes, like most topological sorting algorithms do,
- # we simply remember all satisfied dependencies and check if all
- # of them are met repeatedly
- satisfied_dependencies = {}
-
- # create an empty sorting
- sorting = collections.deque()
-
- # create a set of leafs to start the DFS from
- leafs = collections.deque()
- for blob in self.blobs:
- satisfied_dependencies[blob] = set()
- if len(blob.dependencies) == 0:
- leafs.append(blob)
-
- while len(leafs) > 0:
- # fetch a leaf blob from the DFS queue
- blob = leafs.popleft()
-
- # add it to the sorting
- sorting.append(blob)
-
- # mark this dependency as resolved
- for dependent in blob.dependents:
- satisfied_dependencies[dependent].add(blob)
-
- # add the dependent blob as a leaf if all
- # its dependencies have been resolved
- has = len(satisfied_dependencies[dependent])
- needs = len(dependent.dependencies)
- if has == needs:
- leafs.append(dependent)
-
- # if not all dependencies were resolved on the way, we
- # have found at least one cyclic dependency
- if len(sorting) < len(self.blobs):
- raise Exception('Cyclic dependencies found in the dependency '
- 'graph of %s|%s|%s' %
- (self.root_repo,
- self.root_ref,
- self.root_filename))
-
- return sorting