脚本化应用程序示例#

本示例演示了如何使Qt C++应用程序可脚本化。

它有一个名为MainWindow(文件mainwindow.cpp,h)的类,该类继承自QMainWindow,使用Shiboken生成绑定。

头文件wrappedclasses.h传递给Shiboken,它会生成子目录AppLib/中的类封装器和头文件,这些文件与应用程序链接。

pythonutils.cpp,h文件包含一些代码,将MainWindow的实例绑定到全局Python命名空间(__main___)中的变量mainWindow。然后可以运行Python脚本片段,例如

mainWindow.testFunction1()

这会触发底层的C++函数。

构建项目#

可以使用CMakeQMake构建此示例,但需要考虑一些共同的要求

  • 请确保在当前活动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 软件包。

Linux 共享库说明#

本例中,由于模块是通过轮子安装的,我们链接到共享库的绝对路径(libshibokenlibpyside),因为在软件包中包含符号链接没有完美的解决方案(因此常规的 -lshiboken 无法正常工作)。

Windows 注意事项

应用程序的构建配置(调试或发布)应与 PySide6 的构建配置相匹配,否则应用程序将无法正确运行。

实际上,这意味着唯一受支持的配置是:

  1. 应用程序的发布配置构建 + PySide 的 setup.py 没有包含 --debug 标志 + 用于 PySide 构建过程的 python.exe + 链接到的共享库的 python39.dll + Qt 的发布构建。

  2. 应用程序的调试配置构建 + 包含 --debug 标志的 PySide 的 setup.py + 用于 PySide 构建过程的 python_d.exe + 链接到共享库的 python39_d.dll + Qt 的调试构建。

这是必要的,因为所讨论的所有共享库都必须链接到相同的 C++ 运行时库(msvcrt.dllmsvcrtd.dll)。为了使示例尽可能独立,正在使用的共享库(pyside6.dllshiboken6.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()