Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.
PySide Binding Generation Tutorial: Module 5 Building the generator
This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine. Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean. |
[toc align_right="yes" depth="3"]
English "French":http://qt-devnet.developpez.com/tutoriels/python/pyside/binding-shiboken/#LVII
- Note: this article is a member of the multipart "PySide Binding Generation Tutorial":http://developer.qt.nokia.com/wiki/Category:LanguageBindings::PySide::Shiboken::PySide_Binding_Generation_Tutorial
PySide Binding Generation Tutorial
Three Steps to Build the Binding
As mentioned before, the build system used must perform the following tasks in the correct order:
- Gather data about locations of headers and needed type systems from other projects.
- Run the generator with the correct parameters.
- Compile and link the binding.
Gather Information
There are two options to gather data about locations of headers and needed type systems:
Collect Information with pkg-config
The Qt bindings include compile and build information through the pkg-config mechanism. The pkg-config name for Qt Python bindings is
pyside
and a simple
pkg-config pyside —cflags —libs
will retrieve information required to build the new binding. The Qt bindings file
pyside.pc
for the use of pkg-config requires Qt's
.pc
files to be installed. If the library is in an unusual location, e.g.
/opt/qt47
, remember to export it to the
PKG_CONFIG_PATH
environment variable. For example:
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/qt47/lib/pkgconfig
Information is also available through pkg-config: the
typesystemdir
variable. It is used like this:
pkg-config pyside —variable=typesystemdir
This provides information where to find the type system files used to create the Qt bindings. As mentioned before, the binding being created needs this to complement its own binding information for the generation proccess. Information from the Shiboken binding generator is also needed for the build, it's pkg-config name is
shiboken
. More details on this later.
Collect Information with CMake
When building your binding with CMake the relevant information can be included from your project's
CMakeLists.txt
using:
find_package(Shiboken REQUIRED)
find_package(PySide REQUIRED)
Requiring the Shiboken and PySide packages will set the values of a number of variables, according to
PySideConfig.cmake<code> file:
PYSIDE_INCLUDE_DIR - Directories to include to use PySide PYSIDE_LIBRARY - Files to link against to use PySide PYSIDE_PYTHONPATH - Path to where the PySide Python module files could be found PYSIDE_TYPESYSTEMS - Type system files that should be used by other bindings extending PySide
Similarly <code>ShibokenConfig.cmake
provides needed information:
SHIBOKEN_INCLUDE_DIR - Directories to include to use SHIBOKEN SHIBOKEN_LIBRARIES - Files to link against to use SHIBOKEN SHIBOKEN_BUILD_TYPE - Tells if Shiboken was compiled in Release or Debug mode. SHIBOKEN_PYTHON_INTERPRETER - Python interpreter (regular or debug) to be used with the bindings. SHIBOKEN_PYTHON_LIBRARIES - Python libraries (regular or debug) Shiboken is linked against.
== Run the Generator ==
The generator is called with the following parameters and options:
generatorrunner —generatorSet=shiboken global_header.h —include-paths=$(PATHS_TO_HEADERS)) —typesystem-paths=$(PATHS_TO_TYPESYSTEMS) —output-directory=. typesystem.xml
Note that the variables for include and type system paths could be determined at build time with the pkg-config tool or with information provided by CMake configuration files.
== Build ==
This section will alternate in presenting the two build methods: Makefile and CMake.
=== The Makefile Version ===
Below is a plain Makefile for the binding project.
<code>foobinding-makefile/Makefile
:
LIBFOO_DIR = `pwd`/../libfoo LIBS = `pkg-config pyside —libs` -L$(LIBFOO_DIR) -lfoo CXXFLAGS = -I/usr/share/qt4/mkspecs/linux-g++ -I. -I$(LIBFOO_DIR) -I`pwd`/foo -I`pkg-config —variable=includedir pyside`/QtCore/ -I`pkg-config —variable=includedir QtCore` -I`pkg-config —variable=includedir QtCore`/.. -I`pkg-config —variable=includedir QtGui` `pkg-config pyside —cflags`
QT4TYPESYSTEM_DIR = `pkg-config pyside —variable=typesystemdir` QT4HEADER_DIRS = `pkg-config —variable=includedir QtCore`:`pkg-config —variable=includedir QtCore`/.. PYSIDE_PYTHONPATH = `pkg-config —variable=pythonpath PySide` PYTHON_INTERPRETER = `pkg-config —variable=python_interpreter shiboken`
all: generate compile link
generate:
generatorrunner —generatorSet=shiboken global.h —include-paths=$(LIBFOO_DIR):$(QT4HEADER_DIRS):/usr/include —typesystem-paths=.:$(QT4TYPESYSTEM_DIR) —output-directory=. typesystem_foo.xml
compile:
g++ foo/foo_module_wrapper.cpp foo/math_wrapper.cpp -Wall -fPIC $(CXXFLAGS) -c
link:
g++ foo_module_wrapper.o math_wrapper.o $(LIBS) -fPIC -shared -Wl,-soname,foo.so -o foo.so
test:
LD_LIBRARY_PATH=$(LIBFOO_DIR):$(LD_LIBRARY_PATH) PYTHONPATH=$(PYSIDE_PYTHONPATH):$(PYTHONPATH) $(PYTHON_INTERPRETER) -c "import foo; m = foo.Math(); print '5 squared is d' m.squared(5)"
clean:
rm -rf .o.so .?pp.log .log foo/
Keep in mind that the Makefile above expects the <code>libfoo
and
foobinding-makefile
directories to be in the same level in the directory hierarchy. Remember to change any path references accordingly if you elect to change things.
Build and Test
Now generate, compile and link the binding with make:
cd foobinding-makefile make make test
The <code>make test
causes the Python interpreter to run the line
import foo; m = foo.Math(); print '5 squared is d' m.squared(5)
, which will import the binding module, instantiate the class from it, run a method and print its result (which should be 25).
The CMake Version
foobinding-cmake/CMakeLists.txt
:
project(foobinding)
cmake_minimum_required(VERSION 2.6)
find_package(PythonLibs REQUIRED) find_package(Shiboken REQUIRED) find_package(PySide REQUIRED) find_package(Qt4 4.6.2 REQUIRED)
set(LIBFOO_DIR ${CMAKE_SOURCE_DIR}/../libfoo)
find_program(GENERATOR generatorrunner REQUIRED) if (NOT GENERATOR)
message(FATAL_ERROR "You need to specify GENERATOR variable (-DGENERATOR=value)")
endif()
if(CMAKE_HOST_UNIX)
option(ENABLE_GCC_OPTIMIZATION "Enable specific GCC flags to optimization library size and performance. Only available on Release Mode" 0) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fvisibility=hidden -Wno-strict-aliasing") set(CMAKE_CXX_FLAGS_DEBUG "-g") if(ENABLE_GCC_OPTIMIZATION) set(CMAKE_BUILD_TYPE Release) set(CMAKE_CXX_FLAGS_RELEASE "-DNDEBUG -Os -Wl,-O1") if(NOT CMAKE_HOST_APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,—hash-style=gnu") endif() endif()
if(CMAKE_HOST_APPLE) if (NOT QT_INCLUDE_DIR) set(QT_INCLUDE_DIR "/Library/Frameworks") endif() endif()
endif()
include(${QT_USE_FILE})
enable_testing()
add_subdirectory(foo) add_subdirectory(tests)
This is the main project's <code>CMakeLists.txt
, it is a regular CMake file and general doubts can be checked in the "CMake documentation":http://www.cmake.org/cmake/help/documentation.html Notice the we're going to have tests in this project so we have to enable them with
enable_testing()
.
foobinding-cmake/foo/CMakeLists.txt
:
project(foo)
set(foo_SRC
${CMAKE_CURRENT_BINARY_DIR}/foo/foo_module_wrapper.cpp ${CMAKE_CURRENT_BINARY_DIR}/foo/math_wrapper.cpp
)
set(foo_INCLUDE_DIRECTORIES
${SHIBOKEN_INCLUDE_DIR} ${PYTHON_INCLUDE_PATH} ${PYSIDE_INCLUDE_DIR} ${PYSIDE_INCLUDE_DIR}/QtCore ${QT_INCLUDE_DIR} ${QT_QTCORE_INCLUDE_DIR} ${LIBFOO_DIR}
)
set(foo_LINK_LIBRARIES
${QT_QTCORE_LIBRARY} ${SHIBOKEN_PYTHON_LIBRARIES} ${SHIBOKEN_LIBRARY} ${PYSIDE_LIBRARY} ${LIBFOO_DIR}/libfoo.so
)
include_directories(foo ${foo_INCLUDE_DIRECTORIES}) add_library(foo MODULE ${foo_SRC}) set_property(TARGET foo PROPERTY PREFIX "") target_link_libraries(foo ${foo_LINK_LIBRARIES})
add_custom_command(OUTPUT ${foo_SRC}
COMMAND ${GENERATOR} —generatorSet=shiboken —enable-parent-ctor-heuristic —enable-pyside-extensions —enable-return-value-heuristic ${CMAKE_SOURCE_DIR}/foo/global.h —include-paths=${QT_INCLUDE_DIR}:${LIBFOO_DIR} —typesystem-paths=${typesystem_path}:${PYSIDE_TYPESYSTEMS} —output-directory=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/typesystem_foo.xml WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Running generator for libfoo…" )
This is the <code>CMakeLists.txt
file for the binding directory proper, the
add_custom_command
statement is responsible for the calling of Shiboken generator with the proper parameters and variables. Notice that the command line options
—enable-parent-ctor-heuristic —enable-pyside-extensions —enable-return-value-heuristic
are directly related to Qt bindings idiosyncrasies, for a pure C++ binding none of those will be necessary.
foobinding-cmake/tests/CMakeLists.txt
:
if(WIN32)
set(TEST_PYTHONPATH "${foo_BINARY_DIR};${PYSIDE_PYTHONPATH}") set(TEST_LIBRARY_PATH "${LIBFOO_DIR};$ENV{PATH}") set(LIBRARY_PATH_VAR "PATH") string(REPLACE "" "/" TEST_PYTHONPATH "${TEST_PYTHONPATH}") string(REPLACE "" "/" TEST_LIBRARY_PATH "${TEST_LIBRARY_PATH}")
string(REPLACE ";" ";" TEST_PYTHONPATH "${TEST_PYTHONPATH}") string(REPLACE ";" ";" TEST_LIBRARY_PATH "${TEST_LIBRARY_PATH}")
else()
set(TEST_PYTHONPATH "${foo_BINARY_DIR}:${PYSIDE_PYTHONPATH}") set(TEST_LIBRARY_PATH "${LIBFOO_DIR}:$ENV{LD_LIBRARY_PATH}") set(LIBRARY_PATH_VAR "LD_LIBRARY_PATH")
endif()
add_test(math ${SHIBOKEN_PYTHON_INTERPRETER} ${CMAKE_CURRENT_SOURCE_DIR}/math_test.py) set_tests_properties(math PROPERTIES ENVIRONMENT "PYTHONPATH=${TEST_PYTHONPATH};${LIBRARY_PATH_VAR}=${TEST_LIBRARY_PATH}")
This not very elaborate <code>CMakeLists.txt
informs CMake which tests should be executed, and with which variables.
h4. Build and Test
The best thing to do when building with CMake is to create a build directory and run
cmake
from there.
cd foobinding-cmake mkdir build cd build cmake .. make
Ah, let's not forget the unit test. It's a very simple one.
<code>foobinding-cmake/tests/math.py
- !/usr/bin/env python
- -- coding: utf-8 --
Test cases for foo bindings module.
import unittest import foo
class MathTest(unittest.TestCase):
def testMath(self):
Test case for Math class from foo module. val = 5 math = foo.Math() self.assertEqual(math.squared(5), 5 * 5)
if name == main:
unittest.main()
To run the test:
ctest
The output will be something like this:
Test project YOURPATH/binding-tutorial/foobinding-cmake/build
Start 1: math
1/1 Test #1: math ……………………….. Passed 0.10 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.11 sec
For a more verbose output use <code>ctest -V