Skip to content

WIP: Add depsolving utility #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 6, 2013
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions depsort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#! /usr/bin/env python2

import argparse
import sys

class Graph(object):
def __init__(self):
# `graph`: dict that maps each package to a set of its dependencies.
self.graph = {}

def add(self, dependent, dependency):
"""Add a dependency relationship to the graph"""
self.graph.setdefault(dependent, set())
self.graph.setdefault(dependency, set())
if dependent != dependency:
self.graph[dependent].add(dependency)

def add_optional(self, dependent, dependency):
"""Add an optional (ordering only) dependency relationship to the graph

Only call this after all mandatory requirements are added
"""
if dependent in self.graph and dependency in self.graph:
self.add(dependent, dependency)

def find_order(self):
"""Do a topological sort on a dependency graph

:Parameters:
:Returns:
iterator, sorted items form first to last
"""
graph = dict((k, set(v)) for k, v in self.graph.items())
while graph:
# Find all items without a parent
leftmost = [l for l, s in graph.items() if not s]
if not leftmost:
raise ValueError('Dependency cycle detected! %s' % graph)
# If there is more than one, sort them for predictable order
leftmost.sort()
for result in leftmost:
# Yield and remove them from the graph
yield result
graph.pop(result)
for bset in graph.values():
bset.discard(result)


def lines_to_relationships(lines):
"""Do a topological sort from a list of space-separated lines

:Parameters:
`lines`: Iterable of lines in the format
"dependent dependency_0 dependency_1 ... dependency_n"

:Returns:
iterator of (dependent, dependency) tuples
"""
for line in lines:
line = line.split()
if line:
dependent = line[0]
for dependency in line:
yield dependent, dependency


def topo_sort_lines(lines, lines_optional=()):
"""Do a topological sort from a list of space-separated lines

:Parameters:
`lines`: Iterable of lines in the format
"dependent dependency_0 dependency_1 ... dependency_n"

`lines`: Iterable of lines with *optional* (ordering-only) dependencies
"dependent dependency_0 dependency_1 ... dependency_n"

:Returns:
string, Sorted dependencies, space-separated
"""
graph = Graph()
for dependent, dependency in lines_to_relationships(lines):
graph.add(dependent, dependency)
for dependent, dependency in lines_to_relationships(lines_optional):
graph.add_optional(dependent, dependency)
return ' '.join(graph.find_order())


def test_depsort_1():
lines = [
'c a',
'b c',
'd b',
'w z',
'a w',
]
assert topo_sort_lines(lines) == 'z w a c b d'


def test_depsort_2():
lines = [
'l k',
'm l',
'a k',
'd a',
'l d',
's l',
'm s',
]
assert topo_sort_lines(lines) == 'k a d l s m'


def test_depsort_3():
lines = [
'z a',
's z',
'z z',
's s',
]
assert topo_sort_lines(lines) == 'a z s'


def test_depsort_4():
lines = [
'f d',
'g f',
'r f',
't r',
'y t',
'g y',
]
assert topo_sort_lines(lines) == 'd f r t y g'


def test_depsort_5():
lines = [
'a b c d e f',
'e f',
'g',
]
assert topo_sort_lines(lines) == 'b c d f g e a'


def test_depsort_6():
lines = [
' numpy python ',
' kivy pygame pyjnius android ',
' python hostpython ',
' pygame sdl ',
' android pygame ',
' pyjnius python ',
' sdl python ',
' hostpython ',
]
assert topo_sort_lines(lines) == (
'hostpython python numpy pyjnius sdl pygame android kivy')


def test_depsort_optional_1():
lines = [' myapp openssl python ']
optional = ['python openssl']
assert topo_sort_lines(lines, optional) == 'openssl python myapp'


def test_depsort_optional_2():
lines = [' myapp openssl python ']
# Just for testing purposes, make openssl soft-depend on python
optional = ['openssl python']
assert topo_sort_lines(lines, optional) == 'python openssl myapp'


def test_depsort_optional_3():
lines = ['myapp openssl']
optional = ['python openssl']
assert topo_sort_lines(lines, optional) == 'openssl myapp'


def test_depsort_optional_4():
lines = ['myapp python']
optional = ['python openssl']
assert topo_sort_lines(lines, optional) == 'python myapp'


def test_depsort_optional_4():
lines = ['myapp']
optional = ['python openssl']
assert topo_sort_lines(lines, optional) == 'myapp'


def main(argv):
parser = argparse.ArgumentParser(
description="Sort dependencies given on standard input")
parser.add_argument('--optional', type=argparse.FileType('r'),
help='File with optional (ordering-only) dependencies')
args = parser.parse_args(argv[1:])
return topo_sort_lines(sys.stdin, lines_optional=args.optional or [])


if __name__ == '__main__':
print main(sys.argv)