Initial commit
This commit is contained in:
commit
744cfaebc2
678 changed files with 67709 additions and 0 deletions
p4a/pythonforandroid
170
p4a/pythonforandroid/graph.py
Normal file
170
p4a/pythonforandroid/graph.py
Normal file
|
@ -0,0 +1,170 @@
|
|||
|
||||
from copy import deepcopy
|
||||
from itertools import product
|
||||
from sys import exit
|
||||
|
||||
from pythonforandroid.logger import (info, warning, error)
|
||||
from pythonforandroid.recipe import Recipe
|
||||
from pythonforandroid.bootstrap import Bootstrap
|
||||
|
||||
|
||||
class RecipeOrder(dict):
|
||||
|
||||
def __init__(self, ctx):
|
||||
self.ctx = ctx
|
||||
|
||||
def conflicts(self, name):
|
||||
for name in self.keys():
|
||||
try:
|
||||
recipe = Recipe.get_recipe(name, self.ctx)
|
||||
conflicts = recipe.conflicts
|
||||
except IOError:
|
||||
conflicts = []
|
||||
|
||||
if any([c in self for c in conflicts]):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def recursively_collect_orders(name, ctx, orders=[]):
|
||||
'''For each possible recipe ordering, try to add the new recipe name
|
||||
to that order. Recursively do the same thing with all the
|
||||
dependencies of each recipe.
|
||||
|
||||
'''
|
||||
try:
|
||||
recipe = Recipe.get_recipe(name, ctx)
|
||||
if recipe.depends is None:
|
||||
dependencies = []
|
||||
else:
|
||||
# make all dependencies into lists so that product will work
|
||||
dependencies = [([dependency] if not isinstance(
|
||||
dependency, (list, tuple))
|
||||
else dependency) for dependency in recipe.depends]
|
||||
if recipe.conflicts is None:
|
||||
conflicts = []
|
||||
else:
|
||||
conflicts = recipe.conflicts
|
||||
except IOError:
|
||||
# The recipe does not exist, so we assume it can be installed
|
||||
# via pip with no extra dependencies
|
||||
dependencies = []
|
||||
conflicts = []
|
||||
|
||||
new_orders = []
|
||||
# for each existing recipe order, see if we can add the new recipe name
|
||||
for order in orders:
|
||||
if name in order:
|
||||
new_orders.append(deepcopy(order))
|
||||
continue
|
||||
if order.conflicts(name):
|
||||
continue
|
||||
if any([conflict in order for conflict in conflicts]):
|
||||
continue
|
||||
|
||||
for dependency_set in product(*dependencies):
|
||||
new_order = deepcopy(order)
|
||||
new_order[name] = set(dependency_set)
|
||||
|
||||
dependency_new_orders = [new_order]
|
||||
for dependency in dependency_set:
|
||||
dependency_new_orders = recursively_collect_orders(
|
||||
dependency, ctx, dependency_new_orders)
|
||||
|
||||
new_orders.extend(dependency_new_orders)
|
||||
|
||||
return new_orders
|
||||
|
||||
|
||||
def find_order(graph):
|
||||
'''
|
||||
Do a topological sort on the dependency graph dict.
|
||||
'''
|
||||
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 get_recipe_order_and_bootstrap(ctx, names, bs=None):
|
||||
recipes_to_load = set(names)
|
||||
if bs is not None and bs.recipe_depends:
|
||||
recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
|
||||
|
||||
possible_orders = []
|
||||
|
||||
# get all possible order graphs, as names may include tuples/lists
|
||||
# of alternative dependencies
|
||||
names = [([name] if not isinstance(name, (list, tuple)) else name)
|
||||
for name in names]
|
||||
for name_set in product(*names):
|
||||
new_possible_orders = [RecipeOrder(ctx)]
|
||||
for name in name_set:
|
||||
new_possible_orders = recursively_collect_orders(
|
||||
name, ctx, orders=new_possible_orders)
|
||||
possible_orders.extend(new_possible_orders)
|
||||
|
||||
# turn each order graph into a linear list if possible
|
||||
orders = []
|
||||
for possible_order in possible_orders:
|
||||
try:
|
||||
order = find_order(possible_order)
|
||||
except ValueError: # a circular dependency was found
|
||||
info('Circular dependency found in graph {}, skipping it.'.format(
|
||||
possible_order))
|
||||
continue
|
||||
except:
|
||||
warning('Failed to import recipe named {}; the recipe exists '
|
||||
'but appears broken.'.format(name))
|
||||
warning('Exception was:')
|
||||
raise
|
||||
orders.append(list(order))
|
||||
|
||||
# prefer python2 and SDL2 if available
|
||||
orders = sorted(orders,
|
||||
key=lambda order: -('python2' in order) - ('sdl2' in order))
|
||||
|
||||
if not orders:
|
||||
error('Didn\'t find any valid dependency graphs.')
|
||||
error('This means that some of your requirements pull in '
|
||||
'conflicting dependencies.')
|
||||
error('Exiting.')
|
||||
exit(1)
|
||||
# It would be better to check against possible orders other
|
||||
# than the first one, but in practice clashes will be rare,
|
||||
# and can be resolved by specifying more parameters
|
||||
chosen_order = orders[0]
|
||||
if len(orders) > 1:
|
||||
info('Found multiple valid dependency orders:')
|
||||
for order in orders:
|
||||
info(' {}'.format(order))
|
||||
info('Using the first of these: {}'.format(chosen_order))
|
||||
else:
|
||||
info('Found a single valid recipe set: {}'.format(chosen_order))
|
||||
|
||||
if bs is None:
|
||||
bs = Bootstrap.get_bootstrap_from_recipes(chosen_order, ctx)
|
||||
recipes, python_modules, bs = get_recipe_order_and_bootstrap(
|
||||
ctx, chosen_order, bs=bs)
|
||||
else:
|
||||
# check if each requirement has a recipe
|
||||
recipes = []
|
||||
python_modules = []
|
||||
for name in chosen_order:
|
||||
try:
|
||||
Recipe.get_recipe(name, ctx)
|
||||
except IOError:
|
||||
python_modules.append(name)
|
||||
else:
|
||||
recipes.append(name)
|
||||
|
||||
return recipes, python_modules, bs
|
Loading…
Add table
Add a link
Reference in a new issue