diff --git a/buildozer/targets/android.py b/buildozer/targets/android.py index 29761a197..7a3ebf4e9 100644 --- a/buildozer/targets/android.py +++ b/buildozer/targets/android.py @@ -47,6 +47,12 @@ # does. DEFAULT_SDK_TAG = '4333796' +MSG_P4A_RECOMMENDED_NDK_ERROR = ( + "WARNING: Unable to find recommended android's NDK for current " + "installation of p4...so returning the recommended buildozer's one, which " + "is android's NDK r{android_ndk}".format(android_ndk=ANDROID_NDK_VERSION) +) + class TargetAndroid(Target): targetname = 'android' @@ -54,6 +60,7 @@ class TargetAndroid(Target): p4a_fork = 'kivy' p4a_branch = 'master' p4a_apk_cmd = "apk --debug --bootstrap=" + p4a_best_ndk_version = None extra_p4a_args = '' def __init__(self, *args, **kwargs): @@ -97,6 +104,47 @@ def _p4a(self, cmd, **kwargs): kwargs.setdefault('cwd', self.pa_dir) return self.buildozer.cmd(self._p4a_cmd + cmd + self.extra_p4a_args, **kwargs) + @property + def p4a_best_android_ndk(self): + """ + Return the p4a's recommended android's NDK version, depending on the + p4a version used for our buildozer build. In case that we don't find + it, we will return the buildozer's recommended one, defined by global + variable `ANDROID_NDK_VERSION`. + """ + if self.p4a_best_ndk_version is not None: + # make sure to read p4a version only the first time + return self.p4a_best_ndk_version + + if not hasattr(self, "pa_dir"): + pa_dir = join(self.buildozer.platform_dir, self.p4a_directory) + else: + pa_dir = self.pa_dir + + # check p4a's recommendation file, and in case that exists find the + # recommended android's NDK version, otherwise return buildozer's one + ndk_version = ANDROID_NDK_VERSION + rec_file = join(pa_dir, "pythonforandroid", "recommendations.py") + if not os.path.isfile(rec_file): + self.buildozer.error(MSG_P4A_RECOMMENDED_NDK_ERROR) + return ndk_version + + for line in open(rec_file, "r"): + if line.startswith("RECOMMENDED_NDK_VERSION ="): + ndk_version = line.replace( + "RECOMMENDED_NDK_VERSION =", "") + # clean version of unwanted characters + for i in {"'", '"', "\n", " "}: + ndk_version = ndk_version.replace(i, "") + self.buildozer.info( + "Recommended android's NDK version by p4a is: {}".format( + ndk_version + ) + ) + self.p4a_best_ndk_version = ndk_version + break + return ndk_version + def _sdkmanager(self, *args, **kwargs): """Call the sdkmanager in our Android SDK with the given arguments.""" # Use the android-sdk dir as cwd by default @@ -112,7 +160,7 @@ def _sdkmanager(self, *args, **kwargs): @property def android_ndk_version(self): return self.buildozer.config.getdefault('app', 'android.ndk', - ANDROID_NDK_VERSION) + self.p4a_best_android_ndk) @property def android_api(self): diff --git a/tests/test_buildozer.py b/tests/test_buildozer.py index d81076bd8..2d799a484 100644 --- a/tests/test_buildozer.py +++ b/tests/test_buildozer.py @@ -8,7 +8,9 @@ from six import StringIO import tempfile -from buildozer.targets.android import TargetAndroid +from buildozer.targets.android import ( + TargetAndroid, ANDROID_NDK_VERSION, MSG_P4A_RECOMMENDED_NDK_ERROR +) class TestBuildozer(unittest.TestCase): @@ -200,3 +202,48 @@ def test_cmd_unicode_decode(self): assert m_stdout.write.call_args_list == [ mock.call(command_output) ] + + def test_p4a_best_ndk_version_default_value(self): + self.set_specfile_log_level(self.specfile.name, 1) + buildozer = Buildozer(self.specfile.name, 'android') + assert buildozer.target.p4a_best_ndk_version is None + + def test_p4a_best_android_ndk_error(self): + self.set_specfile_log_level(self.specfile.name, 1) + buildozer = Buildozer(self.specfile.name, 'android') + + with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout: + ndk_version = buildozer.target.p4a_best_android_ndk + assert MSG_P4A_RECOMMENDED_NDK_ERROR in mock_stdout.getvalue() + # and we should get the default android's ndk version of buildozer + assert ndk_version == ANDROID_NDK_VERSION + + @mock.patch('buildozer.targets.android.os.path.isfile') + @mock.patch('buildozer.targets.android.os.path.exists') + @mock.patch('buildozer.targets.android.open') + def test_p4a_best_android_ndk_found( + self, mock_open, mock_exists, mock_isfile + ): + self.set_specfile_log_level(self.specfile.name, 1) + buildozer = Buildozer(self.specfile.name, 'android') + + expected_ndk = '19b' + mock_open.side_effect = [ + mock.mock_open( + read_data='RECOMMENDED_NDK_VERSION = {expected_ndk}\n'.format( + expected_ndk=expected_ndk) + ).return_value + ] + ndk_version = buildozer.target.p4a_best_android_ndk + pa_dir = os.path.join( + buildozer.platform_dir, buildozer.target.p4a_directory) + mock_open.assert_called_once_with( + os.path.join(pa_dir, "pythonforandroid", "recommendations.py"), 'r' + ) + assert ndk_version == expected_ndk + + # now test that we only read one time p4a file, so we call again to + # `p4a_best_android_ndk` and we should still have one call to `open` + # file, the performed above + ndk_version = buildozer.target.p4a_best_android_ndk + mock_open.assert_called_once()