Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
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
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,26 @@ testapps-service_library-aar: virtualenv
--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
43 changes: 43 additions & 0 deletions doc/source/buildoptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,49 @@ systems and frameworks.
include multiple jar files, pass this argument multiple times.
- ``add-source``: Add a source directory to the app's Java code.

Qt
~~

This bootstrap can be used with ``--bootstrap=qt`` or by including the ``PySide6`` or
``shiboken6`` recipe, e.g. ``--requirements=pyside6,shiboken6``. Currently, the only way
to use this bootstrap is through `pyside6-android-deploy <https://www.qt.io/blog/taking-qt-for-python-to-android>`__
tool shipped with ``PySide6``, as the recipes for ``PySide6`` and ``shiboken6`` are created
dynamically. The tool builds ``PySide6`` and ``shiboken6`` wheels for a specific Android platform
and the recipes simply unpack the built wheels. You can see the recipes `here <https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside-tools/deploy_lib/android/recipes>`__.

.. note::
The ``pyside6-android-deploy`` tool and hence the Qt bootstrap does not support multi-architecture
builds currently.

What are Qt and PySide?
%%%%%%%%%%%%%%%%%%%%%%%%

`Qt <https://www.qt.io/>`__ is a popularly used cross-platform C++ framewrok for developing
GUI applications. `PySide6 <https://doc.qt.io/qtforpython-6/quickstart.html>`__ refers to the
Python bindings for Qt6, and enables the Python developers access to the Qt6 API.
`Shiboken6 <https://doc.qt.io/qtforpython-6/shiboken6/index.html>`__ is the binding generator
tool used for generating the Python bindings from C++ code.

.. note:: The `shiboken6` recipe is for the `Shiboken Python module <https://doc.qt.io/qtforpython-6/shiboken6/shibokenmodule.html>`__
which includes a couple of utility functions for inspecting and debugging PySide6 code.

Build Options
%%%%%%%%%%%%%

``pyside6-android-deploy`` works by generating a ``buildozer.spec`` file and thereby using
`buildozer <https://buildozer.readthedocs.io/en/latest/>`__ to control the build options used by
``python-for-android`` with the Qt bootstrap. Apart from the general build options that works
across all the other bootstraps, the Qt bootstrap introduces the following 3 new build options.

- ``--qt-libs``: list of Qt libraries(modules) to be loaded.
- ``--load-local-libs``: list of Qt plugin libraries to be loaded.
- ``--init-classes``: list of Java class names to the loaded from the Qt jar files supplied through
the ``--add-jar`` option.

These build options are automatically populated by the ``pyside6-android-deploy`` tool, but can be
modified by updating the ``buildozer.spec`` file. Apart from the above 3 build options, the tool
also automatically identifies the values to be fed into the cli options ``--permission``, ``--add-jar``
depending on the PySide6 modules used by the applicaiton.

Requirements blacklist (APK size optimization)
----------------------------------------------
Expand Down
8 changes: 4 additions & 4 deletions doc/source/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ behaviour, though not all commands make use of them.

``--debug``
Print extra debug information about the build, including all compilation output.

``--sdk_dir``
The filepath where the Android SDK is installed. This can
alternatively be set in several other ways.

``--android_api``
The Android API level to target; python-for-android will check if
the platform tools for this level are installed.

``--ndk_dir``
The filepath where the Android NDK is installed. This can
alternatively be set in several other ways.
Expand Down Expand Up @@ -74,12 +74,12 @@ supply those that you need.
The architecture to build for. You can specify multiple architectures to build for
at the same time. As an example ``p4a ... --arch arm64-v8a --arch armeabi-v7a ...``
will build a distribution for both ``arm64-v8a`` and ``armeabi-v7a``.

``--bootstrap BOOTSTRAP``
The Java bootstrap to use for your application. You mostly don't
need to worry about this or set it manually, as an appropriate
bootstrap will be chosen from your ``--requirements``. Current
choices are ``sdl2`` (used with Kivy and most other apps) or ``webview``.
choices are ``sdl2`` (used with Kivy and most other apps), ``webview`` or ``qt``.


.. note:: These options are preliminary. Others will include toggles
Expand Down
7 changes: 3 additions & 4 deletions doc/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ Concepts

