From 9d4f2f9335065640411fcffea028595fff8ca96a Mon Sep 17 00:00:00 2001 From: HyTurtle <81598434+HyTurtle@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:38:01 +0000 Subject: [PATCH 01/27] bump openssl to 3 (#3086) * bump openssl to 3 As version 1.1 disabled * Update test_prerequisites.py bump test version of openssl to 3 --- pythonforandroid/prerequisites.py | 2 +- tests/test_prerequisites.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pythonforandroid/prerequisites.py b/pythonforandroid/prerequisites.py index e85991948f..6b592046ed 100644 --- a/pythonforandroid/prerequisites.py +++ b/pythonforandroid/prerequisites.py @@ -262,7 +262,7 @@ def darwin_installer(self): class OpenSSLPrerequisite(Prerequisite): name = "openssl" - homebrew_formula_name = "openssl@1.1" + homebrew_formula_name = "openssl@3" mandatory = dict(linux=False, darwin=True) installer_is_supported = dict(linux=False, darwin=True) diff --git a/tests/test_prerequisites.py b/tests/test_prerequisites.py index 70ffa0c0d1..8d577fde1c 100644 --- a/tests/test_prerequisites.py +++ b/tests/test_prerequisites.py @@ -99,8 +99,8 @@ def setUp(self): self.mandatory = dict(linux=False, darwin=True) self.installer_is_supported = dict(linux=False, darwin=True) self.prerequisite = OpenSSLPrerequisite() - self.expected_homebrew_formula_name = "openssl@1.1" - self.expected_homebrew_location_prefix = "/opt/homebrew/opt/openssl@1.1" + self.expected_homebrew_formula_name = "openssl@3" + self.expected_homebrew_location_prefix = "/opt/homebrew/opt/openssl@3" @mock.patch( "pythonforandroid.prerequisites.Prerequisite._darwin_get_brew_formula_location_prefix" From 534e53a96a3d9dbf86735f8948ed1c7cc0ffec83 Mon Sep 17 00:00:00 2001 From: Robert Niederreiter Date: Wed, 27 Nov 2024 11:35:11 +0100 Subject: [PATCH 02/27] Update pyopenssl --- pythonforandroid/recipes/pyopenssl/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pythonforandroid/recipes/pyopenssl/__init__.py b/pythonforandroid/recipes/pyopenssl/__init__.py index 092a31059e..cb69aca3bd 100644 --- a/pythonforandroid/recipes/pyopenssl/__init__.py +++ b/pythonforandroid/recipes/pyopenssl/__init__.py @@ -3,9 +3,9 @@ class PyOpenSSLRecipe(PythonRecipe): - version = '19.0.0' + version = '24.2.1' url = 'https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-{version}.tar.gz' - depends = ['openssl', 'setuptools'] + depends = ['cffi', 'openssl', 'setuptools'] site_packages_name = 'OpenSSL' call_hostpython_via_targetpython = False From f0177c5e72f7a604d0ac0f6d36b2c9c97a0f5b42 Mon Sep 17 00:00:00 2001 From: Robert Niederreiter Date: Wed, 27 Nov 2024 11:46:44 +0100 Subject: [PATCH 03/27] Try older version --- pythonforandroid/recipes/pyopenssl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/pyopenssl/__init__.py b/pythonforandroid/recipes/pyopenssl/__init__.py index cb69aca3bd..2d4d7a893f 100644 --- a/pythonforandroid/recipes/pyopenssl/__init__.py +++ b/pythonforandroid/recipes/pyopenssl/__init__.py @@ -3,7 +3,7 @@ class PyOpenSSLRecipe(PythonRecipe): - version = '24.2.1' + version = '24.1.0' url = 'https://pypi.python.org/packages/source/p/pyOpenSSL/pyOpenSSL-{version}.tar.gz' depends = ['cffi', 'openssl', 'setuptools'] site_packages_name = 'OpenSSL' From 1544339b89f519392f51aa9a33bf570963aeec59 Mon Sep 17 00:00:00 2001 From: Evstifeev Roman Date: Wed, 11 Dec 2024 00:34:14 +0500 Subject: [PATCH 04/27] Fix rm: cannot remove 'CMakeFiles/': Is a directory fixes #3089 --- pythonforandroid/recipes/jpeg/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/jpeg/__init__.py b/pythonforandroid/recipes/jpeg/__init__.py index a81b82555c..436dc129ba 100644 --- a/pythonforandroid/recipes/jpeg/__init__.py +++ b/pythonforandroid/recipes/jpeg/__init__.py @@ -27,7 +27,7 @@ def build_arch(self, arch): toolchain_file = join(self.ctx.ndk_dir, 'build/cmake/android.toolchain.cmake') - shprint(sh.rm, '-f', 'CMakeCache.txt', 'CMakeFiles/') + shprint(sh.rm, '-rf', 'CMakeCache.txt', 'CMakeFiles/') shprint(sh.cmake, '-G', 'Unix Makefiles', '-DCMAKE_SYSTEM_NAME=Android', '-DCMAKE_POSITION_INDEPENDENT_CODE=1', From ceca3da75349a5286477311260236bedb98621a6 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 10 Dec 2024 22:51:34 +0100 Subject: [PATCH 05/27] :green_heart: Fix sphinx documentation build errors The errors & warnings were: ``` doc/source/quickstart.rst:60: WARNING: Title underline too short. Installing Prerequisites ~~~~~~~~~~~~~~~~~~~~~~~ doc/source/quickstart.rst:60: WARNING: Title underline too short. Installing Prerequisites ~~~~~~~~~~~~~~~~~~~~~~~ doc/source/recipes.rst:77: ERROR: Unexpected indentation. doc/source/recipes.rst:75: WARNING: Inline literal start-string without end-string. doc/source/recipes.rst:75: WARNING: Inline interpreted text or phrase reference start-string without end-string. doc/source/recipes.rst:78: WARNING: Block quote ends without a blank line; unexpected unindent. doc/source/recipes.rst:78: WARNING: Inline literal start-string without end-string. WARNING: autodoc: failed to import class 'Recipe' from module 'toolchain'; the following exception was raised: ``` And more errors popped as we fixed the above ones. Full log: https://github.com/kivy/python-for-android/actions/runs/12263397494/job/34218728454 --- doc/source/quickstart.rst | 2 +- doc/source/recipes.rst | 5 +++-- pythonforandroid/recipe.py | 4 ++-- pythonforandroid/toolchain.py | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index 81c860f888..61c33d6f16 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -57,7 +57,7 @@ You can also test the master branch from Github using:: pip install git+https://github.com/kivy/python-for-android.git Installing Prerequisites -~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~ p4a requires a few dependencies to be installed on your system to work properly. While we're working on a way to automate pre-requisites checks, diff --git a/doc/source/recipes.rst b/doc/source/recipes.rst index bfe49ca717..b6e34319b3 100644 --- a/doc/source/recipes.rst +++ b/doc/source/recipes.rst @@ -73,9 +73,10 @@ For example, when downloading from a private github repository, you can specify ``` (For the DOWNLOAD_HEADERS_my-package-name environment variable - specify as a JSON formatted set of values) -``` +.. code-block:: bash + [["Authorization","token "],["Accept", "application/vnd.github+json"]] -``` + The actual build process takes place via three core methods:: def prebuild_arch(self, arch): diff --git a/pythonforandroid/recipe.py b/pythonforandroid/recipe.py index 0cace3346e..44469aef2c 100644 --- a/pythonforandroid/recipe.py +++ b/pythonforandroid/recipe.py @@ -128,6 +128,7 @@ class Recipe(metaclass=RecipeMeta): keys should be the generated libraries and the values the relative path of the library inside his build folder. This dict will be used to perform different operations: + - copy the library into the right location, depending on if it's shared or static) - check if we have to rebuild the library @@ -571,7 +572,6 @@ def should_build(self, arch): '''Should perform any necessary test and return True only if it needs building again. Per default we implement a library test, in case that we detect so. - ''' if self.built_libraries: return not all( @@ -591,7 +591,7 @@ def install_libraries(self, arch): '''This method is always called after `build_arch`. In case that we detect a library recipe, defined by the class attribute `built_libraries`, we will copy all defined libraries into the - right location. + right location. ''' if not self.built_libraries: return diff --git a/pythonforandroid/toolchain.py b/pythonforandroid/toolchain.py index e05bb3a8fe..3987647f9b 100644 --- a/pythonforandroid/toolchain.py +++ b/pythonforandroid/toolchain.py @@ -750,6 +750,7 @@ def recipes(self, args): """ Prints recipes basic info, e.g. .. code-block:: bash + python3 3.7.1 depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi'] conflicts: [] From 37d272cc42f1ca0d0143743ccd3949cca56dfd5d Mon Sep 17 00:00:00 2001 From: Brent Picasso Date: Fri, 13 Dec 2024 11:25:47 -0800 Subject: [PATCH 06/27] Add hardware acceleration codecs to ffmpeg recipe (#3092) * enable hardware acceleration codecs * Reorder "enable-mediacodec" to fix libx264 issues --------- Co-authored-by: Dexer <73297572+DexerBR@users.noreply.github.com> --- pythonforandroid/recipes/ffmpeg/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pythonforandroid/recipes/ffmpeg/__init__.py b/pythonforandroid/recipes/ffmpeg/__init__.py index 3bc824834f..f7134b3384 100644 --- a/pythonforandroid/recipes/ffmpeg/__init__.py +++ b/pythonforandroid/recipes/ffmpeg/__init__.py @@ -28,6 +28,12 @@ def build_arch(self, arch): cflags = [] ldflags = [] + # enable hardware acceleration codecs + flags = [ + '--enable-jni', + '--enable-mediacodec' + ] + if 'openssl' in self.ctx.recipe_build_order: flags += [ '--enable-openssl', From 10e2ba938229e0bae81bd418a38e4851cdc6b322 Mon Sep 17 00:00:00 2001 From: Evstifeev Roman Date: Sat, 14 Dec 2024 19:15:27 +0500 Subject: [PATCH 07/27] Update sdl2_ttf from 2.20.2 to 2.22.0 fixes #2963 fixes #2902 fixes https://github.com/kivy/buildozer/issues/1772 --- pythonforandroid/recipes/sdl2_ttf/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/sdl2_ttf/__init__.py b/pythonforandroid/recipes/sdl2_ttf/__init__.py index 9f97ae441c..c869e1fc25 100644 --- a/pythonforandroid/recipes/sdl2_ttf/__init__.py +++ b/pythonforandroid/recipes/sdl2_ttf/__init__.py @@ -2,7 +2,7 @@ class LibSDL2TTF(BootstrapNDKRecipe): - version = '2.20.2' + version = '2.22.0' url = 'https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL2_ttf-{version}.tar.gz' dir_name = 'SDL2_ttf' From ea9fb61bc367c7837f51078d424f372980b6adc7 Mon Sep 17 00:00:00 2001 From: Evstifeev Roman Date: Sun, 15 Dec 2024 22:56:55 +0500 Subject: [PATCH 08/27] kivy recipe: add filetype dependency Original PR: https://github.com/kivy/kivy/pull/8889 Fixes #3098 --- pythonforandroid/recipes/kivy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py index 5cb56611e7..20113d88a0 100644 --- a/pythonforandroid/recipes/kivy/__init__.py +++ b/pythonforandroid/recipes/kivy/__init__.py @@ -27,7 +27,7 @@ class KivyRecipe(CythonRecipe): name = 'kivy' depends = ['sdl2', 'pyjnius', 'setuptools'] - python_depends = ['certifi', 'chardet', 'idna', 'requests', 'urllib3'] + python_depends = ['certifi', 'chardet', 'idna', 'requests', 'urllib3', 'filetype'] # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock. # See: https://github.com/kivy/kivy/pull/8025 From 1d7c70147bd2a32949bbd04d9edd24881799b124 Mon Sep 17 00:00:00 2001 From: Dexer <73297572+DexerBR@users.noreply.github.com> Date: Mon, 23 Dec 2024 17:54:55 -0300 Subject: [PATCH 09/27] Add `httpx` recipe (#3100) * add httpx recipe * flake8 fix --- pythonforandroid/recipes/httpx/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 pythonforandroid/recipes/httpx/__init__.py diff --git a/pythonforandroid/recipes/httpx/__init__.py b/pythonforandroid/recipes/httpx/__init__.py new file mode 100644 index 0000000000..60b34a8c46 --- /dev/null +++ b/pythonforandroid/recipes/httpx/__init__.py @@ -0,0 +1,13 @@ +from pythonforandroid.recipe import PyProjectRecipe + + +class HttpxRecipe(PyProjectRecipe): + name = "httpx" + version = "0.28.1" + url = ( + "https://pypi.python.org/packages/source/h/httpx/httpx-{version}.tar.gz" + ) + depends = ["httpcore", "h11", "certifi", "idna", "sniffio"] + + +recipe = HttpxRecipe() From 61ff0b69190204f147ec5df60c2c1f77f30f03d9 Mon Sep 17 00:00:00 2001 From: JasonnnW3000 Date: Wed, 1 Jan 2025 01:01:01 -0500 Subject: [PATCH 10/27] Update LICENSE, fix license year Signed-off-by: JasonnnW3000 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 4e3506010a..06f46c69cc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2010-2023 Kivy Team and other contributors +Copyright (c) 2010-2025 Kivy Team and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From b6d734c3f431464ff846a2b575be1a7a60e8833f Mon Sep 17 00:00:00 2001 From: Evstifeev Roman Date: Wed, 1 Jan 2025 13:03:40 +0500 Subject: [PATCH 11/27] update kivy to 2.3.1 --- pythonforandroid/recipes/kivy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py index 20113d88a0..b28e03a7a5 100644 --- a/pythonforandroid/recipes/kivy/__init__.py +++ b/pythonforandroid/recipes/kivy/__init__.py @@ -22,7 +22,7 @@ def is_kivy_affected_by_deadlock_issue(recipe=None, arch=None): class KivyRecipe(CythonRecipe): - version = '2.3.0' + version = '2.3.1' url = 'https://github.com/kivy/kivy/archive/{version}.zip' name = 'kivy' From 09684e82252a71d0fcde5374a30997210ca681ab Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Thu, 6 Mar 2025 10:40:03 +0100 Subject: [PATCH 12/27] :arrow_up: Migrate to actions/upload-artifact@v4 The v3 is deprecated, the error was: ``` This request has been automatically failed because it uses a deprecated version of `actions/upload-artifact: v3`. Learn more: https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/ ``` Also fix the docker image deletion to fail gracefully on no image. The error was: ``` "docker rmi" requires at least 1 argument. ``` --- .github/workflows/push.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1301975a8a..99f63de53a 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -102,7 +102,7 @@ jobs: if [ -f dist/${{ env.AAB_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAB_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAB_ARTIFACT_FILENAME }}; fi if [ -f dist/${{ env.AAR_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAR_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAR_ARTIFACT_FILENAME }}; fi - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts path: dist @@ -157,7 +157,7 @@ jobs: if [ -f dist/${{ env.APK_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.APK_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.APK_ARTIFACT_FILENAME }}; fi if [ -f dist/${{ env.AAB_ARTIFACT_FILENAME }} ]; then mv dist/${{ env.AAB_ARTIFACT_FILENAME }} dist/${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-${{ env.AAB_ARTIFACT_FILENAME }}; fi - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.runs_on }}-${{ matrix.bootstrap.name }}-artifacts path: dist @@ -184,7 +184,7 @@ jobs: sudo swapoff -a sudo rm -f /swapfile sudo apt -y clean - docker rmi $(docker image ls -aq) + docker images -q | xargs -r docker rmi df -h - name: Pull docker image run: | From ceed0494e1128b4c2b5cf0beaa94cbc53a7edf46 Mon Sep 17 00:00:00 2001 From: Miguel Risco-Castillo Date: Fri, 7 Mar 2025 03:52:48 -0500 Subject: [PATCH 13/27] :bug: fixes Kiwisolver build fails The error was "Python.h not found", closes #3115 Note that the macos-14 build is still failing --- pythonforandroid/recipes/kiwisolver/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pythonforandroid/recipes/kiwisolver/__init__.py b/pythonforandroid/recipes/kiwisolver/__init__.py index c4c19ac257..d94af0f14b 100644 --- a/pythonforandroid/recipes/kiwisolver/__init__.py +++ b/pythonforandroid/recipes/kiwisolver/__init__.py @@ -8,5 +8,13 @@ class KiwiSolverRecipe(PyProjectRecipe): depends = ['cppy'] need_stl_shared = True + # from https://github.com/kivy/python-for-android/issues/3115 + def get_recipe_env(self, arch, **kwargs): + env = super().get_recipe_env(arch, **kwargs) + flags = " -I" + self.ctx.python_recipe.include_root(arch.arch) + env["CFLAGS"] = flags + env["CPPFLAGS"] = flags + return env + recipe = KiwiSolverRecipe() From e710cfc581adaf49f6adfdc64d2540c9201dd7ed Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 22 Mar 2025 15:32:44 +0100 Subject: [PATCH 14/27] :bug: Fix the kiwisolver build on macOS, fixes #3122 --- pythonforandroid/recipes/kiwisolver/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pythonforandroid/recipes/kiwisolver/__init__.py b/pythonforandroid/recipes/kiwisolver/__init__.py index d94af0f14b..3ccfc2d432 100644 --- a/pythonforandroid/recipes/kiwisolver/__init__.py +++ b/pythonforandroid/recipes/kiwisolver/__init__.py @@ -8,12 +8,13 @@ class KiwiSolverRecipe(PyProjectRecipe): depends = ['cppy'] need_stl_shared = True - # from https://github.com/kivy/python-for-android/issues/3115 def get_recipe_env(self, arch, **kwargs): + """Override compile and linker flags, refs: #3115 and #3122""" env = super().get_recipe_env(arch, **kwargs) flags = " -I" + self.ctx.python_recipe.include_root(arch.arch) - env["CFLAGS"] = flags - env["CPPFLAGS"] = flags + env["CFLAGS"] += flags + env["CPPFLAGS"] += flags + env["LDFLAGS"] += " -shared" return env From 44aabede4a375cb7ddfd57e5961890bdaa2150f0 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 22 Mar 2025 22:10:44 +0100 Subject: [PATCH 15/27] :bug: Update the greenlet recipe, closes #2806 Fixes build time errors, but runtime not tested. --- pythonforandroid/recipes/greenlet/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pythonforandroid/recipes/greenlet/__init__.py b/pythonforandroid/recipes/greenlet/__init__.py index 3f2043d57d..d9b208476f 100644 --- a/pythonforandroid/recipes/greenlet/__init__.py +++ b/pythonforandroid/recipes/greenlet/__init__.py @@ -1,8 +1,8 @@ -from pythonforandroid.recipe import CompiledComponentsPythonRecipe +from pythonforandroid.recipe import PyProjectRecipe -class GreenletRecipe(CompiledComponentsPythonRecipe): - version = '0.4.15' +class GreenletRecipe(PyProjectRecipe): + version = '3.1.1' url = 'https://pypi.python.org/packages/source/g/greenlet/greenlet-{version}.tar.gz' depends = ['setuptools'] call_hostpython_via_targetpython = False From d6eeea1d9f9c912ea69dd25541e846cd255bdec3 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 22 Mar 2025 21:26:48 +0100 Subject: [PATCH 16/27] :bug: Update and fix atom recipe, closes #2802 Fixes build time errors, but runtime not tested. --- pythonforandroid/recipes/atom/__init__.py | 13 +++++++------ pythonforandroid/recipes/atom/pyproject.toml.patch | 12 ++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 pythonforandroid/recipes/atom/pyproject.toml.patch diff --git a/pythonforandroid/recipes/atom/__init__.py b/pythonforandroid/recipes/atom/__init__.py index 51923d5487..22fec4cd57 100644 --- a/pythonforandroid/recipes/atom/__init__.py +++ b/pythonforandroid/recipes/atom/__init__.py @@ -1,11 +1,12 @@ -from pythonforandroid.recipe import CppCompiledComponentsPythonRecipe +from pythonforandroid.recipe import PyProjectRecipe -class AtomRecipe(CppCompiledComponentsPythonRecipe): - site_packages_name = 'atom' - version = '0.3.10' - url = 'https://github.com/nucleic/atom/archive/master.zip' - depends = ['setuptools'] +class AtomRecipe(PyProjectRecipe): + site_packages_name = "atom" + version = "0.11.0" + url = "https://files.pythonhosted.org/packages/source/a/atom/atom-{version}.tar.gz" + depends = ["setuptools"] + patches = ["pyproject.toml.patch"] recipe = AtomRecipe() diff --git a/pythonforandroid/recipes/atom/pyproject.toml.patch b/pythonforandroid/recipes/atom/pyproject.toml.patch new file mode 100644 index 0000000000..ebf8cbc454 --- /dev/null +++ b/pythonforandroid/recipes/atom/pyproject.toml.patch @@ -0,0 +1,12 @@ +diff --git a/pyproject.toml b/pyproject.toml +index d41287f..c83b053 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -40,6 +40,7 @@ + [tool.setuptools] + include-package-data = false + package-data = { atom = ["py.typed", "*.pyi"] } ++ packages = ["atom"] + + [tool.setuptools_scm] + write_to = "atom/version.py" From 798e2c2fb0c95d4490e3f28b8996065c92d509f9 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 22 Mar 2025 17:26:20 +0100 Subject: [PATCH 17/27] :bug: Upgrade and fix gevent recipe, closes #2805 Update the gevent recipe from 1.4.0 (2019/01) to the latest version 24.11.1 (2024/11) and fix the build errors. Note that the build fails on macOS, the error is: ``` deps/libuv/src/unix/bsd-ifaddrs.c:31:10: fatal error: 'net/if_dl.h' file not found ^~~~~~~~~~~~~ ``` --- pythonforandroid/recipes/gevent/__init__.py | 27 ++++++++++++----- .../recipes/gevent/cross_compiling.patch | 30 +++++++++---------- tests/recipes/test_gevent.py | 6 ++-- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/pythonforandroid/recipes/gevent/__init__.py b/pythonforandroid/recipes/gevent/__init__.py index 7958a5480f..3206603e82 100644 --- a/pythonforandroid/recipes/gevent/__init__.py +++ b/pythonforandroid/recipes/gevent/__init__.py @@ -1,22 +1,33 @@ +""" +Note that this recipe doesn't yet build on macOS, the error is: +``` +deps/libuv/src/unix/bsd-ifaddrs.c:31:10: fatal error: 'net/if_dl.h' file not found +#include + ^~~~~~~~~~~~~ +1 error generated. +error: command '/Users/runner/.android/android-ndk/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang' failed with exit code 1 +``` +""" import re from pythonforandroid.logger import info -from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.recipe import PyProjectRecipe -class GeventRecipe(CythonRecipe): - version = '1.4.0' - url = 'https://pypi.python.org/packages/source/g/gevent/gevent-{version}.tar.gz' +class GeventRecipe(PyProjectRecipe): + version = '24.11.1' + url = 'https://github.com/gevent/gevent/archive/refs/tags/{version}.tar.gz' depends = ['librt', 'setuptools'] patches = ["cross_compiling.patch"] - def get_recipe_env(self, arch=None, with_flags_in_cc=True): + def get_recipe_env(self, arch, **kwargs): """ - Moves all -I -D from CFLAGS to CPPFLAGS environment. - Moves all -l from LDFLAGS to LIBS environment. - Copies all -l from LDLIBS to LIBS environment. - - Fixes linker name (use cross compiler) and flags (appends LIBS) + - Fixes linker name (use cross compiler) and flags (appends LIBS). + - Feds the command prefix for the configure --host flag. """ - env = super().get_recipe_env(arch, with_flags_in_cc) + env = super().get_recipe_env(arch, **kwargs) # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS regex = re.compile(r'(?:\s|^)-[DI][\S]+') env['CPPFLAGS'] = ''.join(re.findall(regex, env['CFLAGS'])).strip() @@ -28,6 +39,8 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): env['LIBS'] += ' {}'.format(''.join(re.findall(regex, env['LDLIBS'])).strip()) env['LDFLAGS'] = re.sub(regex, '', env['LDFLAGS']) info('Moved "{}" from LDFLAGS to LIBS.'.format(env['LIBS'])) + # used with the `./configure --host` flag for cross compiling, refs #2805 + env['COMMAND_PREFIX'] = arch.command_prefix return env diff --git a/pythonforandroid/recipes/gevent/cross_compiling.patch b/pythonforandroid/recipes/gevent/cross_compiling.patch index 01e55d8c00..6cafbb9f05 100644 --- a/pythonforandroid/recipes/gevent/cross_compiling.patch +++ b/pythonforandroid/recipes/gevent/cross_compiling.patch @@ -1,26 +1,26 @@ diff --git a/_setupares.py b/_setupares.py -index dd184de6..bb16bebe 100644 +index c42fe369..cd8854df 100644 --- a/_setupares.py +++ b/_setupares.py -@@ -43,7 +43,7 @@ else: +@@ -42,7 +42,7 @@ cflags = ('CFLAGS="%s"' % (cflags,)) if cflags else '' ares_configure_command = ' '.join([ "(cd ", quoted_dep_abspath('c-ares'), - " && if [ -r ares_build.h ]; then cp ares_build.h ares_build.h.orig; fi ", -- " && sh ./configure --disable-dependency-tracking " + _m32 + "CONFIG_COMMANDS= ", -+ " && sh ./configure --host={} --disable-dependency-tracking ".format(os.environ['TOOLCHAIN_PREFIX']) + _m32 + "CONFIG_COMMANDS= ", - " && cp ares_config.h ares_build.h \"$OLDPWD\" ", - " && cat ares_build.h ", - " && if [ -r ares_build.h.orig ]; then mv ares_build.h.orig ares_build.h; fi)", + " && if [ -r include/ares_build.h ]; then cp include/ares_build.h include/ares_build.h.orig; fi ", +- " && sh ./configure --disable-dependency-tracking --disable-tests -C " + cflags, ++ " && sh ./configure --host={} --disable-dependency-tracking --disable-tests -C ".format(os.environ['COMMAND_PREFIX']) + cflags, + " && cp src/lib/ares_config.h include/ares_build.h \"$OLDPWD\" ", + " && cat include/ares_build.h ", + " && if [ -r include/ares_build.h.orig ]; then mv include/ares_build.h.orig include/ares_build.h; fi)", diff --git a/_setuplibev.py b/_setuplibev.py -index 2a5841bf..b6433c94 100644 +index f05c2fe9..32f9bd81 100644 --- a/_setuplibev.py +++ b/_setuplibev.py -@@ -31,7 +31,7 @@ LIBEV_EMBED = should_embed('libev') - # and the PyPy branch will clean it up. +@@ -28,7 +28,7 @@ LIBEV_EMBED = should_embed('libev') + # Configure libev in place libev_configure_command = ' '.join([ "(cd ", quoted_dep_abspath('libev'), -- " && sh ./configure ", -+ " && sh ./configure --host={} ".format(os.environ['TOOLCHAIN_PREFIX']), - " && cp config.h \"$OLDPWD\"", +- " && sh ./configure -C > configure-output.txt", ++ " && sh ./configure --host={} -C > configure-output.txt".format(os.environ['COMMAND_PREFIX']), ")", - '> configure-output.txt' + ]) + diff --git a/tests/recipes/test_gevent.py b/tests/recipes/test_gevent.py index 8c6601e255..c434489fe8 100644 --- a/tests/recipes/test_gevent.py +++ b/tests/recipes/test_gevent.py @@ -35,9 +35,9 @@ def test_get_recipe_env(self): 'LDFLAGS': mocked_ldflags, 'LDLIBS': mocked_ldlibs, } - with patch('pythonforandroid.recipe.CythonRecipe.get_recipe_env') as m_get_recipe_env: + with patch('pythonforandroid.recipe.PyProjectRecipe.get_recipe_env') as m_get_recipe_env: m_get_recipe_env.return_value = mocked_env - env = self.recipe.get_recipe_env() + env = self.recipe.get_recipe_env(self.arch) expected_cflags = ( ' -fomit-frame-pointer -mandroid -isystem /path/to/isystem' ' -isysroot /path/to/sysroot' @@ -57,11 +57,13 @@ def test_get_recipe_env(self): ) expected_ldlibs = mocked_ldlibs expected_libs = '-lm -lpython3.7m -lm' + expected_command_prefix = 'aarch64-linux-android' expected_env = { 'CFLAGS': expected_cflags, 'CPPFLAGS': expected_cppflags, 'LDFLAGS': expected_ldflags, 'LDLIBS': expected_ldlibs, 'LIBS': expected_libs, + 'COMMAND_PREFIX': expected_command_prefix, } self.assertEqual(expected_env, env) From e9dbcfd10f8b7786f56125d0a13106484f299bca Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sat, 22 Mar 2025 21:39:19 +0100 Subject: [PATCH 18/27] :fire: Drop libmysqlclient recipe, closes #2808 The recipe was introduced in #587 but has multiple issues and doesn't build. Notable problems: - Never updated since initial commit (aside from formatting) - Uses `master` branch instead of pinning a version - Contains lots of commented-out code - Relies on https://github.com/0x-ff/libmysql-android, which: - Is not an official MySQL repository - Hasn't been updated since 2013 - Lacks English documentation (only available in Russian) --- ci/constants.py | 2 - .../recipes/libmysqlclient/Linux.cmake | 5 -- .../recipes/libmysqlclient/__init__.py | 67 ------------------- .../libmysqlclient/add-custom-platform.patch | 8 --- .../libmysqlclient/disable-soname.patch | 11 --- .../libmysqlclient/disable-soversion.patch | 12 ---- .../recipes/libmysqlclient/p4a.cmake | 3 - tests/recipes/test_libmysqlclient.py | 30 --------- 8 files changed, 138 deletions(-) delete mode 100644 pythonforandroid/recipes/libmysqlclient/Linux.cmake delete mode 100644 pythonforandroid/recipes/libmysqlclient/__init__.py delete mode 100644 pythonforandroid/recipes/libmysqlclient/add-custom-platform.patch delete mode 100644 pythonforandroid/recipes/libmysqlclient/disable-soname.patch delete mode 100644 pythonforandroid/recipes/libmysqlclient/disable-soversion.patch delete mode 100644 pythonforandroid/recipes/libmysqlclient/p4a.cmake delete mode 100644 tests/recipes/test_libmysqlclient.py diff --git a/ci/constants.py b/ci/constants.py index cc1d9ea70a..382a4a0bfe 100644 --- a/ci/constants.py +++ b/ci/constants.py @@ -33,8 +33,6 @@ class TargetPython(Enum): 'twisted', # genericndkbuild is incompatible with sdl2 (which is build by default when targeting sdl2 bootstrap) 'genericndkbuild', - # libmysqlclient gives a linker failure (See issue #2808) - 'libmysqlclient', # boost gives errors (requires numpy? syntax error in .jam?) 'boost', # libtorrent gives errors (requires boost. Also, see issue #2809, to start with) diff --git a/pythonforandroid/recipes/libmysqlclient/Linux.cmake b/pythonforandroid/recipes/libmysqlclient/Linux.cmake deleted file mode 100644 index 42cf0694fd..0000000000 --- a/pythonforandroid/recipes/libmysqlclient/Linux.cmake +++ /dev/null @@ -1,5 +0,0 @@ -asdgasdgasdg -asdg -asdg -include(${CMAKE_ROOT}/Modules/Platform/Linux.cmake) -set(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "") diff --git a/pythonforandroid/recipes/libmysqlclient/__init__.py b/pythonforandroid/recipes/libmysqlclient/__init__.py deleted file mode 100644 index 84fd8d30ac..0000000000 --- a/pythonforandroid/recipes/libmysqlclient/__init__.py +++ /dev/null @@ -1,67 +0,0 @@ -from pythonforandroid.logger import shprint -from pythonforandroid.recipe import Recipe -from pythonforandroid.util import current_directory -import sh -from os.path import join - - -class LibmysqlclientRecipe(Recipe): - name = 'libmysqlclient' - version = 'master' - url = 'https://github.com/0x-ff/libmysql-android/archive/{version}.zip' - # version = '5.5.47' - # url = 'http://dev.mysql.com/get/Downloads/MySQL-5.5/mysql-{version}.tar.gz' - # - # depends = ['ncurses'] - # - - # patches = ['add-custom-platform.patch'] - - patches = ['disable-soversion.patch'] - - def should_build(self, arch): - return not self.has_libs(arch, 'libmysql.so') - - def build_arch(self, arch): - env = self.get_recipe_env(arch) - with current_directory(join(self.get_build_dir(arch.arch), 'libmysqlclient')): - shprint(sh.cp, '-t', '.', join(self.get_recipe_dir(), 'p4a.cmake')) - # ensure_dir('Platform') - # shprint(sh.cp, '-t', 'Platform', join(self.get_recipe_dir(), 'Linux.cmake')) - shprint(sh.rm, '-f', 'CMakeCache.txt') - shprint(sh.cmake, '-G', 'Unix Makefiles', - # '-DCMAKE_MODULE_PATH=' + join(self.get_build_dir(arch.arch), 'libmysqlclient'), - '-DCMAKE_INSTALL_PREFIX=./install', - '-DCMAKE_TOOLCHAIN_FILE=p4a.cmake', _env=env) - shprint(sh.make, _env=env) - - self.install_libs(arch, join('libmysql', 'libmysql.so')) - - # def get_recipe_env(self, arch=None): - # env = super().get_recipe_env(arch) - # env['WITHOUT_SERVER'] = 'ON' - # ncurses = self.get_recipe('ncurses', self) - # # env['CFLAGS'] += ' -I' + join(ncurses.get_build_dir(arch.arch), - # # 'include') - # env['CURSES_LIBRARY'] = join(self.ctx.get_libs_dir(arch.arch), 'libncurses.so') - # env['CURSES_INCLUDE_PATH'] = join(ncurses.get_build_dir(arch.arch), - # 'include') - # return env - # - # def build_arch(self, arch): - # env = self.get_recipe_env(arch) - # with current_directory(self.get_build_dir(arch.arch)): - # # configure = sh.Command('./configure') - # # TODO: should add openssl as an optional dep and compile support - # # shprint(configure, '--enable-shared', '--enable-assembler', - # # '--enable-thread-safe-client', '--with-innodb', - # # '--without-server', _env=env) - # # shprint(sh.make, _env=env) - # shprint(sh.cmake, '.', '-DCURSES_LIBRARY=' + env['CURSES_LIBRARY'], - # '-DCURSES_INCLUDE_PATH=' + env['CURSES_INCLUDE_PATH'], _env=env) - # shprint(sh.make, _env=env) - # - # self.install_libs(arch, 'libmysqlclient.so') - - -recipe = LibmysqlclientRecipe() diff --git a/pythonforandroid/recipes/libmysqlclient/add-custom-platform.patch b/pythonforandroid/recipes/libmysqlclient/add-custom-platform.patch deleted file mode 100644 index e76c69a723..0000000000 --- a/pythonforandroid/recipes/libmysqlclient/add-custom-platform.patch +++ /dev/null @@ -1,8 +0,0 @@ ---- libmysqlclient/libmysqlclient/libmysql/CMakeLists.txt 2013-02-27 00:25:45.000000000 -0600 -+++ b/libmysqlclient/libmysql/CMakeLists.txt 2016-01-11 13:28:51.142356988 -0600 -@@ -152,3 +152,5 @@ - ${CMAKE_SOURCE_DIR}/libmysql/libmysqlclient_r${CMAKE_SHARED_LIBRARY_SUFFIX} - DESTINATION "lib") - ENDIF(WIN32) -+ -+LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_PREFIX}") diff --git a/pythonforandroid/recipes/libmysqlclient/disable-soname.patch b/pythonforandroid/recipes/libmysqlclient/disable-soname.patch deleted file mode 100644 index 5a4dbf2639..0000000000 --- a/pythonforandroid/recipes/libmysqlclient/disable-soname.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- libmysqlclient/libmysqlclient/CMakeLists.txt 2013-02-27 00:25:45.000000000 -0600 -+++ b/libmysqlclient/CMakeLists.txt 2016-01-11 13:48:41.672323738 -0600 -@@ -24,6 +24,8 @@ - SET(CMAKE_BUILD_TYPE "Release") - ENDIF(NOT CMAKE_BUILD_TYPE) - -+SET(CMAKE_SHARED_LIBRARY_SONAME_C_FLAG "") -+ - # This reads user configuration, generated by configure.js. - IF(WIN32 AND EXISTS ${CMAKE_SOURCE_DIR}/win/configure.data) - INCLUDE(${CMAKE_SOURCE_DIR}/win/configure.data) diff --git a/pythonforandroid/recipes/libmysqlclient/disable-soversion.patch b/pythonforandroid/recipes/libmysqlclient/disable-soversion.patch deleted file mode 100644 index d6353de1cb..0000000000 --- a/pythonforandroid/recipes/libmysqlclient/disable-soversion.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- libmysqlclient/libmysqlclient/libmysql/CMakeLists.txt 2013-02-27 00:25:45.000000000 -0600 -+++ b/libmysqlclient/libmysql/CMakeLists.txt 2016-01-11 14:00:26.729332913 -0600 -@@ -97,9 +97,6 @@ - ADD_LIBRARY(libmysql SHARED ${CLIENT_SOURCES} libmysql.def) - TARGET_LINK_LIBRARIES(libmysql ${CMAKE_THREAD_LIBS_INIT}) - STRING(REGEX REPLACE "\\..+" "" LIBMYSQL_SOVERSION ${SHARED_LIB_VERSION}) --SET_TARGET_PROPERTIES(libmysql -- PROPERTIES VERSION ${SHARED_LIB_VERSION} -- SOVERSION ${LIBMYSQL_SOVERSION}) - IF(OPENSSL_LIBRARIES) - TARGET_LINK_LIBRARIES(libmysql ${OPENSSL_LIBRARIES} ${OPENSSL_LIBCRYPTO}) - ENDIF(OPENSSL_LIBRARIES) diff --git a/pythonforandroid/recipes/libmysqlclient/p4a.cmake b/pythonforandroid/recipes/libmysqlclient/p4a.cmake deleted file mode 100644 index 9e4c34339d..0000000000 --- a/pythonforandroid/recipes/libmysqlclient/p4a.cmake +++ /dev/null @@ -1,3 +0,0 @@ -SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) -SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/tests/recipes/test_libmysqlclient.py b/tests/recipes/test_libmysqlclient.py deleted file mode 100644 index 4c85dc92e2..0000000000 --- a/tests/recipes/test_libmysqlclient.py +++ /dev/null @@ -1,30 +0,0 @@ -import unittest -from unittest import mock -from tests.recipes.recipe_lib_test import BaseTestForCmakeRecipe - - -class TestLibmysqlclientRecipe(BaseTestForCmakeRecipe, unittest.TestCase): - """ - An unittest for recipe :mod:`~pythonforandroid.recipes.libmysqlclient` - """ - recipe_name = "libmysqlclient" - - @mock.patch("pythonforandroid.recipes.libmysqlclient.sh.rm") - @mock.patch("pythonforandroid.recipes.libmysqlclient.sh.cp") - @mock.patch("pythonforandroid.util.chdir") - @mock.patch("pythonforandroid.build.ensure_dir") - @mock.patch("shutil.which") - def test_build_arch( - self, - mock_shutil_which, - mock_ensure_dir, - mock_current_directory, - mock_sh_cp, - mock_sh_rm, - ): - # We overwrite the base test method because we need - # to mock a little more (`sh.cp` and rmdir) - super().test_build_arch() - # make sure that the mocked methods are actually called - mock_sh_cp.assert_called() - mock_sh_rm.assert_called() From d9ded99b193c51f4d5fd0ac269bc8da48a778914 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Wed, 26 Mar 2025 09:52:12 +0530 Subject: [PATCH 19/27] `sdl2_image`: fix downloading --- .../recipes/sdl2_image/__init__.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pythonforandroid/recipes/sdl2_image/__init__.py b/pythonforandroid/recipes/sdl2_image/__init__.py index b3ac504fbf..8cea604e59 100644 --- a/pythonforandroid/recipes/sdl2_image/__init__.py +++ b/pythonforandroid/recipes/sdl2_image/__init__.py @@ -2,11 +2,10 @@ import sh from pythonforandroid.logger import shprint from pythonforandroid.recipe import BootstrapNDKRecipe -from pythonforandroid.util import current_directory class LibSDL2Image(BootstrapNDKRecipe): - version = '2.8.0' + version = '2.8.2' url = 'https://github.com/libsdl-org/SDL_image/releases/download/release-{version}/SDL2_image-{version}.tar.gz' dir_name = 'SDL2_image' @@ -20,10 +19,27 @@ def get_include_dirs(self, arch): def prebuild_arch(self, arch): # We do not have a folder for each arch on BootstrapNDKRecipe, so we # need to skip the external deps download if we already have done it. - external_deps_dir = os.path.join(self.get_build_dir(arch.arch), "external") - if not os.path.exists(os.path.join(external_deps_dir, "libwebp")): - with current_directory(external_deps_dir): - shprint(sh.Command("./download.sh")) + + build_dir = self.get_build_dir(arch.arch) + + with open(os.path.join(build_dir, ".gitmodules"), "r") as file: + for section in file.read().split('[submodule "')[1:]: + line_split = section.split(" = ") + # Parse .gitmoulde section + clone_path, url, branch = ( + os.path.join(build_dir, line_split[1].split("\n")[0].strip()), + line_split[2].split("\n")[0].strip(), + line_split[-1].strip() + ) + # Clone if needed + if not os.path.exists(clone_path) or os.listdir(clone_path) == 0: + shprint( + sh.git, "clone", url, + "--depth", "1", "-b", + branch, clone_path, "--recursive" + ) + file.close() + super().prebuild_arch(arch) From 58d148bf81e312ae890cb7762ccb9f0f877ee7a4 Mon Sep 17 00:00:00 2001 From: Mirko Galimberti Date: Sat, 12 Apr 2025 15:55:55 +0200 Subject: [PATCH 20/27] Add `SDL3` bootstrap (alongside `SDL3`, `SDL3_ttf`, `SDL3_mixer`, `SDL3_image` recipes) for Kivy `3.0.0` (#3125) * Add SDL3 bootstrap * Avoid some DRY issues + minor fixes + version bump --- pythonforandroid/bootstrap.py | 25 +- .../bootstraps/_sdl_common/__init__.py | 49 ++ .../{sdl2 => _sdl_common}/build/.gitignore | 0 .../{sdl2 => _sdl_common}/build/blacklist.txt | 0 .../build/jni/Application.mk | 0 .../build/src/main/assets/.gitkeep | 0 .../build/src/main/java}/.gitkeep | 0 .../android/GenericBroadcastReceiver.java | 0 .../GenericBroadcastReceiverCallback.java | 0 .../org/kivy/android/launcher/Project.java | 0 .../kivy/android/launcher/ProjectAdapter.java | 0 .../kivy/android/launcher/ProjectChooser.java | 0 .../build/src/main/jniLibs}/.gitkeep | 0 .../build/src/main/libs}/.gitkeep | 0 .../main/res/drawable-hdpi/ic_launcher.png | Bin .../main/res/drawable-mdpi/ic_launcher.png | Bin .../main/res/drawable-xhdpi/ic_launcher.png | Bin .../main/res/drawable-xxhdpi/ic_launcher.png | Bin .../build/src/main/res/drawable}/.gitkeep | 0 .../src/main/res/layout/chooser_item.xml | 0 .../build/src/main/res/layout/main.xml | 0 .../src/main/res/layout/project_chooser.xml | 0 .../src/main/res/layout/project_empty.xml | 0 .../src/main/res/mipmap-anydpi-v26}/.gitkeep | 0 .../build/src/main/res/mipmap/.gitkeep | 0 .../build/templates/AndroidManifest.tmpl.xml | 0 .../build/templates/strings.tmpl.xml | 0 .../bootstraps/common/build/build.py | 22 +- .../build/jni/application/src/Android.mk | 5 +- .../common/build/jni/application/src/start.c | 8 +- .../java/org/kivy/android/PythonUtil.java | 4 + .../jni/application/src/bootstrap_name.h | 1 - pythonforandroid/bootstraps/sdl2/__init__.py | 50 +- .../sdl2/build/jni/application/src/Android.mk | 22 + pythonforandroid/bootstraps/sdl3/__init__.py | 12 + .../sdl3/build/jni/application/src/Android.mk | 22 + .../jni/application/src/Android_static.mk | 13 + .../jni/application/src/bootstrap_name.h | 5 + .../java/org/kivy/android/PythonActivity.java | 645 ++++++++++++++++++ .../build/src/patches/SDLActivity.java.patch | 49 ++ .../jni/application/src/bootstrap_name.h | 1 - .../jni/application/src/bootstrap_name.h | 1 - .../jni/application/src/bootstrap_name.h | 1 - pythonforandroid/build.py | 4 + pythonforandroid/recipes/android/__init__.py | 15 +- .../recipes/android/src/android/_android.pyx | 2 +- pythonforandroid/recipes/android/src/setup.py | 3 +- .../recipes/genericndkbuild/__init__.py | 2 +- pythonforandroid/recipes/kivy/__init__.py | 20 +- pythonforandroid/recipes/pyjnius/__init__.py | 7 +- .../recipes/pyjnius/sdl3_jnienv_getter.patch | 24 + pythonforandroid/recipes/sdl2/__init__.py | 2 + pythonforandroid/recipes/sdl3/__init__.py | 59 ++ .../recipes/sdl3_image/__init__.py | 41 ++ .../recipes/sdl3_image/enable-webp.patch | 12 + .../recipes/sdl3_mixer/__init__.py | 45 ++ .../recipes/sdl3_mixer/disable-libgme.patch | 12 + pythonforandroid/recipes/sdl3_ttf/__init__.py | 39 ++ tests/test_bootstrap.py | 44 +- tests/test_build.py | 2 +- tests/test_graph.py | 4 +- 61 files changed, 1177 insertions(+), 95 deletions(-) create mode 100644 pythonforandroid/bootstraps/_sdl_common/__init__.py rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/.gitignore (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/blacklist.txt (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/jni/Application.mk (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/assets/.gitkeep (100%) rename pythonforandroid/bootstraps/{sdl2/build/src/main/jniLibs => _sdl_common/build/src/main/java}/.gitkeep (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/java/org/kivy/android/launcher/Project.java (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java (100%) rename pythonforandroid/bootstraps/{sdl2/build/src/main/libs => _sdl_common/build/src/main/jniLibs}/.gitkeep (100%) rename pythonforandroid/bootstraps/{sdl2/build/src/main/res/drawable => _sdl_common/build/src/main/libs}/.gitkeep (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/drawable-hdpi/ic_launcher.png (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/drawable-mdpi/ic_launcher.png (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/drawable-xhdpi/ic_launcher.png (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/drawable-xxhdpi/ic_launcher.png (100%) rename pythonforandroid/bootstraps/{sdl2/build/src/main/res/mipmap-anydpi-v26 => _sdl_common/build/src/main/res/drawable}/.gitkeep (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/layout/chooser_item.xml (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/layout/main.xml (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/layout/project_chooser.xml (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/src/main/res/layout/project_empty.xml (100%) rename pythonforandroid/bootstraps/{sdl2/build/src/main/res/mipmap => _sdl_common/build/src/main/res/mipmap-anydpi-v26}/.gitkeep (100%) create mode 100644 pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap/.gitkeep rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/templates/AndroidManifest.tmpl.xml (100%) rename pythonforandroid/bootstraps/{sdl2 => _sdl_common}/build/templates/strings.tmpl.xml (100%) create mode 100644 pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android.mk create mode 100644 pythonforandroid/bootstraps/sdl3/__init__.py create mode 100644 pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android.mk create mode 100644 pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android_static.mk create mode 100644 pythonforandroid/bootstraps/sdl3/build/jni/application/src/bootstrap_name.h create mode 100644 pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java create mode 100644 pythonforandroid/bootstraps/sdl3/build/src/patches/SDLActivity.java.patch create mode 100644 pythonforandroid/recipes/pyjnius/sdl3_jnienv_getter.patch create mode 100644 pythonforandroid/recipes/sdl3/__init__.py create mode 100644 pythonforandroid/recipes/sdl3_image/__init__.py create mode 100644 pythonforandroid/recipes/sdl3_image/enable-webp.patch create mode 100644 pythonforandroid/recipes/sdl3_mixer/__init__.py create mode 100644 pythonforandroid/recipes/sdl3_mixer/disable-libgme.patch create mode 100644 pythonforandroid/recipes/sdl3_ttf/__init__.py diff --git a/pythonforandroid/bootstrap.py b/pythonforandroid/bootstrap.py index 8bbbcf0eb6..5f4b3e7ab9 100755 --- a/pythonforandroid/bootstrap.py +++ b/pythonforandroid/bootstrap.py @@ -14,6 +14,8 @@ rmdir, move) from pythonforandroid.recipe import Recipe +SDL_BOOTSTRAPS = ("sdl2", "sdl3") + def copy_files(src_root, dest_root, override=True, symlink=False): for root, dirnames, filenames in walk(src_root): @@ -39,7 +41,7 @@ def copy_files(src_root, dest_root, override=True, symlink=False): default_recipe_priorities = [ - "webview", "sdl2", "service_only" # last is highest + "webview", "sdl2", "sdl3", "service_only" # last is highest ] # ^^ NOTE: these are just the default priorities if no special rules # apply (which you can find in the code below), so basically if no @@ -150,18 +152,18 @@ def get_bootstrap_dirs(self): return bootstrap_dirs def _copy_in_final_files(self): - if self.name == "sdl2": - # Get the paths for copying SDL2's java source code: - sdl2_recipe = Recipe.get_recipe("sdl2", self.ctx) - sdl2_build_dir = sdl2_recipe.get_jni_dir() - src_dir = join(sdl2_build_dir, "SDL", "android-project", + if self.name in SDL_BOOTSTRAPS: + # Get the paths for copying SDL's java source code: + sdl_recipe = Recipe.get_recipe(self.name, self.ctx) + sdl_build_dir = sdl_recipe.get_jni_dir() + src_dir = join(sdl_build_dir, "SDL", "android-project", "app", "src", "main", "java", "org", "libsdl", "app") target_dir = join(self.dist_dir, 'src', 'main', 'java', 'org', 'libsdl', 'app') # Do actual copying: - info('Copying in SDL2 .java files from: ' + str(src_dir)) + info('Copying in SDL .java files from: ' + str(src_dir)) if not os.path.exists(target_dir): os.makedirs(target_dir) copy_files(src_dir, target_dir, override=True) @@ -193,7 +195,7 @@ def assemble_distribution(self): @classmethod def all_bootstraps(cls): '''Find all the available bootstraps and return them.''' - forbidden_dirs = ('__pycache__', 'common') + forbidden_dirs = ('__pycache__', 'common', '_sdl_common') bootstraps_dir = join(dirname(__file__), 'bootstraps') result = set() for name in listdir(bootstraps_dir): @@ -272,6 +274,13 @@ def have_dependency_in_recipes(dep): info('Using sdl2 bootstrap since it is in dependencies') return cls.get_bootstrap("sdl2", ctx) + # Special rule: return SDL3 bootstrap if there's an sdl3 dep: + if (have_dependency_in_recipes("sdl3") and + "sdl3" in [b.name for b in acceptable_bootstraps] + ): + info('Using sdl3 bootstrap since it is in dependencies') + return cls.get_bootstrap("sdl3", ctx) + # Special rule: return "webview" if we depend on common web recipe: for possible_web_dep in known_web_packages: if have_dependency_in_recipes(possible_web_dep): diff --git a/pythonforandroid/bootstraps/_sdl_common/__init__.py b/pythonforandroid/bootstraps/_sdl_common/__init__.py new file mode 100644 index 0000000000..034e52c7c1 --- /dev/null +++ b/pythonforandroid/bootstraps/_sdl_common/__init__.py @@ -0,0 +1,49 @@ +from os.path import join + +import sh + +from pythonforandroid.toolchain import ( + Bootstrap, shprint, current_directory, info, info_main) +from pythonforandroid.util import ensure_dir, rmdir + + +class SDLGradleBootstrap(Bootstrap): + name = "_sdl_common" + + recipe_depends = [] + + def assemble_distribution(self): + info_main("# Creating Android project ({})".format(self.name)) + + rmdir(self.dist_dir) + info("Copying SDL/gradle build") + shprint(sh.cp, "-r", self.build_dir, self.dist_dir) + + # either the build use environment variable (ANDROID_HOME) + # or the local.properties if exists + with current_directory(self.dist_dir): + with open('local.properties', 'w') as fileh: + fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) + + with current_directory(self.dist_dir): + info("Copying Python distribution") + + self.distribute_javaclasses(self.ctx.javaclass_dir, + dest_dir=join("src", "main", "java")) + + for arch in self.ctx.archs: + python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle') + ensure_dir(python_bundle_dir) + + self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) + site_packages_dir = self.ctx.python_recipe.create_python_bundle( + join(self.dist_dir, python_bundle_dir), arch) + if not self.ctx.with_debug_symbols: + self.strip_libraries(arch) + self.fry_eggs(site_packages_dir) + + if 'sqlite3' not in self.ctx.recipe_build_order: + with open('blacklist.txt', 'a') as fileh: + fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') + + super().assemble_distribution() diff --git a/pythonforandroid/bootstraps/sdl2/build/.gitignore b/pythonforandroid/bootstraps/_sdl_common/build/.gitignore similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/.gitignore rename to pythonforandroid/bootstraps/_sdl_common/build/.gitignore diff --git a/pythonforandroid/bootstraps/sdl2/build/blacklist.txt b/pythonforandroid/bootstraps/_sdl_common/build/blacklist.txt similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/blacklist.txt rename to pythonforandroid/bootstraps/_sdl_common/build/blacklist.txt diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/Application.mk b/pythonforandroid/bootstraps/_sdl_common/build/jni/Application.mk similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/jni/Application.mk rename to pythonforandroid/bootstraps/_sdl_common/build/jni/Application.mk diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/assets/.gitkeep similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/assets/.gitkeep rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/assets/.gitkeep diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/jniLibs/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/java/.gitkeep similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/jniLibs/.gitkeep rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/java/.gitkeep diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java b/pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/GenericBroadcastReceiver.java diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java b/pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/GenericBroadcastReceiverCallback.java diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java b/pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/Project.java similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/Project.java rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/Project.java diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java b/pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/ProjectAdapter.java diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java b/pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/java/org/kivy/android/launcher/ProjectChooser.java diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/libs/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/jniLibs/.gitkeep similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/libs/.gitkeep rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/jniLibs/.gitkeep diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/libs/.gitkeep similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable/.gitkeep rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/libs/.gitkeep diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-hdpi/ic_launcher.png b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-hdpi/ic_launcher.png similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-hdpi/ic_launcher.png rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-mdpi/ic_launcher.png b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-mdpi/ic_launcher.png similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-mdpi/ic_launcher.png rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-mdpi/ic_launcher.png diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xhdpi/ic_launcher.png b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-xhdpi/ic_launcher.png similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xhdpi/ic_launcher.png rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xxhdpi/ic_launcher.png b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-xxhdpi/ic_launcher.png similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/drawable-xxhdpi/ic_launcher.png rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap-anydpi-v26/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable/.gitkeep similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap-anydpi-v26/.gitkeep rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/drawable/.gitkeep diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/chooser_item.xml b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/chooser_item.xml similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/chooser_item.xml rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/chooser_item.xml diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/main.xml b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/main.xml similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/main.xml rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/main.xml diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_chooser.xml b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/project_chooser.xml similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_chooser.xml rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/project_chooser.xml diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_empty.xml b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/project_empty.xml similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/layout/project_empty.xml rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/layout/project_empty.xml diff --git a/pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap-anydpi-v26/.gitkeep similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/src/main/res/mipmap/.gitkeep rename to pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap-anydpi-v26/.gitkeep diff --git a/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap/.gitkeep b/pythonforandroid/bootstraps/_sdl_common/build/src/main/res/mipmap/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml b/pythonforandroid/bootstraps/_sdl_common/build/templates/AndroidManifest.tmpl.xml similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/templates/AndroidManifest.tmpl.xml rename to pythonforandroid/bootstraps/_sdl_common/build/templates/AndroidManifest.tmpl.xml diff --git a/pythonforandroid/bootstraps/sdl2/build/templates/strings.tmpl.xml b/pythonforandroid/bootstraps/_sdl_common/build/templates/strings.tmpl.xml similarity index 100% rename from pythonforandroid/bootstraps/sdl2/build/templates/strings.tmpl.xml rename to pythonforandroid/bootstraps/_sdl_common/build/templates/strings.tmpl.xml diff --git a/pythonforandroid/bootstraps/common/build/build.py b/pythonforandroid/bootstraps/common/build/build.py index 94382049bf..99c4ea24ab 100644 --- a/pythonforandroid/bootstraps/common/build/build.py +++ b/pythonforandroid/bootstraps/common/build/build.py @@ -20,6 +20,7 @@ from fnmatch import fnmatch import jinja2 +from pythonforandroid.bootstrap import SDL_BOOTSTRAPS from pythonforandroid.util import rmdir, ensure_dir, max_build_tool_version @@ -83,7 +84,7 @@ def get_bootstrap_name(): if PYTHON is not None and not exists(PYTHON): PYTHON = None -if _bootstrap_name in ('sdl2', 'webview', 'service_only', 'qt'): +if _bootstrap_name in ('sdl2', 'sdl3', 'webview', 'service_only', 'qt'): WHITELIST_PATTERNS.append('pyconfig.h') environment = jinja2.Environment(loader=jinja2.FileSystemLoader( @@ -220,6 +221,10 @@ def compile_py_file(python_file, optimize_python=True): return ".".join([os.path.splitext(python_file)[0], "pyc"]) +def is_sdl_bootstrap(): + return get_bootstrap_name() in SDL_BOOTSTRAPS + + def make_package(args): # If no launcher is specified, require a main.py/main.pyc: if (get_bootstrap_name() != "sdl" or args.launcher is None) and \ @@ -541,7 +546,7 @@ def make_package(args): "debug": "debug" in args.build_mode, "native_services": args.native_services } - if get_bootstrap_name() == "sdl2": + if is_sdl_bootstrap(): render_args["url_scheme"] = url_scheme render( @@ -596,7 +601,7 @@ def make_package(args): "args": args, "private_version": hashlib.sha1(private_version.encode()).hexdigest() } - if get_bootstrap_name() == "sdl2": + if is_sdl_bootstrap(): render_args["url_scheme"] = url_scheme render( 'strings.tmpl.xml', @@ -769,7 +774,7 @@ def create_argument_parser(): ap.add_argument('--private', dest='private', help='the directory with the app source code files' + ' (containing your main.py entrypoint)', - required=(get_bootstrap_name() != "sdl2")) + required=(not is_sdl_bootstrap())) ap.add_argument('--package', dest='package', help=('The name of the java package the project will be' ' packaged under.'), @@ -787,7 +792,7 @@ def create_argument_parser(): 'same number of groups of numbers as previous ' 'versions.'), required=True) - if get_bootstrap_name() == "sdl2": + if is_sdl_bootstrap(): ap.add_argument('--launcher', dest='launcher', action='store_true', help=('Provide this argument to build a multi-app ' 'launcher, rather than a single app.')) @@ -1044,7 +1049,7 @@ def _read_configuration(): args.orientation, args.manifest_orientation ) - if get_bootstrap_name() == "sdl2": + if is_sdl_bootstrap(): args.sdl_orientation_hint = get_sdl_orientation_hint(args.orientation) if args.res_xmls and isinstance(args.res_xmls[0], list): @@ -1073,10 +1078,9 @@ def _read_configuration(): if x.strip() and not x.strip().startswith('#')] WHITELIST_PATTERNS += patterns - if args.private is None and \ - get_bootstrap_name() == 'sdl2' and args.launcher is None: + if args.private is None and is_sdl_bootstrap() and args.launcher is None: print('Need --private directory or ' + - '--launcher (SDL2 bootstrap only)' + + '--launcher (SDL2/SDL3 bootstrap only)' + 'to have something to launch inside the .apk!') sys.exit(1) make_package(args) diff --git a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk index fb2b17719d..eced58db08 100644 --- a/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk +++ b/pythonforandroid/bootstraps/common/build/jni/application/src/Android.mk @@ -9,12 +9,11 @@ SDL_PATH := ../../SDL LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include # Add your application source files here... -LOCAL_SRC_FILES := $(SDL_PATH)/src/main/android/SDL_android_main.c \ - start.c +LOCAL_SRC_FILES := start.c LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS) -LOCAL_SHARED_LIBRARIES := SDL2 python_shared +LOCAL_SHARED_LIBRARIES := python_shared LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS) diff --git a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c index 88999faf98..ef910cab3d 100644 --- a/pythonforandroid/bootstraps/common/build/jni/application/src/start.c +++ b/pythonforandroid/bootstraps/common/build/jni/application/src/start.c @@ -16,10 +16,16 @@ #include "bootstrap_name.h" -#ifndef BOOTSTRAP_USES_NO_SDL_HEADERS +#ifdef BOOTSTRAP_NAME_SDL2 #include "SDL.h" #include "SDL_opengles2.h" #endif + +#ifdef BOOTSTRAP_NAME_SDL3 +#include "SDL3/SDL.h" +#include "SDL3/SDL_main.h" +#endif + #include "android/log.h" #define ENTRYPOINT_MAXLEN 128 diff --git a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java index cc04d83f6b..83d11639bb 100644 --- a/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java +++ b/pythonforandroid/bootstraps/common/build/src/main/java/org/kivy/android/PythonUtil.java @@ -49,6 +49,10 @@ protected static ArrayList getLibraries(File libsDir) { addLibraryIfExists(libsList, "SDL2_image", libsDir); addLibraryIfExists(libsList, "SDL2_mixer", libsDir); addLibraryIfExists(libsList, "SDL2_ttf", libsDir); + addLibraryIfExists(libsList, "SDL3", libsDir); + addLibraryIfExists(libsList, "SDL3_image", libsDir); + addLibraryIfExists(libsList, "SDL3_mixer", libsDir); + addLibraryIfExists(libsList, "SDL3_ttf", libsDir); libsList.add("python3.5m"); libsList.add("python3.6m"); libsList.add("python3.7m"); diff --git a/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h index 8a4d8aa464..76709f02c9 100644 --- a/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h +++ b/pythonforandroid/bootstraps/qt/build/jni/application/src/bootstrap_name.h @@ -1,4 +1,3 @@ -#define BOOTSTRAP_USES_NO_SDL_HEADERS const char bootstrap_name[] = "qt"; diff --git a/pythonforandroid/bootstraps/sdl2/__init__.py b/pythonforandroid/bootstraps/sdl2/__init__.py index 9334724a33..0be9f9a23b 100644 --- a/pythonforandroid/bootstraps/sdl2/__init__.py +++ b/pythonforandroid/bootstraps/sdl2/__init__.py @@ -1,54 +1,12 @@ -from os.path import join +from pythonforandroid.bootstraps._sdl_common import SDLGradleBootstrap -import sh -from pythonforandroid.toolchain import ( - Bootstrap, shprint, current_directory, info, info_main) -from pythonforandroid.util import ensure_dir, rmdir - - -class SDL2GradleBootstrap(Bootstrap): - name = 'sdl2' +class SDL2GradleBootstrap(SDLGradleBootstrap): + name = "sdl2" recipe_depends = list( - set(Bootstrap.recipe_depends).union({'sdl2'}) + set(SDLGradleBootstrap.recipe_depends).union({"sdl2"}) ) - def assemble_distribution(self): - info_main("# Creating Android project ({})".format(self.name)) - - rmdir(self.dist_dir) - info("Copying SDL2/gradle build") - shprint(sh.cp, "-r", self.build_dir, self.dist_dir) - - # either the build use environment variable (ANDROID_HOME) - # or the local.properties if exists - with current_directory(self.dist_dir): - with open('local.properties', 'w') as fileh: - fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir)) - - with current_directory(self.dist_dir): - info("Copying Python distribution") - - self.distribute_javaclasses(self.ctx.javaclass_dir, - dest_dir=join("src", "main", "java")) - - for arch in self.ctx.archs: - python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle') - ensure_dir(python_bundle_dir) - - self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)]) - site_packages_dir = self.ctx.python_recipe.create_python_bundle( - join(self.dist_dir, python_bundle_dir), arch) - if not self.ctx.with_debug_symbols: - self.strip_libraries(arch) - self.fry_eggs(site_packages_dir) - - if 'sqlite3' not in self.ctx.recipe_build_order: - with open('blacklist.txt', 'a') as fileh: - fileh.write('\nsqlite3/*\nlib-dynload/_sqlite3.so\n') - - super().assemble_distribution() - bootstrap = SDL2GradleBootstrap() diff --git a/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android.mk new file mode 100644 index 0000000000..09fb3b212e --- /dev/null +++ b/pythonforandroid/bootstraps/sdl2/build/jni/application/src/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +SDL_PATH := ../../SDL + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include + +# Add your application source files here... +LOCAL_SRC_FILES := start.c + +LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS) + +LOCAL_SHARED_LIBRARIES := SDL2 python_shared + +LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS) + +LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS) + +include $(BUILD_SHARED_LIBRARY) diff --git a/pythonforandroid/bootstraps/sdl3/__init__.py b/pythonforandroid/bootstraps/sdl3/__init__.py new file mode 100644 index 0000000000..83f50493f7 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl3/__init__.py @@ -0,0 +1,12 @@ +from pythonforandroid.bootstraps._sdl_common import SDLGradleBootstrap + + +class SDL3GradleBootstrap(SDLGradleBootstrap): + name = "sdl3" + + recipe_depends = list( + set(SDLGradleBootstrap.recipe_depends).union({"sdl3"}) + ) + + +bootstrap = SDL3GradleBootstrap() diff --git a/pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android.mk b/pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android.mk new file mode 100644 index 0000000000..14b4e0ed66 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +SDL_PATH := ../../SDL + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/$(SDL_PATH)/include + +# Add your application source files here... +LOCAL_SRC_FILES := start.c + +LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS) + +LOCAL_SHARED_LIBRARIES := SDL3 python_shared + +LOCAL_LDLIBS := -lGLESv1_CM -lGLESv2 -llog $(EXTRA_LDLIBS) + +LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS) + +include $(BUILD_SHARED_LIBRARY) diff --git a/pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android_static.mk b/pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android_static.mk new file mode 100644 index 0000000000..f4ff2462e6 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl3/build/jni/application/src/Android_static.mk @@ -0,0 +1,13 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := main + +LOCAL_SRC_FILES := start.c + +LOCAL_STATIC_LIBRARIES := SDL3_static + + +include $(BUILD_SHARED_LIBRARY) +$(call import-module,SDL)LOCAL_PATH := $(call my-dir) diff --git a/pythonforandroid/bootstraps/sdl3/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/sdl3/build/jni/application/src/bootstrap_name.h new file mode 100644 index 0000000000..55096a4aad --- /dev/null +++ b/pythonforandroid/bootstraps/sdl3/build/jni/application/src/bootstrap_name.h @@ -0,0 +1,5 @@ + +#define BOOTSTRAP_NAME_SDL3 + +const char bootstrap_name[] = "SDL3"; // capitalized for historic reasons + diff --git a/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java b/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java new file mode 100644 index 0000000000..0a9c884cc4 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl3/build/src/main/java/org/kivy/android/PythonActivity.java @@ -0,0 +1,645 @@ +package org.kivy.android; + +import java.io.InputStream; +import java.io.FileWriter; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.PowerManager; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; +import android.view.SurfaceView; +import android.view.ViewGroup; +import android.view.View; +import android.widget.ImageView; +import android.widget.Toast; +import android.content.res.Resources.NotFoundException; + +import org.libsdl.app.SDLActivity; + +import org.kivy.android.launcher.Project; + +import org.renpy.android.ResourceManager; + + +public class PythonActivity extends SDLActivity { + private static final String TAG = "PythonActivity"; + + public static PythonActivity mActivity = null; + + private ResourceManager resourceManager = null; + private Bundle mMetaData = null; + private PowerManager.WakeLock mWakeLock = null; + + public String getAppRoot() { + String app_root = getFilesDir().getAbsolutePath() + "/app"; + return app_root; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "PythonActivity onCreate running"); + resourceManager = new ResourceManager(this); + + Log.v(TAG, "About to do super onCreate"); + super.onCreate(savedInstanceState); + Log.v(TAG, "Did super onCreate"); + + this.mActivity = this; + this.showLoadingScreen(this.getLoadingScreen()); + + new UnpackFilesTask().execute(getAppRoot()); + } + + public void loadLibraries() { + String app_root = new String(getAppRoot()); + File app_root_file = new File(app_root); + PythonUtil.loadLibraries(app_root_file, + new File(getApplicationInfo().nativeLibraryDir)); + } + + /** + * Show an error using a toast. (Only makes sense from non-UI + * threads.) + */ + public void toastError(final String msg) { + + final Activity thisActivity = this; + + runOnUiThread(new Runnable () { + public void run() { + Toast.makeText(thisActivity, msg, Toast.LENGTH_LONG).show(); + } + }); + + // Wait to show the error. + synchronized (this) { + try { + this.wait(1000); + } catch (InterruptedException e) { + } + } + } + + private class UnpackFilesTask extends AsyncTask { + @Override + protected String doInBackground(String... params) { + File app_root_file = new File(params[0]); + Log.v(TAG, "Ready to unpack"); + PythonUtil.unpackAsset(mActivity, "private", app_root_file, true); + PythonUtil.unpackPyBundle(mActivity, getApplicationInfo().nativeLibraryDir + "/" + "libpybundle", app_root_file, false); + return null; + } + + @Override + protected void onPostExecute(String result) { + // Figure out the directory where the game is. If the game was + // given to us via an intent, then we use the scheme-specific + // part of that intent to determine the file to launch. We + // also use the android.txt file to determine the orientation. + // + // Otherwise, we use the public data, if we have it, or the + // private data if we do not. + mActivity.finishLoad(); + + // finishLoad called setContentView with the SDL view, which + // removed the loading screen. However, we still need it to + // show until the app is ready to render, so pop it back up + // on top of the SDL view. + mActivity.showLoadingScreen(getLoadingScreen()); + + String app_root_dir = getAppRoot(); + if (getIntent() != null && getIntent().getAction() != null && + getIntent().getAction().equals("org.kivy.LAUNCH")) { + File path = new File(getIntent().getData().getSchemeSpecificPart()); + + Project p = Project.scanDirectory(path); + String entry_point = getEntryPoint(p.dir); + SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", p.dir + "/" + entry_point); + SDLActivity.nativeSetenv("ANDROID_ARGUMENT", p.dir); + SDLActivity.nativeSetenv("ANDROID_APP_PATH", p.dir); + + if (p != null) { + if (p.landscape) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + } + + // Let old apps know they started. + try { + FileWriter f = new FileWriter(new File(path, ".launch")); + f.write("started"); + f.close(); + } catch (IOException e) { + // pass + } + } else { + String entry_point = getEntryPoint(app_root_dir); + SDLActivity.nativeSetenv("ANDROID_ENTRYPOINT", entry_point); + SDLActivity.nativeSetenv("ANDROID_ARGUMENT", app_root_dir); + SDLActivity.nativeSetenv("ANDROID_APP_PATH", app_root_dir); + } + + String mFilesDirectory = mActivity.getFilesDir().getAbsolutePath(); + Log.v(TAG, "Setting env vars for start.c and Python to use"); + SDLActivity.nativeSetenv("ANDROID_PRIVATE", mFilesDirectory); + SDLActivity.nativeSetenv("ANDROID_UNPACK", app_root_dir); + SDLActivity.nativeSetenv("PYTHONHOME", app_root_dir); + SDLActivity.nativeSetenv("PYTHONPATH", app_root_dir + ":" + app_root_dir + "/lib"); + SDLActivity.nativeSetenv("PYTHONOPTIMIZE", "2"); + + try { + Log.v(TAG, "Access to our meta-data..."); + mActivity.mMetaData = mActivity.getPackageManager().getApplicationInfo( + mActivity.getPackageName(), PackageManager.GET_META_DATA).metaData; + + PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); + if ( mActivity.mMetaData.getInt("wakelock") == 1 ) { + mActivity.mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "Screen On"); + mActivity.mWakeLock.acquire(); + } + if ( mActivity.mMetaData.getInt("surface.transparent") != 0 ) { + Log.v(TAG, "Surface will be transparent."); + getSurface().setZOrderOnTop(true); + getSurface().getHolder().setFormat(PixelFormat.TRANSPARENT); + } else { + Log.i(TAG, "Surface will NOT be transparent"); + } + } catch (PackageManager.NameNotFoundException e) { + } + + // Launch app if that hasn't been done yet: + if (mActivity.mHasFocus && ( + // never went into proper resume state: + mActivity.mCurrentNativeState == NativeState.INIT || + ( + // resumed earlier but wasn't ready yet + mActivity.mCurrentNativeState == NativeState.RESUMED && + mActivity.mSDLThread == null + ))) { + // Because sometimes the app will get stuck here and never + // actually run, ensure that it gets launched if we're active: + mActivity.resumeNativeThread(); + } + } + + @Override + protected void onPreExecute() { + } + + @Override + protected void onProgressUpdate(Void... values) { + } + } + + public static ViewGroup getLayout() { + return mLayout; + } + + public static SurfaceView getSurface() { + return mSurface; + } + + //---------------------------------------------------------------------------- + // Listener interface for onNewIntent + // + + public interface NewIntentListener { + void onNewIntent(Intent intent); + } + + private List newIntentListeners = null; + + public void registerNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + this.newIntentListeners = Collections.synchronizedList(new ArrayList()); + this.newIntentListeners.add(listener); + } + + public void unregisterNewIntentListener(NewIntentListener listener) { + if ( this.newIntentListeners == null ) + return; + this.newIntentListeners.remove(listener); + } + + @Override + protected void onNewIntent(Intent intent) { + if ( this.newIntentListeners == null ) + return; + this.onResume(); + synchronized ( this.newIntentListeners ) { + Iterator iterator = this.newIntentListeners.iterator(); + while ( iterator.hasNext() ) { + (iterator.next()).onNewIntent(intent); + } + } + } + + //---------------------------------------------------------------------------- + // Listener interface for onActivityResult + // + + public interface ActivityResultListener { + void onActivityResult(int requestCode, int resultCode, Intent data); + } + + private List activityResultListeners = null; + + public void registerActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + this.activityResultListeners = Collections.synchronizedList(new ArrayList()); + this.activityResultListeners.add(listener); + } + + public void unregisterActivityResultListener(ActivityResultListener listener) { + if ( this.activityResultListeners == null ) + return; + this.activityResultListeners.remove(listener); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + if ( this.activityResultListeners == null ) + return; + this.onResume(); + synchronized ( this.activityResultListeners ) { + Iterator iterator = this.activityResultListeners.iterator(); + while ( iterator.hasNext() ) + (iterator.next()).onActivityResult(requestCode, resultCode, intent); + } + } + + public static void start_service( + String serviceTitle, + String serviceDescription, + String pythonServiceArgument + ) { + _do_start_service( + serviceTitle, serviceDescription, pythonServiceArgument, true + ); + } + + public static void start_service_not_as_foreground( + String serviceTitle, + String serviceDescription, + String pythonServiceArgument + ) { + _do_start_service( + serviceTitle, serviceDescription, pythonServiceArgument, false + ); + } + + public static void _do_start_service( + String serviceTitle, + String serviceDescription, + String pythonServiceArgument, + boolean showForegroundNotification + ) { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + String argument = PythonActivity.mActivity.getFilesDir().getAbsolutePath(); + String app_root_dir = PythonActivity.mActivity.getAppRoot(); + String entry_point = PythonActivity.mActivity.getEntryPoint(app_root_dir + "/service"); + serviceIntent.putExtra("androidPrivate", argument); + serviceIntent.putExtra("androidArgument", app_root_dir); + serviceIntent.putExtra("serviceEntrypoint", "service/" + entry_point); + serviceIntent.putExtra("pythonName", "python"); + serviceIntent.putExtra("pythonHome", app_root_dir); + serviceIntent.putExtra("pythonPath", app_root_dir + ":" + app_root_dir + "/lib"); + serviceIntent.putExtra("serviceStartAsForeground", + (showForegroundNotification ? "true" : "false") + ); + serviceIntent.putExtra("serviceTitle", serviceTitle); + serviceIntent.putExtra("serviceDescription", serviceDescription); + serviceIntent.putExtra("pythonServiceArgument", pythonServiceArgument); + PythonActivity.mActivity.startService(serviceIntent); + } + + public static void stop_service() { + Intent serviceIntent = new Intent(PythonActivity.mActivity, PythonService.class); + PythonActivity.mActivity.stopService(serviceIntent); + } + + /** Loading screen view **/ + public static ImageView mImageView = null; + public static View mLottieView = null; + /** Whether main routine/actual app has started yet **/ + protected boolean mAppConfirmedActive = false; + /** Timer for delayed loading screen removal. **/ + protected Timer loadingScreenRemovalTimer = null; + + // Overridden since it's called often, to check whether to remove the + // loading screen: + @Override + protected boolean sendCommand(int command, Object data) { + boolean result = super.sendCommand(command, data); + considerLoadingScreenRemoval(); + return result; + } + + /** Confirm that the app's main routine has been launched. + **/ + @Override + public void appConfirmedActive() { + if (!mAppConfirmedActive) { + Log.v(TAG, "appConfirmedActive() -> preparing loading screen removal"); + mAppConfirmedActive = true; + considerLoadingScreenRemoval(); + } + } + + /** This is called from various places to check whether the app's main + * routine has been launched already, and if it has, then the loading + * screen will be removed. + **/ + public void considerLoadingScreenRemoval() { + if (loadingScreenRemovalTimer != null) + return; + runOnUiThread(new Runnable() { + public void run() { + if (((PythonActivity)PythonActivity.mSingleton).mAppConfirmedActive && + loadingScreenRemovalTimer == null) { + // Remove loading screen but with a delay. + // (app can use p4a's android.loadingscreen module to + // do it quicker if it wants to) + // get a handler (call from main thread) + // this will run when timer elapses + TimerTask removalTask = new TimerTask() { + @Override + public void run() { + // post a runnable to the handler + runOnUiThread(new Runnable() { + @Override + public void run() { + PythonActivity activity = + ((PythonActivity)PythonActivity.mSingleton); + if (activity != null) + activity.removeLoadingScreen(); + } + }); + } + }; + loadingScreenRemovalTimer = new Timer(); + loadingScreenRemovalTimer.schedule(removalTask, 5000); + } + } + }); + } + + public void removeLoadingScreen() { + runOnUiThread(new Runnable() { + public void run() { + View view = mLottieView != null ? mLottieView : mImageView; + if (view != null && view.getParent() != null) { + ((ViewGroup)view.getParent()).removeView(view); + mLottieView = null; + mImageView = null; + } + } + }); + } + + public String getEntryPoint(String search_dir) { + /* Get the main file (.pyc|.py) depending on if we + * have a compiled version or not. + */ + List entryPoints = new ArrayList(); + entryPoints.add("main.pyc"); // python 3 compiled files + for (String value : entryPoints) { + File mainFile = new File(search_dir + "/" + value); + if (mainFile.exists()) { + return value; + } + } + return "main.py"; + } + + protected void showLoadingScreen(View view) { + try { + if (mLayout == null) { + setContentView(view); + } else if (view.getParent() == null) { + mLayout.addView(view); + } + } catch (IllegalStateException e) { + // The loading screen can be attempted to be applied twice if app + // is tabbed in/out, quickly. + // (Gives error "The specified child already has a parent. + // You must call removeView() on the child's parent first.") + } + } + + protected void setBackgroundColor(View view) { + /* + * Set the presplash loading screen background color + * https://developer.android.com/reference/android/graphics/Color.html + * Parse the color string, and return the corresponding color-int. + * If the string cannot be parsed, throws an IllegalArgumentException exception. + * Supported formats are: #RRGGBB #AARRGGBB or one of the following names: + * 'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow', + * 'lightgray', 'darkgray', 'grey', 'lightgrey', 'darkgrey', 'aqua', 'fuchsia', + * 'lime', 'maroon', 'navy', 'olive', 'purple', 'silver', 'teal'. + */ + String backgroundColor = resourceManager.getString("presplash_color"); + if (backgroundColor != null) { + try { + view.setBackgroundColor(Color.parseColor(backgroundColor)); + } catch (IllegalArgumentException e) {} + } + } + + protected View getLoadingScreen() { + // If we have an mLottieView or mImageView already, then do + // nothing because it will have already been made the content + // view or added to the layout. + if (mLottieView != null || mImageView != null) { + // we already have a splash screen + return mLottieView != null ? mLottieView : mImageView; + } + + // first try to load the lottie one + try { + mLottieView = getLayoutInflater().inflate( + this.resourceManager.getIdentifier("lottie", "layout"), + mLayout, + false + ); + try { + if (mLayout == null) { + setContentView(mLottieView); + } else if (PythonActivity.mLottieView.getParent() == null) { + mLayout.addView(mLottieView); + } + } catch (IllegalStateException e) { + // The loading screen can be attempted to be applied twice if app + // is tabbed in/out, quickly. + // (Gives error "The specified child already has a parent. + // You must call removeView() on the child's parent first.") + } + setBackgroundColor(mLottieView); + return mLottieView; + } + catch (NotFoundException e) { + Log.v("SDL", "couldn't find lottie layout or animation, trying static splash"); + } + + // no lottie asset, try to load the static image then + int presplashId = this.resourceManager.getIdentifier("presplash", "drawable"); + InputStream is = this.getResources().openRawResource(presplashId); + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(is); + } finally { + try { + is.close(); + } catch (IOException e) {}; + } + + mImageView = new ImageView(this); + mImageView.setImageBitmap(bitmap); + setBackgroundColor(mImageView); + + mImageView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT)); + mImageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + return mImageView; + } + + @Override + protected void onPause() { + if (this.mWakeLock != null && mWakeLock.isHeld()) { + this.mWakeLock.release(); + } + + Log.v(TAG, "onPause()"); + try { + super.onPause(); + } catch (UnsatisfiedLinkError e) { + // Catch pause while still in loading screen failing to + // call native function (since it's not yet loaded) + } + } + + @Override + protected void onResume() { + if (this.mWakeLock != null) { + this.mWakeLock.acquire(); + } + Log.v(TAG, "onResume()"); + try { + super.onResume(); + } catch (UnsatisfiedLinkError e) { + // Catch resume while still in loading screen failing to + // call native function (since it's not yet loaded) + } + considerLoadingScreenRemoval(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + try { + super.onWindowFocusChanged(hasFocus); + } catch (UnsatisfiedLinkError e) { + // Catch window focus while still in loading screen failing to + // call native function (since it's not yet loaded) + } + considerLoadingScreenRemoval(); + } + + /** + * Used by android.permissions p4a module to register a call back after + * requesting runtime permissions + **/ + public interface PermissionsCallback { + void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults); + } + + private PermissionsCallback permissionCallback; + private boolean havePermissionsCallback = false; + + public void addPermissionsCallback(PermissionsCallback callback) { + permissionCallback = callback; + havePermissionsCallback = true; + Log.v(TAG, "addPermissionsCallback(): Added callback for onRequestPermissionsResult"); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + Log.v(TAG, "onRequestPermissionsResult()"); + if (havePermissionsCallback) { + Log.v(TAG, "onRequestPermissionsResult passed to callback"); + permissionCallback.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + /** + * Used by android.permissions p4a module to check a permission + **/ + public boolean checkCurrentPermission(String permission) { + if (android.os.Build.VERSION.SDK_INT < 23) + return true; + + try { + java.lang.reflect.Method methodCheckPermission = + Activity.class.getMethod("checkSelfPermission", String.class); + Object resultObj = methodCheckPermission.invoke(this, permission); + int result = Integer.parseInt(resultObj.toString()); + if (result == PackageManager.PERMISSION_GRANTED) + return true; + } catch (IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + } + return false; + } + + /** + * Used by android.permissions p4a module to request runtime permissions + **/ + public void requestPermissionsWithRequestCode(String[] permissions, int requestCode) { + if (android.os.Build.VERSION.SDK_INT < 23) + return; + try { + java.lang.reflect.Method methodRequestPermission = + Activity.class.getMethod("requestPermissions", + String[].class, int.class); + methodRequestPermission.invoke(this, permissions, requestCode); + } catch (IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { + } + } + + public void requestPermissions(String[] permissions) { + requestPermissionsWithRequestCode(permissions, 1); + } + + public static void changeKeyboard(int inputType) { + /* + if (SDLActivity.keyboardInputType != inputType){ + SDLActivity.keyboardInputType = inputType; + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.restartInput(mTextEdit); + } + */ + } +} diff --git a/pythonforandroid/bootstraps/sdl3/build/src/patches/SDLActivity.java.patch b/pythonforandroid/bootstraps/sdl3/build/src/patches/SDLActivity.java.patch new file mode 100644 index 0000000000..e1ad50cda5 --- /dev/null +++ b/pythonforandroid/bootstraps/sdl3/build/src/patches/SDLActivity.java.patch @@ -0,0 +1,49 @@ +--- a/src/main/java/org/libsdl/app/SDLActivity.java ++++ b/src/main/java/org/libsdl/app/SDLActivity.java +@@ -259,6 +259,7 @@ + String[] arguments = SDLActivity.mSingleton.getArguments(); + + Log.v("SDL", "Running main function " + function + " from library " + library); ++ SDLActivity.mSingleton.appConfirmedActive(); + SDLActivity.nativeRunMain(library, function, arguments); + Log.v("SDL", "Finished main function"); + } +@@ -351,6 +352,15 @@ + Log.v(TAG, "Model: " + Build.MODEL); + Log.v(TAG, "onCreate()"); + super.onCreate(savedInstanceState); ++ ++ SDL.initialize(); ++ // So we can call stuff from static callbacks ++ mSingleton = this; ++ } ++ ++ // We don't do this in onCreate because we unpack and load the app data on a thread ++ // and we can't run setup tasks until that thread completes. ++ protected void finishLoad() { + + + /* Control activity re-creation */ +@@ -1541,8 +1551,22 @@ + return null; + } + return SDLActivity.mSurface.getNativeSurface(); ++ } ++ ++ /** ++ * Calls turnActive() on singleton to keep loading screen active ++ */ ++ public static void triggerAppConfirmedActive() { ++ mSingleton.appConfirmedActive(); + } + ++ /** ++ * Trick needed for loading screen, overridden by PythonActivity ++ * to keep loading screen active ++ */ ++ public void appConfirmedActive() { ++ } ++ + // Input + + /** diff --git a/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h index 01fd122890..95bd2ef3ae 100644 --- a/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h +++ b/pythonforandroid/bootstraps/service_library/build/jni/application/src/bootstrap_name.h @@ -1,6 +1,5 @@ #define BOOTSTRAP_NAME_LIBRARY -#define BOOTSTRAP_USES_NO_SDL_HEADERS const char bootstrap_name[] = "service_library"; diff --git a/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h index b93a4ae6ce..9598d1abfe 100644 --- a/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h +++ b/pythonforandroid/bootstraps/service_only/build/jni/application/src/bootstrap_name.h @@ -1,6 +1,5 @@ #define BOOTSTRAP_NAME_SERVICEONLY -#define BOOTSTRAP_USES_NO_SDL_HEADERS const char bootstrap_name[] = "service_only"; diff --git a/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h b/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h index 11c7905dfe..87b64ac724 100644 --- a/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h +++ b/pythonforandroid/bootstraps/webview/build/jni/application/src/bootstrap_name.h @@ -1,6 +1,5 @@ #define BOOTSTRAP_NAME_WEBVIEW -#define BOOTSTRAP_USES_NO_SDL_HEADERS const char bootstrap_name[] = "webview"; diff --git a/pythonforandroid/build.py b/pythonforandroid/build.py index 98e2d70b2b..8b1c723423 100644 --- a/pythonforandroid/build.py +++ b/pythonforandroid/build.py @@ -892,6 +892,10 @@ def copylibs_function(soname, objs_paths, extra_link_dirs=None, env=None): 'SDL2_ttf', 'SDL2_image', 'SDL2_mixer', + 'SDL3', + 'SDL3_ttf', + 'SDL3_image', + 'SDL3_mixer', ) found_libs = [] sofiles = [] diff --git a/pythonforandroid/recipes/android/__init__.py b/pythonforandroid/recipes/android/__init__.py index 608d9ee738..5174a69bfa 100644 --- a/pythonforandroid/recipes/android/__init__.py +++ b/pythonforandroid/recipes/android/__init__.py @@ -12,7 +12,7 @@ class AndroidRecipe(IncludedFilesBehaviour, CythonRecipe): src_filename = 'src' - depends = [('sdl2', 'genericndkbuild'), 'pyjnius'] + depends = [('sdl3', 'sdl2', 'genericndkbuild'), 'pyjnius'] config_env = {} @@ -34,8 +34,7 @@ def prebuild_arch(self, arch): if isinstance(ctx_bootstrap, bytes): ctx_bootstrap = ctx_bootstrap.decode('utf-8') bootstrap = bootstrap_name = ctx_bootstrap - is_sdl2 = (bootstrap_name == "sdl2") - if bootstrap_name in ["sdl2", "webview", "service_only", "service_library", "qt"]: + if bootstrap_name in ["sdl2", "sdl3", "webview", "service_only", "service_library", "qt"]: java_ns = u'org.kivy.android' jni_ns = u'org/kivy/android' else: @@ -47,7 +46,8 @@ def prebuild_arch(self, arch): config = { 'BOOTSTRAP': bootstrap, - 'IS_SDL2': int(is_sdl2), + 'IS_SDL2': int(bootstrap_name == "sdl2"), + 'IS_SDL3': int(bootstrap_name == "sdl3"), 'PY2': 0, 'JAVA_NAMESPACE': java_ns, 'JNI_NAMESPACE': jni_ns, @@ -73,11 +73,16 @@ def prebuild_arch(self, arch): )) self.config_env[key] = str(value) - if is_sdl2: + if bootstrap_name == "sdl2": fh.write('JNIEnv *SDL_AndroidGetJNIEnv(void);\n') fh.write( '#define SDL_ANDROID_GetJNIEnv SDL_AndroidGetJNIEnv\n' ) + elif bootstrap_name == "sdl3": + fh.write('JNIEnv *SDL_GetAndroidJNIEnv(void);\n') + fh.write( + '#define SDL_ANDROID_GetJNIEnv SDL_GetAndroidJNIEnv\n' + ) else: fh.write('JNIEnv *WebView_AndroidGetJNIEnv(void);\n') fh.write( diff --git a/pythonforandroid/recipes/android/src/android/_android.pyx b/pythonforandroid/recipes/android/src/android/_android.pyx index 6708b846a8..1d6e65a161 100644 --- a/pythonforandroid/recipes/android/src/android/_android.pyx +++ b/pythonforandroid/recipes/android/src/android/_android.pyx @@ -194,7 +194,7 @@ TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 112 TYPE_TEXT_VARIATION_URI = 16 TYPE_CLASS_PHONE = 3 -IF BOOTSTRAP == 'sdl2': +IF BOOTSTRAP in ['sdl2', 'sdl3']: def remove_presplash(): # Remove android presplash in SDL2 bootstrap. mActivity.removeLoadingScreen() diff --git a/pythonforandroid/recipes/android/src/setup.py b/pythonforandroid/recipes/android/src/setup.py index bcd411f46b..0f5ceb1fd3 100755 --- a/pythonforandroid/recipes/android/src/setup.py +++ b/pythonforandroid/recipes/android/src/setup.py @@ -3,7 +3,8 @@ library_dirs = ['libs/' + os.environ['ARCH']] lib_dict = { - 'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf'] + 'sdl2': ['SDL2', 'SDL2_image', 'SDL2_mixer', 'SDL2_ttf'], + 'sdl3': ['SDL3', 'SDL3_image', 'SDL3_mixer', 'SDL3_ttf'], } sdl_libs = lib_dict.get(os.environ['BOOTSTRAP'], ['main']) diff --git a/pythonforandroid/recipes/genericndkbuild/__init__.py b/pythonforandroid/recipes/genericndkbuild/__init__.py index 8b2a9c26a2..9e85aac5d6 100644 --- a/pythonforandroid/recipes/genericndkbuild/__init__.py +++ b/pythonforandroid/recipes/genericndkbuild/__init__.py @@ -10,7 +10,7 @@ class GenericNDKBuildRecipe(BootstrapNDKRecipe): url = None depends = ['python3'] - conflicts = ['sdl2'] + conflicts = ['sdl2', 'sdl3'] def should_build(self, arch): return True diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py index b28e03a7a5..a5030abf95 100644 --- a/pythonforandroid/recipes/kivy/__init__.py +++ b/pythonforandroid/recipes/kivy/__init__.py @@ -26,8 +26,9 @@ class KivyRecipe(CythonRecipe): url = 'https://github.com/kivy/kivy/archive/{version}.zip' name = 'kivy' - depends = ['sdl2', 'pyjnius', 'setuptools'] + depends = [('sdl2', 'sdl3'), 'pyjnius', 'setuptools'] python_depends = ['certifi', 'chardet', 'idna', 'requests', 'urllib3', 'filetype'] + hostpython_prerequisites = [] # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock. # See: https://github.com/kivy/kivy/pull/8025 @@ -53,7 +54,7 @@ def cythonize_build(self, env, build_dir='.'): def cythonize_file(self, env, build_dir, filename): # We can ignore a few files that aren't important to the # android build, and may not work on Android anyway - do_not_cythonize = ['window_x11.pyx', ] + do_not_cythonize = ['window_x11.pyx', 'camera_avfoundation.pyx', 'img_imageio.pyx', 'egl_angle_metal.pyx'] if basename(filename) in do_not_cythonize: return super().cythonize_file(env, build_dir, filename) @@ -73,6 +74,21 @@ def get_recipe_env(self, arch): *sdl2_mixer_recipe.get_include_dirs(arch), join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'), ]) + if "sdl3" in self.ctx.recipe_build_order: + sdl3_mixer_recipe = self.get_recipe("sdl3_mixer", self.ctx) + sdl3_image_recipe = self.get_recipe("sdl3_image", self.ctx) + sdl3_ttf_recipe = self.get_recipe("sdl3_ttf", self.ctx) + sdl3_recipe = self.get_recipe("sdl3", self.ctx) + env["USE_SDL3"] = "1" + env["KIVY_SPLIT_EXAMPLES"] = "1" + env["KIVY_SDL3_PATH"] = ":".join( + [ + *sdl3_mixer_recipe.get_include_dirs(arch), + *sdl3_image_recipe.get_include_dirs(arch), + *sdl3_ttf_recipe.get_include_dirs(arch), + *sdl3_recipe.get_include_dirs(arch), + ] + ) return env diff --git a/pythonforandroid/recipes/pyjnius/__init__.py b/pythonforandroid/recipes/pyjnius/__init__.py index 0bcb74d392..00369df216 100644 --- a/pythonforandroid/recipes/pyjnius/__init__.py +++ b/pythonforandroid/recipes/pyjnius/__init__.py @@ -9,10 +9,13 @@ class PyjniusRecipe(CythonRecipe): version = '1.6.1' url = 'https://github.com/kivy/pyjnius/archive/{version}.zip' name = 'pyjnius' - depends = [('genericndkbuild', 'sdl2'), 'six'] + depends = [('genericndkbuild', 'sdl2', 'sdl3'), 'six'] site_packages_name = 'jnius' - patches = [('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild'))] + patches = [ + ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild')), + ('sdl3_jnienv_getter.patch', will_build('sdl3')), + ] def get_recipe_env(self, arch): env = super().get_recipe_env(arch) diff --git a/pythonforandroid/recipes/pyjnius/sdl3_jnienv_getter.patch b/pythonforandroid/recipes/pyjnius/sdl3_jnienv_getter.patch new file mode 100644 index 0000000000..d91da76fbb --- /dev/null +++ b/pythonforandroid/recipes/pyjnius/sdl3_jnienv_getter.patch @@ -0,0 +1,24 @@ +diff -Naur pyjnius.orig/jnius/env.py pyjnius/jnius/env.py +--- pyjnius.orig/jnius/env.py 2022-05-28 11:16:02.000000000 +0200 ++++ pyjnius/jnius/env.py 2022-05-28 11:18:30.000000000 +0200 +@@ -268,7 +268,7 @@ + + class AndroidJavaLocation(UnixJavaLocation): + def get_libraries(self): +- return ['SDL2', 'log'] ++ return ['SDL3', 'log'] + + def get_include_dirs(self): + # When cross-compiling for Android, we should not use the include dirs +diff -Naur pyjnius.orig/jnius/jnius_jvm_android.pxi pyjnius/jnius/jnius_jvm_android.pxi +--- pyjnius.orig/jnius/jnius_jvm_android.pxi 2022-05-28 11:16:02.000000000 +0200 ++++ pyjnius/jnius/jnius_jvm_android.pxi 2022-05-28 11:17:17.000000000 +0200 +@@ -1,6 +1,6 @@ + # on android, rely on SDL to get the JNI env +-cdef extern JNIEnv *SDL_AndroidGetJNIEnv() ++cdef extern JNIEnv *SDL_GetAndroidJNIEnv() + + + cdef JNIEnv *get_platform_jnienv() except NULL: +- return SDL_AndroidGetJNIEnv() ++ return SDL_GetAndroidJNIEnv() diff --git a/pythonforandroid/recipes/sdl2/__init__.py b/pythonforandroid/recipes/sdl2/__init__.py index 8d5fbc2dc2..cd0185c717 100644 --- a/pythonforandroid/recipes/sdl2/__init__.py +++ b/pythonforandroid/recipes/sdl2/__init__.py @@ -10,6 +10,8 @@ class LibSDL2Recipe(BootstrapNDKRecipe): url = "https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL2-{version}.tar.gz" md5sum = 'a344eb827a03045c9b399e99af4af13d' + conflicts = ['sdl3'] + dir_name = 'SDL' depends = ['sdl2_image', 'sdl2_mixer', 'sdl2_ttf'] diff --git a/pythonforandroid/recipes/sdl3/__init__.py b/pythonforandroid/recipes/sdl3/__init__.py new file mode 100644 index 0000000000..6b9c8ee7c6 --- /dev/null +++ b/pythonforandroid/recipes/sdl3/__init__.py @@ -0,0 +1,59 @@ +from os.path import exists, join + +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.toolchain import current_directory, shprint +import sh + + +class LibSDL3Recipe(BootstrapNDKRecipe): + version = "3.2.10" + url = "https://github.com/libsdl-org/SDL/releases/download/release-{version}/SDL3-{version}.tar.gz" + md5sum = "70cda886bcf5a4fdac550db796d2efa2" + + conflicts = ["sdl2"] + + dir_name = "SDL" + + depends = ["sdl3_image", "sdl3_mixer", "sdl3_ttf"] + + def get_recipe_env( + self, arch=None, with_flags_in_cc=True, with_python=True + ): + env = super().get_recipe_env( + arch=arch, + with_flags_in_cc=with_flags_in_cc, + with_python=with_python, + ) + env["APP_ALLOW_MISSING_DEPS"] = "true" + return env + + def get_include_dirs(self, arch): + return [ + join(self.ctx.bootstrap.build_dir, "jni", "SDL", "include"), + join(self.ctx.bootstrap.build_dir, "jni", "SDL", "include", "SDL3"), + ] + + def should_build(self, arch): + libdir = join(self.get_build_dir(arch.arch), "../..", "libs", arch.arch) + libs = [ + "libmain.so", + "libSDL3.so", + "libSDL3_image.so", + "libSDL3_mixer.so", + "libSDL3_ttf.so", + ] + return not all(exists(join(libdir, x)) for x in libs) + + def build_arch(self, arch): + env = self.get_recipe_env(arch) + + with current_directory(self.get_jni_dir()): + shprint( + sh.Command(join(self.ctx.ndk_dir, "ndk-build")), + "V=1", + "NDK_DEBUG=" + ("1" if self.ctx.build_as_debuggable else "0"), + _env=env, + ) + + +recipe = LibSDL3Recipe() diff --git a/pythonforandroid/recipes/sdl3_image/__init__.py b/pythonforandroid/recipes/sdl3_image/__init__.py new file mode 100644 index 0000000000..f6d705b168 --- /dev/null +++ b/pythonforandroid/recipes/sdl3_image/__init__.py @@ -0,0 +1,41 @@ +import os +import sh +from pythonforandroid.logger import shprint +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.util import current_directory + + +class LibSDL3Image(BootstrapNDKRecipe): + version = "3.2.4" + url = "https://github.com/libsdl-org/SDL_image/releases/download/release-{version}/SDL3_image-{version}.tar.gz" + dir_name = "SDL3_image" + + patches = ["enable-webp.patch"] + + def get_include_dirs(self, arch): + return [ + os.path.join( + self.ctx.bootstrap.build_dir, "jni", "SDL3_image", "include" + ), + os.path.join( + self.ctx.bootstrap.build_dir, + "jni", + "SDL3_image", + "include", + "SDL3_image", + ), + ] + + def prebuild_arch(self, arch): + # We do not have a folder for each arch on BootstrapNDKRecipe, so we + # need to skip the external deps download if we already have done it. + external_deps_dir = os.path.join( + self.get_build_dir(arch.arch), "external" + ) + if not os.path.exists(os.path.join(external_deps_dir, "libwebp")): + with current_directory(external_deps_dir): + shprint(sh.Command("./download.sh")) + super().prebuild_arch(arch) + + +recipe = LibSDL3Image() diff --git a/pythonforandroid/recipes/sdl3_image/enable-webp.patch b/pythonforandroid/recipes/sdl3_image/enable-webp.patch new file mode 100644 index 0000000000..98d72f2017 --- /dev/null +++ b/pythonforandroid/recipes/sdl3_image/enable-webp.patch @@ -0,0 +1,12 @@ +diff -Naur SDL2_image.orig/Android.mk SDL2_image/Android.mk +--- SDL2_image.orig/Android.mk 2022-10-03 20:51:52.000000000 +0200 ++++ SDL2_image/Android.mk 2022-10-03 20:52:48.000000000 +0200 +@@ -32,7 +32,7 @@ + + # Enable this if you want to support loading WebP images + # The library path should be a relative path to this directory. +-SUPPORT_WEBP ?= false ++SUPPORT_WEBP := true + WEBP_LIBRARY_PATH := external/libwebp + + diff --git a/pythonforandroid/recipes/sdl3_mixer/__init__.py b/pythonforandroid/recipes/sdl3_mixer/__init__.py new file mode 100644 index 0000000000..c60c5bc157 --- /dev/null +++ b/pythonforandroid/recipes/sdl3_mixer/__init__.py @@ -0,0 +1,45 @@ +import os +import sh +from pythonforandroid.logger import shprint +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.util import current_directory + + +class LibSDL3Mixer(BootstrapNDKRecipe): + version = "72a73339731a12c1002f9caca64f1ab924938102" + # url = "https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL3_ttf-{version}.tar.gz" + url = "https://github.com/libsdl-org/SDL_mixer/archive/{version}.tar.gz" + dir_name = "SDL3_mixer" + + patches = ["disable-libgme.patch"] + + def get_include_dirs(self, arch): + return [ + os.path.join( + self.ctx.bootstrap.build_dir, "jni", "SDL3_mixer", "include" + ), + os.path.join( + self.ctx.bootstrap.build_dir, + "jni", + "SDL3_mixer", + "include", + "SDL3_mixer", + ), + ] + + def prebuild_arch(self, arch): + # We do not have a folder for each arch on BootstrapNDKRecipe, so we + # need to skip the external deps download if we already have done it. + external_deps_dir = os.path.join( + self.get_build_dir(arch.arch), "external" + ) + + if not os.path.exists( + os.path.join(external_deps_dir, "libgme", "Android.mk") + ): + with current_directory(external_deps_dir): + shprint(sh.Command("./download.sh")) + super().prebuild_arch(arch) + + +recipe = LibSDL3Mixer() diff --git a/pythonforandroid/recipes/sdl3_mixer/disable-libgme.patch b/pythonforandroid/recipes/sdl3_mixer/disable-libgme.patch new file mode 100644 index 0000000000..233808e7db --- /dev/null +++ b/pythonforandroid/recipes/sdl3_mixer/disable-libgme.patch @@ -0,0 +1,12 @@ +diff -Naur SDL3_mixer.orig/Android.mk SDL3_mixer/Android.mk +--- SDL3_mixer.orig/Android.mk 2025-03-16 21:05:19 ++++ SDL3_mixer/Android.mk 2025-03-16 21:06:33 +@@ -31,7 +31,7 @@ + WAVPACK_LIBRARY_PATH := external/wavpack + + # Enable this if you want to support loading music via libgme +-SUPPORT_GME ?= true ++SUPPORT_GME ?= false + GME_LIBRARY_PATH := external/libgme + + # Enable this if you want to support loading MOD music via XMP-lite diff --git a/pythonforandroid/recipes/sdl3_ttf/__init__.py b/pythonforandroid/recipes/sdl3_ttf/__init__.py new file mode 100644 index 0000000000..a0ebfac7a5 --- /dev/null +++ b/pythonforandroid/recipes/sdl3_ttf/__init__.py @@ -0,0 +1,39 @@ +import os +import sh +from pythonforandroid.logger import shprint +from pythonforandroid.recipe import BootstrapNDKRecipe +from pythonforandroid.util import current_directory + + +class LibSDL3TTF(BootstrapNDKRecipe): + version = "3.2.2" + url = "https://github.com/libsdl-org/SDL_ttf/releases/download/release-{version}/SDL3_ttf-{version}.tar.gz" + dir_name = "SDL3_ttf" + + def get_include_dirs(self, arch): + return [ + os.path.join( + self.ctx.bootstrap.build_dir, "jni", "SDL3_ttf", "include" + ), + os.path.join( + self.ctx.bootstrap.build_dir, + "jni", + "SDL3_ttf", + "include", + "SDL3_ttf", + ), + ] + + def prebuild_arch(self, arch): + # We do not have a folder for each arch on BootstrapNDKRecipe, so we + # need to skip the external deps download if we already have done it. + external_deps_dir = os.path.join( + self.get_build_dir(arch.arch), "external" + ) + if not os.path.exists(os.path.join(external_deps_dir, "harfbuzz")): + with current_directory(external_deps_dir): + shprint(sh.Command("./download.sh")) + super().prebuild_arch(arch) + + +recipe = LibSDL3TTF() diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index eea284b8c9..fc15bd45e6 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -144,9 +144,17 @@ def test_all_bootstraps(self): """A test which will initialize a bootstrap and will check if the method :meth:`~pythonforandroid.bootstrap.Bootstrap.all_bootstraps ` returns the expected values, which should be: `empty", `service_only`, - `webview`, `sdl2` and `qt` + `webview`, `sdl2`, `sdl3` and `qt` """ - expected_bootstraps = {"empty", "service_only", "service_library", "webview", "sdl2", "qt"} + expected_bootstraps = { + "empty", + "service_only", + "service_library", + "webview", + "sdl2", + "sdl3", + "qt", + } set_of_bootstraps = Bootstrap.all_bootstraps() self.assertEqual( expected_bootstraps, expected_bootstraps & set_of_bootstraps @@ -180,8 +188,9 @@ def test_expand_dependencies_with_pure_python_package(self): expanded_result = expand_dependencies( ["python3", "kivy", "peewee"], self.ctx ) - # we expect to one results for python3 - self.assertEqual(len(expanded_result), 1) + # we expect to 2 results for python3 + # (python3, sdl2/sdl3 [one is blacklisted]) + self.assertEqual(len(expanded_result), 2) self.assertIsInstance(expanded_result, list) for i in expanded_result: self.assertIsInstance(i, list) @@ -347,13 +356,13 @@ def bootstrap_name(self): @mock.patch("pythonforandroid.bootstraps.qt.open", create=True) @mock.patch("pythonforandroid.bootstraps.service_only.open", create=True) @mock.patch("pythonforandroid.bootstraps.webview.open", create=True) - @mock.patch("pythonforandroid.bootstraps.sdl2.open", create=True) + @mock.patch("pythonforandroid.bootstraps._sdl_common.open", create=True) @mock.patch("pythonforandroid.distribution.open", create=True) @mock.patch("pythonforandroid.bootstrap.Bootstrap.strip_libraries") @mock.patch("pythonforandroid.util.exists") @mock.patch("pythonforandroid.util.chdir") @mock.patch("pythonforandroid.bootstrap.listdir") - @mock.patch("pythonforandroid.bootstraps.sdl2.rmdir") + @mock.patch("pythonforandroid.bootstraps._sdl_common.rmdir") @mock.patch("pythonforandroid.bootstraps.service_only.rmdir") @mock.patch("pythonforandroid.bootstraps.webview.rmdir") @mock.patch("pythonforandroid.bootstrap.sh.cp") @@ -368,7 +377,7 @@ def test_assemble_distribution( mock_ensure_dir, mock_strip_libraries, mock_open_dist_files, - mock_open_sdl2_files, + mock_open_sdl_files, mock_open_webview_files, mock_open_service_only_files, mock_open_qt_files @@ -409,7 +418,8 @@ def test_assemble_distribution( mock_open_dist_files.assert_called_once_with("dist_info.json", "w") mock_open_bootstraps = { - "sdl2": mock_open_sdl2_files, + "sdl2": mock_open_sdl_files, + "sdl3": mock_open_sdl_files, "webview": mock_open_webview_files, "service_only": mock_open_service_only_files, "qt": mock_open_qt_files @@ -419,6 +429,10 @@ def test_assemble_distribution( mock.call("local.properties", "w"), mock.call("blacklist.txt", "a"), ], + "sdl3": [ + mock.call("local.properties", "w"), + mock.call("blacklist.txt", "a"), + ], "webview": [mock.call("local.properties", "w")], "service_only": [mock.call("local.properties", "w")], "qt": [mock.call("local.properties", "w")] @@ -432,7 +446,7 @@ def test_assemble_distribution( mock.call().__enter__().write("sdk.dir=/opt/android/android-sdk"), mock_open_bs.mock_calls, ) - if self.bootstrap_name == "sdl2": + if self.bootstrap_name in ["sdl2", "sdl3"]: self.assertIn( mock.call() .__enter__() @@ -615,6 +629,18 @@ def bootstrap_name(self): return "sdl2" +class TestBootstrapSdl3(GenericBootstrapTest, unittest.TestCase): + """ + An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which + will be used to perform tests for + :class:`~pythonforandroid.bootstraps.sdl3.BootstrapSdl3`. + """ + + @property + def bootstrap_name(self): + return "sdl3" + + class TestBootstrapServiceOnly(GenericBootstrapTest, unittest.TestCase): """ An inherited class of `GenericBootstrapTest` and `unittest.TestCase` which diff --git a/tests/test_build.py b/tests/test_build.py index 49f6311621..57db29b148 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -82,7 +82,7 @@ def test_android_manifest_xml(self): "native_services": args.native_services } environment = jinja2.Environment( - loader=jinja2.FileSystemLoader('pythonforandroid/bootstraps/sdl2/build/templates/') + loader=jinja2.FileSystemLoader('pythonforandroid/bootstraps/_sdl_common/build/templates/') ) template = environment.get_template('AndroidManifest.tmpl.xml') xml = template.render(**render_args) diff --git a/tests/test_graph.py b/tests/test_graph.py index f7647bcac7..1ac9c68090 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -101,9 +101,9 @@ def test_blacklist(): get_recipe_order_and_bootstrap(ctx, ["flask", "kivy"], wbootstrap) assert "conflict" in e_info.value.message.lower() - # We should no longer get a conflict blacklisting sdl2: + # We should no longer get a conflict blacklisting sdl2 and sdl3 get_recipe_order_and_bootstrap( - ctx, ["flask", "kivy"], wbootstrap, blacklist=["sdl2"] + ctx, ["flask", "kivy"], wbootstrap, blacklist=["sdl2", "sdl3"] ) From 53deb85d5f51a6ae5e15785411aa1923b7eafff6 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Fri, 18 Apr 2025 22:08:28 +0530 Subject: [PATCH 21/27] `kivy` and `pyjnius`: switch to `PyProjectRecipe` (#3139) --- pythonforandroid/recipes/kivy/__init__.py | 43 ++++++------------- .../recipes/kivy/use_cython.patch | 11 +++++ pythonforandroid/recipes/pyjnius/__init__.py | 19 ++++++-- .../recipes/pyjnius/use_cython.patch | 13 ++++++ 4 files changed, 53 insertions(+), 33 deletions(-) create mode 100644 pythonforandroid/recipes/kivy/use_cython.patch create mode 100644 pythonforandroid/recipes/pyjnius/use_cython.patch diff --git a/pythonforandroid/recipes/kivy/__init__.py b/pythonforandroid/recipes/kivy/__init__.py index a5030abf95..f80024155f 100644 --- a/pythonforandroid/recipes/kivy/__init__.py +++ b/pythonforandroid/recipes/kivy/__init__.py @@ -1,10 +1,9 @@ -import glob -from os.path import basename, exists, join +from os.path import join import sys import packaging.version import sh -from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.recipe import PyProjectRecipe from pythonforandroid.toolchain import current_directory, shprint @@ -21,7 +20,7 @@ def is_kivy_affected_by_deadlock_issue(recipe=None, arch=None): ) < packaging.version.Version("2.2.0.dev0") -class KivyRecipe(CythonRecipe): +class KivyRecipe(PyProjectRecipe): version = '2.3.1' url = 'https://github.com/kivy/kivy/archive/{version}.zip' name = 'kivy' @@ -33,34 +32,20 @@ class KivyRecipe(CythonRecipe): # sdl-gl-swapwindow-nogil.patch is needed to avoid a deadlock. # See: https://github.com/kivy/kivy/pull/8025 # WARNING: Remove this patch when a new Kivy version is released. - patches = [("sdl-gl-swapwindow-nogil.patch", is_kivy_affected_by_deadlock_issue)] + patches = [("sdl-gl-swapwindow-nogil.patch", is_kivy_affected_by_deadlock_issue), "use_cython.patch"] - def cythonize_build(self, env, build_dir='.'): - super().cythonize_build(env, build_dir=build_dir) + def get_recipe_env(self, arch, **kwargs): + env = super().get_recipe_env(arch, **kwargs) - if not exists(join(build_dir, 'kivy', 'include')): - return + # Taken from CythonRecipe + env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format( + self.ctx.get_libs_dir(arch.arch) + + ' -L{} '.format(self.ctx.libs_dir) + + ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local', + arch.arch))) + env['LDSHARED'] = env['CC'] + ' -shared' + env['LIBLINK'] = 'NOTNONE' - # If kivy is new enough to use the include dir, copy it - # manually to the right location as we bypass this stage of - # the build - with current_directory(build_dir): - build_libs_dirs = glob.glob(join('build', 'lib.*')) - - for dirn in build_libs_dirs: - shprint(sh.cp, '-r', join('kivy', 'include'), - join(dirn, 'kivy')) - - def cythonize_file(self, env, build_dir, filename): - # We can ignore a few files that aren't important to the - # android build, and may not work on Android anyway - do_not_cythonize = ['window_x11.pyx', 'camera_avfoundation.pyx', 'img_imageio.pyx', 'egl_angle_metal.pyx'] - if basename(filename) in do_not_cythonize: - return - super().cythonize_file(env, build_dir, filename) - - def get_recipe_env(self, arch): - env = super().get_recipe_env(arch) # NDKPLATFORM is our switch for detecting Android platform, so can't be None env['NDKPLATFORM'] = "NOTNONE" if 'sdl2' in self.ctx.recipe_build_order: diff --git a/pythonforandroid/recipes/kivy/use_cython.patch b/pythonforandroid/recipes/kivy/use_cython.patch new file mode 100644 index 0000000000..2a0d2074ba --- /dev/null +++ b/pythonforandroid/recipes/kivy/use_cython.patch @@ -0,0 +1,11 @@ +--- kivy-master/setup.py 2025-02-25 03:08:18.000000000 +0530 ++++ kivy-master.mod/setup.py 2025-03-01 13:10:24.227808612 +0530 +@@ -249,7 +249,7 @@ + # This determines whether Cython specific functionality may be used. + can_use_cython = True + +-if platform in ('ios', 'android'): ++if platform in ('ios'): + # NEVER use or declare cython on these platforms + print('Not using cython on %s' % platform) + can_use_cython = False diff --git a/pythonforandroid/recipes/pyjnius/__init__.py b/pythonforandroid/recipes/pyjnius/__init__.py index 00369df216..cd80b46342 100644 --- a/pythonforandroid/recipes/pyjnius/__init__.py +++ b/pythonforandroid/recipes/pyjnius/__init__.py @@ -1,11 +1,11 @@ -from pythonforandroid.recipe import CythonRecipe +from pythonforandroid.recipe import PyProjectRecipe from pythonforandroid.toolchain import shprint, current_directory, info from pythonforandroid.patching import will_build import sh from os.path import join -class PyjniusRecipe(CythonRecipe): +class PyjniusRecipe(PyProjectRecipe): version = '1.6.1' url = 'https://github.com/kivy/pyjnius/archive/{version}.zip' name = 'pyjnius' @@ -13,12 +13,23 @@ class PyjniusRecipe(CythonRecipe): site_packages_name = 'jnius' patches = [ + "use_cython.patch", ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild')), ('sdl3_jnienv_getter.patch', will_build('sdl3')), ] - def get_recipe_env(self, arch): - env = super().get_recipe_env(arch) + def get_recipe_env(self, arch, **kwargs): + env = super().get_recipe_env(arch, **kwargs) + + # Taken from CythonRecipe + env['LDFLAGS'] = env['LDFLAGS'] + ' -L{} '.format( + self.ctx.get_libs_dir(arch.arch) + + ' -L{} '.format(self.ctx.libs_dir) + + ' -L{}'.format(join(self.ctx.bootstrap.build_dir, 'obj', 'local', + arch.arch))) + env['LDSHARED'] = env['CC'] + ' -shared' + env['LIBLINK'] = 'NOTNONE' + # NDKPLATFORM is our switch for detecting Android platform, so can't be None env['NDKPLATFORM'] = "NOTNONE" return env diff --git a/pythonforandroid/recipes/pyjnius/use_cython.patch b/pythonforandroid/recipes/pyjnius/use_cython.patch new file mode 100644 index 0000000000..59265e99a7 --- /dev/null +++ b/pythonforandroid/recipes/pyjnius/use_cython.patch @@ -0,0 +1,13 @@ +--- pyjnius-1.6.1/setup.py 2023-11-05 21:07:43.000000000 +0530 ++++ pyjnius-1.6.1.mod/setup.py 2025-03-01 14:47:11.964847337 +0530 +@@ -59,10 +59,6 @@ + if NDKPLATFORM is not None and getenv('LIBLINK'): + PLATFORM = 'android' + +-# detect platform +-if PLATFORM == 'android': +- PYX_FILES = [fn[:-3] + 'c' for fn in PYX_FILES] +- + JAVA=get_java_setup(PLATFORM) + + assert JAVA.is_jdk(), "You need a JDK, we only found a JRE. Try setting JAVA_HOME" From f491c6eb20007f9d3897c5c83cfb625c9fee07cf Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Fri, 18 Apr 2025 22:11:04 +0530 Subject: [PATCH 22/27] `primp`: update to `0.14.0` (#3138) --- .../recipes/{pyreqwest_impersonate => primp}/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename pythonforandroid/recipes/{pyreqwest_impersonate => primp}/__init__.py (81%) diff --git a/pythonforandroid/recipes/pyreqwest_impersonate/__init__.py b/pythonforandroid/recipes/primp/__init__.py similarity index 81% rename from pythonforandroid/recipes/pyreqwest_impersonate/__init__.py rename to pythonforandroid/recipes/primp/__init__.py index 7e8d5db9ae..b932eb3e61 100644 --- a/pythonforandroid/recipes/pyreqwest_impersonate/__init__.py +++ b/pythonforandroid/recipes/primp/__init__.py @@ -2,9 +2,9 @@ from pythonforandroid.recipe import RustCompiledComponentsRecipe -class Pyreqwest_impersonateRecipe(RustCompiledComponentsRecipe): - version = "v0.4.5" - url = "https://github.com/deedy5/pyreqwest_impersonate/archive/refs/tags/{version}.tar.gz" +class PrimpRecipe(RustCompiledComponentsRecipe): + version = "v0.14.0" + url = "https://github.com/deedy5/primp/archive/refs/tags/{version}.tar.gz" def get_recipe_env_post(self, arch, **kwargs): env = super().get_recipe_env(arch, **kwargs) @@ -30,4 +30,4 @@ def build_arch(self, arch): prebuild_(arch) -recipe = Pyreqwest_impersonateRecipe() +recipe = PrimpRecipe() From d9e943b9934d06733d9e03e14b72c2f5e58bde82 Mon Sep 17 00:00:00 2001 From: William Scaff Date: Mon, 5 May 2025 18:59:32 -0300 Subject: [PATCH 23/27] Fix Cmake compatibility issue The jpeg recipe doesn't compile anymore because CMake dropped compatibility with versions below 3.5. "Calls to cmake_minimum_required() or cmake_policy() that set the policy version to an older value now issue an error." Source: https://cmake.org/cmake/help/latest/release/4.0.html --- pythonforandroid/recipes/jpeg/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pythonforandroid/recipes/jpeg/__init__.py b/pythonforandroid/recipes/jpeg/__init__.py index 436dc129ba..33a9ba44da 100644 --- a/pythonforandroid/recipes/jpeg/__init__.py +++ b/pythonforandroid/recipes/jpeg/__init__.py @@ -48,6 +48,9 @@ def build_arch(self, arch): # Force disable shared, with the static ones is enough '-DENABLE_SHARED=0', '-DENABLE_STATIC=1', + + # Fix cmake compatibility issue + '-DCMAKE_POLICY_VERSION_MINIMUM=3.5', _env=env) shprint(sh.make, _env=env) From bd32caf401aa499b267e462c5fa1e5e57fd08271 Mon Sep 17 00:00:00 2001 From: Ansh Dadwal Date: Sun, 11 May 2025 20:39:31 +0530 Subject: [PATCH 24/27] `pyjnius`: pin cython version --- pythonforandroid/recipes/pyjnius/__init__.py | 1 + .../recipes/pyjnius/cython_version_pin.patch | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 pythonforandroid/recipes/pyjnius/cython_version_pin.patch diff --git a/pythonforandroid/recipes/pyjnius/__init__.py b/pythonforandroid/recipes/pyjnius/__init__.py index cd80b46342..c6b6746fa1 100644 --- a/pythonforandroid/recipes/pyjnius/__init__.py +++ b/pythonforandroid/recipes/pyjnius/__init__.py @@ -14,6 +14,7 @@ class PyjniusRecipe(PyProjectRecipe): patches = [ "use_cython.patch", + "cython_version_pin.patch", ('genericndkbuild_jnienv_getter.patch', will_build('genericndkbuild')), ('sdl3_jnienv_getter.patch', will_build('sdl3')), ] diff --git a/pythonforandroid/recipes/pyjnius/cython_version_pin.patch b/pythonforandroid/recipes/pyjnius/cython_version_pin.patch new file mode 100644 index 0000000000..3d5cea2350 --- /dev/null +++ b/pythonforandroid/recipes/pyjnius/cython_version_pin.patch @@ -0,0 +1,9 @@ +--- pyjnius-1.6.1/pyproject.toml 2023-11-05 21:07:43.000000000 +0530 ++++ pyjnius-1.6.1.mod/pyproject.toml 2025-05-11 20:31:14.699072764 +0530 +@@ -2,5 +2,5 @@ + requires = [ + "setuptools>=58.0.0", + "wheel", +- "Cython" ++ "Cython==3.0.0" + ] From 5a844e7248ea195f40e1d01c21db3614f7ce89f1 Mon Sep 17 00:00:00 2001 From: clayote Date: Tue, 13 May 2025 04:48:57 +1200 Subject: [PATCH 25/27] Fix typo in comment --- pythonforandroid/recipes/sdl2_image/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/sdl2_image/__init__.py b/pythonforandroid/recipes/sdl2_image/__init__.py index 8cea604e59..fe2e258e16 100644 --- a/pythonforandroid/recipes/sdl2_image/__init__.py +++ b/pythonforandroid/recipes/sdl2_image/__init__.py @@ -25,7 +25,7 @@ def prebuild_arch(self, arch): with open(os.path.join(build_dir, ".gitmodules"), "r") as file: for section in file.read().split('[submodule "')[1:]: line_split = section.split(" = ") - # Parse .gitmoulde section + # Parse .gitmodule section clone_path, url, branch = ( os.path.join(build_dir, line_split[1].split("\n")[0].strip()), line_split[2].split("\n")[0].strip(), From 2d5ef7f54af54b46e9913cdce74a4bb917a32260 Mon Sep 17 00:00:00 2001 From: clayote Date: Tue, 13 May 2025 04:50:34 +1200 Subject: [PATCH 26/27] Remove redundant `file.close()` The end of a `with` block already does that. --- pythonforandroid/recipes/sdl2_image/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pythonforandroid/recipes/sdl2_image/__init__.py b/pythonforandroid/recipes/sdl2_image/__init__.py index fe2e258e16..340e688315 100644 --- a/pythonforandroid/recipes/sdl2_image/__init__.py +++ b/pythonforandroid/recipes/sdl2_image/__init__.py @@ -38,7 +38,6 @@ def prebuild_arch(self, arch): "--depth", "1", "-b", branch, clone_path, "--recursive" ) - file.close() super().prebuild_arch(arch) From d2911a7b25bf95f258d194466a56245782f067ee Mon Sep 17 00:00:00 2001 From: clayote Date: Tue, 13 May 2025 05:01:20 +1200 Subject: [PATCH 27/27] Fix bad empty-directory check --- pythonforandroid/recipes/sdl2_image/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonforandroid/recipes/sdl2_image/__init__.py b/pythonforandroid/recipes/sdl2_image/__init__.py index 340e688315..39411a740f 100644 --- a/pythonforandroid/recipes/sdl2_image/__init__.py +++ b/pythonforandroid/recipes/sdl2_image/__init__.py @@ -32,7 +32,7 @@ def prebuild_arch(self, arch): line_split[-1].strip() ) # Clone if needed - if not os.path.exists(clone_path) or os.listdir(clone_path) == 0: + if not os.path.exists(clone_path) or not os.listdir(clone_path): shprint( sh.git, "clone", url, "--depth", "1", "-b",