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 ,
@@ -138,36 +139,52 @@ def run_distribute(self):
138
139
self .distribution .save_info (self .dist_dir )
139
140
140
141
@classmethod
141
- def list_bootstraps (cls ):
142
+ def all_bootstraps (cls ):
142
143
'''Find all the available bootstraps and return them.'''
143
144
forbidden_dirs = ('__pycache__' , 'common' )
144
145
bootstraps_dir = join (dirname (__file__ ), 'bootstraps' )
146
+ result = set ()
145
147
for name in listdir (bootstraps_dir ):
146
148
if name in forbidden_dirs :
147
149
continue
148
150
filen = join (bootstraps_dir , name )
149
151
if isdir (filen ):
150
- yield name
152
+ result .add (name )
153
+ return result
151
154
152
155
@classmethod
153
156
def get_bootstrap_from_recipes (cls , recipes , ctx ):
154
157
'''Returns a bootstrap whose recipe requirements do not conflict with
155
158
the given recipes.'''
156
159
info ('Trying to find a bootstrap that matches the given recipes.' )
157
160
bootstraps = [cls .get_bootstrap (name , ctx )
158
- for name in cls .list_bootstraps ()]
159
- acceptable_bootstraps = []
161
+ for name in cls .all_bootstraps ()]
162
+ acceptable_bootstraps = set ()
163
+ known_web_packages = {"flask" } # to pick webview over service_only
164
+ default_recipe_priorities = [
165
+ "webview" , "sdl2" , "service_only" # last is highest
166
+ ]
167
+ recipes_with_deps_lists = expand_dependencies (recipes , ctx )
168
+ # ^^ NOTE: these are just the default priorities if no special rules
169
+ # apply (which you can find in the code below), so basically if no
170
+ # known graphical lib or web lib is used - in which case service_only
171
+ # is the most reasonable guess.
172
+
173
+ # Find out which bootstraps are acceptable:
160
174
for bs in bootstraps :
161
175
if not bs .can_be_chosen_automatically :
162
176
continue
163
- possible_dependency_lists = expand_dependencies (bs .recipe_depends )
177
+ possible_dependency_lists = expand_dependencies (bs .recipe_depends , ctx )
164
178
for possible_dependencies in possible_dependency_lists :
165
179
ok = True
180
+ # Check if the bootstap's dependencies have an internal conflict:
166
181
for recipe in possible_dependencies :
167
182
recipe = Recipe .get_recipe (recipe , ctx )
168
183
if any ([conflict in recipes for conflict in recipe .conflicts ]):
169
184
ok = False
170
185
break
186
+ # Check if bootstrap's dependencies conflict with chosen
187
+ # packages:
171
188
for recipe in recipes :
172
189
try :
173
190
recipe = Recipe .get_recipe (recipe , ctx )
@@ -180,14 +197,61 @@ def get_bootstrap_from_recipes(cls, recipes, ctx):
180
197
ok = False
181
198
break
182
199
if ok and bs not in acceptable_bootstraps :
183
- acceptable_bootstraps .append (bs )
200
+ acceptable_bootstraps .add (bs )
201
+
184
202
info ('Found {} acceptable bootstraps: {}' .format (
185
203
len (acceptable_bootstraps ),
186
204
[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 ]
205
+
206
+ def have_dependency_in_recipes (dep ):
207
+ for dep_list in recipes_with_deps_lists :
208
+ if dep in dep_list :
209
+ return True
210
+ return False
211
+
212
+ # Special rule: return SDL2 bootstrap if there's an sdl2 dep:
213
+ if (have_dependency_in_recipes ("sdl2" ) and
214
+ "sdl2" in [b .name for b in acceptable_bootstraps ]
215
+ ):
216
+ info ('Using sdl2 bootstrap since it is in dependencies' )
217
+ return cls .get_bootstrap ("sdl2" , ctx )
218
+
219
+ # Special rule: return "webview" if we depend on common web recipe:
220
+ for possible_web_dep in known_web_packages :
221
+ if have_dependency_in_recipes (possible_web_dep ):
222
+ # We have a web package dep!
223
+ if "webview" in [b .name for b in acceptable_bootstraps ]:
224
+ info ('Using webview bootstrap since common web packages '
225
+ 'were found {}' .format (
226
+ known_web_packages .intersection (recipes )
227
+ ))
228
+ return cls .get_bootstrap ("webview" , ctx )
229
+
230
+ # Otherwise, go by a default priority ordering to pick best one:
231
+ def bootstrap_priority (a , b ):
232
+ def rank_bootstrap (bootstrap ):
233
+ """ Returns a ranking index for each bootstrap,
234
+ with higher priority ranked with higher number. """
235
+ if bootstrap .name in default_recipe_priorities :
236
+ return default_recipe_priorities .index (bootstrap .name ) + 1
237
+ return 0
238
+ # Rank bootstraps in order:
239
+ rank_a = rank_bootstrap (a )
240
+ rank_b = rank_bootstrap (b )
241
+ if rank_a != rank_b :
242
+ return (rank_b - rank_a )
243
+ else :
244
+ return (a .name - b .name ) # alphabetic sort for determinism
245
+
246
+ prioritized_acceptable_bootstraps = sorted (
247
+ list (acceptable_bootstraps ),
248
+ key = functools .cmp_to_key (bootstrap_priority )
249
+ )
250
+
251
+ if prioritized_acceptable_bootstraps :
252
+ info ('Using the highest ranked/first of these: {}'
253
+ .format (prioritized_acceptable_bootstraps [0 ].name ))
254
+ return prioritized_acceptable_bootstraps [0 ]
191
255
return None
192
256
193
257
@classmethod
@@ -299,9 +363,26 @@ def fry_eggs(self, sitepackages):
299
363
shprint (sh .rm , '-rf' , d )
300
364
301
365
302
- def expand_dependencies (recipes ):
366
+ def expand_dependencies (recipes , ctx ):
367
+ """ This function expands to lists of all different available
368
+ alternative recipe combinations, with the dependencies added in
369
+ ONLY for all the not-with-alternative recipes.
370
+ (So this is like the deps graph very simplified and incomplete, but
371
+ hopefully good enough for most basic bootstrap compatibility checks)
372
+ """
373
+
374
+ # Add in all the deps of recipes where there is no alternative:
375
+ recipes_with_deps = list (recipes )
376
+ for entry in recipes :
377
+ if not isinstance (entry , (tuple , list )) or len (entry ) == 1 :
378
+ if isinstance (entry , (tuple , list )):
379
+ entry = entry [0 ]
380
+ recipe = Recipe .get_recipe (entry , ctx )
381
+ recipes_with_deps += recipe .depends
382
+
383
+ # Split up lists by available alternatives:
303
384
recipe_lists = [[]]
304
- for recipe in recipes :
385
+ for recipe in recipes_with_deps :
305
386
if isinstance (recipe , (tuple , list )):
306
387
new_recipe_lists = []
307
388
for alternative in recipe :
@@ -311,6 +392,6 @@ def expand_dependencies(recipes):
311
392
new_recipe_lists .append (new_list )
312
393
recipe_lists = new_recipe_lists
313
394
else :
314
- for old_list in recipe_lists :
315
- old_list .append (recipe )
395
+ for existing_list in recipe_lists :
396
+ existing_list .append (recipe )
316
397
return recipe_lists
0 commit comments