Skip to content

Commit 98b2973

Browse files
committed
Made bootstrap selection automatic if not specified
1 parent 2f84ad6 commit 98b2973

File tree

3 files changed

+152
-50
lines changed

3 files changed

+152
-50
lines changed

pythonforandroid/bootstraps/empty/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class EmptyBootstrap(Bootstrap):
99

1010
recipe_depends = []
1111

12+
can_be_chosen_automatically = False
13+
1214
def run_distribute(self):
1315
print('empty bootstrap has no distribute')
1416
exit(1)

pythonforandroid/bootstraps/sdl2python3/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class SDL2Bootstrap(Bootstrap):
99

1010
recipe_depends = ['sdl2python3', 'python3']
1111

12+
can_be_chosen_automatically = False
13+
1214
def run_distribute(self):
1315
info_main('# Creating Android project from build and {} bootstrap'.format(
1416
self.name))

pythonforandroid/toolchain.py

Lines changed: 148 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,12 @@ class Bootstrap(object):
11471147
distribution = None
11481148

11491149
recipe_depends = []
1150+
1151+
can_be_chosen_automatically = True
1152+
'''Determines whether the bootstrap can be chosen as one that
1153+
satisfies user requirements. If False, it will not be returned
1154+
from Bootstrap.get_bootstrap_from_recipes.
1155+
'''
11501156

11511157
# Other things a Bootstrap might need to track (maybe separately):
11521158
# ndk_main.c
@@ -1221,6 +1227,39 @@ def list_bootstraps(cls):
12211227
if isdir(filen):
12221228
yield name
12231229

1230+
@classmethod
1231+
def get_bootstrap_from_recipes(cls, recipes, ctx):
1232+
'''Returns a bootstrap whose recipe requirements do not conflict with
1233+
the given recipes.'''
1234+
info('Trying to find a bootstrap that matches the given recipes.')
1235+
bootstraps = [cls.get_bootstrap(name, ctx) for name in cls.list_bootstraps()]
1236+
acceptable_bootstraps = []
1237+
for bs in bootstraps:
1238+
ok = True
1239+
if not bs.can_be_chosen_automatically:
1240+
ok = False
1241+
for recipe in bs.recipe_depends:
1242+
recipe = Recipe.get_recipe(recipe, ctx)
1243+
if any([conflict in recipes for conflict in recipe.conflicts]):
1244+
ok = False
1245+
break
1246+
for recipe in recipes:
1247+
recipe = Recipe.get_recipe(recipe, ctx)
1248+
if any([conflict in bs.recipe_depends for conflict in recipe.conflicts]):
1249+
ok = False
1250+
break
1251+
if ok:
1252+
acceptable_bootstraps.append(bs)
1253+
info('Found {} acceptable bootstraps: {}'.format(
1254+
len(acceptable_bootstraps), [bs.name for bs in acceptable_bootstraps]))
1255+
if acceptable_bootstraps:
1256+
info('Using the first of these: {}'.format(acceptable_bootstraps[0].name))
1257+
return acceptable_bootstraps[0]
1258+
return None
1259+
1260+
1261+
1262+
12241263
@classmethod
12251264
def get_bootstrap(cls, name, ctx):
12261265
'''Returns an instance of a bootstrap with the given name.
@@ -1230,6 +1269,8 @@ def get_bootstrap(cls, name, ctx):
12301269
'''
12311270
# AND: This method will need to check user dirs, and access
12321271
# bootstraps in a slightly different way
1272+
if name is None:
1273+
return None
12331274
if not hasattr(cls, 'bootstraps'):
12341275
cls.bootstraps = {}
12351276
if name in cls.bootstraps:
@@ -1946,54 +1987,9 @@ def get_recipe_env(self, arch):
19461987
return env
19471988

19481989

