1+ import functools
2+ import glob
3+ import importlib
4+ import os
15from os .path import (join , dirname , isdir , normpath , splitext , basename )
26from os import listdir , walk , sep
37import sh
48import shlex
5- import glob
6- import importlib
7- import os
89import shutil
910
1011from pythonforandroid .logger import (warning , shprint , info , logger ,
@@ -34,6 +35,35 @@ def copy_files(src_root, dest_root, override=True):
3435 os .makedirs (dest_file )
3536
3637
38+ default_recipe_priorities = [
39+ "webview" , "sdl2" , "service_only" # last is highest
40+ ]
41+ # ^^ NOTE: these are just the default priorities if no special rules
42+ # apply (which you can find in the code below), so basically if no
43+ # known graphical lib or web lib is used - in which case service_only
44+ # is the most reasonable guess.
45+
46+
47+ def _cmp_bootstraps_by_priority (a , b ):
48+ def rank_bootstrap (bootstrap ):
49+ """ Returns a ranking index for each bootstrap,
50+ with higher priority ranked with higher number. """
51+ if bootstrap .name in default_recipe_priorities :
52+ return default_recipe_priorities .index (bootstrap .name ) + 1
53+ return 0
54+
55+ # Rank bootstraps in order:
56+ rank_a = rank_bootstrap (a )
57+ rank_b = rank_bootstrap (b )
58+ if rank_a != rank_b :
59+ return (rank_b - rank_a )
60+ else :
61+ if a .name < b .name : # alphabetic sort for determinism
62+ return - 1
63+ else :
64+ return 1
65+
66+
3767class Bootstrap (object ):
3868 '''An Android project template, containing recipe stuff for
3969 compilation and templated fields for APK info.
@@ -138,36 +168,43 @@ def run_distribute(self):
138168 self .distribution .save_info (self .dist_dir )
139169
140170 @classmethod
141- def list_bootstraps (cls ):
171+ def all_bootstraps (cls ):
142172 '''Find all the available bootstraps and return them.'''
143173 forbidden_dirs = ('__pycache__' , 'common' )
144174 bootstraps_dir = join (dirname (__file__ ), 'bootstraps' )
175+ result = set ()
145176 for name in listdir (bootstraps_dir ):
146177 if name in forbidden_dirs :
147178 continue
148179 filen = join (bootstraps_dir , name )
149180 if isdir (filen ):
150- yield name
181+ result .add (name )
182+ return result
151183
152184 @classmethod
153- def get_bootstrap_from_recipes (cls , recipes , ctx ):
154- '''Returns a bootstrap whose recipe requirements do not conflict with
155- the given recipes.'''
185+ def get_usable_bootstraps_for_recipes (cls , recipes , ctx ):
186+ '''Returns all bootstrap whose recipe requirements do not conflict
187+ with the given recipes, in no particular order .'''
156188 info ('Trying to find a bootstrap that matches the given recipes.' )
157189 bootstraps = [cls .get_bootstrap (name , ctx )
158- for name in cls .list_bootstraps ()]
159- acceptable_bootstraps = []
190+ for name in cls .all_bootstraps ()]
191+ acceptable_bootstraps = set ()
192+
193+ # Find out which bootstraps are acceptable:
160194 for bs in bootstraps :
161195 if not bs .can_be_chosen_automatically :
162196 continue
163- possible_dependency_lists = expand_dependencies (bs .recipe_depends )
197+ possible_dependency_lists = expand_dependencies (bs .recipe_depends , ctx )
164198 for possible_dependencies in possible_dependency_lists :
165199 ok = True
200+ # Check if the bootstap's dependencies have an internal conflict:
166201 for recipe in possible_dependencies :
167202 recipe = Recipe .get_recipe (recipe , ctx )
168203 if any ([conflict in recipes for conflict in recipe .conflicts ]):
169204 ok = False
170205 break
206+ # Check if bootstrap's dependencies conflict with chosen
207+ # packages:
171208 for recipe in recipes :
172209 try :
173210 recipe = Recipe .get_recipe (recipe , ctx )
@@ -180,14 +217,58 @@ def get_bootstrap_from_recipes(cls, recipes, ctx):
180217 ok = False
181218 break
182219 if ok and bs not in acceptable_bootstraps :
183- acceptable_bootstraps .append (bs )
220+ acceptable_bootstraps .add (bs )
221+
184222 info ('Found {} acceptable bootstraps: {}' .format (
185223 len (acceptable_bootstraps ),
186224 [bs .name for bs in acceptable_bootstraps ]))
187- if acceptable_bootstraps :
188- info ('Using the first of these: {}'
189- .format (acceptable_bootstraps [0 ].name ))
190- return acceptable_bootstraps [0 ]
225+ return acceptable_bootstraps
226+
227+ @classmethod
228+ def get_bootstrap_from_recipes (cls , recipes , ctx ):
229+ '''Picks a single recommended default bootstrap out of
230+ all_usable_bootstraps_from_recipes() for the given reicpes,
231+ and returns it.'''
232+
233+ known_web_packages = {"flask" } # to pick webview over service_only
234+ recipes_with_deps_lists = expand_dependencies (recipes , ctx )
235+ acceptable_bootstraps = cls .get_usable_bootstraps_for_recipes (
236+ recipes , ctx
237+ )
238+
239+ def have_dependency_in_recipes (dep ):
240+ for dep_list in recipes_with_deps_lists :
241+ if dep in dep_list :
242+ return True
243+ return False
244+
245+ # Special rule: return SDL2 bootstrap if there's an sdl2 dep:
246+ if (have_dependency_in_recipes ("sdl2" ) and
247+ "sdl2" in [b .name for b in acceptable_bootstraps ]
248+ ):
249+ info ('Using sdl2 bootstrap since it is in dependencies' )
250+ return cls .get_bootstrap ("sdl2" , ctx )
251+
252+ # Special rule: return "webview" if we depend on common web recipe:
253+ for possible_web_dep in known_web_packages :
254+ if have_dependency_in_recipes (possible_web_dep ):
255+ # We have a web package dep!
256+ if "webview" in [b .name for b in acceptable_bootstraps ]:
257+ info ('Using webview bootstrap since common web packages '
258+ 'were found {}' .format (
259+ known_web_packages .intersection (recipes )
260+ ))
261+ return cls .get_bootstrap ("webview" , ctx )
262+
263+ prioritized_acceptable_bootstraps = sorted (
264+ list (acceptable_bootstraps ),
265+ key = functools .cmp_to_key (_cmp_bootstraps_by_priority )
266+ )
267+
268+ if prioritized_acceptable_bootstraps :
269+ info ('Using the highest ranked/first of these: {}'
270+ .format (prioritized_acceptable_bootstraps [0 ].name ))
271+ return prioritized_acceptable_bootstraps [0 ]
191272 return None
192273
193274 @classmethod
@@ -299,9 +380,26 @@ def fry_eggs(self, sitepackages):
299380 shprint (sh .rm , '-rf' , d )
300381
301382
302- def expand_dependencies (recipes ):
383+ def expand_dependencies (recipes , ctx ):
384+ """ This function expands to lists of all different available
385+ alternative recipe combinations, with the dependencies added in
386+ ONLY for all the not-with-alternative recipes.
387+ (So this is like the deps graph very simplified and incomplete, but
388+ hopefully good enough for most basic bootstrap compatibility checks)
389+ """
390+
391+ # Add in all the deps of recipes where there is no alternative:
392+ recipes_with_deps = list (recipes )
393+ for entry in recipes :
394+ if not isinstance (entry , (tuple , list )) or len (entry ) == 1 :
395+ if isinstance (entry , (tuple , list )):
396+ entry = entry [0 ]
397+ recipe = Recipe .get_recipe (entry , ctx )
398+ recipes_with_deps += recipe .depends
399+
400+ # Split up lists by available alternatives:
303401 recipe_lists = [[]]
304- for recipe in recipes :
402+ for recipe in recipes_with_deps :
305403 if isinstance (recipe , (tuple , list )):
306404 new_recipe_lists = []
307405 for alternative in recipe :
@@ -311,6 +409,6 @@ def expand_dependencies(recipes):
311409 new_recipe_lists .append (new_list )
312410 recipe_lists = new_recipe_lists
313411 else :
314- for old_list in recipe_lists :
315- old_list .append (recipe )
412+ for existing_list in recipe_lists :
413+ existing_list .append (recipe )
316414 return recipe_lists
0 commit comments