Andrew Moa Blog Site

Dependency Issues of CPack

Previously, when using CPack to package a QML program, I noticed that the files installed by the CPack package differed significantly from those installed using the cmake install command. Below, I will explain and resolve this issue.

1. Problem code

Let’s take the previously written QML program as an example. To facilitate code improvements, the CMakeLists.txt is rewritten as follows, with dependency installation and packaging modules written separately into cmake/dependencies.cmake and cmake/packaging.cmake.

8 minutes to read
Andrew Moa

Use CPack to package and create an installer

In fact, besides build management, CMake can also handle testing and creating installation packages. The packaging feature is supported by the CPack module1, which can generate suitable installers for different platforms.

1. Qt Program CMake Configuration

Taking the previously written QML program as an example, the CMakeLists.txt configuration file is written as follows. The packaging.cmake file included at the end defines information related to packaging configuration.

cmake_minimum_required(VERSION 3.16)
set(PROJECT_NAME sample)
project(${PROJECT_NAME} VERSION 0.1 LANGUAGES CXX)
set(EXECUTABLE_NAME ${PROJECT_NAME}_app)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Quick)

if(Qt6_FOUND)
    message(STATUS "Qt6 found: ${Qt6_DIR}")
else()
    message(FATAL_ERROR "Qt6 not found!")
endif()

qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(${EXECUTABLE_NAME}
    main.cpp
)
qt_add_qml_module(${EXECUTABLE_NAME}
    URI ${PROJECT_NAME}
    VERSION 1.0
    QML_FILES
    Main.qml
)

# set static linking for MSVC
if(MSVC AND (${VCPKG_TARGET_TRIPLET} MATCHES "static"))
    message(STATUS "Configuring for MSVC static linking")
    set_property(TARGET ${EXECUTABLE_NAME} PROPERTY
        MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

set_target_properties(${EXECUTABLE_NAME} PROPERTIES

    # MACOSX_BUNDLE_GUI_IDENTIFIER com.example.${EXECUTABLE_NAME}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

target_link_libraries(${EXECUTABLE_NAME}
    PRIVATE Qt6::Quick
)

include(GNUInstallDirs)
install(TARGETS ${EXECUTABLE_NAME}
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(MSVC AND (${VCPKG_TARGET_TRIPLET} MATCHES "static"))
    message(STATUS "Linking static MSVC runtime, skipping install of MSVC runtime DLLs")
else()
    message(STATUS "Configuring install of runtime dependencies")
    set(deploy_tool_options_arg "")

    if(APPLE)
        set(deploy_tool_options_arg --hardened-runtime)
    elseif(WIN32)
        set(deploy_tool_options_arg --no-compiler-runtime)
    endif()

    qt_generate_deploy_qml_app_script(
        TARGET ${EXECUTABLE_NAME}
        OUTPUT_SCRIPT deploy_script
        MACOS_BUNDLE_POST_BUILD
        NO_UNSUPPORTED_PLATFORM_ERROR
        DEPLOY_USER_QML_MODULES_ON_UNSUPPORTED_PLATFORM
        DEPLOY_TOOL_OPTIONS ${deploy_tool_options_arg}
    )

    install(SCRIPT ${deploy_script})

    install(CODE [[
    if("$ENV{VCPKG_TARGET_TRIPLET}" STREQUAL "")
      find_program(QMAKE qmake6 HINTS $ENV{PATH})
      if(NOT QMAKE)
        message(FATAL_ERROR "qmake not found in PATH")
      else()
        message(STATUS "qmake found: ${QMAKE}")
        cmake_path(GET QMAKE PARENT_PATH QT_BIN_DIR)
        message(STATUS "qmake bin dir: ${QT_BIN_DIR}")
      endif()
    else()
      set(QT_BIN_DIR "$ENV{VCPKG_ROOT}/installed/$ENV{VCPKG_TARGET_TRIPLET}/bin")
      if(NOT EXISTS ${QT_BIN_DIR})
        message(FATAL_ERROR "qmake bin dir not found: ${QT_BIN_DIR}")
      endif()
    endif()
    file(GET_RUNTIME_DEPENDENCIES
        RESOLVED_DEPENDENCIES_VAR RESOLVED_DEPS
        UNRESOLVED_DEPENDENCIES_VAR UNRESOLVED_DEPS
        # path to the executable files
        EXECUTABLES $<TARGET_FILE:sample_app>
        # directories to search for library files
        DIRECTORIES ${QT_BIN_DIR}
        PRE_EXCLUDE_REGEXES "system32"
        PRE_EXCLUDE_REGEXES "api-ms-*"
        POST_EXCLUDE_REGEXES "system32"
    )
    foreach(DEP_LIB ${RESOLVED_DEPS})
        file(INSTALL ${DEP_LIB} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
    endforeach()
    ]])
endif()

include(${CMAKE_CURRENT_SOURCE_DIR}/packaging.cmake)

After defining what to install in install, add packaging-related content at the end of the CMake configuration file2.

6 minutes to read
Andrew Moa

Method for Static Compile with the MSYS2 Toolchain

The advantage of static compilation is that you don’t need to include a bunch of dynamic link libraries when publishing. In theory, the Msys2 toolchain supports static compilation, but in practice, even if you directly add the -static flag, the generated executable still reports missing dynamic link libraries at runtime, especially the three libraries required by the Mingw toolchain: libwinpthread-1.dll, libstdc++-6.dll, and libgcc_s_seh-1.dll. For the clang compiler, the runtime dependency becomes libc++.dll. This article explores a simple method to facilitate static link compilation management for different compilers.
4 minutes to read
Andrew Moa