1949-
def build_recipes(names, ctx):
1990+
def build_recipes(build_order, python_modules, ctx):
19501991
# Put recipes in correct build order
1951-
graph = Graph()
1952-
recipes_to_load = set(names)
19531992
bs = ctx.bootstrap
1954-
if bs is not None and bs.recipe_depends:
1955-
info_notify('Bootstrap requires recipes {}'.format(bs.recipe_depends))
1956-
recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
1957-
recipes_to_load = list(recipes_to_load)
1958-
recipe_loaded = []
1959-
python_modules = []
1960-
while recipes_to_load:
1961-
name = recipes_to_load.pop(0)
1962-
if name in recipe_loaded or isinstance(name, (list, tuple)):
1963-
continue
1964-
try:
1965-
recipe = Recipe.get_recipe(name, ctx)
1966-
except ImportError:
1967-
info('No recipe named {}; will attempt to install with pip'.format(name))
1968-
python_modules.append(name)
1969-
continue
1970-
graph.add(name, name)
1971-
info('Loaded recipe {} (depends on {}{})'.format(
1972-
name, recipe.depends,
1973-
', conflicts {}'.format(recipe.conflicts) if recipe.conflicts else ''))
1974-
for depend in recipe.depends:
1975-
graph.add(name, depend)
1976-
recipes_to_load += recipe.depends
1977-
for conflict in recipe.conflicts:
1978-
if graph.conflicts(conflict):
1979-
warning(
1980-
('{} conflicts with {}, but both have been '
1981-
'included or pulled into the requirements.'.format(recipe.name, conflict)))
1982-
warning('Due to this conflict the build cannot continue, exiting.')
1983-
exit(1)
1984-
recipe_loaded.append(name)
1985-
graph.remove_remaining_conflicts(ctx)
1986-
if len(graph.graphs) > 1:
1987-
info('Found multiple valid recipe sets:')
1988-
for g in graph.graphs:
1989-
info(' {}'.format(g.keys()))
1990-
info_notify('Using the first of these: {}'.format(graph.graphs[0].keys()))
1991-
elif len(graph.graphs) == 0:
1992-
warning('Didn\'t find any valid dependency graphs, exiting.')
1993-
exit(1)
1994-
else:
1995-
info('Found a single valid recipe set (this is good)')
1996-
build_order = list(graph.find_order(0))
19971993
info_notify("Recipe build order is {}".format(build_order))
19981994
if python_modules:
19991995
info_notify(('The requirements ({}) were not found as recipes, they will be '
@@ -2192,11 +2188,15 @@ def build_dist_from_args(ctx, dist, args_list):
21922188
parser = argparse.ArgumentParser(
21932189
description='Create a newAndroid project')
21942190
parser.add_argument('--bootstrap', help=('The name of the bootstrap type, \'pygame\' '
2195-
'or \'sdl2\''),
2196-
default='sdl2')
2191+
'or \'sdl2\', or leave empty to let a '
2192+
'bootstrap be chosen automatically from your '
2193+
'requirements.'),
2194+
default=None)
21972195
args, unknown = parser.parse_known_args(args_list)
21982196

21992197
bs = Bootstrap.get_bootstrap(args.bootstrap, ctx)
2198+
build_order, python_modules, bs = get_recipe_order_and_bootstrap(ctx, dist.recipes, bs)
2199+
22002200
info('The selected bootstrap is {}'.format(bs.name))
22012201
info_main('# Creating dist with {} bootstrap'.format(bs.name))
22022202
bs.distribution = dist
@@ -2207,8 +2207,7 @@ def build_dist_from_args(ctx, dist, args_list):
22072207
ctx.prepare_bootstrap(bs)
22082208
ctx.prepare_dist(ctx.dist_name)
22092209

2210-
recipes = dist.recipes
2211-
build_recipes(recipes, ctx)
2210+
build_recipes(build_order, python_modules, ctx)
22122211

22132212
ctx.bootstrap.run_distribute()
22142213

@@ -2217,6 +2216,105 @@ def build_dist_from_args(ctx, dist, args_list):
22172216

22182217
return unknown
22192218

