From caadfb22ee50ef125a3dd327d65809afb2329b9d Mon Sep 17 00:00:00 2001 From: Mirko Galimberti Date: Sun, 5 Nov 2023 11:28:04 +0100 Subject: [PATCH] Remove distutils usage, as is not available anymore on Python 3.12 --- pythonforandroid/archs.py | 4 +- .../bootstraps/common/build/build.py | 7 +-- pythonforandroid/recipe.py | 7 ++- pythonforandroid/recommendations.py | 27 ++++----- pythonforandroid/toolchain.py | 22 +++----- pythonforandroid/util.py | 35 ++++++++++++ tests/recipes/recipe_lib_test.py | 24 ++++---- tests/recipes/test_icu.py | 12 ++-- tests/recipes/test_libgeos.py | 4 +- tests/recipes/test_libmysqlclient.py | 4 +- tests/recipes/test_libpq.py | 4 +- tests/recipes/test_libvorbis.py | 4 +- tests/recipes/test_openal.py | 12 ++-- tests/recipes/test_openssl.py | 4 +- tests/recipes/test_pandas.py | 8 +-- tests/recipes/test_pyicu.py | 8 +-- tests/recipes/test_python3.py | 12 ++-- tests/test_archs.py | 56 +++++++++---------- tests/test_bootstrap.py | 11 ++-- tests/test_build.py | 2 +- tests/test_recipe.py | 24 ++++++-- tests/test_recommendations.py | 11 +++- tests/test_util.py | 43 ++++++++++++++ 23 files changed, 216 insertions(+), 129 deletions(-) diff --git a/pythonforandroid/archs.py b/pythonforandroid/archs.py index b960ca6b4d..b065174592 100644 --- a/pythonforandroid/archs.py +++ b/pythonforandroid/archs.py @@ -1,7 +1,7 @@ -from distutils.spawn import find_executable from os import environ from os.path import join from multiprocessing import cpu_count +import shutil from pythonforandroid.recipe import Recipe from pythonforandroid.util import BuildInterruptingException, build_platform @@ -172,7 +172,7 @@ def get_env(self, with_flags_in_cc=True): # Compiler: `CC` and `CXX` (and make sure that the compiler exists) env['PATH'] = self.ctx.env['PATH'] - cc = find_executable(self.clang_exe, path=env['PATH']) + cc = shutil.which(self.clang_exe, path=env['PATH']) if cc is None: print('Searching path are: {!r}'.format(env['PATH'])) raise BuildInterruptingException( diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py index 0b6b9832f0..59c38b609c 100644 --- a/pythonforandroid/bootstraps/common/build/build.py +++ b/pythonforandroid/bootstraps/common/build/build.py @@ -17,11 +17,10 @@ import tempfile import time -from distutils.version import LooseVersion from fnmatch import fnmatch import jinja2 -from pythonforandroid.util import rmdir, ensure_dir +from pythonforandroid.util import rmdir, ensure_dir, max_build_tool_version def get_dist_info_for(key, error_if_missing=True): @@ -512,9 +511,7 @@ def make_package(args): # Try to build with the newest available build tools ignored = {".DS_Store", ".ds_store"} build_tools_versions = [x for x in listdir(join(sdk_dir, 'build-tools')) if x not in ignored] - build_tools_versions = sorted(build_tools_versions, - key=LooseVersion) - build_tools_version = build_tools_versions[-1] + build_tools_version = max_build_tool_version(build_tools_versions) # Folder name for launcher (used by SDL2 bootstrap) url_scheme = 'kivy' diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index fa0bb4e790..dc53fd4337 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -16,6 +16,9 @@ from urlparse import urlparse except ImportError: from urllib.parse import urlparse + +import packaging.version + from pythonforandroid.logger import ( logger, info, warning, debug, shprint, info_main) from pythonforandroid.util import ( @@ -1145,8 +1148,8 @@ def link_root(self): @property def major_minor_version_string(self): - from distutils.version import LooseVersion - return '.'.join([str(v) for v in LooseVersion(self.version).version[:2]]) + parsed_version = packaging.version.parse(self.version) + return f"{parsed_version.major}.{parsed_version.minor}" def create_python_bundle(self, dirn, arch): """ diff --git a/pythonforandroid/recommendations.py b/pythonforandroid/recommendations.py index 040c96234a..cbcfdd2b6e 100644 --- a/pythonforandroid/recommendations.py +++ b/pythonforandroid/recommendations.py @@ -1,9 +1,10 @@ """Simple functions for checking dependency versions.""" import sys -from distutils.version import LooseVersion from os.path import join +import packaging.version + from pythonforandroid.logger import info, warning from pythonforandroid.util import BuildInterruptingException @@ -59,9 +60,9 @@ def check_ndk_version(ndk_dir): rewrote to raise an exception in case that an NDK version lower than the minimum supported is detected. """ - version = read_ndk_version(ndk_dir) + ndk_version = read_ndk_version(ndk_dir) - if version is None: + if ndk_version is None: warning(READ_ERROR_NDK_MESSAGE.format(ndk_dir=ndk_dir)) warning( ENSURE_RIGHT_NDK_MESSAGE.format( @@ -81,16 +82,11 @@ def check_ndk_version(ndk_dir): minor_to_letter.update( {n + 1: chr(i) for n, i in enumerate(range(ord('b'), ord('b') + 25))} ) - - major_version = version.version[0] - letter_version = minor_to_letter[version.version[1]] - string_version = '{major_version}{letter_version}'.format( - major_version=major_version, letter_version=letter_version - ) + string_version = f"{ndk_version.major}{minor_to_letter[ndk_version.minor]}" info(CURRENT_NDK_VERSION_MESSAGE.format(ndk_version=string_version)) - if major_version < MIN_NDK_VERSION: + if ndk_version.major < MIN_NDK_VERSION: raise BuildInterruptingException( NDK_LOWER_THAN_SUPPORTED_MESSAGE.format( min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL @@ -104,7 +100,7 @@ def check_ndk_version(ndk_dir): ) ), ) - elif major_version > MAX_NDK_VERSION: + elif ndk_version.major > MAX_NDK_VERSION: warning( RECOMMENDED_NDK_VERSION_MESSAGE.format( recommended_ndk_version=RECOMMENDED_NDK_VERSION @@ -130,9 +126,9 @@ def read_ndk_version(ndk_dir): return # Line should have the form "Pkg.Revision = ..." - ndk_version = LooseVersion(line.split('=')[-1].strip()) + unparsed_ndk_version = line.split('=')[-1].strip() - return ndk_version + return packaging.version.parse(unparsed_ndk_version) MIN_TARGET_API = 30 @@ -191,8 +187,9 @@ def check_ndk_api(ndk_api, android_api): MIN_PYTHON_MAJOR_VERSION = 3 MIN_PYTHON_MINOR_VERSION = 6 -MIN_PYTHON_VERSION = LooseVersion('{major}.{minor}'.format(major=MIN_PYTHON_MAJOR_VERSION, - minor=MIN_PYTHON_MINOR_VERSION)) +MIN_PYTHON_VERSION = packaging.version.Version( + f"{MIN_PYTHON_MAJOR_VERSION}.{MIN_PYTHON_MINOR_VERSION}" +) PY2_ERROR_TEXT = ( 'python-for-android no longer supports running under Python 2. Either upgrade to ' 'Python {min_version} or higher (recommended), or revert to python-for-android 2019.07.08.' diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index 7a5461f30d..1347038b8b 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -24,7 +24,7 @@ from pythonforandroid.checkdependencies import check check() -from packaging.version import Version, InvalidVersion +from packaging.version import Version import sh from pythonforandroid import __version__ @@ -41,7 +41,12 @@ from pythonforandroid.recommendations import ( RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations) from pythonforandroid.util import ( - current_directory, BuildInterruptingException, load_source, rmdir) + current_directory, + BuildInterruptingException, + load_source, + rmdir, + max_build_tool_version, +) user_dir = dirname(realpath(os.path.curdir)) toolchain_dir = dirname(__file__) @@ -1009,18 +1014,7 @@ def _build_package(self, args, package_type): self.hook("before_apk_assemble") build_tools_versions = os.listdir(join(ctx.sdk_dir, 'build-tools')) - - def sort_key(version_text): - try: - # Historically, Android build release candidates have had - # spaces in the version number. - return Version(version_text.replace(" ", "")) - except InvalidVersion: - # Put badly named versions at worst position. - return Version("0") - - build_tools_versions.sort(key=sort_key) - build_tools_version = build_tools_versions[-1] + build_tools_version = max_build_tool_version(build_tools_versions) info(('Detected highest available build tools ' 'version to be {}').format(build_tools_version)) diff --git a/pythonforandroid/util.py b/pythonforandroid/util.py index af363b2e3f..2738d59990 100644 --- a/pythonforandroid/util.py +++ b/pythonforandroid/util.py @@ -8,6 +8,8 @@ import shutil from tempfile import mkdtemp +import packaging.version + from pythonforandroid.logger import (logger, Err_Fore, error, info) LOGGER = logging.getLogger("p4a.util") @@ -128,3 +130,36 @@ def move(source, destination): def touch(filename): Path(filename).touch() + + +def build_tools_version_sort_key( + version_string: str, +) -> packaging.version.Version: + """ + Returns a packaging.version.Version object for comparison purposes. + It includes canonicalization of the version string to allow for + comparison of versions with spaces in them (historically, RC candidates) + + If the version string is invalid, it returns a version object with + version 0, which will be sorted at worst position. + """ + + try: + # Historically, Android build release candidates have had + # spaces in the version number. + return packaging.version.Version(version_string.replace(" ", "")) + except packaging.version.InvalidVersion: + # Put badly named versions at worst position. + return packaging.version.Version("0") + + +def max_build_tool_version( + build_tools_versions: list, +) -> str: + """ + Returns the maximum build tools version from a list of build tools + versions. It uses the :meth:`build_tools_version_sort_key` function to + canonicalize the version strings and then returns the maximum version. + """ + + return max(build_tools_versions, key=build_tools_version_sort_key) diff --git a/tests/recipes/recipe_lib_test.py b/tests/recipes/recipe_lib_test.py index d1b058206e..1f57fe23c1 100644 --- a/tests/recipes/recipe_lib_test.py +++ b/tests/recipes/recipe_lib_test.py @@ -35,10 +35,10 @@ def __init__(self, *args, **kwargs): @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_get_recipe_env( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_check_recipe_choices, ): @@ -46,7 +46,7 @@ def test_get_recipe_env( Test that get_recipe_env contains some expected arch flags and that some internal methods has been called. """ - mock_find_executable.return_value = self.expected_compiler.format( + mock_shutil_which.return_value = self.expected_compiler.format( android_ndk=self.ctx._ndk_dir, system=system().lower() ) mock_check_recipe_choices.return_value = sorted( @@ -67,19 +67,19 @@ def test_get_recipe_env( # make sure that the mocked methods are actually called mock_ensure_dir.assert_called() - mock_find_executable.assert_called() + mock_shutil_which.assert_called() mock_check_recipe_choices.assert_called() @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, ): - mock_find_executable.return_value = self.expected_compiler.format( + mock_shutil_which.return_value = self.expected_compiler.format( android_ndk=self.ctx._ndk_dir, system=system().lower() ) @@ -101,7 +101,7 @@ def test_build_arch( mock_make.assert_called() mock_ensure_dir.assert_called() mock_current_directory.assert_called() - mock_find_executable.assert_called() + mock_shutil_which.assert_called() class BaseTestForCmakeRecipe(BaseTestForMakeRecipe): @@ -116,14 +116,14 @@ class BaseTestForCmakeRecipe(BaseTestForMakeRecipe): @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, ): - mock_find_executable.return_value = self.expected_compiler.format( + mock_shutil_which.return_value = self.expected_compiler.format( android_ndk=self.ctx._ndk_dir, system=system().lower() ) @@ -141,4 +141,4 @@ def test_build_arch( mock_make.assert_called() mock_ensure_dir.assert_called() mock_current_directory.assert_called() - mock_find_executable.assert_called() + mock_shutil_which.assert_called() diff --git a/tests/recipes/test_icu.py b/tests/recipes/test_icu.py index b928b99bf5..239b99e4c1 100644 --- a/tests/recipes/test_icu.py +++ b/tests/recipes/test_icu.py @@ -33,17 +33,17 @@ def test_get_recipe_dir(self): @mock.patch("pythonforandroid.bootstrap.sh.Command") @mock.patch("pythonforandroid.recipes.icu.sh.make") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_sh_make, mock_sh_command, mock_chdir, mock_makedirs, ): - mock_find_executable.return_value = os.path.join( + mock_shutil_which.return_value = os.path.join( self.ctx._ndk_dir, f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang", ) @@ -89,10 +89,10 @@ def test_build_arch( ) mock_makedirs.assert_called() - mock_find_executable.assert_called_once() + mock_shutil_which.assert_called_once() self.assertEqual( - mock_find_executable.call_args[0][0], - mock_find_executable.return_value, + mock_shutil_which.call_args[0][0], + mock_shutil_which.return_value, ) @mock.patch("pythonforandroid.recipes.icu.sh.cp") diff --git a/tests/recipes/test_libgeos.py b/tests/recipes/test_libgeos.py index d819825294..7a8b4258dc 100644 --- a/tests/recipes/test_libgeos.py +++ b/tests/recipes/test_libgeos.py @@ -12,10 +12,10 @@ class TestLibgeosRecipe(BaseTestForCmakeRecipe, unittest.TestCase): @mock.patch("pythonforandroid.util.makedirs") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_makedirs, diff --git a/tests/recipes/test_libmysqlclient.py b/tests/recipes/test_libmysqlclient.py index e484393398..4c85dc92e2 100644 --- a/tests/recipes/test_libmysqlclient.py +++ b/tests/recipes/test_libmysqlclient.py @@ -13,10 +13,10 @@ class TestLibmysqlclientRecipe(BaseTestForCmakeRecipe, unittest.TestCase): @mock.patch("pythonforandroid.recipes.libmysqlclient.sh.cp") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_sh_cp, diff --git a/tests/recipes/test_libpq.py b/tests/recipes/test_libpq.py index c4ae38a886..5e2f9f3d61 100644 --- a/tests/recipes/test_libpq.py +++ b/tests/recipes/test_libpq.py @@ -13,10 +13,10 @@ class TestLibpqRecipe(BaseTestForMakeRecipe, unittest.TestCase): @mock.patch("pythonforandroid.recipes.libpq.sh.cp") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_sh_cp, diff --git a/tests/recipes/test_libvorbis.py b/tests/recipes/test_libvorbis.py index 663c1ccfc1..d8aed7b728 100644 --- a/tests/recipes/test_libvorbis.py +++ b/tests/recipes/test_libvorbis.py @@ -14,10 +14,10 @@ class TestLibvorbisRecipe(BaseTestForMakeRecipe, unittest.TestCase): @mock.patch("pythonforandroid.recipes.libvorbis.sh.cp") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_sh_cp, diff --git a/tests/recipes/test_openal.py b/tests/recipes/test_openal.py index 21f3196798..a03d5cd271 100644 --- a/tests/recipes/test_openal.py +++ b/tests/recipes/test_openal.py @@ -14,17 +14,17 @@ class TestOpenalRecipe(BaseTestForCmakeRecipe, unittest.TestCase): @mock.patch("pythonforandroid.recipes.openal.sh.cp") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_prebuild_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_sh_cp, mock_sh_make, mock_sh_cmake, ): - mock_find_executable.return_value = ( + mock_shutil_which.return_value = ( "/opt/android/android-ndk/toolchains/" "llvm/prebuilt/linux-x86_64/bin/clang" ) @@ -33,7 +33,7 @@ def test_prebuild_arch( # make sure that the mocked methods are actually called mock_ensure_dir.assert_called() mock_current_directory.assert_called() - mock_find_executable.assert_called() + mock_shutil_which.assert_called() mock_sh_cp.assert_called() mock_sh_make.assert_called() mock_sh_cmake.assert_called() @@ -41,10 +41,10 @@ def test_prebuild_arch( @mock.patch("pythonforandroid.recipes.openal.sh.cp") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_sh_cp, diff --git a/tests/recipes/test_openssl.py b/tests/recipes/test_openssl.py index 861e73bd39..f7ed362f68 100644 --- a/tests/recipes/test_openssl.py +++ b/tests/recipes/test_openssl.py @@ -14,10 +14,10 @@ class TestOpensslRecipe(BaseTestForMakeRecipe, unittest.TestCase): @mock.patch("pythonforandroid.recipes.openssl.sh.patch") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_current_directory, mock_sh_patch, diff --git a/tests/recipes/test_pandas.py b/tests/recipes/test_pandas.py index 410c2c43c0..b8366863fe 100644 --- a/tests/recipes/test_pandas.py +++ b/tests/recipes/test_pandas.py @@ -14,10 +14,10 @@ class TestPandasRecipe(RecipeCtx, unittest.TestCase): @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_get_recipe_env( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_check_recipe_choices, ): @@ -27,7 +27,7 @@ def test_get_recipe_env( returns the expected flags """ - mock_find_executable.return_value = ( + mock_shutil_which.return_value = ( "/opt/android/android-ndk/toolchains/" "llvm/prebuilt/linux-x86_64/bin/clang" ) @@ -43,5 +43,5 @@ def test_get_recipe_env( # make sure that the mocked methods are actually called mock_ensure_dir.assert_called() - mock_find_executable.assert_called() + mock_shutil_which.assert_called() mock_check_recipe_choices.assert_called() diff --git a/tests/recipes/test_pyicu.py b/tests/recipes/test_pyicu.py index 8720a5f4d7..5babfbc114 100644 --- a/tests/recipes/test_pyicu.py +++ b/tests/recipes/test_pyicu.py @@ -12,10 +12,10 @@ class TestPyIcuRecipe(RecipeCtx, unittest.TestCase): @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_get_recipe_env( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_check_recipe_choices, ): @@ -26,7 +26,7 @@ def test_get_recipe_env( """ icu_recipe = Recipe.get_recipe("icu", self.ctx) - mock_find_executable.return_value = ( + mock_shutil_which.return_value = ( "/opt/android/android-ndk/toolchains/" "llvm/prebuilt/linux-x86_64/bin/clang" ) @@ -44,5 +44,5 @@ def test_get_recipe_env( # make sure that the mocked methods are actually called mock_ensure_dir.assert_called() - mock_find_executable.assert_called() + mock_shutil_which.assert_called() mock_check_recipe_choices.assert_called() diff --git a/tests/recipes/test_python3.py b/tests/recipes/test_python3.py index f22b2ffdb2..f1d652b6c1 100644 --- a/tests/recipes/test_python3.py +++ b/tests/recipes/test_python3.py @@ -60,10 +60,10 @@ def test_compile_python_files(self, mock_subprocess): ) @mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_get_recipe_env( self, - mock_find_executable, + mock_shutil_which, mock_check_recipe_choices, ): """ @@ -71,7 +71,7 @@ def test_get_recipe_env( :meth:`~pythonforandroid.recipes.python3.Python3Recipe.get_recipe_env` returns the expected flags """ - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler mock_check_recipe_choices.return_value = sorted( self.ctx.recipe_build_order ) @@ -91,13 +91,13 @@ def test_set_libs_flags(self): # and `set_libs_flags`, since these calls are tested separately @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.util.makedirs") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_build_arch( self, - mock_find_executable, + mock_shutil_which, mock_makedirs, mock_chdir): - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler # specific `build_arch` mocks with mock.patch( diff --git a/tests/test_archs.py b/tests/test_archs.py index 44ff6237ba..58530886ff 100644 --- a/tests/test_archs.py +++ b/tests/test_archs.py @@ -92,9 +92,9 @@ class TestArchARM(ArchSetUpBaseClass, unittest.TestCase): will be used to perform tests for :class:`~pythonforandroid.archs.ArchARM`. """ - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") @mock.patch("pythonforandroid.build.ensure_dir") - def test_arch_arm(self, mock_ensure_dir, mock_find_executable): + def test_arch_arm(self, mock_ensure_dir, mock_shutil_which): """ Test that class :class:`~pythonforandroid.archs.ArchARM` returns some expected attributes and environment variables. @@ -103,13 +103,13 @@ def test_arch_arm(self, mock_ensure_dir, mock_find_executable): Here we mock two methods: - `ensure_dir` because we don't want to create any directory - - `find_executable` because otherwise we will + - `shutil.which` because otherwise we will get an error when trying to find the compiler (we are setting some fake paths for our android sdk and ndk so probably will not exist) """ - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler mock_ensure_dir.return_value = True arch = ArchARM(self.ctx) @@ -126,8 +126,8 @@ def test_arch_arm(self, mock_ensure_dir, mock_find_executable): expected_env_gcc_keys, set(env.keys()) & expected_env_gcc_keys ) - # check find_executable calls - mock_find_executable.assert_called_once_with( + # check shutil.which calls + mock_shutil_which.assert_called_once_with( self.expected_compiler, path=environ["PATH"] ) @@ -163,7 +163,7 @@ def test_arch_arm(self, mock_ensure_dir, mock_find_executable): self.assertEqual(env["NDK_CCACHE"], "/usr/bin/ccache") # Check exception in case that CC is not found - mock_find_executable.return_value = None + mock_shutil_which.return_value = None with self.assertRaises(BuildInterruptingException) as e: arch.get_env() self.assertEqual( @@ -182,10 +182,10 @@ class TestArchARMv7a(ArchSetUpBaseClass, unittest.TestCase): :class:`~pythonforandroid.archs.ArchARMv7_a`. """ - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") @mock.patch("pythonforandroid.build.ensure_dir") def test_arch_armv7a( - self, mock_ensure_dir, mock_find_executable + self, mock_ensure_dir, mock_shutil_which ): """ Test that class :class:`~pythonforandroid.archs.ArchARMv7_a` returns @@ -197,7 +197,7 @@ def test_arch_armv7a( This has to be done because here we tests the `get_env` with clang """ - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler mock_ensure_dir.return_value = True arch = ArchARMv7_a(self.ctx) @@ -207,8 +207,8 @@ def test_arch_armv7a( self.assertEqual(arch.target, "armv7a-linux-androideabi21") env = arch.get_env() - # check find_executable calls - mock_find_executable.assert_called_once_with( + # check shutil.which calls + mock_shutil_which.assert_called_once_with( self.expected_compiler, path=environ["PATH"] ) @@ -241,9 +241,9 @@ class TestArchX86(ArchSetUpBaseClass, unittest.TestCase): will be used to perform tests for :class:`~pythonforandroid.archs.Archx86`. """ - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") @mock.patch("pythonforandroid.build.ensure_dir") - def test_arch_x86(self, mock_ensure_dir, mock_find_executable): + def test_arch_x86(self, mock_ensure_dir, mock_shutil_which): """ Test that class :class:`~pythonforandroid.archs.Archx86` returns some expected attributes and environment variables. @@ -255,7 +255,7 @@ def test_arch_x86(self, mock_ensure_dir, mock_find_executable): which is probably the case. This has to be done because here we tests the `get_env` with clang """ - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler mock_ensure_dir.return_value = True arch = Archx86(self.ctx) @@ -265,8 +265,8 @@ def test_arch_x86(self, mock_ensure_dir, mock_find_executable): self.assertEqual(arch.target, "i686-linux-android21") env = arch.get_env() - # check find_executable calls - mock_find_executable.assert_called_once_with( + # check shutil.which calls + mock_shutil_which.assert_called_once_with( self.expected_compiler, path=environ["PATH"] ) @@ -284,10 +284,10 @@ class TestArchX86_64(ArchSetUpBaseClass, unittest.TestCase): :class:`~pythonforandroid.archs.Archx86_64`. """ - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") @mock.patch("pythonforandroid.build.ensure_dir") def test_arch_x86_64( - self, mock_ensure_dir, mock_find_executable + self, mock_ensure_dir, mock_shutil_which ): """ Test that class :class:`~pythonforandroid.archs.Archx86_64` returns @@ -300,7 +300,7 @@ def test_arch_x86_64( which is probably the case. This has to be done because here we tests the `get_env` with clang """ - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler mock_ensure_dir.return_value = True arch = Archx86_64(self.ctx) @@ -310,13 +310,13 @@ def test_arch_x86_64( self.assertEqual(arch.target, "x86_64-linux-android21") env = arch.get_env() - # check find_executable calls - mock_find_executable.assert_called_once_with( + # check shutil.which calls + mock_shutil_which.assert_called_once_with( self.expected_compiler, path=environ["PATH"] ) # For x86_64 we expect some extra cflags in our `environment` - mock_find_executable.assert_called_once() + mock_shutil_which.assert_called_once() self.assertIn( " -march=x86-64 -msse4.2 -mpopcnt -m64", env["CFLAGS"] ) @@ -329,10 +329,10 @@ class TestArchAArch64(ArchSetUpBaseClass, unittest.TestCase): :class:`~pythonforandroid.archs.ArchAarch_64`. """ - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") @mock.patch("pythonforandroid.build.ensure_dir") def test_arch_aarch_64( - self, mock_ensure_dir, mock_find_executable + self, mock_ensure_dir, mock_shutil_which ): """ Test that class :class:`~pythonforandroid.archs.ArchAarch_64` returns @@ -345,7 +345,7 @@ def test_arch_aarch_64( which is probably the case. This has to be done because here we tests the `get_env` with clang """ - mock_find_executable.return_value = self.expected_compiler + mock_shutil_which.return_value = self.expected_compiler mock_ensure_dir.return_value = True arch = ArchAarch_64(self.ctx) @@ -355,8 +355,8 @@ def test_arch_aarch_64( self.assertEqual(arch.target, "aarch64-linux-android21") env = arch.get_env() - # check find_executable calls - mock_find_executable.assert_called_once_with( + # check shutil.which calls + mock_shutil_which.assert_called_once_with( self.expected_compiler, path=environ["PATH"] ) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 99620fee75..1e17a9bd18 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -523,15 +523,15 @@ def reset_mocks(): @mock.patch("pythonforandroid.bootstrap.shprint") @mock.patch("pythonforandroid.bootstrap.sh.Command") @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("pythonforandroid.archs.find_executable") + @mock.patch("shutil.which") def test_bootstrap_strip( self, - mock_find_executable, + mock_shutil_which, mock_ensure_dir, mock_sh_command, mock_sh_print, ): - mock_find_executable.return_value = os.path.join( + mock_shutil_which.return_value = os.path.join( self.ctx._ndk_dir, f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang", ) @@ -544,10 +544,9 @@ def test_bootstrap_strip( # test that strip_libraries runs with a fake distribution bs.strip_libraries(arch) - mock_find_executable.assert_called_once() self.assertEqual( - mock_find_executable.call_args[0][0], - mock_find_executable.return_value, + mock_shutil_which.call_args[0][0], + mock_shutil_which.return_value, ) mock_sh_command.assert_called_once_with( os.path.join( diff --git a/tests/test_build.py b/tests/test_build.py index cf9fa7801d..7556d68bbc 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -28,7 +28,7 @@ def test_run_pymodules_install_optional_project_dir(self): def test_strip_if_with_debug_symbols(self): ctx = mock.Mock() - ctx.python_recipe.major_minor_version_string = "python3.6" + ctx.python_recipe.major_minor_version_string = "3.6" ctx.get_site_packages_dir.return_value = "test-doesntexist" ctx.build_dir = "nonexistant_directory" ctx.archs = ["arm64"] diff --git a/tests/test_recipe.py b/tests/test_recipe.py index b6e4c99225..e60db55c81 100644 --- a/tests/test_recipe.py +++ b/tests/test_recipe.py @@ -7,7 +7,7 @@ from backports import tempfile from pythonforandroid.build import Context -from pythonforandroid.recipe import Recipe, import_recipe +from pythonforandroid.recipe import Recipe, TargetPythonRecipe, import_recipe from pythonforandroid.archs import ArchAarch_64 from pythonforandroid.bootstrap import Bootstrap from test_bootstrap import BaseClassSetupBootstrap @@ -182,6 +182,20 @@ def test_download_file_scheme_https_oserror(self): assert m_sleep.call_args_list == expected_call_args_list +class TestTargetPythonRecipe(unittest.TestCase): + + def test_major_minor_version_string(self): + """ + Test that the major_minor_version_string property returns the correct + string. + """ + class DummyTargetPythonRecipe(TargetPythonRecipe): + version = '1.2.3' + + recipe = DummyTargetPythonRecipe() + assert recipe.major_minor_version_string == '1.2' + + class TestLibraryRecipe(BaseClassSetupBootstrap, unittest.TestCase): def setUp(self): """ @@ -249,10 +263,10 @@ def setUp(self): self.setUp_distribution_with_bootstrap(self.ctx.bootstrap) self.ctx.python_recipe = Recipe.get_recipe('python3', self.ctx) - @mock.patch('pythonforandroid.archs.find_executable') + @mock.patch('shutil.which') @mock.patch('pythonforandroid.build.ensure_dir') def test_get_recipe_env_with( - self, mock_ensure_dir, mock_find_executable + self, mock_ensure_dir, mock_shutil_which ): """ Test that :meth:`~pythonforandroid.recipe.STLRecipe.get_recipe_env` @@ -266,7 +280,7 @@ def test_get_recipe_env_with( f"/opt/android/android-ndk/toolchains/" f"llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang" ) - mock_find_executable.return_value = expected_compiler + mock_shutil_which.return_value = expected_compiler arch = ArchAarch_64(self.ctx) recipe = Recipe.get_recipe('libgeos', self.ctx) @@ -274,7 +288,7 @@ def test_get_recipe_env_with( env = recipe.get_recipe_env(arch) # check that the mocks have been called mock_ensure_dir.assert_called() - mock_find_executable.assert_called_once_with( + mock_shutil_which.assert_called_once_with( expected_compiler, path=self.ctx.env['PATH'] ) self.assertIsInstance(env, dict) diff --git a/tests/test_recommendations.py b/tests/test_recommendations.py index df68bbe3e0..443ec52b39 100644 --- a/tests/test_recommendations.py +++ b/tests/test_recommendations.py @@ -2,6 +2,7 @@ from os.path import join from sys import version as py_version +import packaging.version from unittest import mock from pythonforandroid.recommendations import ( check_ndk_api, @@ -53,7 +54,8 @@ def setUp(self): @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+") @mock.patch("pythonforandroid.recommendations.read_ndk_version") def test_check_ndk_version_greater_than_recommended(self, mock_read_ndk): - mock_read_ndk.return_value.version = [MAX_NDK_VERSION + 1, 0, 5232133] + _version_string = f"{MIN_NDK_VERSION + 1}.0.5232133" + mock_read_ndk.return_value = packaging.version.Version(_version_string) with self.assertLogs(level="INFO") as cm: check_ndk_version(self.ndk_dir) mock_read_ndk.assert_called_once_with(self.ndk_dir) @@ -76,7 +78,8 @@ def test_check_ndk_version_greater_than_recommended(self, mock_read_ndk): @mock.patch("pythonforandroid.recommendations.read_ndk_version") def test_check_ndk_version_lower_than_recommended(self, mock_read_ndk): - mock_read_ndk.return_value.version = [MIN_NDK_VERSION - 1, 0, 5232133] + _version_string = f"{MIN_NDK_VERSION - 1}.0.5232133" + mock_read_ndk.return_value = packaging.version.Version(_version_string) with self.assertRaises(BuildInterruptingException) as e: check_ndk_version(self.ndk_dir) self.assertEqual( @@ -124,7 +127,9 @@ def test_read_ndk_version(self, mock_open_src_prop): mock_open_src_prop.assert_called_once_with( join(self.ndk_dir, "source.properties") ) - assert version == "17.2.4988734" + assert version.major == 17 + assert version.minor == 2 + assert version.micro == 4988734 @unittest.skipIf(running_in_py2, "`assertLogs` requires Python 3.4+") @mock.patch("pythonforandroid.recommendations.open") diff --git a/tests/test_util.py b/tests/test_util.py index a4d7ea816e..7a60bc73fb 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -187,3 +187,46 @@ def test_touch(self): assert not new_file_path.exists() util.touch(new_file_path) assert new_file_path.exists() + + def test_build_tools_version_sort_key(self): + + build_tools_versions = [ + "26.0.1", + "26.0.0", + "26.0.2", + "32.0.0 rc1", + "31.0.0", + "999something", + ] + + expected_result = [ + "999something", # invalid version + "26.0.0", + "26.0.1", + "26.0.2", + "31.0.0", + "32.0.0 rc1", + ] + + result = sorted( + build_tools_versions, key=util.build_tools_version_sort_key + ) + + self.assertEqual(result, expected_result) + + def test_max_build_tool_version(self): + + build_tools_versions = [ + "26.0.1", + "26.0.0", + "26.0.2", + "32.0.0 rc1", + "31.0.0", + "999something", + ] + + expected_result = "32.0.0 rc1" + + result = util.max_build_tool_version(build_tools_versions) + + self.assertEqual(result, expected_result)