- **bootstrap:** A bootstrap is the app backend that will start your
application. The default for graphical applications is SDL2.
You can also use e.g. the webview for web apps, or service_only/service_library for
background services. Different bootstraps have different additional
You can also use e.g. the webview for web apps, or service_only/service_library for
background services, or qt for PySide6 apps. Different bootstraps have different additional
build options.

*Advanced:*
Expand Down Expand Up @@ -281,7 +281,7 @@ Recipe management
You can see the list of the available recipes with::

p4a recipes

If you are contributing to p4a and want to test a recipes again,
you need to clean the build and rebuild your distribution::

Expand All @@ -295,7 +295,6 @@ it (edit the ``__init__.py``)::

mkdir -p p4a-recipes/myrecipe
touch p4a-recipes/myrecipe/__init__.py


Distribution management
~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
34 changes: 32 additions & 2 deletions pythonforandroid/bootstraps/common/build/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def get_bootstrap_name():
if PYTHON is not None and not exists(PYTHON):
PYTHON = None

if _bootstrap_name in ('sdl2', 'webview', 'service_only'):
if _bootstrap_name in ('sdl2', 'webview', 'service_only', 'qt'):
WHITELIST_PATTERNS.append('pyconfig.h')

environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
Expand Down Expand Up @@ -543,6 +543,7 @@ def make_package(args):
}
if get_bootstrap_name() == "sdl2":
render_args["url_scheme"] = url_scheme

render(
'AndroidManifest.tmpl.xml',
manifest_path,
Expand Down Expand Up @@ -571,7 +572,8 @@ def make_package(args):
render(
'gradle.tmpl.properties',
'gradle.properties',
args=args)
args=args,
bootstrap_name=get_bootstrap_name())

# ant build templates
render(
Expand Down Expand Up @@ -601,6 +603,26 @@ def make_package(args):
join(res_dir, 'values/strings.xml'),
**render_args)

# Library resources from Qt
# These are referred by QtLoader.java in Qt6AndroidBindings.jar
# qt_libs and load_local_libs are loaded at App startup
if get_bootstrap_name() == "qt":
qt_libs = args.qt_libs.split(",")
load_local_libs = args.load_local_libs.split(",")
init_classes = args.init_classes
if init_classes:
init_classes = init_classes.split(",")
init_classes = ":".join(init_classes)
arch = get_dist_info_for("archs")[0]
render(
'libs.tmpl.xml',
join(res_dir, 'values/libs.xml'),
qt_libs=qt_libs,
load_local_libs=load_local_libs,
init_classes=init_classes,
arch=arch
)

if exists(join("templates", "custom_rules.tmpl.xml")):
render(
'custom_rules.tmpl.xml',
Expand Down Expand Up @@ -951,6 +973,14 @@ def create_argument_parser():
help='Use that parameter if you need to implement your own PythonServive Java class')
ap.add_argument('--activity-class-name', dest='activity_class_name', default=DEFAULT_PYTHON_ACTIVITY_JAVA_CLASS,
help='The full java class name of the main activity')
if get_bootstrap_name() == "qt":
ap.add_argument('--qt-libs', dest='qt_libs', required=True,
help='comma separated list of Qt libraries to be loaded')
ap.add_argument('--load-local-libs', dest='load_local_libs', required=True,
help='comma separated list of Qt plugin libraries to be loaded')
ap.add_argument('--init-classes', dest='init_classes', default='',
help='comma separated list of java class names to be loaded from the Qt jar files, '
'specified through add_jar cli option')

return ap

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{% if bootstrap_name == "qt" %}
# For tweaking memory settings. Otherwise, a p4a session with Qt bootstrap and PySide6 recipe
# terminates with a Java out of memory exception
org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
{% endif %}
{% if args.enable_androidx %}
android.useAndroidX=true
android.enableJetifier=true
Expand Down
53 changes: 53 additions & 0 deletions pythonforandroid/bootstraps/qt/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import sh
from os.path import join
from pythonforandroid.toolchain import (
Bootstrap, current_directory, info, info_main, shprint)
from pythonforandroid.util import ensure_dir, rmdir


