4.3.5. Bindings
Shiboken is a Python binding generator. It uses CLang to parse and analyze the structure of the C++ code (both from Qt, and our own widget), and typesystem to create the generated code through instructions.

Qt uses Shiboken to create its Python bindings, and already provides typesystem files for all its libraries. This is how PySide is built.
The mayor issue of Shiboken, is that it uses CMake3 and a huge CMakeList.txt that is very difficult to understand. There is no other build system supported. In this page, I will explain how Shiboken works, and how the CMakeLists.txt file was translated.
4.3.5.1. Input for Bindings
Input files for binding generation in Shiboken are simple. Two files are needed:
C++ header file, that includes every class we need to bind.
Typesetting XML file, that indicates which will be the name of the target Python module, and which classes will be bind into said module.
The header file is just a list of #include
sentences, no extra code is needed. Every class
that needs binding has to be included in this file, through the use of common #include
macro
instructions. If two classes are defined in the same file, then only one include is needed.
1/****************************************************************************
2* ESO License
3****************************************************************************/
4
5#ifndef BINDINGS_H
6#define BINDINGS_H
7
8#define QT_ANNOTATE_ACCESS_SPECIFIER(a) __attribute__((annotate(#a)))
9
10#include "QeExamplesWidgets.h"
11
12#endif // BINDINGS_H
Line 8 indicates to the C++ preprocessor that it needs to make classifiers used by Qt visible. These classifiers are used in QObjects. (signals:, slots: in the header files).
In Line 10, you should include every widget you need. In here, we recommend to use only file named as the resulting target name. This will ensure compabitility with Python module names, allowing both C++ and Python version of the widget to provide correct include/import statements.
The typesystem file is a bit more complex.
1<?xml version="1.0"?>
2<typesystem package="QeExamplesWidgets">
3 <load-typesystem name="typesystem_core.xml" generate="no"/>
4 <load-typesystem name="typesystem_core_common.xml" generate="no" />
5 <load-typesystem name="typesystem_widgets.xml" generate="no" />
6 <load-typesystem name="typesystem_gui_common.xml" generate="no" />
7 <primitive-type name="double"/>
8 <object-type name="QeExampleDmsLabel">
9 </object-type>
10</typesystem>
As you see here, the XML defines in line 2 a package called QeExamplesWidgets. This will be the name of the resulting Python package.
Lines 3 to 6 indicate the typesystem engine to load more typesystem definitions. These ones are the base set that Qt needs for widgets.
Then one primitive is declare in line 7, as it is used in slots and signals in the QeExampleDmsLabel class. If any other primitive is used, but it is not in the signature, then no definition for them is needed.
In line 8 an object is declared. This object is a C++ class named QeExampleDmsLabel. This should be the same class name as in the C++ code declaration. The resulting Python library will have a class in package QeExamplesWidgets named QeExampleDmsLabel.
4.3.5.2. Code generation
In Qt’s examples, CMake is used. I will indicate here how to do this without a build system (and eventually, how to do it in WAF).
The Code generation is one command shiboken with several options for includes of options, and passes two mandatory arguments, the C++ header file, and the typesystem XML file:
42 code_generation = bld(
43 rule="${SHIBOKEN} "\
44 "--generator-set=shiboken "\
45 "--enable-parent-ctor-heuristic "\
46 "--enable-return-value-heuristic "\
47 "--enable-pyside-extensions "\
48 "--use-isnull-as-nb_nonzero "\
49 "--avoid-protected-hack "\
50 "--language-level=c++17 "\
51 "-I${SRC_DIR}/../../widgets/src/include "\
52 "-I${SRC_DIR} "\
53 "-I${INTROOT}/include "\
54 + bld.env.GEN_QT_INCLUDES +
55 "-I/usr/include/ "\
56 "-T" + str(bld.env.GEN_PYSIDE_TS_typesystemdir) + " "\
57 "-T${SRC_DIR} "\
58 "--output-directory=${DEST_DIR} "\
59 "${SRC_DIR}/bindings.h "\
60 "${SRC_DIR}/bindings.xml",
61 source=['src/bindings.xml', 'src/bindings.h'],
62 target=[
63 'src/QeExamplesWidgets/qeexampleswidgets_module_wrapper.cpp',
64 'src/QeExamplesWidgets/qeexampledmslabel_wrapper.cpp'
65 ],
66 name='bindings-generate',
67 )
The SHIBOKEN variable is path to the code generator. We use waf
to locate the executable
in the configuration section. At the moment, the include statements -I
for the code generation
are still manually constructed from pkg-config
information.
Line 51 indicates where the header files for the source widgets are.
Line 53 indicates where the bindings.h file is located
Line 54 give access to QtWidgets, QtCore and QtGui shiboken, and pyside obtained through pkg-config.
The ones in -T
entries are special. -T
indicates where the typesettings files are
located, in case we need to “include” other libraries.
Line 56 indicates the location of all typesettings files from Qt. In this case, I do not want to specify how to bind a QWidget, QPen, QBrush classes, so I just indicate that my project uses typesettings files from Qt project.
Line 57 indicate where the typesetting file from our project is located.
The options do not change from one project to another, only the target entry form the generation stage.
Tip
If you need more example, we suggest to look at typesystem files from Qt:
All of them: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside6/PySide6
QtWidgets (the most useful ones, as most widgets E-ELT will develop should look like these): https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside6/PySide6/QtWidgets
As a result, this will generate a directory in DEST_DIR, that has 2 files for each binded class, and 2 more for each Python module indicated as target.
4.3.5.3. Compilation
The compilation
of these are more or less straight C++ objects, that need to be linked
together as a shared library.
A compilation
step prepares the shared library, using as source the same list as the target
from the generation step.
69 compilation = bld(
70 features='pyext cxx cxxshlib wdep ' + bld.env.GEN_COMP_QT_FEATURE,
71 source=[
72 'src/QeExamplesWidgets/qeexampleswidgets_module_wrapper.cpp',
73 'src/QeExamplesWidgets/qeexampledmslabel_wrapper.cpp'
74 ],
75 target='QeExamplesWidgets',
76 includes=[
77 str(bld.env.COMP_PYSIDE_I_includedir)+'/QtCore',
78 str(bld.env.COMP_PYSIDE_I_includedir)+'/QtWidgets',
79 str(bld.env.COMP_PYSIDE_I_includedir)+'/QtGui',
80 ],
81 use=
82 bld.env.GEN_COMP_QT_USE +
83 [
84 "widgets"
85 ],
86 name='bindings',
87 # Fix for ELTDEV-853
88 pytest_path = [ bld.path.get_bld().abspath() ],
89 )
90 compilation.cxxflags = ["-Wno-pedantic"]
This compilation
step has the necessary features that provides its with the compiler
options to properly generate a c++ shared library, that has python and qt as depedency.
An extra set of includes entries for the PySide module are added, and a use statement is introduced to depend on the widgets waf module from the previous chapter of this guide.
This is how the module would look like with bindings added:
bindings
├── src
│ ├── bindings.h
│ └── bindings.xml
└── wscript