Skip to content

Commit

Permalink
Remove distutils usage, as is not available anymore on Python 3.12 (
Browse files Browse the repository at this point in the history
#2912)

* Remove distutils usage, as is not available anymore on Python 3.12

* Updated testapps to use setuptools instead of distutils
  • Loading branch information
misl6 authored Nov 6, 2023
1 parent 0be5572 commit d8c3947
Show file tree
Hide file tree
Showing 29 changed files with 223 additions and 142 deletions.
4 changes: 2 additions & 2 deletions pythonforandroid/archs.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand Down
7 changes: 2 additions & 5 deletions pythonforandroid/bootstraps/common/build/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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'
Expand Down
7 changes: 5 additions & 2 deletions pythonforandroid/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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):
"""
Expand Down
27 changes: 12 additions & 15 deletions pythonforandroid/recommendations.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.'
Expand Down
22 changes: 8 additions & 14 deletions pythonforandroid/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand All @@ -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__)
Expand Down Expand Up @@ -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))

Expand Down
35 changes: 35 additions & 0 deletions pythonforandroid/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
install_reqs = [
'appdirs', 'colorama>=0.3.3', 'jinja2',
'sh>=1.10, <2.0; sys_platform!="win32"',
'build', 'toml', 'packaging',
'build', 'toml', 'packaging', 'setuptools'
]
# (build and toml are used by pythonpackage.py)

Expand Down
4 changes: 2 additions & 2 deletions testapps/on_device_unit_tests/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@

import os
import sys
from distutils.core import setup
from setuptools import find_packages

from setuptools import setup, find_packages

# define a basic test app, which can be override passing the proper args to cli
options = {
Expand Down
4 changes: 1 addition & 3 deletions testapps/setup_testapp_python3_sqlite_openssl.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

from distutils.core import setup
from setuptools import find_packages
from setuptools import setup, find_packages

options = {'apk': {'requirements': 'requests,peewee,sdl2,pyjnius,kivy,python3',
'android-api': 27,
Expand Down
4 changes: 1 addition & 3 deletions testapps/setup_vispy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@

from distutils.core import setup
from setuptools import find_packages
from setuptools import setup, find_packages

options = {'apk': {'debug': None,
'requirements': 'python3,vispy',
Expand Down
3 changes: 1 addition & 2 deletions testapps/testlauncher_setup/sdl2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from distutils.core import setup
from setuptools import find_packages
from setuptools import setup

options = {'apk': {'debug': None,
'bootstrap': 'sdl2',
Expand Down
3 changes: 1 addition & 2 deletions testapps/testlauncherreboot_setup/sdl2.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@

# pylint: disable=import-error,no-name-in-module
from subprocess import Popen
from distutils.core import setup
from os import listdir
from os.path import join, dirname, abspath, exists
from pprint import pprint
from setuptools import find_packages
from setuptools import setup, find_packages

ROOT = dirname(abspath(__file__))
LAUNCHER = join(ROOT, 'launcherapp')
Expand Down
24 changes: 12 additions & 12 deletions tests/recipes/recipe_lib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ 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,
):
"""
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(
Expand All @@ -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()
)

Expand All @@ -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):
Expand All @@ -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()
)

Expand All @@ -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()
12 changes: 6 additions & 6 deletions tests/recipes/test_icu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand Down Expand Up @@ -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")
Expand Down
Loading

0 comments on commit d8c3947

Please sign in to comment.