脚本化应用程序示例#
本示例演示了如何使Qt C++应用程序可脚本化。
它有一个名为MainWindow
(文件mainwindow.cpp,h
)的类,该类继承自QMainWindow
,使用Shiboken生成绑定。
头文件wrappedclasses.h
传递给Shiboken,它会生成子目录AppLib/
中的类封装器和头文件,这些文件与应用程序链接。
pythonutils.cpp,h
文件包含一些代码,将MainWindow
的实例绑定到全局Python命名空间(__main___
)中的变量mainWindow
。然后可以运行Python脚本片段,例如
mainWindow.testFunction1()
这会触发底层的C++函数。
构建项目#
可以使用CMake
或QMake
构建此示例,但需要考虑一些共同的要求
请确保在当前活动Python环境(系统或虚拟环境)中安装了捆绑在Qt库中的独立PySide软件包
qmake 必须在你的 PATH 中
这样才能让 CMake find_package(Qt6 COMPONENTS Core) 起作用(用于头文件包含),
用于使用 qmake 而不是 CMake 构建 应用程序
构建示例应用程序时使用与构建 PySide 相同的 Qt 版本,以确保新生成的绑定库、PySide 库和 Qt 库之间的二进制兼容性。
对于 Windows,你还需要:* 一个在终端中激活的 Visual Studio 环境
选择正确的 visual studio 架构(32 位对 64 位)
确保你的 Qt + Python + PySide 软件包 + 应用程序构建配置相同(全部是发布版本,这是更可能的情况,或者全部是调试版本)。
确保你的 Qt + Python + PySide 软件包 + 应用程序使用与 MSVC 兼容的版本构建,以避免 C++ 运行时库的混合。
两个构建选项都将使用 pyside_config.py
文件来配置项目,使用当前的 PySide/Shiboken 安装(通过 pyside.pri
使用 qmake,通过项目的 CMakeLists.txt
使用 CMake)。
使用 CMake#
要使用 CMake 构建此示例,你需要一个较新的 CMake 版本(3.16+)。
你可以在终端中执行以下命令(根据您的文件系统布局进行了一些修改)来构建此示例
macOS/Linux
cd ~/pyside-setup/examples/scriptableapplication
在 Windows 上
cd C:\pyside-setup\examples\scriptableapplication
mkdir build
cd build
cmake -S.. -B. -G Ninja -DCMAKE_BUILD_TYPE=Release
ninja
./scriptableapplication
使用 QMake#
当使用 qmake 时,scriptableapplication.pro
文件是与示例关联的项目文件。
你可以通过执行以下命令来构建此示例
mkdir build
cd build
qmake ..
make # or nmake / jom for Windows
Windows 故障排除#
使用 qmake
应该会正常工作,之前有一个已知的问题涉及目录和空格,通过使用“~1”字符已解决,因此路径将从:c:\Program Files\Python39\libs
更改为 c:\Progra~1\Python39\libs
,这将避免生成 Makefile 时出现的问题。
在使用 CMake
时,可能会选择错误架构的编译器,但可以通过设置 CC
环境变量来明确解决。
set CC=cl
通过命令行传递编译器
cmake -S.. -B. -DCMAKE_C_COMPILER=cl.exe -DCMAKE_CXX_COMPILER=cl.exe
或使用 -G 选项
cmake -S.. -B. -G "Visual Studio 14 Win64" -DCMAKE_BUILD_TYPE=Release
如果使用了“-G "Visual Studio 14 Win64"”选项,将生成一个 sln
文件,可以用 MSBuild
替代 ninja
使用。
MSBuild scriptableapplication.sln "/p:Configuration=Release"
请注意,使用“Ninja”生成器比使用“MSBuild”生成器更受欢迎,因为在后一种情况下,可执行文件将放置在包含依赖 dll(shiboken,pyside)的目录之外。如果应用程序在包含依赖的目录而不是 Release 子目录中启动,这可能导致执行问题。
Virtualenv 支持#
如果应用程序是从激活了 python 虚拟环境的终端启动的,则该环境中的软件包将被用于 python 模块导入过程。在这种情况下,请确保应用程序是在虚拟环境 virtualenv 激活时构建的,以便构建系统选择正确的 python 共享库和 PySide 软件包。
Windows 注意事项
应用程序的构建配置(调试或发布)应与 PySide6 的构建配置相匹配,否则应用程序将无法正确运行。
实际上,这意味着唯一受支持的配置是:
应用程序的发布配置构建 + PySide 的
setup.py
没有包含--debug
标志 + 用于 PySide 构建过程的python.exe
+ 链接到的共享库的python39.dll
+ Qt 的发布构建。应用程序的调试配置构建 + 包含
--debug
标志的 PySide 的setup.py
+ 用于 PySide 构建过程的python_d.exe
+ 链接到共享库的python39_d.dll
+ Qt 的调试构建。
这是必要的,因为所讨论的所有共享库都必须链接到相同的 C++ 运行时库(msvcrt.dll
或 msvcrtd.dll
)。为了使示例尽可能独立,正在使用的共享库(pyside6.dll
、shiboken6.dll
)被硬链接到应用程序的构建文件夹中。
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mainwindow.h"
#include <QApplication>
#include <QScreen>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow mainWindow;
const QRect availableGeometry = mainWindow.screen()->availableGeometry();
mainWindow.resize(availableGeometry.width() / 2, availableGeometry.height() / 2);
mainWindow.show();
return a.exec();
}
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mainwindow.h"
#include "pythonutils.h"
#include <QtWidgets/QApplication>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPlainTextEdit>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QVBoxLayout>
#include <QtGui/QAction>
#include <QtGui/QFontDatabase>
#include <QtGui/QIcon>
#include <QtCore/QDebug>
#include <QtCore/QTextStream>
using namespace Qt::StringLiterals;
static const auto defaultScript = R"(import AppLib
print("Hello, world")
mainWindow.testFunction1()
)"_L1;
MainWindow::MainWindow()
: m_scriptEdit(new QPlainTextEdit(defaultScript, this))
{
setWindowTitle(tr("Scriptable Application"));
auto *fileMenu = menuBar()->addMenu(tr("&File"));
const QIcon runIcon = QIcon::fromTheme("system-run"_L1);
auto *runAction = fileMenu->addAction(runIcon, tr("&Run..."),
this, &MainWindow::slotRunScript);
runAction->setShortcut(Qt::CTRL | Qt::Key_R);
auto *diagnosticAction = fileMenu->addAction(tr("&Print Diagnostics"),
this, &MainWindow::slotPrintDiagnostics);
diagnosticAction->setShortcut(Qt::CTRL | Qt::Key_D);
fileMenu->addAction(tr("&Invoke testFunction1()"),
this, &MainWindow::testFunction1);
const QIcon quitIcon = QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit);
auto *quitAction = fileMenu->addAction(quitIcon, tr("&Quit"),
qApp, &QCoreApplication::quit);
quitAction->setShortcut(Qt::CTRL | Qt::Key_Q);
auto *editMenu = menuBar()->addMenu(tr("&Edit"));
const QIcon clearIcon = QIcon::fromTheme(QIcon::ThemeIcon::EditClear);
auto *clearAction = editMenu->addAction(clearIcon, tr("&Clear"),
m_scriptEdit, &QPlainTextEdit::clear);
auto *helpMenu = menuBar()->addMenu(tr("&Help"));
const QIcon aboutIcon = QIcon::fromTheme(QIcon::ThemeIcon::HelpAbout);
auto *aboutAction = helpMenu->addAction(aboutIcon, tr("&About Qt"),
qApp, &QApplication::aboutQt);
auto *toolBar = new QToolBar;
addToolBar(toolBar);
toolBar->addAction(quitAction);
toolBar->addSeparator();
toolBar->addAction(clearAction);
toolBar->addSeparator();
toolBar->addAction(runAction);
toolBar->addSeparator();
toolBar->addAction(aboutAction);
m_scriptEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
setCentralWidget(m_scriptEdit);
if (!PythonUtils::bindAppObject("__main__"_L1, "mainWindow"_L1,
PythonUtils::MainWindowType, this)) {
statusBar()->showMessage(tr("Error loading the application module"));
}
}
void MainWindow::slotRunScript()
{
const QString text = m_scriptEdit->toPlainText().trimmed();
if (!text.isEmpty())
runScript(text);
}
void MainWindow::slotPrintDiagnostics()
{
const QString script = R"P(import sys
print('Path=', sys.path)
print('Executable=', sys.executable)
)P"_L1;
runScript(script);
}
void MainWindow::runScript(const QString &script)
{
if (!::PythonUtils::runScript(script))
statusBar()->showMessage(tr("Error running script"));
}
void MainWindow::testFunction1()
{
static int n = 1;
QString message;
QTextStream(&message) << __FUNCTION__ << " called #" << n++;
qDebug().noquote() << message;
statusBar()->showMessage(message);
}
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtWidgets/QMainWindow>
QT_FORWARD_DECLARE_CLASS(QPlainTextEdit)
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
void testFunction1();
static constexpr auto TEST = QLatin1StringView("test");
private Q_SLOTS:
void slotRunScript();
void slotPrintDiagnostics();
private:
void runScript(const QString &);
QPlainTextEdit *m_scriptEdit;
};
#endif // MAINWINDOW_H
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "pythonutils.h"
#include <QtCore/QByteArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QOperatingSystemVersion>
#include <QtCore/QStringList>
#include <QtCore/QTemporaryFile>
#include <QtCore/QDir>
#include <sbkpython.h>
#include <sbkconverter.h>
#include <sbkmodule.h>
extern "C" PyObject *PyInit_AppLib();
static const char moduleName[] = "AppLib";
// This variable stores all Python types exported by this module.
extern PyTypeObject **SbkAppLibTypes;
// This variable stores all type converters exported by this module.
extern SbkConverter **SbkAppLibTypeConverters;
namespace PythonUtils {
static State state = PythonUninitialized;
static void cleanup()
{
if (state > PythonUninitialized) {
Py_Finalize();
state = PythonUninitialized;
}
}
static const char virtualEnvVar[] = "VIRTUAL_ENV";
// If there is an active python virtual environment, use that environment's
// packages location.
static void initVirtualEnvironment()
{
// As of Python 3.8, Python is no longer able to run stand-alone in a
// virtualenv due to missing libraries. Add the path to the modules instead.
if (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows
&& (PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 8))) {
const QByteArray virtualEnvPath = qgetenv(virtualEnvVar);
qputenv("PYTHONPATH", virtualEnvPath + "\\Lib\\site-packages");
}
}
State init()
{
if (state > PythonUninitialized)
return state;
if (qEnvironmentVariableIsSet(virtualEnvVar))
initVirtualEnvironment();
if (PyImport_AppendInittab(moduleName, PyInit_AppLib) == -1) {
qWarning("Failed to add the module '%s' to the table of built-in modules.", moduleName);
return state;
}
Py_Initialize();
qAddPostRoutine(cleanup);
state = PythonInitialized;
const bool pythonInitialized = PyInit_AppLib() != nullptr;
const bool pyErrorOccurred = PyErr_Occurred() != nullptr;
if (pythonInitialized && !pyErrorOccurred) {
state = AppModuleLoaded;
} else {
if (pyErrorOccurred)
PyErr_Print();
qWarning("Failed to initialize the module.");
}
return state;
}
bool bindAppObject(const QString &moduleName, const QString &name,
int index, QObject *o)
{
if (init() != AppModuleLoaded)
return false;
PyTypeObject *typeObject = SbkAppLibTypes[index];
PyObject *po = Shiboken::Conversions::pointerToPython(typeObject, o);
if (!po) {
qWarning() << __FUNCTION__ << "Failed to create wrapper for" << o;
return false;
}
Py_INCREF(po);
PyObject *module = PyImport_AddModule(moduleName.toLocal8Bit().constData());
if (!module) {
Py_DECREF(po);
if (PyErr_Occurred())
PyErr_Print();
qWarning() << __FUNCTION__ << "Failed to locate module" << moduleName;
return false;
}
if (PyModule_AddObject(module, name.toLocal8Bit().constData(), po) < 0) {
if (PyErr_Occurred())
PyErr_Print();
qWarning() << __FUNCTION__ << "Failed add object" << name << "to" << moduleName;
return false;
}
return true;
}
bool runScript(const QString &script)
{
if (init() == PythonUninitialized)
return false;
// Executing the whole script as one line
bool result = true;
const QByteArray line = script.toUtf8();
if (PyRun_SimpleString(line.constData()) == -1) {
if (PyErr_Occurred())
PyErr_Print();
result = false;
}
return result;
}
} // namespace PythonUtils
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef PYTHONUTILS_H
#define PYTHONUTILS_H
#include <QtCore/QStringList>
QT_FORWARD_DECLARE_CLASS(QObject)
namespace PythonUtils {
enum AppLibTypes
{
MainWindowType = 0 // SBK_MAINWINDOW_IDX
};
enum State
{
PythonUninitialized,
PythonInitialized,
AppModuleLoaded
};
State init();
bool bindAppObject(const QString &moduleName, const QString &name,
int index, QObject *o);
bool runScript(const QString &script);
} // namespace PythonUtils
#endif // PYTHONUTILS_H
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef WRAPPEDCLASSES_H
#define WRAPPEDCLASSES_H
#include <mainwindow.h>
#endif // WRAPPEDCLASSES_H
# 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 run automoc on generated files.
if(POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
project(scriptableapplication)
# Set CPP standard to C++17 minimum.
set(CMAKE_CXX_STANDARD 17)
# Find required Qt packages.
find_package(Qt6 COMPONENTS Core Gui Widgets)
# 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.
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 shiboken6-generator path, PySide6 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(--python-include-path PYTHON_INCLUDE_DIR)
pyside_config(--shiboken-generator-include-path SHIBOKEN_GENERATOR_INCLUDE_DIR 1)
pyside_config(--pyside-include-path PYSIDE_INCLUDE_DIR 1)
pyside_config(--python-link-flags-cmake PYTHON_LINKING_DATA 0)
pyside_config(--shiboken-module-shared-libraries-cmake SHIBOKEN_MODULE_SHARED_LIBRARIES 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()
# Get all 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)
# PYSIDE-623: We move up until the directory contains all the frameworks.
# This is "lib" in ".../lib/QtCore.framework/Versions/A/QtCore".
get_filename_component(lib_dir "${qt_core_library_location}/../../../.." ABSOLUTE)
list(APPEND INCLUDES "--framework-include-paths=${lib_dir}")
endif()
# Set up the options to pass to shiboken.
set(WRAPPED_HEADER ${CMAKE_SOURCE_DIR}/wrappedclasses.h)
set(TYPESYSTEM_FILE ${CMAKE_SOURCE_DIR}/scriptableapplication.xml)
set(SHIBOKEN_OPTIONS --generator-set=shiboken --enable-parent-ctor-heuristic
--enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb-bool
--avoid-protected-hack
${INCLUDES}
-I${CMAKE_SOURCE_DIR}
-T${CMAKE_SOURCE_DIR}
-T${PYSIDE_PATH}/typesystems
--output-directory=${CMAKE_CURRENT_BINARY_DIR}
)
# Specify which sources will be generated by shiboken, and their dependencies.
set(GENERATED_SOURCES
${CMAKE_CURRENT_BINARY_DIR}/AppLib/applib_module_wrapper.cpp
${CMAKE_CURRENT_BINARY_DIR}/AppLib/mainwindow_wrapper.cpp)
set(GENERATED_SOURCES_DEPENDENCIES
${WRAPPED_HEADER}
${TYPESYSTEM_FILE}
)
# Add custom target to run shiboken.
add_custom_command(OUTPUT ${GENERATED_SOURCES}
COMMAND ${SHIBOKEN_PATH}
${SHIBOKEN_OPTIONS} ${WRAPPED_HEADER} ${TYPESYSTEM_FILE}
DEPENDS ${GENERATED_SOURCES_DEPENDENCIES}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Running generator for ${TYPESYSTEM_FILE}.")
# Set the CPP files.
set(SOURCES
mainwindow.cpp
pythonutils.cpp
${GENERATED_SOURCES}
)
# 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()
# =============================================================================================
# !!! (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 example can be executed from the build dir.
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(CMAKE_INSTALL_RPATH ${PYSIDE_PATH} ${SHIBOKEN_MODULE_PATH})
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
# Declare executable so we can enable automoc.
add_executable(${PROJECT_NAME} main.cpp)
# Enable automoc.
set_property(TARGET ${PROJECT_NAME} PROPERTY AUTOMOC 1)
# Add the rest of the sources.
target_sources(${PROJECT_NAME} PUBLIC ${SOURCES})
# Apply relevant include and link flags.
target_include_directories(${PROJECT_NAME} PRIVATE ${PYTHON_INCLUDE_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${SHIBOKEN_GENERATOR_INCLUDE_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE_INCLUDE_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE_ADDITIONAL_INCLUDES})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets)
target_link_libraries(${PROJECT_NAME} PRIVATE ${SHIBOKEN_MODULE_SHARED_LIBRARIES})
target_link_libraries(${PROJECT_NAME} PRIVATE ${PYSIDE_SHARED_LIBRARIES})
# Find and link to the python library.
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(${PROJECT_NAME} PRIVATE ${PYTHON_LINK_FLAGS})
# Same as CONFIG += no_keywords to avoid syntax errors in object.h due to the usage of the word Slot
target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_KEYWORDS)
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(${PROJECT_NAME} PROPERTIES LINK_FLAGS "${PYTHON_ADDITIONAL_LINK_FLAGS}")
# Add custom target to hard link PySide6 shared libraries (just like in qmake example), so you
# don't have to set PATH manually to point to the PySide6 package.
set(shared_libraries ${SHIBOKEN_MODULE_SHARED_LIBRARIES} ${PYSIDE_SHARED_LIBRARIES})
foreach(LIBRARY_PATH ${shared_libraries})
string(REGEX REPLACE ".lib$" ".dll" LIBRARY_PATH ${LIBRARY_PATH})
get_filename_component(BASE_NAME ${LIBRARY_PATH} NAME)
file(TO_NATIVE_PATH ${LIBRARY_PATH} SOURCE_PATH)
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${BASE_NAME}" DEST_PATH)
add_custom_command(OUTPUT "${BASE_NAME}"
COMMAND mklink /H "${DEST_PATH}" "${SOURCE_PATH}"
DEPENDS ${LIBRARY_PATH}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Creating hardlink to PySide6 shared library ${BASE_NAME}")
# Fake target that depends on the previous one, but has special ALL keyword, which means
# it will always be executed.
add_custom_target("fake_${BASE_NAME}" ALL DEPENDS ${BASE_NAME})
endforeach()
# =============================================================================================
# !!! End of dubious section.
# =============================================================================================
endif()