Skip to content

Commit caadfb2

Browse files
committed
Remove distutils usage, as is not available anymore on Python 3.12
1 parent 0be5572 commit caadfb2

23 files changed

+216
-129
lines changed

pythonforandroid/archs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from distutils.spawn import find_executable
21
from os import environ
32
from os.path import join
43
from multiprocessing import cpu_count
4+
import shutil
55

66
from pythonforandroid.recipe import Recipe
77
from pythonforandroid.util import BuildInterruptingException, build_platform
@@ -172,7 +172,7 @@ def get_env(self, with_flags_in_cc=True):
172172

173173
# Compiler: `CC` and `CXX` (and make sure that the compiler exists)
174174
env['PATH'] = self.ctx.env['PATH']
175-
cc = find_executable(self.clang_exe, path=env['PATH'])
175+
cc = shutil.which(self.clang_exe, path=env['PATH'])
176176
if cc is None:
177177
print('Searching path are: {!r}'.format(env['PATH']))
178178
raise BuildInterruptingException(

pythonforandroid/bootstraps/common/build/build.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
import tempfile
1818
import time
1919

20-
from distutils.version import LooseVersion
2120
from fnmatch import fnmatch
2221
import jinja2
2322

24-
from pythonforandroid.util import rmdir, ensure_dir
23+
from pythonforandroid.util import rmdir, ensure_dir, max_build_tool_version
2524

2625

2726
def get_dist_info_for(key, error_if_missing=True):
@@ -512,9 +511,7 @@ def make_package(args):
512511
# Try to build with the newest available build tools
513512
ignored = {".DS_Store", ".ds_store"}
514513
build_tools_versions = [x for x in listdir(join(sdk_dir, 'build-tools')) if x not in ignored]
515-
build_tools_versions = sorted(build_tools_versions,
516-
key=LooseVersion)
517-
build_tools_version = build_tools_versions[-1]
514+
build_tools_version = max_build_tool_version(build_tools_versions)
518515

519516
# Folder name for launcher (used by SDL2 bootstrap)
520517
url_scheme = 'kivy'

pythonforandroid/recipe.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
from urlparse import urlparse
1717
except ImportError:
1818
from urllib.parse import urlparse
19+
20+
import packaging.version
21+
1922
from pythonforandroid.logger import (
2023
logger, info, warning, debug, shprint, info_main)
2124
from pythonforandroid.util import (
@@ -1145,8 +1148,8 @@ def link_root(self):
11451148

11461149
@property
11471150
def major_minor_version_string(self):
1148-
from distutils.version import LooseVersion
1149-
return '.'.join([str(v) for v in LooseVersion(self.version).version[:2]])
1151+
parsed_version = packaging.version.parse(self.version)
1152+
return f"{parsed_version.major}.{parsed_version.minor}"
11501153

11511154
def create_python_bundle(self, dirn, arch):
11521155
"""

pythonforandroid/recommendations.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Simple functions for checking dependency versions."""
22

33
import sys
4-
from distutils.version import LooseVersion
54
from os.path import join
65

6+
import packaging.version
7+
78
from pythonforandroid.logger import info, warning
89
from pythonforandroid.util import BuildInterruptingException
910

@@ -59,9 +60,9 @@ def check_ndk_version(ndk_dir):
5960
rewrote to raise an exception in case that an NDK version lower than
6061
the minimum supported is detected.
6162
"""
62-
version = read_ndk_version(ndk_dir)
63+
ndk_version = read_ndk_version(ndk_dir)
6364

64-
if version is None:
65+
if ndk_version is None:
6566
warning(READ_ERROR_NDK_MESSAGE.format(ndk_dir=ndk_dir))
6667
warning(
6768
ENSURE_RIGHT_NDK_MESSAGE.format(
@@ -81,16 +82,11 @@ def check_ndk_version(ndk_dir):
8182
minor_to_letter.update(
8283
{n + 1: chr(i) for n, i in enumerate(range(ord('b'), ord('b') + 25))}
8384
)
84-
85-
major_version = version.version[0]
86-
letter_version = minor_to_letter[version.version[1]]
87-
string_version = '{major_version}{letter_version}'.format(
88-
major_version=major_version, letter_version=letter_version
89-
)
85+
string_version = f"{ndk_version.major}{minor_to_letter[ndk_version.minor]}"
9086

9187
info(CURRENT_NDK_VERSION_MESSAGE.format(ndk_version=string_version))
9288

93-
if major_version < MIN_NDK_VERSION:
89+
if ndk_version.major < MIN_NDK_VERSION:
9490
raise BuildInterruptingException(
9591
NDK_LOWER_THAN_SUPPORTED_MESSAGE.format(
9692
min_supported=MIN_NDK_VERSION, ndk_url=NDK_DOWNLOAD_URL
@@ -104,7 +100,7 @@ def check_ndk_version(ndk_dir):
104100
)
105101
),
106102
)
107-
elif major_version > MAX_NDK_VERSION:
103+
elif ndk_version.major > MAX_NDK_VERSION:
108104
warning(
109105
RECOMMENDED_NDK_VERSION_MESSAGE.format(
110106
recommended_ndk_version=RECOMMENDED_NDK_VERSION
@@ -130,9 +126,9 @@ def read_ndk_version(ndk_dir):
130126
return
131127

132128
# Line should have the form "Pkg.Revision = ..."
133-
ndk_version = LooseVersion(line.split('=')[-1].strip())
129+
unparsed_ndk_version = line.split('=')[-1].strip()
134130

135-
return ndk_version
131+
return packaging.version.parse(unparsed_ndk_version)
136132

137133

138134
MIN_TARGET_API = 30
@@ -191,8 +187,9 @@ def check_ndk_api(ndk_api, android_api):
191187

192188
MIN_PYTHON_MAJOR_VERSION = 3
193189
MIN_PYTHON_MINOR_VERSION = 6
194-
MIN_PYTHON_VERSION = LooseVersion('{major}.{minor}'.format(major=MIN_PYTHON_MAJOR_VERSION,
195-
minor=MIN_PYTHON_MINOR_VERSION))
190+
MIN_PYTHON_VERSION = packaging.version.Version(
191+
f"{MIN_PYTHON_MAJOR_VERSION}.{MIN_PYTHON_MINOR_VERSION}"
192+
)
196193
PY2_ERROR_TEXT = (
197194
'python-for-android no longer supports running under Python 2. Either upgrade to '
198195
'Python {min_version} or higher (recommended), or revert to python-for-android 2019.07.08.'

pythonforandroid/toolchain.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from pythonforandroid.checkdependencies import check
2525
check()
2626

27-
from packaging.version import Version, InvalidVersion
27+
from packaging.version import Version
2828
import sh
2929

3030
from pythonforandroid import __version__
@@ -41,7 +41,12 @@
4141
from pythonforandroid.recommendations import (
4242
RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations)
4343
from pythonforandroid.util import (
44-
current_directory, BuildInterruptingException, load_source, rmdir)
44+
current_directory,
45+
BuildInterruptingException,
46+
load_source,
47+
rmdir,
48+
max_build_tool_version,
49+
)
4550

4651
user_dir = dirname(realpath(os.path.curdir))
4752
toolchain_dir = dirname(__file__)
@@ -1009,18 +1014,7 @@ def _build_package(self, args, package_type):
10091014
self.hook("before_apk_assemble")
10101015
build_tools_versions = os.listdir(join(ctx.sdk_dir,
10111016
'build-tools'))
1012-
1013-
def sort_key(version_text):
1014-
try:
1015-
# Historically, Android build release candidates have had
1016-
# spaces in the version number.
1017-
return Version(version_text.replace(" ", ""))
1018-
except InvalidVersion:
1019-
# Put badly named versions at worst position.
1020-
return Version("0")
1021-
1022-
build_tools_versions.sort(key=sort_key)
1023-
build_tools_version = build_tools_versions[-1]
1017+
build_tools_version = max_build_tool_version(build_tools_versions)
10241018
info(('Detected highest available build tools '
10251019
'version to be {}').format(build_tools_version))
10261020

pythonforandroid/util.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import shutil
99
from tempfile import mkdtemp
1010

11+
import packaging.version
12+
1113
from pythonforandroid.logger import (logger, Err_Fore, error, info)
1214

1315
LOGGER = logging.getLogger("p4a.util")
@@ -128,3 +130,36 @@ def move(source, destination):
128130

129131
def touch(filename):
130132
Path(filename).touch()
133+
134+
135+
def build_tools_version_sort_key(
136+
version_string: str,
137+
) -> packaging.version.Version:
138+
"""
139+
Returns a packaging.version.Version object for comparison purposes.
140+
It includes canonicalization of the version string to allow for
141+
comparison of versions with spaces in them (historically, RC candidates)
142+
143+
If the version string is invalid, it returns a version object with
144+
version 0, which will be sorted at worst position.
145+
"""
146+
147+
try:
148+
# Historically, Android build release candidates have had
149+
# spaces in the version number.
150+
return packaging.version.Version(version_string.replace(" ", ""))
151+
except packaging.version.InvalidVersion:
152+
# Put badly named versions at worst position.
153+
return packaging.version.Version("0")
154+
155+
156+
def max_build_tool_version(
157+
build_tools_versions: list,
158+
) -> str:
159+
"""
160+
Returns the maximum build tools version from a list of build tools
161+
versions. It uses the :meth:`build_tools_version_sort_key` function to
162+
canonicalize the version strings and then returns the maximum version.
163+
"""
164+
165+
return max(build_tools_versions, key=build_tools_version_sort_key)

tests/recipes/recipe_lib_test.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,18 @@ def __init__(self, *args, **kwargs):
3535

3636
@mock.patch("pythonforandroid.recipe.Recipe.check_recipe_choices")
3737
@mock.patch("pythonforandroid.build.ensure_dir")
38-
@mock.patch("pythonforandroid.archs.find_executable")
38+
@mock.patch("shutil.which")
3939
def test_get_recipe_env(
4040
self,
41-
mock_find_executable,
41+
mock_shutil_which,
4242
mock_ensure_dir,
4343
mock_check_recipe_choices,
4444
):
4545
"""
4646
Test that get_recipe_env contains some expected arch flags and that
4747
some internal methods has been called.
4848
"""
49-
mock_find_executable.return_value = self.expected_compiler.format(
49+
mock_shutil_which.return_value = self.expected_compiler.format(
5050
android_ndk=self.ctx._ndk_dir, system=system().lower()
5151
)
5252
mock_check_recipe_choices.return_value = sorted(
@@ -67,19 +67,19 @@ def test_get_recipe_env(
6767

6868
# make sure that the mocked methods are actually called
6969
mock_ensure_dir.assert_called()
70-
mock_find_executable.assert_called()
70+
mock_shutil_which.assert_called()
7171
mock_check_recipe_choices.assert_called()
7272

7373
@mock.patch("pythonforandroid.util.chdir")
7474
@mock.patch("pythonforandroid.build.ensure_dir")
75-
@mock.patch("pythonforandroid.archs.find_executable")
75+
@mock.patch("shutil.which")
7676
def test_build_arch(
7777
self,
78-
mock_find_executable,
78+
mock_shutil_which,
7979
mock_ensure_dir,
8080
mock_current_directory,
8181
):
82-
mock_find_executable.return_value = self.expected_compiler.format(
82+
mock_shutil_which.return_value = self.expected_compiler.format(
8383
android_ndk=self.ctx._ndk_dir, system=system().lower()
8484
)
8585

@@ -101,7 +101,7 @@ def test_build_arch(
101101
mock_make.assert_called()
102102
mock_ensure_dir.assert_called()
103103
mock_current_directory.assert_called()
104-
mock_find_executable.assert_called()
104+
mock_shutil_which.assert_called()
105105

106106

107107
class BaseTestForCmakeRecipe(BaseTestForMakeRecipe):
@@ -116,14 +116,14 @@ class BaseTestForCmakeRecipe(BaseTestForMakeRecipe):
116116

117117
@mock.patch("pythonforandroid.util.chdir")
118118
@mock.patch("pythonforandroid.build.ensure_dir")
119-
@mock.patch("pythonforandroid.archs.find_executable")
119+
@mock.patch("shutil.which")
120120
def test_build_arch(
121121
self,
122-
mock_find_executable,
122+
mock_shutil_which,
123123
mock_ensure_dir,
124124
mock_current_directory,
125125
):
126-
mock_find_executable.return_value = self.expected_compiler.format(
126+
mock_shutil_which.return_value = self.expected_compiler.format(
127127
android_ndk=self.ctx._ndk_dir, system=system().lower()
128128
)
129129

@@ -141,4 +141,4 @@ def test_build_arch(
141141
mock_make.assert_called()
142142
mock_ensure_dir.assert_called()
143143
mock_current_directory.assert_called()
144-
mock_find_executable.assert_called()
144+
mock_shutil_which.assert_called()

tests/recipes/test_icu.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ def test_get_recipe_dir(self):
3333
@mock.patch("pythonforandroid.bootstrap.sh.Command")
3434
@mock.patch("pythonforandroid.recipes.icu.sh.make")
3535
@mock.patch("pythonforandroid.build.ensure_dir")
36-
@mock.patch("pythonforandroid.archs.find_executable")
36+
@mock.patch("shutil.which")
3737
def test_build_arch(
3838
self,
39-
mock_find_executable,
39+
mock_shutil_which,
4040
mock_ensure_dir,
4141
mock_sh_make,
4242
mock_sh_command,
4343
mock_chdir,
4444
mock_makedirs,
4545
):
46-
mock_find_executable.return_value = os.path.join(
46+
mock_shutil_which.return_value = os.path.join(
4747
self.ctx._ndk_dir,
4848
f"toolchains/llvm/prebuilt/{self.ctx.ndk.host_tag}/bin/clang",
4949
)
@@ -89,10 +89,10 @@ def test_build_arch(
8989
)
9090
mock_makedirs.assert_called()
9191

92-
mock_find_executable.assert_called_once()
92+
mock_shutil_which.assert_called_once()
9393
self.assertEqual(
94-
mock_find_executable.call_args[0][0],
95-
mock_find_executable.return_value,
94+
mock_shutil_which.call_args[0][0],
95+
mock_shutil_which.return_value,
9696
)
9797

9898
@mock.patch("pythonforandroid.recipes.icu.sh.cp")

tests/recipes/test_libgeos.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ class TestLibgeosRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
1212
@mock.patch("pythonforandroid.util.makedirs")
1313
@mock.patch("pythonforandroid.util.chdir")
1414
@mock.patch("pythonforandroid.build.ensure_dir")
15-
@mock.patch("pythonforandroid.archs.find_executable")
15+
@mock.patch("shutil.which")
1616
def test_build_arch(
1717
self,
18-
mock_find_executable,
18+
mock_shutil_which,
1919
mock_ensure_dir,
2020
mock_current_directory,
2121
mock_makedirs,

tests/recipes/test_libmysqlclient.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ class TestLibmysqlclientRecipe(BaseTestForCmakeRecipe, unittest.TestCase):
1313
@mock.patch("pythonforandroid.recipes.libmysqlclient.sh.cp")
1414
@mock.patch("pythonforandroid.util.chdir")
1515
@mock.patch("pythonforandroid.build.ensure_dir")
16-
@mock.patch("pythonforandroid.archs.find_executable")
16+
@mock.patch("shutil.which")
1717
def test_build_arch(
1818
self,
19-
mock_find_executable,
19+
mock_shutil_which,
2020
mock_ensure_dir,
2121
mock_current_directory,
2222
mock_sh_cp,

tests/recipes/test_libpq.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ class TestLibpqRecipe(BaseTestForMakeRecipe, unittest.TestCase):
1313
@mock.patch("pythonforandroid.recipes.libpq.sh.cp")
1414
@mock.patch("pythonforandroid.util.chdir")
1515
@mock.patch("pythonforandroid.build.ensure_dir")
16-
@mock.patch("pythonforandroid.archs.find_executable")
16+
@mock.patch("shutil.which")
1717
def test_build_arch(
1818
self,
19-
mock_find_executable,
19+
mock_shutil_which,
2020
mock_ensure_dir,
2121
mock_current_directory,
2222
mock_sh_cp,

tests/recipes/test_libvorbis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ class TestLibvorbisRecipe(BaseTestForMakeRecipe, unittest.TestCase):
1414
@mock.patch("pythonforandroid.recipes.libvorbis.sh.cp")
1515
@mock.patch("pythonforandroid.util.chdir")
1616
@mock.patch("pythonforandroid.build.ensure_dir")
17-
@mock.patch("pythonforandroid.archs.find_executable")
17+
@mock.patch("shutil.which")
1818
def test_build_arch(
1919
self,
20-
mock_find_executable,
20+
mock_shutil_which,
2121
mock_ensure_dir,
2222
mock_current_directory,
2323
mock_sh_cp,

0 commit comments

Comments
 (0)