1
+ import functools
2
+ import glob
3
+ import importlib
4
+ import os
1
5
from os .path import (join , dirname , isdir , normpath , splitext , basename )
2
6
from os import listdir , walk , sep
3
7
import sh
4
8
import shlex
5
- import glob
6
- import importlib
7
- import os
8
9
import shutil
9
10
10
11
from pythonforandroid .logger import (warning , shprint , info , logger ,
@@ -34,6 +35,35 @@ def copy_files(src_root, dest_root, override=True):
34
35
os .makedirs (dest_file )
35
36
36
37
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
+
37
67
class Bootstrap (object ):
38
68
'''An Android project template, containing recipe stuff for
39
69
compilation and templated fields for APK info.
@@ -138,36 +168,43 @@ def run_distribute(self):
138
168
self .distribution .save_info (self .dist_dir )
139
169
140
170
@classmethod
141
- def list_bootstraps (cls ):
171
+ def all_bootstraps (cls ):
142
172
'''Find all the available bootstraps and return them.'''
143
173
forbidden_dirs = ('__pycache__' , 'common' )
144
174
bootstraps_dir = join (dirname (__file__ ), 'bootstraps' )
175
+ result = set ()
145
176
for name in listdir (bootstraps_dir ):
146
177
if name in forbidden_dirs :
147
178
continue
148
179
filen = join (bootstraps_dir , name )
149
180
if isdir (filen ):
150
- yield name
181
+ result .add (name )
182
+ return result
151
183
152
184
@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 .'''
156
188
info ('Trying to find a bootstrap that matches the given recipes.' )
157
189
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:
160
194
for bs in bootstraps :
161
195
if not bs .can_be_chosen_automatically :
162
196
continue
163
- possible_dependency_lists = expand_dependencies (bs .recipe_depends )
197
+ possible_dependency_lists = expand_dependencies (bs .recipe_depends , ctx )
164
198
for possible_dependencies in possible_dependency_lists :
165
199
ok = True
200
+ # Check if the bootstap's dependencies have an internal conflict:
166
201
for recipe in possible_dependencies :
167
202
recipe = Recipe .get_recipe (recipe , ctx )
168
203
if any ([conflict in recipes for conflict in recipe .conflicts ]):
169
204
ok = False
170
205
break
206
+ # Check if bootstrap's dependencies conflict with chosen
207
+ # packages:
171
208
for recipe in recipes :
172
209
try :
173
210
recipe = Recipe .get_recipe (recipe , ctx )
@@ -180,14 +217,58 @@ def get_bootstrap_from_recipes(cls, recipes, ctx):
180
217
ok = False
181
218
break
182
219
if ok and bs not in acceptable_bootstraps :
183
- acceptable_bootstraps .append (bs )
220
+ acceptable_bootstraps .add (bs )
221
+
184
222
info ('Found {} acceptable bootstraps: {}' .format (
185
223
len (acceptable_bootstraps ),
186
224
[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 ]
191
272
return None
192
273
193
274
@classmethod
@@ -299,9 +380,26 @@ def fry_eggs(self, sitepackages):
299
380
shprint (sh .rm , '-rf' , d )
300
381
301
382
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:
303
401
recipe_lists = [[]]
304
- for recipe in recipes :
402
+ for recipe in recipes_with_deps :
305
403
if isinstance (recipe , (tuple , list )):
306
404
new_recipe_lists = []
307
405
for alternative in recipe :
@@ -311,6 +409,6 @@ def expand_dependencies(recipes):
311
409
new_recipe_lists .append (new_list )
312
410
recipe_lists = new_recipe_lists
313
411
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 )
316
414
return recipe_lists
0 commit comments