From 6fc130309f3a49146e78ef3e6d6ad6590fb73014 Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Thu, 31 Jan 2019 14:52:31 +0000 Subject: [PATCH 1/4] Added separate module for checking user SDK, NDK, API etc. Also fixed some issues found during testing --- pythonforandroid/build.py | 89 ++------------- pythonforandroid/recommendations.py | 103 ++++++++++++++++++ pythonforandroid/toolchain.py | 38 +++++-- testapps/on_device_unit_tests/buildozer.spec | 2 +- .../test_app/tests/test_requirements.py | 2 +- 5 files changed, 143 insertions(+), 91 deletions(-) create mode 100644 pythonforandroid/recommendations.py diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index 23a4392589..4d50ff141c 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -15,11 +15,9 @@ from pythonforandroid.logger import (info, warning, info_notify, info_main, shprint) from pythonforandroid.archs import ArchARM, ArchARMv7_a, ArchAarch_64, Archx86, Archx86_64 from pythonforandroid.recipe import CythonRecipe, Recipe - -DEFAULT_ANDROID_API = 15 - -DEFAULT_NDK_API = 21 - +from pythonforandroid.recommendations import ( + check_ndk_version, check_target_api, check_ndk_api, + RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API) class Context(object): '''A build context. If anything will be built, an instance this class @@ -140,19 +138,6 @@ def ndk_api(self): def ndk_api(self, value): self._ndk_api = value - @property - def ndk_ver(self): - '''The version of the NDK being used for compilation.''' - if self._ndk_ver is None: - raise ValueError('Tried to access ndk_ver but it has not ' - 'been set - this should not happen, something ' - 'went wrong!') - return self._ndk_ver - - @ndk_ver.setter - def ndk_ver(self, value): - self._ndk_ver = value - @property def sdk_dir(self): '''The path to the Android SDK.''' @@ -183,7 +168,6 @@ def prepare_build_environment(self, user_sdk_dir, user_ndk_dir, user_android_api, - user_ndk_ver, user_ndk_api): '''Checks that build dependencies exist and sets internal variables for the Android SDK etc. @@ -237,17 +221,12 @@ def prepare_build_environment(self, info('Found Android API target in $ANDROIDAPI: {}'.format(android_api)) else: info('Android API target was not set manually, using ' - 'the default of {}'.format(DEFAULT_ANDROID_API)) - android_api = DEFAULT_ANDROID_API + 'the default of {}'.format(RECOMMENDED_TARGET_API)) + android_api = RECOMMENDED_TARGET_API android_api = int(android_api) self.android_api = android_api - if self.android_api >= 21 and self.archs[0].arch == 'armeabi': - raise BuildInterruptingException( - 'Asked to build for armeabi architecture with API ' - '{}, but API 21 or greater does not support armeabi'.format( - self.android_api), - instructions='You probably want to build with --arch=armeabi-v7a instead') + check_target_api(android_api, self.archs[0].arch) if exists(join(sdk_dir, 'tools', 'bin', 'avdmanager')): avdmanager = sh.Command(join(sdk_dir, 'tools', 'bin', 'avdmanager')) @@ -306,47 +285,7 @@ def prepare_build_environment(self, raise BuildInterruptingException('Android NDK dir was not specified') self.ndk_dir = realpath(ndk_dir) - # Find the NDK version, and check it against what the NDK dir - # seems to report - ndk_ver = None - if user_ndk_ver: - ndk_ver = user_ndk_ver - if ndk_dir is not None: - info('Got NDK version from from user argument: {}'.format(ndk_ver)) - if ndk_ver is None: - ndk_ver = environ.get('ANDROIDNDKVER', None) - if ndk_ver is not None: - info('Got NDK version from $ANDROIDNDKVER: {}'.format(ndk_ver)) - - self.ndk = 'google' - - try: - with open(join(ndk_dir, 'RELEASE.TXT')) as fileh: - reported_ndk_ver = fileh.read().split(' ')[0].strip() - except IOError: - pass - else: - if reported_ndk_ver.startswith('crystax-ndk-'): - reported_ndk_ver = reported_ndk_ver[12:] - self.ndk = 'crystax' - if ndk_ver is None: - ndk_ver = reported_ndk_ver - info(('Got Android NDK version from the NDK dir: {}').format(ndk_ver)) - else: - if ndk_ver != reported_ndk_ver: - warning('NDK version was set as {}, but checking ' - 'the NDK dir claims it is {}.'.format( - ndk_ver, reported_ndk_ver)) - warning('The build will try to continue, but it may ' - 'fail and you should check ' - 'that your setting is correct.') - warning('If the NDK dir result is correct, you don\'t ' - 'need to manually set the NDK ver.') - if ndk_ver is None: - warning('Android NDK version could not be found. This probably' - 'won\'t cause any problems, but if necessary you can' - 'set it with `--ndk-version=...`.') - self.ndk_ver = ndk_ver + check_ndk_version(ndk_dir) ndk_api = None if user_ndk_api: @@ -356,21 +295,14 @@ def prepare_build_environment(self, ndk_api = environ.get('NDKAPI', None) info('Found Android API target in $NDKAPI') else: - ndk_api = min(self.android_api, DEFAULT_NDK_API) + ndk_api = min(self.android_api, RECOMMENDED_NDK_API) warning('NDK API target was not set manually, using ' 'the default of {} = min(android-api={}, default ndk-api={})'.format( - ndk_api, self.android_api, DEFAULT_NDK_API)) + ndk_api, self.android_api, RECOMMENDED_NDK_API)) ndk_api = int(ndk_api) self.ndk_api = ndk_api - if self.ndk_api > self.android_api: - raise BuildInterruptingException( - 'Target NDK API is {}, higher than the target Android API {}.'.format( - self.ndk_api, self.android_api), - instructions=('The NDK API is a minimum supported API number and must be lower ' - 'than the target Android API')) - - info('Using {} NDK {}'.format(self.ndk.capitalize(), self.ndk_ver)) + check_ndk_api(ndk_api, self.android_api) virtualenv = None if virtualenv is None: @@ -483,7 +415,6 @@ def __init__(self): self._ndk_dir = None self._android_api = None self._ndk_api = None - self._ndk_ver = None self.ndk = None self.toolchain_prefix = None diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py new file mode 100644 index 0000000000..7cd7164b61 --- /dev/null +++ b/pythonforandroid/recommendations.py @@ -0,0 +1,103 @@ +"""Simple functions for checking dependency versions.""" + +from distutils.version import LooseVersion +from os.path import join +from pythonforandroid.logger import info, warning +from pythonforandroid.util import BuildInterruptingException + +# We only check the NDK major version +MIN_NDK_VERSION = 17 +MAX_NDK_VERSION = 17 + +RECOMMENDED_NDK_VERSION = '17c' +OLD_NDK_MESSAGE = 'Older NDKs may not be compatible with all p4a features.' +NEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.' + + +def get_recommended_ndk(): + pass + +def check_ndk_version(ndk_dir): + # Check the NDK version against what is currently recommended + version = read_ndk_version(ndk_dir) + + if version is None: + return # it doesn't matter + + major_version = version.version[0] + + info('Found NDK revision {}'.format(version)) + + if major_version < MIN_NDK_VERSION: + warning('Minimum recommended NDK version is {}'.format( + RECOMMENDED_NDK_VERSION)) + warning(OLD_NDK_MESSAGE) + elif major_version > MAX_NDK_VERSION: + warning('Maximum recommended NDK version is {}'.format( + RECOMMENDED_NDK_VERSION)) + warning(NEW_NDK_MESSAGE) + +def read_ndk_version(ndk_dir): + """Read the NDK version from the NDK dir, if possible""" + try: + with open(join(ndk_dir, 'source.properties')) as fileh: + ndk_data = fileh.read() + except IOError: + info('Could not determine NDK version, no source.properties ' + 'in the NDK dir') + return + + for line in ndk_data.split('\n'): + if line.startswith('Pkg.Revision'): + break + else: + info('Could not parse $NDK_DIR/source.properties, not checking ' + 'NDK version') + + # Line should have the form "Pkg.Revision = ..." + ndk_version = LooseVersion(line.split('=')[-1].strip()) + + return ndk_version + + +MIN_TARGET_API = 26 +RECOMMENDED_TARGET_API = 27 # highest version tested to work fine with SDL2 + # should be a good default for other bootstraps too +ARMEABI_MAX_TARGET_API = 21 +OLD_API_MESSAGE = ( + 'Target APIs lower than 26 are no longer supported on Google Play, ' + 'and are not recommended. Note that the Target API can be higher than ' + 'your device Android version, and should usually be as high as possible.') + +def check_target_api(api, arch): + """Warn if the user's target API is less than the current minimum + recommendation + """ + + if api >= ARMEABI_MAX_TARGET_API and arch == 'armeabi': + raise BuildInterruptingException( + 'Asked to build for armeabi architecture with API ' + '{}, but API {} or greater does not support armeabi'.format( + self.android_api, ARMEABI_MAX_TARGET_API), + instructions='You probably want to build with --arch=armeabi-v7a instead') + + if api < MIN_TARGET_API: + warning('Target API {} < {}'.format(api, MIN_TARGET_API)) + warning(OLD_API_MESSAGE) + + +MIN_NDK_API = 21 +RECOMMENDED_NDK_API = 21 +OLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API)) + +def check_ndk_api(ndk_api, android_api): + """Warn if the user's NDK is too high or low.""" + if ndk_api > android_api: + raise BuildInterruptingException( + 'Target NDK API is {}, higher than the target Android API {}.'.format( + ndk_api, android_api), + instructions=('The NDK API is a minimum supported API number and must be lower ' + 'than the target Android API')) + + if ndk_api < MIN_NDK_API: + warning(OLD_NDK_API_MESSAGE) diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index 40a0ed3dc9..c7d8719d52 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -7,8 +7,10 @@ """ from __future__ import print_function +from os import environ from pythonforandroid import __version__ -from pythonforandroid.build import DEFAULT_NDK_API, DEFAULT_ANDROID_API +from pythonforandroid.recommendations import ( + RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API) from pythonforandroid.util import BuildInterruptingException, handle_build_exception @@ -139,7 +141,6 @@ def wrapper_func(self, args): ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir, user_ndk_dir=self.ndk_dir, user_android_api=self.android_api, - user_ndk_ver=self.ndk_version, user_ndk_api=self.ndk_api) dist = self._dist if dist.needs_build: @@ -258,16 +259,16 @@ def __init__(self): default=0, type=int, help=('The Android API level to build against defaults to {} if ' - 'not specified.').format(DEFAULT_ANDROID_API)) + 'not specified.').format(RECOMMENDED_TARGET_API)) generic_parser.add_argument( - '--ndk-version', '--ndk_version', dest='ndk_version', default='', - help=('The version of the Android NDK. This is optional: ' - 'we try to work it out automatically from the ndk_dir.')) + '--ndk-version', '--ndk_version', dest='ndk_version', default=None, + help=('DEPRECATED: the NDK version is now found automatically or ' + 'not at all.')) generic_parser.add_argument( '--ndk-api', type=int, default=None, help=('The Android API level to compile against. This should be your ' '*minimal supported* API, not normally the same as your --android-api. ' - 'Defaults to min(ANDROID_API, {}) if not specified.').format(DEFAULT_NDK_API)) + 'Defaults to min(ANDROID_API, {}) if not specified.').format(RECOMMENDED_NDK_API)) generic_parser.add_argument( '--symlink-java-src', '--symlink_java_src', action='store_true', @@ -360,6 +361,11 @@ def add_parser(subparsers, *args, **kwargs): kwargs.pop('aliases') return subparsers.add_parser(*args, **kwargs) + parser_recommendations = add_parser( + subparsers, + 'recommendations', + parents=[generic_parser], + help='List recommended p4a dependencies') parser_recipes = add_parser( subparsers, 'recipes', @@ -533,13 +539,14 @@ def add_parser(subparsers, *args, **kwargs): requirements.append(requirement) args.requirements = u",".join(requirements) + self.warn_on_deprecated_args(args) + self.ctx = Context() self.storage_dir = args.storage_dir self.ctx.setup_dirs(self.storage_dir) self.sdk_dir = args.sdk_dir self.ndk_dir = args.ndk_dir self.android_api = args.android_api - self.ndk_version = args.ndk_version self.ndk_api = args.ndk_api self.ctx.symlink_java_src = args.symlink_java_src self.ctx.java_build_tool = args.java_build_tool @@ -552,6 +559,19 @@ def add_parser(subparsers, *args, **kwargs): # Each subparser corresponds to a method getattr(self, args.subparser_name.replace('-', '_'))(args) + def warn_on_deprecated_args(self, args): + """ + Print warning messages for any deprecated arguments that were passed. + """ + + # NDK version is now determined automatically + if args.ndk_version is not None: + warning('--ndk-version is deprecated and no longer necessary, ' + 'the value you passed is ignored') + if 'ANDROIDNDKVER' in environ: + warning('$ANDROIDNDKVER is deprecated and no longer necessary, ' + 'the value you set is ignored') + def hook(self, name): if not self.args.hook: return @@ -959,7 +979,6 @@ def sdk_tools(self, args): ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir, user_ndk_dir=self.ndk_dir, user_android_api=self.android_api, - user_ndk_ver=self.ndk_version, user_ndk_api=self.ndk_api) android = sh.Command(join(ctx.sdk_dir, 'tools', args.tool)) output = android( @@ -987,7 +1006,6 @@ def _adb(self, commands): ctx.prepare_build_environment(user_sdk_dir=self.sdk_dir, user_ndk_dir=self.ndk_dir, user_android_api=self.android_api, - user_ndk_ver=self.ndk_version, user_ndk_api=self.ndk_api) if platform in ('win32', 'cygwin'): adb = sh.Command(join(ctx.sdk_dir, 'platform-tools', 'adb.exe')) diff --git a/testapps/on_device_unit_tests/buildozer.spec b/testapps/on_device_unit_tests/buildozer.spec index e1cbbab48a..b313bad059 100644 --- a/testapps/on_device_unit_tests/buildozer.spec +++ b/testapps/on_device_unit_tests/buildozer.spec @@ -153,7 +153,7 @@ android.whitelist = unittest/* #android.add_activites = com.example.ExampleActivity # (str) python-for-android branch to use, defaults to master -#p4a.branch = master +p4a.branch = master # (str) OUYA Console category. Should be one of GAME or APP # If you leave this blank, OUYA support will not be enabled diff --git a/testapps/on_device_unit_tests/test_app/tests/test_requirements.py b/testapps/on_device_unit_tests/test_app/tests/test_requirements.py index a35a9a425a..625a99e5db 100644 --- a/testapps/on_device_unit_tests/test_app/tests/test_requirements.py +++ b/testapps/on_device_unit_tests/test_app/tests/test_requirements.py @@ -47,4 +47,4 @@ class PyjniusTestCase(PythonTestMixIn, TestCase): def test_run_module(self): from jnius import autoclass - autoclass('org.kivy.PythonActivity') + autoclass('org.kivy.android.PythonActivity') From 7630222e660c0ad36b868df48951c4d43592210f Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Thu, 31 Jan 2019 14:55:39 +0000 Subject: [PATCH 2/4] Fixed ndk version check when no version found --- pythonforandroid/recommendations.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py index 7cd7164b61..2fcdb842d8 100644 --- a/pythonforandroid/recommendations.py +++ b/pythonforandroid/recommendations.py @@ -17,13 +17,14 @@ def get_recommended_ndk(): pass + def check_ndk_version(ndk_dir): # Check the NDK version against what is currently recommended version = read_ndk_version(ndk_dir) if version is None: - return # it doesn't matter - + return # if we failed to read the version, just don't worry about it + major_version = version.version[0] info('Found NDK revision {}'.format(version)) @@ -37,6 +38,7 @@ def check_ndk_version(ndk_dir): RECOMMENDED_NDK_VERSION)) warning(NEW_NDK_MESSAGE) + def read_ndk_version(ndk_dir): """Read the NDK version from the NDK dir, if possible""" try: @@ -44,7 +46,7 @@ def read_ndk_version(ndk_dir): ndk_data = fileh.read() except IOError: info('Could not determine NDK version, no source.properties ' - 'in the NDK dir') + 'in the NDK dir') return for line in ndk_data.split('\n'): @@ -53,12 +55,13 @@ def read_ndk_version(ndk_dir): else: info('Could not parse $NDK_DIR/source.properties, not checking ' 'NDK version') + return # Line should have the form "Pkg.Revision = ..." ndk_version = LooseVersion(line.split('=')[-1].strip()) return ndk_version - + MIN_TARGET_API = 26 RECOMMENDED_TARGET_API = 27 # highest version tested to work fine with SDL2 @@ -69,6 +72,7 @@ def read_ndk_version(ndk_dir): 'and are not recommended. Note that the Target API can be higher than ' 'your device Android version, and should usually be as high as possible.') + def check_target_api(api, arch): """Warn if the user's target API is less than the current minimum recommendation @@ -78,7 +82,7 @@ def check_target_api(api, arch): raise BuildInterruptingException( 'Asked to build for armeabi architecture with API ' '{}, but API {} or greater does not support armeabi'.format( - self.android_api, ARMEABI_MAX_TARGET_API), + api, ARMEABI_MAX_TARGET_API), instructions='You probably want to build with --arch=armeabi-v7a instead') if api < MIN_TARGET_API: @@ -90,6 +94,7 @@ def check_target_api(api, arch): RECOMMENDED_NDK_API = 21 OLD_NDK_API_MESSAGE = ('NDK API less than {} is not supported'.format(MIN_NDK_API)) + def check_ndk_api(ndk_api, android_api): """Warn if the user's NDK is too high or low.""" if ndk_api > android_api: @@ -97,7 +102,7 @@ def check_ndk_api(ndk_api, android_api): 'Target NDK API is {}, higher than the target Android API {}.'.format( ndk_api, android_api), instructions=('The NDK API is a minimum supported API number and must be lower ' - 'than the target Android API')) + 'than the target Android API')) if ndk_api < MIN_NDK_API: warning(OLD_NDK_API_MESSAGE) From 540076197e4ab52f0ad97018673b3c665a024a20 Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Thu, 31 Jan 2019 15:24:59 +0000 Subject: [PATCH 3/4] Removed unused function --- pythonforandroid/build.py | 1 + pythonforandroid/recommendations.py | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index 4d50ff141c..6c1c401924 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -19,6 +19,7 @@ check_ndk_version, check_target_api, check_ndk_api, RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API) + class Context(object): '''A build context. If anything will be built, an instance this class will be instantiated and used to hold all the build state.''' diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py index 2fcdb842d8..3cfb79dbf4 100644 --- a/pythonforandroid/recommendations.py +++ b/pythonforandroid/recommendations.py @@ -14,10 +14,6 @@ NEW_NDK_MESSAGE = 'Newer NDKs may not be fully supported by p4a.' -def get_recommended_ndk(): - pass - - def check_ndk_version(ndk_dir): # Check the NDK version against what is currently recommended version = read_ndk_version(ndk_dir) From ac1d5d23e27b202a31a7ddd3dc4e3fa59bbb0570 Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Thu, 31 Jan 2019 15:55:11 +0000 Subject: [PATCH 4/4] Rearranged comment to keep flake8 happy I don't think this kind of change is a good one for the ci to catch, the original comment structure was fine. --- pythonforandroid/recommendations.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py index 3cfb79dbf4..fd2fd3a8be 100644 --- a/pythonforandroid/recommendations.py +++ b/pythonforandroid/recommendations.py @@ -60,8 +60,11 @@ def read_ndk_version(ndk_dir): MIN_TARGET_API = 26 -RECOMMENDED_TARGET_API = 27 # highest version tested to work fine with SDL2 - # should be a good default for other bootstraps too + +# highest version tested to work fine with SDL2 +# should be a good default for other bootstraps too +RECOMMENDED_TARGET_API = 27 + ARMEABI_MAX_TARGET_API = 21 OLD_API_MESSAGE = ( 'Target APIs lower than 26 are no longer supported on Google Play, '