Skip to content

Generate Python Bindings for C++ code using Shiboken

Thursday, 15 August 2024  |  Manuel Alcaraz

This will be a guide on how to generate Python bindings for your C++ library using Shiboken. Shiboken is a tool specifically created to build PySide, so it supports Qt code perfectly fine.

The steps described here require at least KDE Frameworks 6.8. I’ll use KUnitConversion as example, because it’s a small library.

We’ll start adding the building instructions. This part is mostly boilerplate code as it’s the same for any library (except for the obvious thing of changing the library name):

 1set(bindings_library "KUnitConversion")
 2
 3set(wrapped_header ${CMAKE_SOURCE_DIR}/python/bindings.h)
 4set(typesystem_file ${CMAKE_SOURCE_DIR}/python/bindings.xml)
 5
 6set(generated_sources
 7    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_module_wrapper.cpp
 8    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_wrapper.cpp
 9    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_converter_wrapper.cpp
10    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_unit_wrapper.cpp
11    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_unitcategory_wrapper.cpp
12    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_updatejob_wrapper.cpp
13    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_value_wrapper.cpp)
14
15ecm_generate_python_bindings(
16    PACKAGE_NAME ${bindings_library}
17    VERSION ${KF_VERSION}
18    WRAPPED_HEADER ${wrapped_header}
19    TYPESYSTEM ${typesystem_file}
20    GENERATED_SOURCES ${generated_sources}
21    DEPENDENCIES KF6::UnitConversion
22    QT_VERSION ${REQUIRED_QT_VERSION}
23    HOMEPAGE_URL "https://invent.kde.org/frameworks/kunitconversion"
24    ISSUES_URL "https://bugs.kde.org/describecomponents.cgi?product=frameworks-kunitconversion"
25    AUTHOR "The KDE Community"
26    README ../README.md
27)
28
29target_link_libraries(${bindings_library} PRIVATE KF6UnitConversion)
30install(TARGETS ${bindings_library} LIBRARY DESTINATION "${KDE_INSTALL_LIBDIR}/python-kf6")

Let’s see what each part does.

 1set(bindings_library "KUnitConversion")
 2
 3set(wrapped_header ${CMAKE_SOURCE_DIR}/python/bindings.h)
 4set(typesystem_file ${CMAKE_SOURCE_DIR}/python/bindings.xml)
 5
 6set(generated_sources
 7    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_module_wrapper.cpp
 8    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_wrapper.cpp
 9    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_converter_wrapper.cpp
10    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_unit_wrapper.cpp
11    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_unitcategory_wrapper.cpp
12    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_updatejob_wrapper.cpp
13    ${CMAKE_CURRENT_BINARY_DIR}/KUnitConversion/kunitconversion_value_wrapper.cpp)

The first line just defines the name of the Python library we’ll build later. Then we set the header file that includes all the necessary headers of the library. We’ll see that file later. The generated sources list is a bit more complicated, as you need to guess the names of the files generated by Shiboken. Fortunately, you can probably guess the pattern from the example above: the first two files are always the same (the name of the library + _module_wrapper or _wrapper) and the rest is the list of classes defined in your XML file (more about it later). The qt_libs variable just contains a list of the Qt modules that our library requires. You should list all of them, even if one depends on another, because otherwise Shiboken won’t be able to find the include directories.

15ecm_generate_python_bindings(
16    PACKAGE_NAME ${bindings_library}
17    VERSION ${KF_VERSION}
18    WRAPPED_HEADER ${wrapped_header}
19    TYPESYSTEM ${typesystem_file}
20    GENERATED_SOURCES ${generated_sources}
21    DEPENDENCIES KF6::UnitConversion
22    QT_VERSION ${REQUIRED_QT_VERSION}
23    HOMEPAGE_URL "https://invent.kde.org/frameworks/kunitconversion"
24    ISSUES_URL "https://bugs.kde.org/describecomponents.cgi?product=frameworks-kunitconversion"
25    AUTHOR "The KDE Community"
26    README ../README.md
27)

This is the magic part. The ecm_generate_python_bindings function takes care of running Shiboken with all the required arguments, building the Python library and a wheel file to publish it on the Python Package Index (pypi.org). It has the following arguments:

  • PACKAGE_NAME: Name of the Python library.
  • VERSION: Version of the resulting library.
  • WRAPPED_HEADER: The header file we talked about above.
  • TYPESYSTEM: XML file with the type system information.
  • GENERATED_SOURCES: The list of files that Shiboken will generate.
  • DEPENDENCIES: A list of libraries that the bindings require, typically the library we’re building the bindings of.
  • QT_VERSION: The minimum required Qt version.
  • HOMEPAGE_URL: A URL to the homepage of the project.
  • ISSUES_URL: A URL where users can report bugs.
  • AUTHOR: The author of the library.
  • README: The README file for the Python library.
29target_link_libraries(${bindings_library} PRIVATE KF6UnitConversion)
30install(TARGETS ${bindings_library} LIBRARY DESTINATION "${KDE_INSTALL_LIBDIR}/python-kf6")

The last part links the C++ library with the Python bindings and installs it. Now let’s take a look at the header file:

1#pragma once
2
3// Make "signals:", "slots:" visible as access specifiers
4#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
5
6#include <KUnitConversion/Converter>
7#include <KUnitConversion/Unit>
8#include <KUnitConversion/UnitCategory>
9#include <KUnitConversion/Value>

Nothing exciting there, just the list of includes (and some Qt thing that I don’t understand).

The last file you need is the typesystem definition, where you tell Shiboken which things (classes, structs, enums, namespaces…) you want to include in your bindings and how it should interpret them. You can delete or rename functions, change the return type, modify the input parameters and many other things. You may want to take a look at the documentation because the list of posible options is very large.

 1<?xml version="1.0"?>
 2<typesystem package="KUnitConversion">
 3    <load-typesystem name="typesystem_core.xml" generate="no" />
 4
 5    <namespace-type name="KUnitConversion">
 6        <enum-type name="CategoryId" />
 7        <object-type name="Converter" />
 8        <object-type name="Unit" />
 9        <object-type name="UnitCategory" />
10        <enum-type name="UnitId" />
11        <object-type name="UpdateJob" />
12        <object-type name="Value" />
13    </namespace-type>
14</typesystem>

You need to load the typesystems of the Qt libraries that you are using so Shiboken can understand what your code is referring to. They come included with PySide.

That’s all you need to generate the Python bindings for your library. The last step is building the project as you usually do.

Update 2024-11-16: Updated the post with the released version of the module.