WigglyWidget 示例¶
这个例子展示了如何通过两种不同的方式与自定义小部件进行交互:
一个完整的从C++示例翻译的Python版本,
从C++文件生成的Python绑定。
原始示例包含三个不同的文件:
main.cpp/h
,被翻译为main.py
,dialog.cpp/h
,被翻译为dialog.py
,wigglywidget.cpp/h
,它被翻译为wigglywidget.py
, 但仍然保留原样,以便通过Shiboken生成绑定。
在dialog.py
文件中,您将找到两个导入,它们将分别与之前描述的两种方法相关:
# Python translated file
from wigglywidget import WigglyWidget
# Binding module create with Shiboken
from wiggly import WigglyWidget
构建绑定的步骤¶
最重要的文件是:
bindings.xml
,用于指定我们希望从C++暴露到Python的类,bindings.h
包含我们想要暴露的类的头文件CMakeList.txt
,包含构建共享库(DLL 或 dylib)的所有指令pyside_config.py
位于上一级的 utils 目录中,用于获取 Shiboken 和 PySide 的路径。
现在创建一个build/
目录,并在其中运行cmake
以使用提供的CMakeLists.txt
:
在macOS/Linux上运行CMake:
cd ~/pyside-setup/examples/widgetbinding
cd build
cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
在Windows上运行CMake:
cd C:\pyside-setup\examples\widgetbinding
mkdir build
cd build
cmake .. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=cl.exe
构建:
ninja
ninja install
cd ..
最终示例可以通过以下方式运行:
python main.py
你应该看到两个相同的自定义小部件,一个是Python翻译的,另一个是C++的。
最后的话¶
由于这个例子是通过混合scriptableapplication
和samplebinding
例子的概念而产生的,你可以用这些目录中的README来补充这个README。
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef BINDINGS_H
#define BINDINGS_H
#include "wigglywidget.h"
#endif // BINDINGS_H
<?xml version="1.0" encoding="UTF-8"?>
<!--
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-->
<typesystem package="wiggly">
<load-typesystem name="typesystem_widgets.xml" generate="no"/>
<object-type name="WigglyWidget"/>
</typesystem>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtWidgets import QDialog, QLineEdit, QVBoxLayout
# Python binding from the C++ widget
from wiggly import WigglyWidget as WigglyWidgetCPP
# Python-only widget
from wigglywidget import WigglyWidget as WigglyWidgetPY
class Dialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
wiggly_widget_py = WigglyWidgetPY(self)
wiggly_widget_cpp = WigglyWidgetCPP(self)
lineEdit = QLineEdit(self)
layout = QVBoxLayout(self)
layout.addWidget(wiggly_widget_py)
layout.addWidget(wiggly_widget_cpp)
layout.addWidget(lineEdit)
lineEdit.setClearButtonEnabled(True)
wiggly_widget_py.running = True
wiggly_widget_cpp.setRunning(True)
lineEdit.textChanged.connect(wiggly_widget_py.setText)
lineEdit.textChanged.connect(wiggly_widget_cpp.setText)
lineEdit.setText("🖖 Hello world!")
self.setWindowTitle("Wiggly")
self.resize(360, 145)
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef MACROS_H
#define MACROS_H
#include <QtCore/qglobal.h>
// Export symbols when creating .dll and .lib, and import them when using .lib.
#if BINDINGS_BUILD
# define BINDINGS_API Q_DECL_EXPORT
#else
# define BINDINGS_API Q_DECL_IMPORT
#endif
#endif // MACROS_H
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import sys
from PySide6.QtWidgets import QApplication
from dialog import Dialog
if __name__ == "__main__":
app = QApplication()
w = Dialog()
w.show()
sys.exit(app.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from wigglywidget import WigglyWidget
# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
TOOLTIP = "A cool wiggly widget (Python)"
DOM_XML = """
<ui language='c++'>
<widget class='WigglyWidget' name='wigglyWidget'>
<property name='geometry'>
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>200</height>
</rect>
</property>
<property name='text'>
<string>Hello, world</string>
</property>
</widget>
</ui>
"""
if __name__ == '__main__':
QPyDesignerCustomWidgetCollection.registerCustomWidget(WigglyWidget, module="wigglywidget",
tool_tip=TOOLTIP, xml=DOM_XML)
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "wigglywidget.h"
#include <QtGui/QFontMetrics>
#include <QtGui/QPainter>
#include <QtCore/QTimerEvent>
//! [0]
WigglyWidget::WigglyWidget(QWidget *parent)
: QWidget(parent)
{
setBackgroundRole(QPalette::Midlight);
setAutoFillBackground(true);
QFont newFont = font();
newFont.setPointSize(newFont.pointSize() + 20);
setFont(newFont);
}
//! [0]
//! [1]
void WigglyWidget::paintEvent(QPaintEvent * /* event */)
//! [1] //! [2]
{
if (m_text.isEmpty())
return;
static constexpr int sineTable[16] = {
0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38
};
QFontMetrics metrics(font());
int x = (width() - metrics.horizontalAdvance(m_text)) / 2;
int y = (height() + metrics.ascent() - metrics.descent()) / 2;
QColor color;
//! [2]
//! [3]
QPainter painter(this);
//! [3] //! [4]
int offset = 0;
const auto codePoints = m_text.toUcs4();
for (char32_t codePoint : codePoints) {
const int index = (m_step + offset++) % 16;
color.setHsv((15 - index) * 16, 255, 191);
painter.setPen(color);
QString symbol = QString::fromUcs4(&codePoint, 1);
const int dy = (sineTable[index] * metrics.height()) / 400;
painter.drawText(x, y - dy, symbol);
x += metrics.horizontalAdvance(symbol);
}
}
//! [4]
//! [5]
void WigglyWidget::timerEvent(QTimerEvent *event)
//! [5] //! [6]
{
if (event->timerId() == m_timer.timerId()) {
++m_step;
update();
} else {
QWidget::timerEvent(event);
}
//! [6]
}
QString WigglyWidget::text() const
{
return m_text;
}
void WigglyWidget::setText(const QString &newText)
{
m_text = newText;
}
bool WigglyWidget::isRunning() const
{
return m_timer.isActive();
}
void WigglyWidget::setRunning(bool r)
{
if (r == isRunning())
return;
if (r)
m_timer.start(60, this);
else
m_timer.stop();
}
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef WIGGLYWIDGET_H
#define WIGGLYWIDGET_H
#include "macros.h"
#include <QtWidgets/QWidget>
#include <QtCore/QBasicTimer>
//! [0]
class BINDINGS_API WigglyWidget : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool running READ isRunning WRITE setRunning)
Q_PROPERTY(QString text READ text WRITE setText)
public:
WigglyWidget(QWidget *parent = nullptr);
QString text() const;
bool isRunning() const;
public slots:
void setText(const QString &newText);
void setRunning(bool r);
protected:
void paintEvent(QPaintEvent *event) override;
void timerEvent(QTimerEvent *event) override;
private:
QBasicTimer m_timer;
QString m_text;
int m_step = 0;
};
//! [0]
#endif
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import QBasicTimer, Property
from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPalette
from PySide6.QtWidgets import QWidget
class WigglyWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._step = 0
self._text = ""
self.setBackgroundRole(QPalette.Midlight)
self.setAutoFillBackground(True)
new_font = self.font()
new_font.setPointSize(new_font.pointSize() + 20)
self.setFont(new_font)
self._timer = QBasicTimer()
def isRunning(self):
return self._timer.isActive()
def setRunning(self, r):
if r == self.isRunning():
return
if r:
self._timer.start(60, self)
else:
self._timer.stop()
def paintEvent(self, event):
if not self._text:
return
sineTable = [0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100,
-92, -71, -38]
metrics = QFontMetrics(self.font())
x = (self.width() - metrics.horizontalAdvance(self.text)) / 2
y = (self.height() + metrics.ascent() - metrics.descent()) / 2
color = QColor()
with QPainter(self) as painter:
for i in range(len(self.text)):
index = (self._step + i) % 16
color.setHsv((15 - index) * 16, 255, 191)
painter.setPen(color)
dy = (sineTable[index] * metrics.height()) / 400
c = self._text[i]
painter.drawText(x, y - dy, str(c))
x += metrics.horizontalAdvance(c)
def timerEvent(self, event):
if event.timerId() == self._timer.timerId():
self._step += 1
self.update()
else:
QWidget.timerEvent(event)
def text(self):
return self._text
def setText(self, text):
self._text = text
running = Property(bool, isRunning, setRunning)
text = Property(str, text, setText)
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.18)
cmake_policy(VERSION 3.18)
# Enable policy to not use RPATH settings for install_name on macOS.
if(POLICY CMP0068)
cmake_policy(SET CMP0068 NEW)
endif()
# Enable policy to run automoc on generated files.
if(POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
# Consider changing the project name to something relevant for you.
project(wiggly LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
find_package(Qt6 COMPONENTS Core Gui Widgets)
# ================================ General configuration ======================================
# Set CPP standard to C++17 minimum.
set(CMAKE_CXX_STANDARD 17)
# The wiggly library for which we will create bindings. You can change the name to something
# relevant for your project.
set(wiggly_library "libwiggly")
# The name of the generated bindings module (as imported in Python). You can change the name
# to something relevant for your project.
set(bindings_library "wiggly")
# The header file with all the types and functions for which bindings will be generated.
# Usually it simply includes other headers of the library you are creating bindings for.
set(wrapped_header ${CMAKE_SOURCE_DIR}/bindings.h)
# The typesystem xml file which defines the relationships between the C++ types / functions
# and the corresponding Python equivalents.
set(typesystem_file ${CMAKE_SOURCE_DIR}/bindings.xml)
# Specify which C++ files will be generated by shiboken. This includes the module wrapper
# and a '.cpp' file per C++ type. These are needed for generating the module shared
# library.
set(generated_sources
${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wiggly_module_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/wigglywidget_wrapper.cpp)
# ================================== Shiboken detection ======================================
# Use provided python interpreter if given.
if(NOT python_interpreter)
if(WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
find_program(python_interpreter "python_d")
if(NOT python_interpreter)
message(FATAL_ERROR
"A debug Python interpreter could not be found, which is a requirement when "
"building this example in a debug configuration. Make sure python_d.exe is in "
"PATH.")
endif()
else()
find_program(python_interpreter "python")
if(NOT python_interpreter)
message(FATAL_ERROR
"No Python interpreter could be found. Make sure python is in PATH.")
endif()
endif()
endif()
message(STATUS "Using python interpreter: ${python_interpreter}")
# Macro to get various pyside / python include / link flags and paths.
# Uses the not entirely supported utils/pyside_config.py file.
macro(pyside_config option output_var)
if(${ARGC} GREATER 2)
set(is_list ${ARGV2})
else()
set(is_list "")
endif()
execute_process(
COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside_config.py"
${option}
OUTPUT_VARIABLE ${output_var}
OUTPUT_STRIP_TRAILING_WHITESPACE)
if ("${${output_var}}" STREQUAL "")
message(FATAL_ERROR "Error: Calling pyside_config.py ${option} returned no output.")
endif()
if(is_list)
string (REPLACE " " ";" ${output_var} "${${output_var}}")
endif()
endmacro()
# Query for the shiboken generator path, Python path, include paths and linker flags.
pyside_config(--shiboken-module-path shiboken_module_path)
pyside_config(--shiboken-generator-path shiboken_generator_path)
pyside_config(--pyside-path pyside_path)
pyside_config(--pyside-include-path pyside_include_dir 1)
pyside_config(--python-include-path python_include_dir)
pyside_config(--shiboken-generator-include-path shiboken_include_dir 1)
pyside_config(--shiboken-module-shared-libraries-cmake shiboken_shared_libraries 0)
pyside_config(--python-link-flags-cmake python_linking_data 0)
pyside_config(--pyside-shared-libraries-cmake pyside_shared_libraries 0)
set(shiboken_path "${shiboken_generator_path}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}")
if(NOT EXISTS ${shiboken_path})
message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}")
endif()
# ==================================== RPATH configuration ====================================
# =============================================================================================
# !!! (The section below is deployment related, so in a real world application you will want to
# take care of this properly with some custom script or tool).
# =============================================================================================
# Enable rpaths so that the built shared libraries find their dependencies.
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH ${shiboken_module_path} ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
# =============================== CMake target - wiggly_library ===============================
# Get the relevant Qt include dirs, to pass them on to shiboken.
get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt6::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
set(INCLUDES "")
foreach(INCLUDE_DIR ${QT_WIDGETS_INCLUDE_DIRS})
list(APPEND INCLUDES "-I${INCLUDE_DIR}")
endforeach()
# On macOS, check if Qt is a framework build. This affects how include paths should be handled.
get_target_property(QtCore_is_framework Qt6::Core FRAMEWORK)
if (QtCore_is_framework)
get_target_property(qt_core_library_location Qt6::Core LOCATION)
get_filename_component(qt_core_library_location_dir "${qt_core_library_location}" DIRECTORY)
get_filename_component(lib_dir "${qt_core_library_location_dir}/../" ABSOLUTE)
list(APPEND INCLUDES "--framework-include-paths=${lib_dir}")
endif()
# We need to include the headers for the module bindings that we use.
set(pyside_additional_includes "")
foreach(INCLUDE_DIR ${pyside_include_dir})
list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtCore")
list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtGui")
list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtWidgets")
endforeach()
# Define the wiggly shared library for which we will create bindings.
set(${wiggly_library}_sources wigglywidget.cpp)
add_library(${wiggly_library} SHARED ${${wiggly_library}_sources})
set_property(TARGET ${wiggly_library} PROPERTY PREFIX "")
# Needed mostly on Windows to export symbols, and create a .lib file, otherwise the binding
# library can't link to the wiggly library.
target_compile_definitions(${wiggly_library} PRIVATE BINDINGS_BUILD)
# ====================== Shiboken target for generating binding C++ files ====================
# Set up the options to pass to shiboken.
set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic
--enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero
--avoid-protected-hack
${INCLUDES}
-I${CMAKE_SOURCE_DIR}
-T${CMAKE_SOURCE_DIR}
-T${pyside_path}/typesystems
--output-directory=${CMAKE_CURRENT_BINARY_DIR}
)
set(generated_sources_dependencies ${wrapped_header} ${typesystem_file})
# Add custom target to run shiboken to generate the binding cpp files.
add_custom_command(OUTPUT ${generated_sources}
COMMAND ${shiboken_path}
${shiboken_options} ${wrapped_header} ${typesystem_file}
DEPENDS ${generated_sources_dependencies}
#IMPLICIT_DEPENDS CXX ${wrapped_header}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Running generator for ${typesystem_file}.")
# =============================== CMake target - bindings_library =============================
# Set the cpp files which will be used for the bindings library.
set(${bindings_library}_sources ${generated_sources})
# Define and build the bindings library.
add_library(${bindings_library} SHARED ${${bindings_library}_sources})
# Apply relevant include and link flags.
target_include_directories(${bindings_library} PRIVATE ${pyside_additional_includes})
target_include_directories(${bindings_library} PRIVATE ${pyside_include_dir})
target_include_directories(${bindings_library} PRIVATE ${python_include_dir})
target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir})
target_link_libraries(${wiggly_library} PRIVATE Qt6::Widgets)
target_link_libraries(${bindings_library} PRIVATE Qt6::Widgets)
target_link_libraries(${bindings_library} PRIVATE ${wiggly_library})
target_link_libraries(${bindings_library} PRIVATE ${pyside_shared_libraries})
target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries})
# Adjust the name of generated module.
set_property(TARGET ${bindings_library} PROPERTY PREFIX "")
set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME
"${bindings_library}${PYTHON_EXTENSION_SUFFIX}")
if(WIN32)
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set_property(TARGET ${bindings_library} PROPERTY SUFFIX "_d.pyd")
else()
set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd")
endif()
endif()
# Make sure the linker doesn't complain about not finding Python symbols on macOS.
if(APPLE)
set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)
# Find and link to the python import library only on Windows.
# On Linux and macOS, the undefined symbols will get resolved by the dynamic linker
# (the symbols will be picked up in the Python executable).
if (WIN32)
list(GET python_linking_data 0 python_libdir)
list(GET python_linking_data 1 python_lib)
find_library(python_link_flags ${python_lib} PATHS ${python_libdir} HINTS ${python_libdir})
target_link_libraries(${bindings_library} PRIVATE ${python_link_flags})
endif()
# ================================= Dubious deployment section ================================
set(windows_shiboken_shared_libraries)
if(WIN32)
# =========================================================================================
# !!! (The section below is deployment related, so in a real world application you will
# want to take care of this properly (this is simply to eliminate errors that users usually
# encounter.
# =========================================================================================
# Circumvent some "#pragma comment(lib)"s in "include/pyconfig.h" which might force to link
# against a wrong python shared library.
set(python_versions_list 3 36 37 38 39)
set(python_additional_link_flags "")
foreach(ver ${python_versions_list})
set(python_additional_link_flags
"${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}_d.lib\"")
set(python_additional_link_flags
"${python_additional_link_flags} /NODEFAULTLIB:\"python${ver}.lib\"")
endforeach()
set_target_properties(${bindings_library}
PROPERTIES LINK_FLAGS "${python_additional_link_flags}")
# Compile a list of shiboken shared libraries to be installed, so that
# the user doesn't have to set the PATH manually to point to the PySide package.
foreach(library_path ${shiboken_shared_libraries})
string(REGEX REPLACE ".lib$" ".dll" library_path ${library_path})
file(TO_CMAKE_PATH ${library_path} library_path)
list(APPEND windows_shiboken_shared_libraries "${library_path}")
endforeach()
# =========================================================================================
# !!! End of dubious section.
# =========================================================================================
endif()
# =============================================================================================
# !!! (The section below is deployment related, so in a real world application you will want to
# take care of this properly with some custom script or tool).
# =============================================================================================
# Install the library and the bindings module into the source folder near the main.py file, so
# that the Python interpeter successfully imports the used module.
install(TARGETS ${bindings_library} ${wiggly_library}
LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
)
install(FILES ${windows_shiboken_shared_libraries} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR})
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import sysconfig
from enum import Enum
import glob
import os
import re
import sys
PYSIDE = 'pyside6'
PYSIDE_MODULE = 'PySide6'
SHIBOKEN = 'shiboken6'
class Package(Enum):
SHIBOKEN_MODULE = 1
SHIBOKEN_GENERATOR = 2
PYSIDE_MODULE = 3
generic_error = ('Did you forget to activate your virtualenv? Or perhaps'
f' you forgot to build / install {PYSIDE_MODULE} into your currently active Python'
' environment?')
pyside_error = f'Unable to locate {PYSIDE_MODULE}. {generic_error}'
shiboken_module_error = f'Unable to locate {SHIBOKEN}-module. {generic_error}'
shiboken_generator_error = f'Unable to locate shiboken-generator. {generic_error}'
pyside_libs_error = f'Unable to locate the PySide shared libraries. {generic_error}'
python_link_error = 'Unable to locate the Python library for linking.'
python_include_error = 'Unable to locate the Python include headers directory.'
options = []
# option, function, error, description
options.append(("--shiboken-module-path",
lambda: find_shiboken_module(),
shiboken_module_error,
"Print shiboken module location"))
options.append(("--shiboken-generator-path",
lambda: find_shiboken_generator(),
shiboken_generator_error,
"Print shiboken generator location"))
options.append(("--pyside-path", lambda: find_pyside(), pyside_error,
f"Print {PYSIDE_MODULE} location"))
options.append(("--python-include-path",
lambda: get_python_include_path(),
python_include_error,
"Print Python include path"))
options.append(("--shiboken-generator-include-path",
lambda: get_package_include_path(Package.SHIBOKEN_GENERATOR),
pyside_error,
"Print shiboken generator include paths"))
options.append(("--pyside-include-path",
lambda: get_package_include_path(Package.PYSIDE_MODULE),
pyside_error,
"Print PySide6 include paths"))
options.append(("--python-link-flags-qmake", lambda: python_link_flags_qmake(), python_link_error,
"Print python link flags for qmake"))
options.append(("--python-link-flags-cmake", lambda: python_link_flags_cmake(), python_link_error,
"Print python link flags for cmake"))
options.append(("--shiboken-module-qmake-lflags",
lambda: get_package_qmake_lflags(Package.SHIBOKEN_MODULE), pyside_error,
"Print shiboken6 shared library link flags for qmake"))
options.append(("--pyside-qmake-lflags",
lambda: get_package_qmake_lflags(Package.PYSIDE_MODULE), pyside_error,
"Print PySide6 shared library link flags for qmake"))
options.append(("--shiboken-module-shared-libraries-qmake",
lambda: get_shared_libraries_qmake(Package.SHIBOKEN_MODULE), pyside_libs_error,
"Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for qmake"))
options.append(("--shiboken-module-shared-libraries-cmake",
lambda: get_shared_libraries_cmake(Package.SHIBOKEN_MODULE), pyside_libs_error,
"Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for cmake"))
options.append(("--pyside-shared-libraries-qmake",
lambda: get_shared_libraries_qmake(Package.PYSIDE_MODULE), pyside_libs_error,
"Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) "
"for qmake"))
options.append(("--pyside-shared-libraries-cmake",
lambda: get_shared_libraries_cmake(Package.PYSIDE_MODULE), pyside_libs_error,
f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) "
"for cmake"))
options_usage = ''
for i, (flag, _, _, description) in enumerate(options):
options_usage += f' {flag:<45} {description}'
if i < len(options) - 1:
options_usage += '\n'
usage = f"""
Utility to determine include/link options of shiboken/PySide and Python for qmake/CMake projects
that would like to embed or build custom shiboken/PySide bindings.
Usage: pyside_config.py [option]
Options:
{options_usage}
-a Print all options and their values
--help/-h Print this help
"""
option = sys.argv[1] if len(sys.argv) == 2 else '-a'
if option == '-h' or option == '--help':
print(usage)
sys.exit(0)
def clean_path(path):
return path if sys.platform != 'win32' else path.replace('\\', '/')
def shared_library_suffix():
if sys.platform == 'win32':
return 'lib'
elif sys.platform == 'darwin':
return 'dylib'
# Linux
else:
return 'so.*'
def import_suffixes():
import importlib.machinery
return importlib.machinery.EXTENSION_SUFFIXES
def is_debug():
debug_suffix = '_d.pyd' if sys.platform == 'win32' else '_d.so'
return any([s.endswith(debug_suffix) for s in import_suffixes()])
def shared_library_glob_pattern():
glob = '*.' + shared_library_suffix()
return glob if sys.platform == 'win32' else 'lib' + glob
def filter_shared_libraries(libs_list):
def predicate(lib_name):
basename = os.path.basename(lib_name)
if 'shiboken' in basename or 'pyside6' in basename:
return True
return False
result = [lib for lib in libs_list if predicate(lib)]
return result
# Return qmake link option for a library file name
def link_option(lib):
# On Linux:
# Since we cannot include symlinks with wheel packages
# we are using an absolute path for the libpyside and libshiboken
# libraries when compiling the project
baseName = os.path.basename(lib)
link = ' -l'
if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so'
link = lib
elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo'
link += os.path.splitext(baseName[3:])[0]
else: # Windows: 'libfoo.dll' -> 'libfoo.dll'
link += os.path.splitext(baseName)[0]
return link
# Locate PySide6 via sys.path package path.
def find_pyside():
return find_package_path(PYSIDE_MODULE)
def find_shiboken_module():
return find_package_path(SHIBOKEN)
def find_shiboken_generator():
return find_package_path(f"{SHIBOKEN}_generator")
def find_package(which_package):
if which_package == Package.SHIBOKEN_MODULE:
return find_shiboken_module()
if which_package == Package.SHIBOKEN_GENERATOR:
return find_shiboken_generator()
if which_package == Package.PYSIDE_MODULE:
return find_pyside()
return None
def find_package_path(dir_name):
for p in sys.path:
if 'site-' in p:
package = os.path.join(p, dir_name)
if os.path.exists(package):
return clean_path(os.path.realpath(package))
return None
# Return version as "x.y" (e.g. 3.9, 3.12, etc)
def python_version():
return str(sys.version_info[0]) + '.' + str(sys.version_info[1])
def get_python_include_path():
return sysconfig.get_path('include')
def python_link_flags_qmake():
flags = python_link_data()
if sys.platform == 'win32':
libdir = flags['libdir']
# This will add the "~1" shortcut for directories that
# contain white spaces
# e.g.: "Program Files" to "Progra~1"
for d in libdir.split("\\"):
if " " in d:
libdir = libdir.replace(d, d.split(" ")[0][:-1] + "~1")
lib_flags = flags['lib']
return f'-L{libdir} -l{lib_flags}'
elif sys.platform == 'darwin':
libdir = flags['libdir']
lib_flags = flags['lib']
return f'-L{libdir} -l{lib_flags}'
else:
# Linux and anything else
libdir = flags['libdir']
lib_flags = flags['lib']
return f'-L{libdir} -l{lib_flags}'
def python_link_flags_cmake():
flags = python_link_data()
libdir = flags['libdir']
lib = re.sub(r'.dll$', '.lib', flags['lib'])
return f'{libdir};{lib}'
def python_link_data():
# @TODO Fix to work with static builds of Python
libdir = sysconfig.get_config_var('LIBDIR')
if libdir is None:
libdir = os.path.abspath(os.path.join(
sysconfig.get_config_var('LIBDEST'), "..", "libs"))
version = python_version()
version_no_dots = version.replace('.', '')
flags = {}
flags['libdir'] = libdir
if sys.platform == 'win32':
suffix = '_d' if is_debug() else ''
flags['lib'] = f'python{version_no_dots}{suffix}'
elif sys.platform == 'darwin':
flags['lib'] = f'python{version}'
# Linux and anything else
else:
flags['lib'] = f'python{version}{sys.abiflags}'
return flags
def get_package_include_path(which_package):
package_path = find_package(which_package)
if package_path is None:
return None
includes = f"{package_path}/include"
return includes
def get_package_qmake_lflags(which_package):
package_path = find_package(which_package)
if package_path is None:
return None
link = f"-L{package_path}"
glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern()))
for lib in filter_shared_libraries(glob_result):
link += ' '
link += link_option(lib)
return link
def get_shared_libraries_data(which_package):
package_path = find_package(which_package)
if package_path is None:
return None
glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern()))
filtered_libs = filter_shared_libraries(glob_result)
libs = []
if sys.platform == 'win32':
for lib in filtered_libs:
libs.append(os.path.realpath(lib))
else:
for lib in filtered_libs:
libs.append(lib)
return libs
def get_shared_libraries_qmake(which_package):
libs = get_shared_libraries_data(which_package)
if libs is None:
return None
if sys.platform == 'win32':
if not libs:
return ''
dlls = ''
for lib in libs:
dll = os.path.splitext(lib)[0] + '.dll'
dlls += dll + ' '
return dlls
else:
libs_string = ''
for lib in libs:
libs_string += lib + ' '
return libs_string
def get_shared_libraries_cmake(which_package):
libs = get_shared_libraries_data(which_package)
result = ';'.join(libs)
return result
print_all = option == "-a"
for argument, handler, error, _ in options:
if option == argument or print_all:
handler_result = handler()
if handler_result is None:
sys.exit(error)
line = handler_result
if print_all:
line = f"{argument:<40}: {line}"
print(line)