from copy import deepcopyfrom itertools import productfrom pythonforandroid.logger import infofrom pythonforandroid.recipe import Recipefrom pythonforandroid.bootstrap import Bootstrapfrom pythonforandroid.util import BuildInterruptingExceptiondef fix_deplist(deps):""" Turn a dependency list into lowercase, and make sure all entriesthat are just a string become a tuple of strings"""deps = [((dep.lower(),)if not isinstance(dep, (list, tuple))else tuple([dep_entry.lower()for dep_entry in dep]))for dep in deps]return depsclass RecipeOrder(dict):def __init__(self, ctx):self.ctx = ctxdef conflicts(self):for name in self.keys():try:recipe = Recipe.get_recipe(name, self.ctx)conflicts = [dep.lower() for dep in recipe.conflicts]except ValueError:conflicts = []if any([c in self for c in conflicts]):return Truereturn Falsedef get_dependency_tuple_list_for_recipe(recipe, blacklist=None):""" Get the dependencies of a recipe with filtered out blacklist, andturned into tuples with fix_deplist()"""if blacklist is None:blacklist = set()assert type(blacklist) is setif recipe.depends is None:dependencies = []else:# Turn all dependencies into tuples so that product will workdependencies = fix_deplist(recipe.depends)# Filter out blacklisted items and turn lowercase:dependencies = [tuple(set(deptuple) - blacklist)for deptuple in dependenciesif tuple(set(deptuple) - blacklist)]return dependenciesdef recursively_collect_orders(name, ctx, all_inputs, orders=None, blacklist=None):'''For each possible recipe ordering, try to add the new recipe nameto that order. Recursively do the same thing with all thedependencies of each recipe.'''name = name.lower()if orders is None:orders = []if blacklist is None:blacklist = set()try:recipe = Recipe.get_recipe(name, ctx)dependencies = get_dependency_tuple_list_for_recipe(recipe, blacklist=blacklist)# handle opt_depends: these impose requirements on the build# order only if already present in the list of recipes to builddependencies.extend(fix_deplist([[d] for d in recipe.get_opt_depends_in_list(all_inputs)if d.lower() not in blacklist]))if recipe.conflicts is None:conflicts = []else:conflicts = [dep.lower() for dep in recipe.conflicts]except ValueError:# The recipe does not exist, so we assume it can be installed# via pip with no extra dependenciesdependencies = []conflicts = []new_orders = []# for each existing recipe order, see if we can add the new recipe namefor order in orders:if name in order:new_orders.append(deepcopy(order))continueif order.conflicts():continueif any([conflict in order for conflict in conflicts]):continuefor 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, all_inputs, dependency_new_orders,blacklist=blacklist)new_orders.extend(dependency_new_orders)return new_ordersdef find_order(graph):'''Do a topological sort on the dependency graph dict.'''while graph:# Find all items without a parentleftmost = [name for name, dep in graph.items() if not dep]if not leftmost:raise ValueError('Dependency cycle detected! %s' % graph)# If there is more than one, sort them for predictable orderleftmost.sort()for result in leftmost:# Yield and remove them from the graphyield resultgraph.pop(result)for bset in graph.values():bset.discard(result)def obvious_conflict_checker(ctx, name_tuples, blacklist=None):""" This is a pre-flight check function that will completely ignorerecipe order or choosing an actual value in any of the multiplechoice tuples/dependencies, and just do a very basic obviousconflict check."""deps_were_added_by = dict()deps = set()if blacklist is None:blacklist = set()# Add dependencies for all recipes:to_be_added = [(name_tuple, None) for name_tuple in name_tuples]while len(to_be_added) > 0:current_to_be_added = list(to_be_added)to_be_added = []for (added_tuple, adding_recipe) in current_to_be_added:assert type(added_tuple) is tupleif len(added_tuple) > 1:# No obvious commitment in what to add, don't check it itself# but throw it into deps for later comparing against# (Remember this function only catches obvious issues)deps.add(added_tuple)continuename = added_tuple[0]recipe_conflicts = set()recipe_dependencies = []try:# Get recipe to add and who's ultimately adding it:recipe = Recipe.get_recipe(name, ctx)recipe_conflicts = {c.lower() for c in recipe.conflicts}recipe_dependencies = get_dependency_tuple_list_for_recipe(recipe, blacklist=blacklist)except ValueError:passadder_first_recipe_name = adding_recipe or name# Collect the conflicts:triggered_conflicts = []for dep_tuple_list in deps:# See if the new deps conflict with things added before:if set(dep_tuple_list).intersection(recipe_conflicts) == set(dep_tuple_list):triggered_conflicts.append(dep_tuple_list)continue# See if what was added before conflicts with the new deps:if len(dep_tuple_list) > 1:# Not an obvious commitment to a specific recipe/dep# to be added, so we won't check.# (remember this function only catches obvious issues)continuetry:dep_recipe = Recipe.get_recipe(dep_tuple_list[0], ctx)except ValueError:continueconflicts = [c.lower() for c in dep_recipe.conflicts]if name in conflicts:triggered_conflicts.append(dep_tuple_list)# Throw error on conflict:if triggered_conflicts:# Get first conflict and see who added that one:adder_second_recipe_name = "'||'".join(triggered_conflicts[0])second_recipe_original_adder = deps_were_added_by.get((adder_second_recipe_name,), None)if second_recipe_original_adder:adder_second_recipe_name = second_recipe_original_adder# Prompt error:raise BuildInterruptingException("Conflict detected: '{}'"" inducing dependencies {}, and '{}'"" inducing conflicting dependencies {}".format(adder_first_recipe_name,(recipe.name,),adder_second_recipe_name,triggered_conflicts[0]))# Actually add it to our list:deps.add(added_tuple)deps_were_added_by[added_tuple] = adding_recipe# Schedule dependencies to be addedto_be_added += [(dep, adder_first_recipe_name or name)for dep in recipe_dependenciesif dep not in deps]# If we came here, then there were no obvious conflicts.return Nonedef get_recipe_order_and_bootstrap(ctx, names, bs=None, blacklist=None):# Get set of recipe/dependency names, clean up and add bootstrap deps:names = set(names)if bs is not None and bs.recipe_depends:names = names.union(set(bs.recipe_depends))names = fix_deplist([([name] if not isinstance(name, (list, tuple)) else name)for name in names])if blacklist is None:blacklist = set()blacklist = {bitem.lower() for bitem in blacklist}# Remove all values that are in the blacklist:names_before_blacklist = list(names)names = []for name in names_before_blacklist:cleaned_up_tuple = tuple([item for item in name if item not in blacklist])if cleaned_up_tuple:names.append(cleaned_up_tuple)# Do check for obvious conflicts (that would trigger in any order, and# without comitting to any specific choice in a multi-choice tuple of# dependencies):obvious_conflict_checker(ctx, names, blacklist=blacklist)# If we get here, no obvious conflicts!# get all possible order graphs, as names may include tuples/lists# of alternative dependenciespossible_orders = []for name_set in product(*names):new_possible_orders = [RecipeOrder(ctx)]for name in name_set:new_possible_orders = recursively_collect_orders(name, ctx, name_set, orders=new_possible_orders,blacklist=blacklist)possible_orders.extend(new_possible_orders)# turn each order graph into a linear list if possibleorders = []for possible_order in possible_orders:try:order = find_order(possible_order)except ValueError: # a circular dependency was foundinfo('Circular dependency found in graph {}, skipping it.'.format(possible_order))continueorders.append(list(order))# prefer python3 and SDL2 if availableorders = sorted(orders,key=lambda order: -('python3' in order) - ('sdl2' in order))if not orders:raise BuildInterruptingException('Didn\'t find any valid dependency graphs. ''This means that some of your ''requirements pull in conflicting dependencies.')# 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 parameterschosen_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)if bs is None:# Note: don't remove this without thought, causes infinite loopraise BuildInterruptingException("Could not find any compatible bootstrap!")recipes, python_modules, bs = get_recipe_order_and_bootstrap(ctx, chosen_order, bs=bs, blacklist=blacklist)else:# check if each requirement has a reciperecipes = []python_modules = []for name in chosen_order:try:recipe = Recipe.get_recipe(name, ctx)python_modules += recipe.python_dependsexcept ValueError:python_modules.append(name)else:recipes.append(name)python_modules = list(set(python_modules))return recipes, python_modules, bs
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。