Skip to content

Commit 01c0607

Browse files
authored
Made p4a use per-arch dist build dirs (#1986)
* Made p4a use per-arch dist build dirs * Fixed up check on existing folder when overwriting distributions * Improved Distribution.get_distribution api * Fixed test_archs.py * Fixed test_bootstrap.py * Fixed test_distribution.py * Fixed recipes tests * Fixed formatting * Made sure whole build process accesses dist_dir correctly * Fixed apk output name formatting by moving version to end of string * Further test fixes following dist_dir changes * Removed unnecessary variable
1 parent 9f6d6fc commit 01c0607

File tree

9 files changed

+140
-60
lines changed

9 files changed

+140
-60
lines changed

pythonforandroid/bootstrap.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@
88
import shlex
99
import shutil
1010

11-
from pythonforandroid.logger import (warning, shprint, info, logger,
12-
debug)
13-
from pythonforandroid.util import (current_directory, ensure_dir,
14-
temp_directory)
11+
from pythonforandroid.logger import (shprint, info, logger, debug)
12+
from pythonforandroid.util import (
13+
current_directory, ensure_dir, temp_directory, BuildInterruptingException)
1514
from pythonforandroid.recipe import Recipe
1615

1716

@@ -75,7 +74,6 @@ class Bootstrap(object):
7574
bootstrap_dir = None
7675

7776
build_dir = None
78-
dist_dir = None
7977
dist_name = None
8078
distribution = None
8179

@@ -97,9 +95,9 @@ class Bootstrap(object):
9795
def dist_dir(self):
9896
'''The dist dir at which to place the finished distribution.'''
9997
if self.distribution is None:
100-
warning('Tried to access {}.dist_dir, but {}.distribution '
101-
'is None'.format(self, self))
102-
exit(1)
98+
raise BuildInterruptingException(
99+
'Internal error: tried to access {}.dist_dir, but {}.distribution '
100+
'is None'.format(self, self))
103101
return self.distribution.dist_dir
104102

105103
@property
@@ -158,7 +156,7 @@ def prepare_build_dir(self):
158156
with open('project.properties', 'w') as fileh:
159157
fileh.write('target=android-{}'.format(self.ctx.android_api))
160158

161-
def prepare_dist_dir(self, name):
159+
def prepare_dist_dir(self):
162160
ensure_dir(self.dist_dir)
163161

164162
def run_distribute(self):

pythonforandroid/build.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,13 @@ class Context(object):
9292
# in which bootstraps are copied for building
9393
# and recipes are built
9494
build_dir = None
95+
96+
distribution = None
97+
"""The Distribution object representing the current build target location."""
98+
9599
# the Android project folder where everything ends up
96100
dist_dir = None
101+
97102
# where Android libs are cached after build
98103
# but before being placed in dists
99104
libs_dir = None
@@ -106,7 +111,6 @@ class Context(object):
106111

107112
ndk_platform = None # the ndk platform directory
108113

109-
dist_name = None # should be deprecated in favour of self.dist.dist_name
110114
bootstrap = None
111115
bootstrap_build_dir = None
112116

@@ -485,9 +489,8 @@ def prepare_bootstrap(self, bs):
485489
self.bootstrap.prepare_build_dir()
486490
self.bootstrap_build_dir = self.bootstrap.build_dir
487491

488-
def prepare_dist(self, name):
489-
self.dist_name = name
490-
self.bootstrap.prepare_dist_dir(self.dist_name)
492+
def prepare_dist(self):
493+
self.bootstrap.prepare_dist_dir()
491494

492495
def get_site_packages_dir(self, arch=None):
493496
'''Returns the location of site-packages in the python-install build

pythonforandroid/distribution.py

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Distribution(object):
2424
ndk_api = None
2525

2626
archs = []
27-
'''The arch targets that the dist is built for.'''
27+
'''The names of the arch targets that the dist is built for.'''
2828

2929
recipes = []
3030

@@ -42,12 +42,19 @@ def __repr__(self):
4242
return str(self)
4343

4444
@classmethod
45-
def get_distribution(cls, ctx, name=None, recipes=[],
46-
ndk_api=None,
47-
force_build=False,
48-
extra_dist_dirs=[],
49-
require_perfect_match=False,
50-
allow_replace_dist=True):
45+
def get_distribution(
46+
cls,
47+
ctx,
48+
*,
49+
arch_name, # required keyword argument: there is no sensible default
50+
name=None,
51+
recipes=[],
52+
ndk_api=None,
53+
force_build=False,
54+
extra_dist_dirs=[],
55+
require_perfect_match=False,
56+
allow_replace_dist=True
57+
):
5158
'''Takes information about the distribution, and decides what kind of
5259
distribution it will be.
5360
@@ -60,6 +67,12 @@ def get_distribution(cls, ctx, name=None, recipes=[],
6067
name : str
6168
The name of the distribution. If a dist with this name already '
6269
exists, it will be used.
70+
ndk_api : int
71+
The NDK API to compile against, included in the dist because it cannot
72+
be changed later during APK packaging.
73+
arch_name : str
74+
The target architecture name to compile against, included in the dist because
75+
it cannot be changed later during APK packaging.
6376
recipes : list
6477
The recipes that the distribution must contain.
6578
force_download: bool
@@ -77,17 +90,24 @@ def get_distribution(cls, ctx, name=None, recipes=[],
7790
a new one with the current requirements.
7891
'''
7992

80-
existing_dists = Distribution.get_distributions(ctx)
93+
possible_dists = Distribution.get_distributions(ctx)
8194

82-
possible_dists = existing_dists
95+
# Will hold dists that would be built in the same folder as an existing dist
96+
folder_match_dist = None
8397

84-
name_match_dist = None
85-
86-
# 0) Check if a dist with that name already exists
98+
# 0) Check if a dist with that name and architecture already exists
8799
if name is not None and name:
88-
possible_dists = [d for d in possible_dists if d.name == name]
100+
possible_dists = [
101+
d for d in possible_dists if
102+
(d.name == name) and (arch_name in d.archs)]
103+
89104
if possible_dists:
90-
name_match_dist = possible_dists[0]
105+
# There should only be one folder with a given dist name *and* arch.
106+
# We could check that here, but for compatibility let's let it slide
107+
# and just record the details of one of them. We only use this data to
108+
# possibly fail the build later, so it doesn't really matter if there
109+
# was more than one clash.
110+
folder_match_dist = possible_dists[0]
91111

92112
# 1) Check if any existing dists meet the requirements
93113
_possible_dists = []
@@ -110,35 +130,37 @@ def get_distribution(cls, ctx, name=None, recipes=[],
110130
else:
111131
info('No existing dists meet the given requirements!')
112132

113-
# If any dist has perfect recipes and ndk API, return it
133+
# If any dist has perfect recipes, arch and NDK API, return it
114134
for dist in possible_dists:
115135
if force_build:
116136
continue
117137
if ndk_api is not None and dist.ndk_api != ndk_api:
118138
continue
139+
if arch_name is not None and arch_name not in dist.archs:
140+
continue
119141
if (set(dist.recipes) == set(recipes) or
120142
(set(recipes).issubset(set(dist.recipes)) and
121143
not require_perfect_match)):
122144
info_notify('{} has compatible recipes, using this one'
123145
.format(dist.name))
124146
return dist
125147

126-
assert len(possible_dists) < 2
127-
128148
# If there was a name match but we didn't already choose it,
129149
# then the existing dist is incompatible with the requested
130150
# configuration and the build cannot continue
131-
if name_match_dist is not None and not allow_replace_dist:
151+
if folder_match_dist is not None and not allow_replace_dist:
132152
raise BuildInterruptingException(
133153
'Asked for dist with name {name} with recipes ({req_recipes}) and '
134154
'NDK API {req_ndk_api}, but a dist '
135155
'with this name already exists and has either incompatible recipes '
136156
'({dist_recipes}) or NDK API {dist_ndk_api}'.format(
137157
name=name,
138158
req_ndk_api=ndk_api,
139-
dist_ndk_api=name_match_dist.ndk_api,
159+
dist_ndk_api=folder_match_dist.ndk_api,
140160
req_recipes=', '.join(recipes),
141-
dist_recipes=', '.join(name_match_dist.recipes)))
161+
dist_recipes=', '.join(folder_match_dist.recipes)))
162+
163+
assert len(possible_dists) < 2
142164

143165
# If we got this far, we need to build a new dist
144166
dist = Distribution(ctx)
@@ -152,9 +174,16 @@ def get_distribution(cls, ctx, name=None, recipes=[],
152174
name = filen.format(i)
153175

154176
dist.name = name
155-
dist.dist_dir = join(ctx.dist_dir, dist.name)
177+
dist.dist_dir = join(
178+
ctx.dist_dir,
179+
generate_dist_folder_name(
180+
name,
181+
[arch_name] if arch_name is not None else None,
182+
)
183+
)
156184
dist.recipes = recipes
157185
dist.ndk_api = ctx.ndk_api
186+
dist.archs = [arch_name]
158187

159188
return dist
160189

@@ -182,7 +211,7 @@ def get_distributions(cls, ctx, extra_dist_dirs=[]):
182211
with open(join(folder, 'dist_info.json')) as fileh:
183212
dist_info = json.load(fileh)
184213
dist = cls(ctx)
185-
dist.name = folder.split('/')[-1]
214+
dist.name = dist_info['dist_name']
186215
dist.dist_dir = folder
187216
dist.needs_build = False
188217
dist.recipes = dist_info['recipes']
@@ -210,7 +239,7 @@ def save_info(self, dirn):
210239
with current_directory(dirn):
211240
info('Saving distribution info')
212241
with open('dist_info.json', 'w') as fileh:
213-
json.dump({'dist_name': self.ctx.dist_name,
242+
json.dump({'dist_name': self.name,
214243
'bootstrap': self.ctx.bootstrap.name,
215244
'archs': [arch.arch for arch in self.ctx.archs],
216245
'ndk_api': self.ctx.ndk_api,
@@ -236,3 +265,23 @@ def pretty_log_dists(dists, log_func=info):
236265

237266
for line in infos:
238267
log_func('\t' + line)
268+
269+
270+
def generate_dist_folder_name(base_dist_name, arch_names=None):
271+
"""Generate the distribution folder name to use, based on a
272+
combination of the input arguments.
273+
274+
Parameters
275+
----------
276+
base_dist_name : str
277+
The core distribution identifier string
278+
arch_names : list of str
279+
The architecture compile targets
280+
"""
281+
if arch_names is None:
282+
arch_names = ["no_arch_specified"]
283+
284+
return '{}__{}'.format(
285+
base_dist_name,
286+
'_'.join(arch_names)
287+
)

pythonforandroid/python.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,11 @@ def create_python_bundle(self, dirn, arch):
341341
python_lib_name = 'libpython' + self.major_minor_version_string
342342
if self.major_minor_version_string[0] == '3':
343343
python_lib_name += 'm'
344-
shprint(sh.cp, join(python_build_dir, python_lib_name + '.so'),
345-
join(self.ctx.dist_dir, self.ctx.dist_name, 'libs', arch.arch))
344+
shprint(
345+
sh.cp,
346+
join(python_build_dir, python_lib_name + '.so'),
347+
join(self.ctx.bootstrap.dist_dir, 'libs', arch.arch)
348+
)
346349

347350
info('Renaming .so files to reflect cross-compile')
348351
self.reduce_object_file_names(join(dirn, 'site-packages'))

pythonforandroid/toolchain.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def check_python_dependencies():
101101
toolchain_dir = dirname(__file__)
102102
sys.path.insert(0, join(toolchain_dir, "tools", "external"))
103103

104+
APK_SUFFIX = '.apk'
105+
104106

105107
def add_boolean_option(parser, names, no_names=None,
106108
default=True, dest=None, description=None):
@@ -163,6 +165,7 @@ def dist_from_args(ctx, args):
163165
ctx,
164166
name=args.dist_name,
165167
recipes=split_argument_list(args.requirements),
168+
arch_name=args.arch,
166169
ndk_api=args.ndk_api,
167170
force_build=args.force_build,
168171
require_perfect_match=args.require_perfect_match,
@@ -195,10 +198,10 @@ def build_dist_from_args(ctx, dist, args):
195198
info('Dist will also contain modules ({}) installed from pip'.format(
196199
', '.join(ctx.python_modules)))
197200

198-
ctx.dist_name = bs.distribution.name
201+
ctx.distribution = dist
199202
ctx.prepare_bootstrap(bs)
200203
if dist.needs_build:
201-
ctx.prepare_dist(ctx.dist_name)
204+
ctx.prepare_dist()
202205

203206
build_recipes(build_order, python_modules, ctx,
204207
getattr(args, "private", None),
@@ -211,7 +214,7 @@ def build_dist_from_args(ctx, dist, args):
211214

212215
info_main('# Your distribution was created successfully, exiting.')
213216
info('Dist can be found at (for now) {}'
214-
.format(join(ctx.dist_dir, ctx.dist_name)))
217+
.format(join(ctx.dist_dir, ctx.distribution.dist_dir)))
215218

216219

217220
def split_argument_list(l):
@@ -304,7 +307,7 @@ def __init__(self):
304307
'(default: {})'.format(default_storage_dir)))
305308

306309
generic_parser.add_argument(
307-
'--arch', help='The archs to build for, separated by commas.',
310+
'--arch', help='The arch to build for.',
308311
default='armeabi-v7a')
309312

310313
# Options for specifying the Distribution
@@ -912,6 +915,7 @@ def export_dist(self, args):
912915
def _dist(self):
913916
ctx = self.ctx
914917
dist = dist_from_args(ctx, self.args)
918+
ctx.distribution = dist
915919
return dist
916920

917921
@require_prebuilt_dist
@@ -1062,9 +1066,9 @@ def apk(self, args):
10621066
info_main('# Found APK file: {}'.format(apk_file))
10631067
if apk_add_version:
10641068
info('# Add version number to APK')
1065-
apk_name, apk_suffix = basename(apk_file).split("-", 1)
1069+
apk_name = basename(apk_file)[:-len(APK_SUFFIX)]
10661070
apk_file_dest = "{}-{}-{}".format(
1067-
apk_name, build_args.version, apk_suffix)
1071+
apk_name, build_args.version, APK_SUFFIX)
10681072
info('# APK renamed to {}'.format(apk_file_dest))
10691073
shprint(sh.cp, apk_file, apk_file_dest)
10701074
else:

tests/recipes/recipe_ctx.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ class RecipeCtx:
2626
contain the target recipe to test as well as a python recipe."""
2727
recipe_build_order = []
2828
"""A recipe_build_order which should take into account the recipe we want
29-
to test as well as the possible dependant recipes"""
29+
to test as well as the possible dependent recipes"""
30+
31+
TEST_ARCH = 'arm64-v8a'
3032

3133
def setUp(self):
3234
self.ctx = Context()
@@ -37,7 +39,7 @@ def setUp(self):
3739
self.ctx.setup_dirs(os.getcwd())
3840
self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx)
3941
self.ctx.bootstrap.distribution = Distribution.get_distribution(
40-
self.ctx, name="sdl2", recipes=self.recipes
42+
self.ctx, name="sdl2", recipes=self.recipes, arch_name=self.TEST_ARCH,
4143
)
4244
self.ctx.recipe_build_order = self.recipe_build_order
4345
self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)

tests/test_archs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class ArchSetUpBaseClass(object):
5050
ctx = None
5151
expected_compiler = ""
5252

53+
TEST_ARCH = 'armeabi-v7a'
54+
5355
def setUp(self):
5456
self.ctx = Context()
5557
self.ctx.ndk_api = 21
@@ -59,7 +61,10 @@ def setUp(self):
5961
self.ctx.setup_dirs(os.getcwd())
6062
self.ctx.bootstrap = Bootstrap().get_bootstrap("sdl2", self.ctx)
6163
self.ctx.bootstrap.distribution = Distribution.get_distribution(
62-
self.ctx, name="sdl2", recipes=["python3", "kivy"]
64+
self.ctx,
65+
name="sdl2",
66+
recipes=["python3", "kivy"],
67+
arch_name=self.TEST_ARCH,
6368
)
6469
self.ctx.python_recipe = Recipe.get_recipe("python3", self.ctx)
6570
# Here we define the expected compiler, which, as per ndk >= r19,

0 commit comments

Comments
 (0)