Skip to content

Commit dba74d6

Browse files
authored
Merge pull request kivy#1640 from inclement/improve_ndk_version_check
Added separate module for checking user SDK, NDK, API etc.
2 parents 2cc008b + ac1d5d2 commit dba74d6

File tree

5 files changed

+147
-90
lines changed

5 files changed

+147
-90
lines changed

pythonforandroid/build.py

Lines changed: 10 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@
1515
from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint)
1616
from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64
1717
from pythonforandroid.recipe import CythonRecipe, Recipe
18-
19-
DEFAULT_ANDROID_API = 15
20-
21-
DEFAULT_NDK_API = 21
18+
from pythonforandroid.recommendations import (
19+
check_ndk_version, check_target_api, check_ndk_api,
20+
RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
2221

2322

2423
class Context(object):
@@ -140,19 +139,6 @@ def ndk_api(self):
140139
def ndk_api(self, value):
141140
self._ndk_api = value
142141

143-
@property
144-
def ndk_ver(self):
145-
'''The version of the NDK being used for compilation.'''
146-
if self._ndk_ver is None:
147-
raise ValueError('Tried to access ndk_ver but it has not '
148-
'been set - this should not happen, something '
149-
'went wrong!')
150-
return self._ndk_ver
151-
152-
@ndk_ver.setter
153-
def ndk_ver(self, value):
154-
self._ndk_ver = value
155-
156142
@property
157143
def sdk_dir(self):
158144
'''The path to the Android SDK.'''
@@ -183,7 +169,6 @@ def prepare_build_environment(self,
183169
user_sdk_dir,
184170
user_ndk_dir,
185171
user_android_api,
186-
user_ndk_ver,
187172
user_ndk_api):
188173
'''Checks that build dependencies exist and sets internal variables
189174
for the Android SDK etc.
@@ -237,17 +222,12 @@ def prepare_build_environment(self,
237222
info('Found Android API target in $ANDROIDAPI: {}'.format(android_api))
238223
else:
239224
info('Android API target was not set manually, using '
240-
'the default of {}'.format(DEFAULT_ANDROID_API))
241-
android_api = DEFAULT_ANDROID_API
225+
'the default of {}'.format(RECOMMENDED_TARGET_API))
226+
android_api = RECOMMENDED_TARGET_API
242227
android_api = int(android_api)
243228
self.android_api = android_api
244229

245-
if self.android_api >= 21 and self.archs[0].arch == 'armeabi':
246-
raise BuildInterruptingException(
247-
'Asked to build for armeabi architecture with API '
248-
'{}, but API 21 or greater does not support armeabi'.format(
249-
self.android_api),
250-
instructions='You probably want to build with --arch=armeabi-v7a instead')
230+
check_target_api(android_api, self.archs[0].arch)
251231

252232
if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')):
253233
avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager'))
@@ -306,47 +286,7 @@ def prepare_build_environment(self,
306286
raise BuildInterruptingException('Android NDK dir was not specified')
307287
self.ndk_dir = realpath(ndk_dir)
308288

309-
# Find the NDK version, and check it against what the NDK dir
310-
# seems to report
311-
ndk_ver = None
312-
if user_ndk_ver:
313-
ndk_ver = user_ndk_ver
314-
if ndk_dir is not None:
315-
info('Got NDK version from from user argument: {}'.format(ndk_ver))
316-
if ndk_ver is None:
317-
ndk_ver = environ.get('ANDROIDNDKVER', None)
318-
if ndk_ver is not None:
319-
info('Got NDK version from $ANDROIDNDKVER: {}'.format(ndk_ver))
320-
321-
self.ndk = 'google'
322-
323-
try:
324-
with open(join(ndk_dir, 'RELEASE.TXT')) as fileh:
325-
reported_ndk_ver = fileh.read().split(' ')[0].strip()
326-
except IOError:
327-
pass
328-
else:
329-
if reported_ndk_ver.startswith('crystax-ndk-'):
330-
reported_ndk_ver = reported_ndk_ver[12:]
331-
self.ndk = 'crystax'
332-
if ndk_ver is None:
333-
ndk_ver = reported_ndk_ver
334-
info(('Got Android NDK version from the NDK dir: {}').format(ndk_ver))
335-
else:
336-
if ndk_ver != reported_ndk_ver:
337-
warning('NDK version was set as {}, but checking '
338-
'the NDK dir claims it is {}.'.format(
339-
ndk_ver, reported_ndk_ver))
340-
warning('The build will try to continue, but it may '
341-
'fail and you should check '
342-
'that your setting is correct.')
343-
warning('If the NDK dir result is correct, you don\'t '
344-
'need to manually set the NDK ver.')
345-
if ndk_ver is None:
346-
warning('Android NDK version could not be found. This probably'
347-
'won\'t cause any problems, but if necessary you can'
348-
'set it with `--ndk-version=...`.')
349-
self.ndk_ver = ndk_ver
289+
check_ndk_version(ndk_dir)
350290

351291
ndk_api = None
352292
if user_ndk_api:
@@ -356,21 +296,14 @@ def prepare_build_environment(self,
356296
ndk_api = environ.get('NDKAPI', None)
357297
info('Found Android API target in $NDKAPI')
358298
else:
359-
ndk_api = min(self.android_api, DEFAULT_NDK_API)
299+
ndk_api = min(self.android_api, RECOMMENDED_NDK_API)
360300
warning('NDK API target was not set manually, using '
361301
'the default of {} = min(android-api={}, default ndk-api={})'.format(
362-
ndk_api, self.android_api, DEFAULT_NDK_API))
302+
ndk_api, self.android_api, RECOMMENDED_NDK_API))
363303
ndk_api = int(ndk_api)
364304
self.ndk_api = ndk_api
365305

366-
if self.ndk_api > self.android_api:
367-
raise BuildInterruptingException(
368-
'Target NDK API is {}, higher than the target Android API {}.'.format(
369-
self.ndk_api, self.android_api),
370-
instructions=('The NDK API is a minimum supported API number and must be lower '
371-
'than the target Android API'))
372-
373-
info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver))
306+
check_ndk_api(ndk_api, self.android_api)
374307

375308
virtualenv = None
376309
if virtualenv is None:
@@ -483,7 +416,6 @@ def __init__(self):
483416
self._ndk_dir = None
484417
self._android_api = None
485418
self._ndk_api = None
486-
self._ndk_ver = None
487419
self.ndk = None
488420

489421
self.toolchain_prefix = None

pythonforandroid/recommendations.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""Simple functions for checking dependency versions."""
2+
3+
from distutils.version import LooseVersion
4+
from os.path import join
5+
from pythonforandroid.logger import info, warning
6+
from pythonforandroid.util import BuildInterruptingException
7+
8+
# We only check the NDK major version
9+
MIN_NDK_VERSION = 17
10+
MAX_NDK_VERSION = 17
11+
12+
RECOMMENDED_NDK_VERSION = '17c'
13+
OLD_NDK_MESSAGE = 'Older NDKs may not be compatible with all p4a features.'
14+
NEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.'
15+
16+
17+
def check_ndk_version(ndk_dir):
18+
# Check the NDK version against what is currently recommended
19+
version = read_ndk_version(ndk_dir)
20+
21+
if version is None:
22+
return # if we failed to read the version, just don't worry about it
23+
24+
major_version = version.version[0]
25+
26+
info('Found NDK revision {}'.format(version))
27+
28+
if major_version < MIN_NDK_VERSION:
29+
warning('Minimum recommended NDK version is {}'.format(
30+
RECOMMENDED_NDK_VERSION))
31+
warning(OLD_NDK_MESSAGE)
32+
elif major_version > MAX_NDK_VERSION:
33+
warning('Maximum recommended NDK version is {}'.format(
34+
RECOMMENDED_NDK_VERSION))
35+
warning(NEW_NDK_MESSAGE)
36+
37+
38+
def read_ndk_version(ndk_dir):
39+
"""Read the NDK version from the NDK dir, if possible"""
40+
try:
41+
with open(join(ndk_dir, 'source.properties')) as fileh:
42+
ndk_data = fileh.read()
43+
except IOError:
44+
info('Could not determine NDK version, no source.properties '
45+
'in the NDK dir')
46+
return
47+
48+
for line in ndk_data.split('\n'):
49+
if line.startswith('Pkg.Revision'):
50+
break
51+
else:
52+
info('Could not parse $NDK_DIR/source.properties, not checking '
53+
'NDK version')
54+
return
55+
56+
# Line should have the form "Pkg.Revision = ..."
57+
ndk_version = LooseVersion(line.split('=')[-1].strip())
58+
59+
return ndk_version
60+
61+
62+
MIN_TARGET_API = 26
63+
64+
# highest version tested to work fine with SDL2
65+
# should be a good default for other bootstraps too
66+
RECOMMENDED_TARGET_API = 27
67+
68+
ARMEABI_MAX_TARGET_API = 21
69+
OLD_API_MESSAGE = (
70+
'Target APIs lower than 26 are no longer supported on Google Play, '
71+
'and are not recommended. Note that the Target API can be higher than '
72+
'your device Android version, and should usually be as high as possible.')
73+
74+
75+
def check_target_api(api, arch):
76+
"""Warn if the user's target API is less than the current minimum
77+
recommendation
78+
"""
79+
80+
if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi':
81+
raise BuildInterruptingException(
82+
'Asked to build for armeabi architecture with API '
83+
'{}, but API {} or greater does not support armeabi'.format(
84+
api, ARMEABI_MAX_TARGET_API),
85+
instructions='You probably want to build with --arch=armeabi-v7a instead')
86+
87+
if api < MIN_TARGET_API:
88+
warning('Target API {} < {}'.format(api, MIN_TARGET_API))
89+
warning(OLD_API_MESSAGE)
90+
91+
92+
MIN_NDK_API = 21
93+
RECOMMENDED_NDK_API = 21
94+
OLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API))
95+
96+
97+
def check_ndk_api(ndk_api, android_api):
98+
"""Warn if the user's NDK is too high or low."""
99+
if ndk_api > android_api:
100+
raise BuildInterruptingException(
101+
'Target NDK API is {}, higher than the target Android API {}.'.format(
102+
ndk_api, android_api),
103+
instructions=('The NDK API is a minimum supported API number and must be lower '
104+
'than the target Android API'))
105+
106+
if ndk_api < MIN_NDK_API:
107+
warning(OLD_NDK_API_MESSAGE)

pythonforandroid/toolchain.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
"""
88

99
from __future__ import print_function
10+
from os import environ
1011
from pythonforandroid import __version__
11-
from pythonforandroid.build import DEFAULT_NDK_API, DEFAULT_ANDROID_API
12+
from pythonforandroid.recommendations import (
13+
RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API)
1214
from pythonforandroid.util import BuildInterruptingException, handle_build_exception
1315

1416

@@ -139,7 +141,6 @@ def wrapper_func(self, args):
139141
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
140142
user_ndk_dir=self.ndk_dir,
141143
user_android_api=self.android_api,
142-
user_ndk_ver=self.ndk_version,
143144
user_ndk_api=self.ndk_api)
144145
dist = self._dist
145146
if dist.needs_build:
@@ -258,16 +259,16 @@ def __init__(self):
258259
default=0,
259260
type=int,
260261
help=('The Android API level to build against defaults to {} if '
261-
'not specified.').format(DEFAULT_ANDROID_API))
262+
'not specified.').format(RECOMMENDED_TARGET_API))
262263
generic_parser.add_argument(
263-
'--ndk-version', '--ndk_version', dest='ndk_version', default='',
264-
help=('The version of the Android NDK. This is optional: '
265-
'we try to work it out automatically from the ndk_dir.'))
264+
'--ndk-version', '--ndk_version', dest='ndk_version', default=None,
265+
help=('DEPRECATED: the NDK version is now found automatically or '
266+
'not at all.'))
266267
generic_parser.add_argument(
267268
'--ndk-api', type=int, default=None,
268269
help=('The Android API level to compile against. This should be your '
269270
'*minimal supported* API, not normally the same as your --android-api. '
270-
'Defaults to min(ANDROID_API, {}) if not specified.').format(DEFAULT_NDK_API))
271+
'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API))
271272
generic_parser.add_argument(
272273
'--symlink-java-src', '--symlink_java_src',
273274
action='store_true',
@@ -360,6 +361,11 @@ def add_parser(subparsers, *args, **kwargs):
360361
kwargs.pop('aliases')
361362
return subparsers.add_parser(*args, **kwargs)
362363

364+
parser_recommendations = add_parser(
365+
subparsers,
366+
'recommendations',
367+
parents=[generic_parser],
368+
help='List recommended p4a dependencies')
363369
parser_recipes = add_parser(
364370
subparsers,
365371
'recipes',
@@ -533,13 +539,14 @@ def add_parser(subparsers, *args, **kwargs):
533539
requirements.append(requirement)
534540
args.requirements = u",".join(requirements)
535541

542+
self.warn_on_deprecated_args(args)
543+
536544
self.ctx = Context()
537545
self.storage_dir = args.storage_dir
538546
self.ctx.setup_dirs(self.storage_dir)
539547
self.sdk_dir = args.sdk_dir
540548
self.ndk_dir = args.ndk_dir
541549
self.android_api = args.android_api
542-
self.ndk_version = args.ndk_version
543550
self.ndk_api = args.ndk_api
544551
self.ctx.symlink_java_src = args.symlink_java_src
545552
self.ctx.java_build_tool = args.java_build_tool
@@ -552,6 +559,19 @@ def add_parser(subparsers, *args, **kwargs):
552559
# Each subparser corresponds to a method
553560
getattr(self, args.subparser_name.replace('-', '_'))(args)
554561

562+
def warn_on_deprecated_args(self, args):
563+
"""
564+
Print warning messages for any deprecated arguments that were passed.
565+
"""
566+
567+
# NDK version is now determined automatically
568+
if args.ndk_version is not None:
569+
warning('--ndk-version is deprecated and no longer necessary, '
570+
'the value you passed is ignored')
571+
if 'ANDROIDNDKVER' in environ:
572+
warning('$ANDROIDNDKVER is deprecated and no longer necessary, '
573+
'the value you set is ignored')
574+
555575
def hook(self, name):
556576
if not self.args.hook:
557577
return
@@ -959,7 +979,6 @@ def sdk_tools(self, args):
959979
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
960980
user_ndk_dir=self.ndk_dir,
961981
user_android_api=self.android_api,
962-
user_ndk_ver=self.ndk_version,
963982
user_ndk_api=self.ndk_api)
964983
android = sh.Command(join(ctx.sdk_dir, 'tools', args.tool))
965984
output = android(
@@ -987,7 +1006,6 @@ def _adb(self, commands):
9871006
ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir,
9881007
user_ndk_dir=self.ndk_dir,
9891008
user_android_api=self.android_api,
990-
user_ndk_ver=self.ndk_version,
9911009
user_ndk_api=self.ndk_api)
9921010
if platform in ('win32', 'cygwin'):
9931011
adb = sh.Command(join(ctx.sdk_dir, 'platform-tools', 'adb.exe'))

testapps/on_device_unit_tests/buildozer.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ android.whitelist = unittest/*
153153
#android.add_activites = com.example.ExampleActivity
154154

155155
# (str) python-for-android branch to use, defaults to master
156-
#p4a.branch = master
156+
p4a.branch = master
157157

158158
# (str) OUYA Console category. Should be one of GAME or APP
159159
# If you leave this blank, OUYA support will not be enabled

testapps/on_device_unit_tests/test_app/tests/test_requirements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ class PyjniusTestCase(PythonTestMixIn, TestCase):
4747

4848
def test_run_module(self):
4949
from jnius import autoclass
50-
autoclass('org.kivy.PythonActivity')
50+
autoclass('org.kivy.android.PythonActivity')

0 commit comments

Comments
 (0)