# 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()