Development/Language Bindings/Ship Frameworks via Pip
Ship Frameworks via pip
Part of KDE Goals for Streamlined Application Development Experience.
Following the work underway on Python bindings for KDE Frameworks, this page covers the work underway on Python packaging for the frameworks so they can be shipped via pip.
The principles goals are:
- End users able to
pip install {framework}across same platforms as supported by PySide6's pypi packages. - Maintaining as 'Pythonic' an approach as possible, though implementing similar workarounds as PySide6 where necessary.
- Inclusion of Python stubs for LSP support and type checking.
- Implement similar tooling as PySide to support end users package their Python apps using KDE Frameworks, including packaging for Android.
Relevant KDE Invent issues:
Current stage: Packaging Kirigami
Kirigami for manylinux 'works', but issues with current build when trying to run it with the Kirigami with Python tutorial project.
QQmlApplicationEngine failed to load component
file:///home/{usr}/kirigami_python/src/qml/Main.qml: module "org.kde.desktop" is not installed
One issue is that org.kde.desktop cannot be found. This suggest whatever package supplies this will also need to be packaged. The org/kde/desktop is mostly qml but includes lib_kde_desktop.so and liborg_kde_desktop_private.so . Their dependencies are system files manylinux excludes and Qt6, so will be easy to package. Ideal candidates for the PyKDE/KDE meta package. However, a missing org.kde.desktop.so looks to be the source of the issue instead, and may be harder to address.
Even when disabling styling that looks for org.kde.desktop (i.e. the qml folder) a different error is received:
kf.kirigami.platform: Failed to find a Kirigami platform plugin for style "Fusion"
From what can find a previous KDE Discuss post would suggest the Fusion issue is part of same problem. It's not that PySide6 Fusion cannot be found, but the Kirigami platform plugin for the style is missing i.e. the org.kde.desktop.so file.
So, it looks like will need Python packaging for whichever KDE package provides org.kde.desktop.so . However, looking at the ldd output for org.kde.desktop.so, it looks to have a lot of further dependencies.
Did a similar issue arise with other language bindings for Kirigami or did they still rely on some system libraries?
Potential Solutions (Shift to Craft)
Solution 3
(Moved to top as most likely solution that will work.)
Ditch current scikit-build-core process and use Craft. Very basic process would be:
- Overarching CI process
- Within images/vms use Craft to build frameworks
- Fixing runpaths and packaging as wheel with correct tag
Solution 3a:
What is unclear is whether can pass same build options with Craft as currently doing with scikit-build-core's CMakeLists.
The alternative would be let Craft build as normal and then yoink what is needed into the structure for Python packaging. That though would require identifying all the files to move, and will potentially become annoying as split things out into individual packages.
Again though may find after doing initial tests that this is less of a problem than initially thinking it might. If able to get individual packages to build to set structure and/or pull files after build into set structure without needing include/exclude lists then it remains easy enough.
Solution 3b
Other option would be checking the existing build process for building the Python bindings that are installed in system library. If they bundle all that's needed per package then can yoink those wheels and run existing runpath fix during wheel repair.
If not - and likely as think the Python binding wheels are the bindings and nothing else - could extend the ECM for a 'Python packaging' option that extends the existing 'Python bindings' to add additional files needed. For 'runtime' packages that don't have Python bindings, the 'Python packaging' option could then be setup with just the components from the framework as needed.
In other words:
- Setup Python packaging via ECM, either extending existing Python bindings setup or a thin layer where just bundling required files into a wheel.
- Add this to CMakeLists for packages (able to have temp setup for this whilst testing prototype?).
- Use Craft to build with option for Python packaging.
- Yoink wheels for repair and tagging.
This would be easier than 3a for then:
- Setting up individual packages as their own wheels, as creating a wheel is done per package within its existing build process.
- Merging wheels for 'essential' frameworks into a runtime package of common components most individual packages would need. (Pending what the dependency tree looks like could perhaps split this out into 2 or more packages long-term.)
That requires moving to earlier stage things originally planned once prototype was working, but looks like whilst more work this will be the best route for the prototype setup anyway.
Solution 1: Runtime Package Required
Perhaps need a slimmed Python equivalent to Flatpak's KDE Runtime. Would not need the Qt packages, and shouldn't require all the KDE ones.
- Likely require: qqc2 desktop style (and qqc2 breeze style?)
- Potentially require: Sonnet, KColorScheme, KConfig, KIconThemes
- May also require: KArchive, KCoreAddons, KI18n, KGuiAddons, KWidgetsAddons, breeze-icons
Doing lazy runpath edit of qqc2 desktop style and adding it within site-packages and errors when running kirigami tutorial app change to things like sonnet, adding evidence to potential need for a 'runtime' package.
That would pull in quite a lot of non-QML libraries, where if a runtime package is the solution then long-term may need to identify precisely what is causing the chain of dependency and if can slim it down / make packages optional.
Solution 2: qqc2 desktop style only
Alternatively, the issue may be combination of qqc2 desktop style being required and further setup within Python. First thing to try will be packaging qqc2 and set the OS plugin Qt plugin variable to include the relative folder of where the Python package is installed. That alone may fix it the problem without needing the rest.
Note - quick and dirty test of that didn't work. Tried setting QT_PLUGIN_PATH and app.addLibraryPath() but no luck. However, may be due to copying across system versions and modifying runpath, where may work with version built from source against same version of Qt.
Running debug / logging the only issue seems to be:
qt.core.library: "/home/.../.venv/lib/python3.11/site-packages/kirigami/kf/plugins/kf6/kirigami/platform/org.kde.desktop.so" cannot load: Cannot load library /home/.../.venv/lib/python3.11/site-packages/kirigami/kf/plugins/kf6/kirigami/platform/org.kde.desktop.so: /lib/x86_64-linux-gnu/libQt6WaylandClient.so.6: undefined symbol: _Z17lcQpaInputDevicesv, version Qt_6 qt.core.plugin.loader: QLibraryPrivate::loadPlugin failed on "/home/.../.venv/lib/python3.11/site-packages/kirigami/kf/plugins/kf6/kirigami/platform/org.kde.desktop.so" : "Cannot load library /home/.../.venv/lib/python3.11/site-packages/kirigami/kf/plugins/kf6/kirigami/platform/org.kde.desktop.so: /lib/x86_64-linux-gnu/libQt6WaylandClient.so.6: undefined symbol: _Z17lcQpaInputDevicesv, version Qt_6" kf.kirigami.platform: Loading style plugin from "/home/.../.venv/lib/python3.11/site-packages/kirigami/kf/plugins/kf6/kirigami/platform/org.kde.desktop.so" kf.kirigami.platform: Failed to find a Kirigami platform plugin for style "org.kde.desktop"
So, it looks like it can find the plugin, just can't load it due to 'undefined symbol' errors when then tries to load LibQt6WaylandClient.so.6 alongside it?
Community Decisions
Assuming proposed solution to issues and design for Python packaging set out below, community decisions will include:
- Name for the Python packages, i.e. 'PyKDE', 'KDE', or something else. For example, this would result in
from PyKDE import kirigami. - pip install options, such as
pip install pykde[all]to install all KDE Framework packages andpip install pykde[qml]to just install KDE Frameworks used for qml based apps. (Based on framework dependencies on other frameworks this may end up being mute, as may end up with functional equivalent of essential, addons, ...)
Main Issues to Address
The 'Pythonic' way for packaging wheels is for packages to avoid dependence on other packages for shared libraries. This is to avoid conflicts resulting in mismatches between system libraries and different versions of the libraries potentially across multiple packages. Instead, Python packaging tooling vendors all shared libraries, adding a random hash to the file names to avoid potential conflicts.
To avoid duplication across wheels, the preferred setup would want to achieve:
- KDE Framework packages able to access shared libraries from PySide6.
- KDE Framework packages able to access shared libraries from other framework packages.
Linux versions of packages also need to meet the manylinux specs. The gist of this is that Linux packages should be able to run on most popular commonly used distros. System libraries that would by default be available across these are excluded from packaging. Any additional though are expected to be bundled into the wheel.
Windows and macOS should be more straight-forward to incorporate given as long as it builds for them its fine to package, though will need to check what libraries are built on those platforms, and any considerations for how to then handle them for Python packaging.
Proposed Solutions
To address the above issues, it is proposed that the KDE Framework packages:
- Follow PySide6 in installing to same folder within site-packages.
- Are setup to assume "../PySide6/.." for looking for any Qt shared libraries.
- Where necessary ship a 'pykde-third-party' package for additional non-KDE libraries that KDE Frameworks depend on.
This is not 'Pythonic' but builds on pattern already used by PySide6. This reduces complications in ensuring framework packages can see shared libraries from each other and as PySide6 will be a dependency for all packages, it can be assumed it will be present in site-packages.
How much of an issue or not non-KDE shared libraries are will become clearer once have initial working prototype of the build process. It appears manylinux spec of system libraries shared across distros is inclusive of headless setups (though that is TBC). KDE packages could then exclude more from packaging on grounds that anyone using Kirigami would have these libraries installed across commonly used distros.
Overall, whilst that does not follow Python best practice, it follows workarounds already used by other packages and these workarounds do not negatively impact other non-KDE python packages.
Prototype Setup
Initial work on the build process is being done outwith the KDE CI. Once having working prototype it will be clearer how to translate this across to the KDE CI.
Phases for the prototype setup:
- Able to build and push to pypi test server kirigami manylinux x86 package with end users able to pip install and run basic boilerplate app.
- Extend above to all platforms and architectures covered by PySide6 packages.
- Extend this to kirigami-addons and the other frameworks it depends on, with end users able to run basic boilerplate app. This phase should also incorporate steps for creating the stub files. (In prior testing the stub files didn't work due to shiboken issue, but that may be fixed now. TBC.)
- Extend this to remaining KDE Frameworks with python bindings, again testing with basic boilerplate app.
- Test above with any existing apps using the Python bindings to identify any issues.
After that, it should be safe to translate the process over to KDE CI. After that, the main task remaining will be creating pykde-deploy scripts for end users to package their apps.
Packages
Assuming PyKDE as name, have following package convention:
- pykde
- pykde-kirigami
- pykde-kirigami-addons
- ...
Here, 'pykde' is a meta-package that includes helpers that removes complexity and boilerplate setup for end users.
This will be a slightly more complex version of what PySide6 itself does. It has a pyside6 meta-package alongside pyside6-essentials, pysid6-addons, and shiboken8 packages. All of these install to the same site-packages folder. The meta-package then has a __init__.py that checks what is available, handles platform specific setup for libraries, etc.
Most of this can be replicated for KDE Framework packages, where main difference is just more packages than simply essential and addons. One addition will be also adding helper for end users to add the qml path to qml engine.
Project Folder Structure
This will be implemented once have working Kirigami packaging.
Subject to change, but rough working idea will be -
PyKDE/
.gitignore # dist/, wheels/, etc
# folder for wheels before 'repair'
dist/
scripts/
render_pyproject-tomls.py
audit_wheels.py # use for repair-wheel-command
linux_builds.sh # unclear whether need
src/
# meta with loaders for libs and qml
pykde/
__init__.py
# regular source code for packages
kirigami/
...
kirigami-addons/
...
packaging/
# long-term a scikit-build-core metadata plugin could handle this, but is currently still experimental.
pyproject.meta.toml # can include KDE framework dependency tree etc
pyproject.template.toml
# folder for wheels after 'repair'
wheels/
...
CMakeLists.txt # used by scikit-build-core
pyproject.toml # specifies pykde, dev tools, uv setup etc
Note: Probably worth adding tar for ECM version required to avoid downloading it repeatedly as part of the before-all-build process.
Package Folder Structure
Each wheel package will have the following folder layout
package/
python-bindings.so
python-stubs.pyi
include/
kf/
lib/
qml/
translations/
This is to mirror similar pattern that PySide6 itself uses.
One potential issue at present is that the qml folder structure differs to how PySide6 handles things. Within PySide6 there is a simple 'qml' with all main packages directly underneath. What look to be the QML .so plugins are in 'PySide6/Qt/plugins' instead.
The way KDE Frameworks are built, the qml path instead has 'qml/org/kde' before the individual packages. Any .so files have runpath to the system lib folder, which in turn contains symlinks to the actual files, that appear to be within the 'qml/org/kde' path. As far as can tell all actual files being in the 'qml/org/kde' path is due to how Qt expects third-party packages to be structured.
The current prototype builds to kf/lib and that seems to result in some duplication - files in 'qml/org/kde' and 'lib', but with all files being present and all .so files able to find others. Longer-term would want to avoid any duplication. If all files being in the 'qml/org/kde' path is not enforced by what Qt expects, it'd also be good to split things out into 'qml' and 'plugin' folders.
Unclear whether the issue holding up Current Stage may be result of .so files in 'qml' tree pointing to the duplicates in 'lib' or whether that is not ideal but non-issue from runtime perspective.
pyproject.toml
Current working pyproject.toml, setup for just kirigami at the moment.
[build-system]
# Will need to confirm exactly what min scikit-build-core version to specify
requires = ["scikit-build-core>=0.11.6"]
build-backend = "scikit_build_core.build"
[project]
name = "kirigami"
version = "0.0.1"
description = "Test manylinux build of KDE Kirigami"
# Match PySide6's 3.9+
requires-python = ">=3.9"
# Need to include all same licenses as under kirigami/LICENSES? or just kirigami's license?
# license=
# KDE? KDE Community?
authors = [{ name = "KDE Community" }]
# Need to pin exact PySide6 version
dependencies = ["PySide6==6.8"]
[tool.scikit-build]
# Matching kirigami's MakeLists
cmake.version = ">=3.16"
wheel.install-dir = "kirigami"
# Turns on the abi build
wheel.py-api = "cp39"
[tool.cibuildwheel]
# Will need to figure out full matrix required after get initial package working
build = ["cp39-manylinux_x86_64"]
[tool.cibuildwheel.linux]
# Will need to adjust for handling version changes
before-all = "bash before-all-manylinux.sh"
# this is effectively --exclude '*.so*'... and may want to change it to that
repair-wheel-command = """
python tools/fix_rpaths.py {wheel} && \
auditwheel repair \
-w {dest_dir} \
--exclude 'libQt6*.so*' \
--exclude 'libKirigami*.so.*' \
--exclude 'libgomp.so.1' \
--exclude 'libGLX.so.0' \
--exclude 'libOpenGL.so.0' \
--exclude 'libxkb*.so*' \
{wheel}
"""
[tool.cibuildwheel.linux.environment]
# Will need to change to variables for non-test version
# Will need to figure out which needed for which frameworks
Qt6_DIR = "/opt/qt/6.8.0/gcc_64/lib/cmake/Qt6"
Qt6QmlTools_DIR = "/opt/qt/6.8.0/gcc_64/lib/cmake/Qt6QmlTools"
Qt6QuickTools_DIR = "/opt/qt/6.8.0/gcc_64/lib/cmake/Qt6QuickTools"
QT_DEBUG_FIND_PACKAGE = "ON"
CMAKE_PREFIX_PATH = "/opt/kde-ecm"
scikit-build-core CMakeLists.txt
Example of current version of CMakeLists.txt being used for Kirigami.
cmake_minimum_required(VERSION 3.16)
project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES CXX)
# Place into desired package structure
set(KDE_INSTALL_QMLDIR "kf/qml" CACHE PATH "" FORCE)
set(KDE_INSTALL_LIBDIR "kf/lib" CACHE PATH "" FORCE)
set(CMAKE_INSTALL_LIBDIR "kf/lib" CACHE PATH "" FORCE)
set(KDE_INSTALL_LOCALEDIR "kf/translations" CACHE PATH "" FORCE)
# Do not want in package
set(KDE_INSTALL_CMAKEPACKAGEDIR "${CMAKE_BINARY_DIR}/_cmake-packages"
CACHE PATH "" FORCE)
set(KDE_INSTALL_KAPPTEMPLATESDIR "${CMAKE_BINARY_DIR}/_kapptemplates"
CACHE PATH "" FORCE)
set(KDE_INSTALL_LOGGINGCATEGORIESDIR "${CMAKE_BINARY_DIR}/_logging-categories"
CACHE PATH "" FORCE)
set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libs" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "Disable tests" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "Disable examples" FORCE)
add_subdirectory(extern/kirigami)
Note, this is mix of info found online and skimming through Kirigami's CMakeLists and other build files. May not be best setup / may include code safe to just remove.
Other things will temporarily handle with a Python script, but longer-term can expand the existing KDE build files with options that scikit-build-core's CMakeLists.txt can call.
Build Process
General build process:
- Create pyproject.toml files from meta and template
- Run pre-build setup for image (This can be removed later in creating own custom manylinux compliant CI image. Generation of .pyi stubs may also be a pre-build or something to setup within main KDE CI to avoid needless duplication.)
- Iterate through all the builds. (This may require a linux_builds.sh or similar given limitation in setting this up in pyproject.toml.)
- (Potential) script for creation of "pykde-third-party" or similar.
- 'Repair' wheels by checking abi13, shared libraries paths, and adding relevant tags. (Long-term, potentially could add additional tests here to double-check packages run as expected and no mismatch in versioning.)
- Push to pypi.
The final output should be a series of files with the following naming convention:
package-version-python_version-os-architecture.whl
This should cover:
cp39-abi3-win_arm64cp39-abi3-win_amd64cp39-abi3-manylinux_2_39_aarch64cp39-abi3-manylinux_2_34_x86_64cp39-abi3-macosx_13_0_universal2
manylinux
The prototype is using the manylinux docker images. PySide6's wheels are tagged for the ALPHA AlmaLinux 9 based and AlmaLinux/RockLinux 10 based images.
Before-all for the manylinux images so far:
yum install -y libxkbcommon-devel- install qt using aptinstall (guessing this will need to change to building qt from source?)
- build ECM
And the before-all-manylinux.sh file:
set -euxo pipefail
# Kirigami build fails without this
yum install -y libxkbcommon-devel
# Qt version already specified in Kirigami make file
# So this is duplication can likely remove at later stage
QT_VER=6.8.0
QT_ARCH=linux_gcc_64
QT_ARCH_DIR=gcc_64
# Grab Qt
uvx --from aqtinstall aqt install-qt \
linux desktop ${QT_VER} ${QT_ARCH} --outputdir /opt/qt \
--modules qtshadertools
# Again ECM version in Kirigami make file
ECM_VER=6.20.0
# ECM_SERIES=6.20
# No point downloading again and again
# curl -L -o ecm.tar.xz \
# "https://download.kde.org/stable/frameworks/${ECM_SERIES}/extra-cmake-modules-${ECM_VER}.tar.xz"
tar xf ecm.tar.xz
cmake -S "extra-cmake-modules-${ECM_VER}" -B ecm-build \
-DCMAKE_INSTALL_PREFIX=/opt/kde-ecm \
-DCMAKE_PREFIX_PATH="/opt/qt/${QT_VER}/${QT_ARCH_DIR}"
cmake --build ecm-build --target install -j"$(nproc)"
export CMAKE_PREFIX_PATH="/opt/qt/${QT_VER}/${QT_ARCH_DIR};/opt/kde-ecm"
As can see from comments, it's likely can tidy this up at later stages. Ideally, would also have a CI image with everything setup so do not need any before-all.
PyKDE meta package
This will be needed for the 'glue' that ensures things work as expected across platforms and off-loads lot of the common setup tasks to single package.
It will need a __init__.py file that:
- Checks what bindings and QML files are available and sets these up based on platform. (PySide6's file gives template for this.)
- Passing path for QML folder can use with QML engine. Extent set this up for end users likely require community decision, but a path object at minimum will need to be made available.
Unsure how PySide6 achieves it, but the meta package will also need script / info for passing translations to i18n/ki18n.
Moving to KDE CI
CI Image
Need to confirm whether existing KDE CI images that are manylinux compliant with all tooling needed for Python packaging or whether need to create separate images.
If separate process is needed for doing the Python package builds, then a lot of the prototype setup could be maintained as is, using cibuildwheel for CI.
ECMGeneratePythonBindings
At present ECMGeneratePythonBindings is setup to generate bindings for installing in the system's site-packages.
Below is the general changes will want to have, either placing it all in ECMGeneratePythonBindings or splitting it out as 2-3 files:
- SetupPython packaging folder structure and add template pyproject.toml, license files, and README.md
- Setup folders for wheel, in general this being
qml/,bin, etc as needed. All .so files that are not qml plugins likely best placed in parent directory for the wheel. - Run shiboken6-genpyi to create the stubs. (Be useful to also have a script to collate all type errors raised during this process. This may also be something to split into its own process where instead these are then copied over into the wheel folder.)
- If has dependencies on other python packages, add project.dependencies table in pyproject.toml
- (potential) If optional dependencies add project.optional-dependencies table in pyproject.toml
- Links to Qt libraries should point to where PySide6 will be installed.
- Links to other KDE libraries should largely be
$ORIGINas most will be in same pykde folder after pip installed. (Any divergences from this TBC.) - If has binaries we also want to package move to module, generate wrapper.py, add project.scripts and project.gui-scripts tables in pyproject.toml
- Build and 'repair' wheels. (This needs to be separate I think to the current build wheels given different linking for libraries.)
- Push to pypi.
The following changes should be made to pyproject.toml.base to flesh out the recommended metadata:
- Add build-system table
- Add keywords to project table
- Add license to project table
- Any modules that will need licenses plural?
- Add license-files to project table
- Add documentation and issues to project.urls table
- Need to specify exact version of PySide6
- Will need ${PB_OPERATING_SYSTEM} for specifying platform
This section will be used for including information on how we will need to handle library dependencies.
Repair Wheel Exclusions
Basic pattern for repair-wheel-command will be:
auditwheel repair -w {dest_dir} --exclude 'libQt6*.so*' --exclude 'libKirigami*.so.*' --exclude 'libK...*.so.*' {wheel}"
Will need to replace 'libK...' will patterns covering all KDE Frameworks.
Edit: Actually, it looks like in many cases the pattern will effectively be --exclude '*.so*'. However, will want to collate what auditwheel wants to vendor when expanding to cover additional framework packages to identify any where cannot assume 'most likely installed on system and safe to exclude'.
Unless made mistake, it looks like there are at least 47 libraries that PySide6 links to, but does not package. Given it does that and still has manylinux tag, so excluding a few here and there for KDE Frameworks shouldn't be massive issue.
General Package Paths
We will have three kinds of .so files in the wheels:
- QML plugins that are in a QML directory tree.
- Python extension bindings that will be in top-level folder.
- KDE Framework shared libs can stick in
kf/lib/to mirror PySide'sQt/lib.
What want to ensure then is that for .so files:
- If it links to a Qt library, add a
$ORIGIN-relative path toPySide6/Qt/lib.- Python bindings it is
$ORIGIN/../PySide6/Qt/lib. - Shared libraries it is
$ORIGIN/../../PySide6/Qt/lib. - QML .so files the
/../will need to be adjusted to have far down in the directory tree it is.
- Python bindings it is
- If it is a Python binding and links to a KDE Frameworks library, add a
$ORIGIN-relative path tokf/lib - If it is not a Python binding and links to a KDE Framework library, add a
$ORIGINpath (as it will be in thekf/libfolder already). - If it links to anything else have a verbose option for tooling to check whether something safe to exclude or could cause potential issue.
I assume above can be handled during build process. It looks like the current build process for the qml .so files is to have their runpath point to the system lib which in turn has a symlink to the .so files in qml directory tree. Unsure whether can preserve that within a Python package, and even if so how that'd translate to Windows and macOS setup.
Framework Specific
Kirigami
Aside from Qt6 libraries, Kirigami has dependencies on:
- libgomp.so1 (libKirigamiDelegatesplugin.so, libKirigamiDialogsplugin.so, libKirigamiLayoutsplugin.so, libKirigamiLayoutsPrivateplugin.so, libKirigamiplugin.so, libKirigamiPrimitivesplugin.so, libKirigamiTemplatesplugin.so)
- libGLX.so.0 (libKirigamiPlatformplugin.so, libKirigamiplugin.so)
- libOpenGL.so.0 (libKirigamiPlatformplugin.so, libKirigamiplugin.so)
- libxbcommon
Assumptions:
- OpenGL safe to exclude as most systems should have it available. The draft PEP 725 proposes permitting specifying external dependencies in pyproject.toml and gives OpenGL as an example. So, whilst not technically permitted at present, it looks like known current issue and something not desirable to package.
- libgomp we want to exclude, but seems not all systems may have it? Packaging libgomp causes issues for PyTorch. Another potential relevant Blender thread.
- libxbcommon is another that is fairly safe to assume most Linux distros will have installed. However, will likely want to create small set of vms can run and test this with - and will probably have further packages other frameworks depend on that want to exclude that can use the vms to test whether to exclude or vendor.
Python Apps to Test
TODO. Section for listing existing apps which use the kirigami and/or python bindings for the KDE Frameworks that can then be used for testing.