class QtBootstrap(Bootstrap):
name = 'qt'
recipe_depends = ['python3', 'genericndkbuild', 'PySide6', 'shiboken6']
# this is needed because the recipes PySide6 and shiboken6 resides in the PySide Qt repository
# - https://code.qt.io/cgit/pyside/pyside-setup.git/
# Without this some tests will error because it cannot find the recipes within pythonforandroid
# repository
can_be_chosen_automatically = False

def assemble_distribution(self):
info_main("# Creating Android project using Qt bootstrap")

rmdir(self.dist_dir)
info("Copying gradle build")
shprint(sh.cp, '-r', self.build_dir, self.dist_dir)

with current_directory(self.dist_dir):
with open('local.properties', 'w') as fileh:
fileh.write('sdk.dir={}'.format(self.ctx.sdk_dir))

arch = self.ctx.archs[0]
if len(self.ctx.archs) > 1:
raise ValueError("Trying to build for more than one arch. Qt bootstrap cannot handle that yet")

info(f"Bootstrap running with arch {arch}")

with current_directory(self.dist_dir):
info("Copying Python distribution")

self.distribute_libs(arch, [self.ctx.get_libs_dir(arch.arch)])
self.distribute_aars(arch)
self.distribute_javaclasses(self.ctx.javaclass_dir,
dest_dir=join("src", "main", "java"))

python_bundle_dir = join(f'_python_bundle__{arch.arch}', '_python_bundle')
ensure_dir(python_bundle_dir)
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)
super().assemble_distribution()


bootstrap = QtBootstrap()
14 changes: 14 additions & 0 deletions pythonforandroid/bootstraps/qt/build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.gradle
/build/

# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar

# Cache of project
.gradletasknamecache

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
70 changes: 70 additions & 0 deletions pythonforandroid/bootstraps/qt/build/blacklist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# prevent user to include invalid extensions
*.apk
*.aab
*.apks
*.pxd

# eggs
*.egg-info

# unit test
unittest/*

# python config
config/makesetup

# unused encodings
lib-dynload/*codec*
encodings/cp*.pyo
encodings/tis*
encodings/shift*
encodings/bz2*
encodings/iso*
encodings/undefined*
encodings/johab*
encodings/p*
encodings/m*
encodings/euc*
encodings/k*
encodings/unicode_internal*
encodings/quo*
encodings/gb*
encodings/big5*
encodings/hp*
encodings/hz*

# unused python modules
bsddb/*
wsgiref/*
hotshot/*
pydoc_data/*
tty.pyo
anydbm.pyo
nturl2path.pyo
LICENCE.txt
macurl2path.pyo
dummy_threading.pyo
audiodev.pyo
antigravity.pyo
dumbdbm.pyo
sndhdr.pyo
__phello__.foo.pyo
sunaudio.pyo
os2emxpath.pyo
multiprocessing/dummy*

# unused binaries python modules
lib-dynload/termios.so
lib-dynload/_lsprof.so
lib-dynload/*audioop.so
lib-dynload/_hotshot.so
lib-dynload/_heapq.so
lib-dynload/_json.so
lib-dynload/grp.so
lib-dynload/resource.so
lib-dynload/pyexpat.so
lib-dynload/_ctypes_test.so
lib-dynload/_testcapi.so

# odd files
plat-linux3/regen
8 changes: 8 additions & 0 deletions pythonforandroid/bootstraps/qt/build/jni/Application.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

# Uncomment this if you're using STL in your project
# See CPLUSPLUS-SUPPORT.html in the NDK documentation for more information
# APP_STL := stlport_static

# APP_ABI := armeabi armeabi-v7a x86
APP_ABI := $(ARCH)
APP_PLATFORM := $(NDK_API)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := main_$(PREFERRED_ABI)

# Add your application source files here...
LOCAL_SRC_FILES := start.c

LOCAL_CFLAGS += -I$(PYTHON_INCLUDE_ROOT) $(EXTRA_CFLAGS)

LOCAL_SHARED_LIBRARIES := python_shared

LOCAL_LDLIBS := -llog $(EXTRA_LDLIBS)

LOCAL_LDFLAGS += -L$(PYTHON_LINK_ROOT) $(APPLICATION_ADDITIONAL_LDFLAGS)

include $(BUILD_SHARED_LIBRARY)
Loading