2219+
def get_recipe_order_and_bootstrap(ctx, names, bs=None):
2220+
'''Takes a list of recipe names and (optionally) a bootstrap. Then
2221+
works out the dependency graph (including bootstrap recipes if
2222+
necessary). Finally, if no bootstrap was initially selected,
2223+
chooses one that supports all the recipes.
2224+
'''
2225+
graph = Graph()
2226+
recipes_to_load = set(names)
2227+
if bs is not None and bs.recipe_depends:
2228+
info_notify('Bootstrap requires recipes {}'.format(bs.recipe_depends))
2229+
recipes_to_load = recipes_to_load.union(set(bs.recipe_depends))
2230+
recipes_to_load = list(recipes_to_load)
2231+
recipe_loaded = []
2232+
python_modules = []
2233+
while recipes_to_load:
2234+
name = recipes_to_load.pop(0)
2235+
if name in recipe_loaded or isinstance(name, (list, tuple)):
2236+
continue
2237+
try:
2238+
recipe = Recipe.get_recipe(name, ctx)
2239+
except ImportError:
2240+
info('No recipe named {}; will attempt to install with pip'.format(name))
2241+
python_modules.append(name)
2242+
continue
2243+
graph.add(name, name)
2244+
info('Loaded recipe {} (depends on {}{})'.format(
2245+
name, recipe.depends,
2246+
', conflicts {}'.format(recipe.conflicts) if recipe.conflicts else ''))
2247+
for depend in recipe.depends:
2248+
graph.add(name, depend)
2249+
recipes_to_load += recipe.depends
2250+
for conflict in recipe.conflicts:
2251+
if graph.conflicts(conflict):
2252+
warning(
2253+
('{} conflicts with {}, but both have been '
2254+
'included or pulled into the requirements.'.format(recipe.name, conflict)))
2255+
warning('Due to this conflict the build cannot continue, exiting.')
2256+
exit(1)
2257+
recipe_loaded.append(name)
2258+
graph.remove_remaining_conflicts(ctx)
2259+
if len(graph.graphs) > 1:
2260+
info('Found multiple valid recipe sets:')
2261+
for g in graph.graphs:
2262+
info(' {}'.format(g.keys()))
2263+
info_notify('Using the first of these: {}'.format(graph.graphs[0].keys()))
2264+
elif len(graph.graphs) == 0:
2265+
warning('Didn\'t find any valid dependency graphs, exiting.')
2266+
exit(1)
2267+
else:
2268+
info('Found a single valid recipe set (this is good)')
2269+
2270+
build_order = list(graph.find_order(0))
2271+
if bs is None: # It would be better to check against possible
2272+
# orders other than the first one, but in practice
2273+
# there will rarely be clashes, and the user can
2274+
# specify more parameters if necessary to resolve
2275+
# them.
2276+
bs = Bootstrap.get_bootstrap_from_recipes(build_order, ctx)
2277+
if bs is None:
2278+
info('Could not find a bootstrap compatible with the required recipes.')
2279+
info('If you think such a combination should exist, try '
2280+
'specifying the bootstrap manually with --bootstrap.')
2281+
exit(1)
2282+
info('{} bootstrap appears compatible with the required recipes.'.format(bs.name))
2283+
info('Checking this...')
2284+
recipes_to_load = bs.recipe_depends
2285+
# This code repeats the code from earlier! Should move to a function:
2286+
while recipes_to_load:
2287+
name = recipes_to_load.pop(0)
2288+
if name in recipe_loaded or isinstance(name, (list, tuple)):
2289+
continue
2290+
try:
2291+
recipe = Recipe.get_recipe(name, ctx)
2292+
except ImportError:
2293+
info('No recipe named {}; will attempt to install with pip'.format(name))
2294+
python_modules.append(name)
2295+
continue
2296+
graph.add(name, name)
2297+
info('Loaded recipe {} (depends on {}{})'.format(
2298+
name, recipe.depends,
2299+
', conflicts {}'.format(recipe.conflicts) if recipe.conflicts else ''))
2300+
for depend in recipe.depends:
2301+
graph.add(name, depend)
2302+
recipes_to_load += recipe.depends
2303+
for conflict in recipe.conflicts:
2304+
if graph.conflicts(conflict):
2305+
warning(
2306+
('{} conflicts with {}, but both have been '
2307+
'included or pulled into the requirements.'.format(recipe.name, conflict)))
2308+
warning('Due to this conflict the build cannot continue, exiting.')
2309+
exit(1)
2310+
recipe_loaded.append(name)
2311+
graph.remove_remaining_conflicts(ctx)
2312+
build_order = list(graph.find_order(0))
2313+
return build_order, python_modules, bs
2314+
2315+
# Do a final check that the new bs doesn't pull in any conflicts
2316+
2317+
22202318

22212319
def split_argument_list(l):
22222320
if not len(l):

0 commit comments

Comments
 (0)