cmake_minimum_required(VERSION 3.1) cmake_policy(VERSION 3.1) # Enable policy to run automoc on generated files. if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() project(scriptableapplication) # Set CPP standard to C++11 minimum. set(CMAKE_CXX_STANDARD 11) # Find required Qt packages. find_package(Qt5 5.9 REQUIRED COMPONENTS Core Gui Widgets) # Macro to get various pyside / python include / link flags. macro(pyside2_config option output_var) if(${ARGC} GREATER 2) set(is_list ${ARGV2}) else() set(is_list "") endif() execute_process( COMMAND python "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py" ${option} OUTPUT_VARIABLE ${output_var} OUTPUT_STRIP_TRAILING_WHITESPACE) if ("${${output_var}}" STREQUAL "") message(FATAL_ERROR "Error: Calling pyside2_config.py ${option} returned no output.") endif() if(is_list) string (REPLACE " " ";" ${output_var} "${${output_var}}") endif() endmacro() # Query for the PySide2 path, Python path, include paths and linker flags. pyside2_config(--pyside2 PYSIDE2_PATH) pyside2_config(--python-include PYTHON_INCLUDE_DIR) pyside2_config(--pyside2-include PYSIDE2_INCLUDE_DIR 1) pyside2_config(--pyside2-shared-libraries-cmake PYSIDE2_SHARED_LIBRARIES 0) pyside2_config(--python-link-cmake PYTHON_LINKING_DATA 0) set(SHIBOKEN_PATH "${PYSIDE2_PATH}/shiboken2${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_CORE_INCLUDE_DIRS TARGET Qt5::Core PROPERTY INTERFACE_INCLUDE_DIRECTORIES) get_property(QT_GUI_INCLUDE_DIRS TARGET Qt5::Gui PROPERTY INTERFACE_INCLUDE_DIRECTORIES) get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt5::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES) set(QT_INCLUDE_DIRS ${QT_CORE_INCLUDE_DIRS} ${QT_GUI_INCLUDE_DIRS} ${QT_WIDGETS_INCLUDE_DIRS}) set(INCLUDES "") foreach(INCLUDE_DIR ${QT_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 Qt5::Core FRAMEWORK) if (QtCore_is_framework) get_target_property(qt_core_library_location Qt5::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() # 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_nonzero --avoid-protected-hack ${INCLUDES} -I${CMAKE_SOURCE_DIR} -T${CMAKE_SOURCE_DIR} -T${PYSIDE2_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(PYSIDE2_ADDITIONAL_INCLUDES "") foreach(INCLUDE_DIR ${PYSIDE2_INCLUDE_DIR}) list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtCore") list(APPEND PYSIDE2_ADDITIONAL_INCLUDES "${INCLUDE_DIR}/QtGui") list(APPEND PYSIDE2_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 ${PYSIDE2_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 ${PYSIDE2_INCLUDE_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE2_ADDITIONAL_INCLUDES}) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}) target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets) target_link_libraries(${PROJECT_NAME} PRIVATE ${PYSIDE2_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 32 33 34 35 36 37 38) 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 PySide2 shared libraries (just like in qmake example), so you # don't have to set PATH manually to point to the PySide2 package. foreach(LIBRARY_PATH ${PYSIDE2_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 PySide2 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()