Skip to content

Commit

Permalink
Add a test app build to CI that uses Qt bootstrap
Browse files Browse the repository at this point in the history
- for the purpose of testing, the pyside6 and shiboken6 wheels, the
  extra .jar files needed and the recipes for pyside6 and shiboken6
  are manually added into testapps/on_device_unit_tests/test_qt.
  These files are normally generated by the `pyside6-android-deploy`
  tool that is shipped with PySide.
  Generating the wheels and the .jar files belongs to the scope of
  PySide and not python-for-android. Hence, they are not done here.
  This also reduces the load on the CI which will otherwise have to
  cross-compile CPython and PySide.
- Only a minimal PySide with an essential non-ui module is added to
  the PySide6 wheel. This was mainly done to reduce the size of the
  wheel included into the python-for-android repository.
- Tests were added in test_requirements.py so that when running the apk
  the current date and time are printed on the terminal.
  • Loading branch information
Shyamnath Premnadh authored and Shyamnath Premnadh committed Dec 20, 2023
1 parent b56bf17 commit 14dab2d
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/push.yml
Expand Up @@ -70,6 +70,8 @@ jobs:
target: testapps-webview
- name: service_library
target: testapps-service_library-aar
- name: qt
target: testapps-qt
steps:
- name: Checkout python-for-android
uses: actions/checkout@v4
Expand Down
22 changes: 21 additions & 1 deletion Makefile
Expand Up @@ -75,13 +75,33 @@ testapps-webview/%: virtualenv
--requirements sqlite3,libffi,openssl,pyjnius,flask,python3,genericndkbuild \
--arch=armeabi-v7a --arch=arm64-v8a --arch=x86_64 --arch=x86

testapps-service_library-aar: virtualenv
testapps-service_library-aar: virtualenv
. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
python setup.py aar --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
--bootstrap service_library \
--requirements python3 \
--arch=arm64-v8a --arch=x86 --release

testapps-qt: testapps-qt/debug/apk testapps-qt/release/aab

# testapps-webview/MODE/ARTIFACT
testapps-qt/%: virtualenv
$(eval MODE := $(word 2, $(subst /, ,$@)))
$(eval ARTIFACT := $(word 3, $(subst /, ,$@)))
@echo Building testapps-qt for $(MODE) mode and $(ARTIFACT) artifact
. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
python setup.py $(ARTIFACT) --$(MODE) --sdk-dir $(ANDROID_SDK_HOME) --ndk-dir $(ANDROID_NDK_HOME) \
--bootstrap qt \
--requirements python3,shiboken6,PySide6 \
--arch=arm64-v8a \
--local-recipes ./test_qt/recipes \
--qt-libs Core \
--load-local-libs plugins_platforms_qtforandroid \
--add-jar ./test_qt/jar/PySide6/jar/Qt6Android.jar \
--add-jar ./test_qt/jar/PySide6/jar/Qt6AndroidBindings.jar \
--permission android.permission.WRITE_EXTERNAL_STORAGE \
--permission android.permission.INTERNET

testapps/%: virtualenv
$(eval $@_APP_ARCH := $(shell basename $*))
. $(ACTIVATE) && cd testapps/on_device_unit_tests/ && \
Expand Down
2 changes: 1 addition & 1 deletion pythonforandroid/recipe.py
Expand Up @@ -449,7 +449,7 @@ def unpack(self, arch):
extraction_filename = join(
self.ctx.packages_path, self.name, filename)
if isfile(extraction_filename):
if extraction_filename.endswith('.zip'):
if extraction_filename.endswith('.zip') or extraction_filename.endswith('.whl'):
try:
sh.unzip(extraction_filename)
except (sh.ErrorReturnCode_1, sh.ErrorReturnCode_2):
Expand Down
1 change: 1 addition & 0 deletions testapps/on_device_unit_tests/test_app/main.py
Expand Up @@ -13,6 +13,7 @@
- A kivy unittest app (sdl2 bootstrap)
- A unittest app (webview bootstrap)
- A non-gui unittests app
- A non-gui Qt app (qt bootstrap)
If you install/build this app via the `setup.py` file, a file named
`app_requirements.txt` will be generated which will contain the requirements
Expand Down
22 changes: 22 additions & 0 deletions testapps/on_device_unit_tests/test_app/tests/test_requirements.py
Expand Up @@ -251,3 +251,25 @@ def test_run_module(self):
import libtorrent as lt

print('Imported libtorrent version {}'.format(lt.version))


class Pyside6TestCase(PythonTestMixIn, TestCase):
module_import = 'PySide6'

def test_run_module(self):
import PySide6
from PySide6.QtCore import QDateTime
from PySide6 import QtWidgets

print(f"Imported PySide6 version {PySide6.__version__}")
print(f"Current date and time obtained from PySide6 : {QDateTime.currentDateTime().toString()}")


class Shiboken6TestCase(PythonTestMixIn, TestCase):
module_import = 'shiboken6'

def test_run_module(self):
import shiboken6
from shiboken6 import Shiboken

print('Imported shiboken6 version {}'.format(shiboken6.__version__))
Binary file not shown.
Binary file not shown.
59 changes: 59 additions & 0 deletions testapps/on_device_unit_tests/test_qt/recipes/PySide6/__init__.py
@@ -0,0 +1,59 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

import shutil
import zipfile
from os.path import join
from pathlib import Path

from pythonforandroid.logger import info
from pythonforandroid.recipe import PythonRecipe


class PySideRecipe(PythonRecipe):
version = '6.6.0a1'
url = "https://download.qt.io/snapshots/ci/pyside/test/Android/aarch64/PySide6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl"
wheel_name = 'PySide6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl'
depends = ["shiboken6"]
call_hostpython_via_targetpython = False
install_in_hostpython = False

def build_arch(self, arch):
"""Unzip the wheel and copy into site-packages of target"""

self.wheel_path = join(self.ctx.packages_path, self.name, self.wheel_name)
info("Copying libc++_shared.so from SDK to be loaded on startup")
libcpp_path = f"{self.ctx.ndk.sysroot_lib_dir}/{arch.command_prefix}/libc++_shared.so"
shutil.copyfile(libcpp_path, Path(self.ctx.get_libs_dir(arch.arch)) / "libc++_shared.so")

info(f"Installing {self.name} into site-packages")
with zipfile.ZipFile(self.wheel_path, "r") as zip_ref:
info("Unzip wheels and copy into {}".format(self.ctx.get_python_install_dir(arch.arch)))
zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))

lib_dir = Path(f"{self.ctx.get_python_install_dir(arch.arch)}/PySide6/Qt/lib")

info("Copying Qt libraries to be loaded on startup")
shutil.copytree(lib_dir, self.ctx.get_libs_dir(arch.arch), dirs_exist_ok=True)
shutil.copyfile(lib_dir.parent.parent / "libpyside6.abi3.so",
Path(self.ctx.get_libs_dir(arch.arch)) / "libpyside6.abi3.so")

shutil.copyfile(lib_dir.parent.parent / "QtCore.abi3.so",
Path(self.ctx.get_libs_dir(arch.arch)) / "QtCore.abi3.so")

shutil.copyfile(lib_dir.parent.parent / "QtWidgets.abi3.so",
Path(self.ctx.get_libs_dir(arch.arch)) / "QtWidgets.abi3.so")

shutil.copyfile(lib_dir.parent.parent / "QtGui.abi3.so",
Path(self.ctx.get_libs_dir(arch.arch)) / "QtGui.abi3.so")

plugin_path = (lib_dir.parent / "plugins" / "platforms" /
f"libplugins_platforms_qtforandroid_{arch.arch}.so")

if plugin_path.exists():
shutil.copyfile(plugin_path,
(Path(self.ctx.get_libs_dir(arch.arch)) /
f"libplugins_platforms_qtforandroid_{arch.arch}.so"))


recipe = PySideRecipe()
@@ -0,0 +1,35 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

import shutil
import zipfile
from os.path import join
from pathlib import Path

from pythonforandroid.logger import info
from pythonforandroid.recipe import PythonRecipe


class ShibokenRecipe(PythonRecipe):
version = '6.6.0a1'
url = "https://download.qt.io/snapshots/ci/pyside/test/Android/aarch64/shiboken6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl"
wheel_name = 'shiboken6-6.6.0a1-6.6.0-cp37-abi3-android_aarch64.whl'

call_hostpython_via_targetpython = False
install_in_hostpython = False

def build_arch(self, arch):
''' Unzip the wheel and copy into site-packages of target'''

self.wheel_path = join(self.ctx.packages_path, self.name, self.wheel_name)
info('Installing {} into site-packages'.format(self.name))
with zipfile.ZipFile(self.wheel_path, 'r') as zip_ref:
info('Unzip wheels and copy into {}'.format(self.ctx.get_python_install_dir(arch.arch)))
zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch))

lib_dir = Path(f"{self.ctx.get_python_install_dir(arch.arch)}/shiboken6")
shutil.copyfile(lib_dir / "libshiboken6.abi3.so",
Path(self.ctx.get_libs_dir(arch.arch)) / "libshiboken6.abi3.so")


recipe = ShibokenRecipe()

0 comments on commit 14dab2d

Please sign in to comment.