5

I'm writing a cross-platform program which I'd like to package on Windows. I got it to run by putting three DLLs (Qt5Core, Qt5Widgets, Qt5Gui) in the build directory, then made a tarball and gave it to someone else with a Windows box, but it would not run on his Windows box. I searched for a way to get whatever else it needed and found Nathan Osman's post about windeployqt, so I copied windeployqt.cmake to the cmake/Modules directory, but couldn't figure out how to use it. Is there a way to use CPack to make a package (even if it's just a tarball) that has everything needed to run the program on a Windows box that doesn't have the compiler or Qt installed?

Here's the CMakeLists.txt file with some (hopefully irrelevant) stuff removed:

project(perfecttin)
cmake_minimum_required(VERSION 3.8.0)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_STANDARD 17) # appeared in CMake 3.8
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(SHARE_DIR ${CMAKE_INSTALL_PREFIX}/share/perfecttin)
add_definitions(-DBOOST_ALL_NO_LIB)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
set(Boost_USE_STATIC_LIBS ON)
else ()
set(Boost_USE_STATIC_LIBS OFF)
endif ()

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/")
find_package(LibPLYXX)
find_package(Qt5 COMPONENTS Core Widgets Gui LinguistTools REQUIRED)
find_package(Boost COMPONENTS program_options)
find_package(Threads)

qt5_add_resources(lib_resources perfecttin.qrc)
qt5_add_translation(qm_files perfecttin_en.ts perfecttin_es.ts)
# To update translations, run "lupdate *.cpp -ts *.ts" in the source directory.

add_executable(perfecttin-gui adjelev.cpp angle.cpp binio.cpp
           <snip lots of source files>
               unitbutton.cpp ${lib_resources} ${qm_files})
target_link_libraries(perfecttin-gui ${CMAKE_THREAD_LIBS_INIT} Qt5::Widgets Qt5::Core)
target_link_libraries(fuzzptin ${CMAKE_THREAD_LIBS_INIT})
target_compile_definitions(fuzzptin PUBLIC _USE_MATH_DEFINES)
target_compile_definitions(perfecttin-gui PUBLIC _USE_MATH_DEFINES)
set_target_properties(perfecttin-gui PROPERTIES WIN32_EXECUTABLE TRUE)

install(TARGETS perfecttin-gui DESTINATION bin)
install(FILES ${qm_files} DESTINATION share/perfecttin)

include_directories(${PROJECT_BINARY_DIR})
configure_file (config.h.in config.h)

set(CPACK_PACKAGE_VERSION_MAJOR ${PERFECTTIN_MAJOR_VERSION})
set(CPACK_PACKAGE_VERSION_MINOR ${PERFECTTIN_MINOR_VERSION})
set(CPACK_PACKAGE_VERSION_PATCH ${PERFECTTIN_PATCH_VERSION})
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/COPYING)
set(CPACK_SOURCE_IGNORE_FILES /\\\\.git;.*~)
include(CPack)

include(CTest)
Pierre Abbat
  • 485
  • 4
  • 10

3 Answers3

2

If you already have in your ${CMAKE_SOURCE_DIR}/cmake/Modules/ directory the file Windeployqt.cmake, you may use it like this:

install(TARGETS perfecttin-gui 
    DESTINATION "${INSTALL_BIN_PATH}"
)

if(WIN32)
    include(Windeployqt)
    windeployqt(nitroshare-cli ${INSTALL_BIN_PATH})
endif()

Here is the full example.

Former contributor
  • 2,466
  • 2
  • 10
  • 15
  • I tried setting DESTINATION to ${INSTALL_BIN_PATH} and got this error: ``` install TARGETS given no RUNTIME DESTINATION for executable target "perfecttin-gui". ``` Where is this variable set? I don't find it in the CMake documentation. – Pierre Abbat Nov 22 '19 at 14:23
  • You need to define the value of the INSTALL_BIN_PATH variable for your project, using `set(INSTALL_BIN_PATH "bin")` or something else. [Here is the relevant documentation](https://cmake.org/cmake/help/latest/command/install.html) – Former contributor Nov 22 '19 at 15:01
  • there's no mention of `INSTALL_BIN_PATH` on that page. I've been using `bin` with no problem on Linux and BSD; I don't know Windows. – Pierre Abbat Nov 22 '19 at 17:01
  • `INSTALL_BIN_PATH ` is simply the name of a variable. You can create and give your variables whatever name you prefer. There is nothing special with that name. If you include(GNUInstallDirs) in your CMakeLists.txt, it will give you a variable with the name `CMAKE_INSTALL_BINDIR` and the default value `bin`. That variable is mentioned in the documentation: _For regular executables, static libraries and shared libraries, the DESTINATION argument is not required..._ – Former contributor Nov 22 '19 at 20:10
  • I just got around to building it on Windows. First I got `windeployqt Function invoked with incorrect arguments for function`. I then replaced the variable with "bin", `windeployqt(perfecttin-gui bin)`, and it built on MinGW, producing directories `iconengines`, `imageformats`, `platforms`, `styles`, and `translations`. On MSVC, however, it failed, saying `C:\Users\phma\build\perfecttin\vs\dbg\perfecttin.exe does not seem to be a Qt executable.`. What's the next step? – Pierre Abbat Nov 24 '19 at 19:51
  • Figured it out. perfecttin.exe is a Boost app; perfecttin-gui.exe is a Qt app. I made a tarball, copied it to my Linux box, and successfully ran it on Wine. – Pierre Abbat Nov 25 '19 at 02:00
  • https://github.com/phma/perfecttin/blob/master/CMakeLists.txt – Pierre Abbat Nov 25 '19 at 02:41
2

I did borrow a lot from this answer, however it was missing the necessary environmental setup which leads to Warning: Cannot find GCC installation directory. g++.exe must be in the path..

Qt has the following documentation on using windeployqt.exe

The tool can be found in QTDIR/bin/windeployqt. It needs to be run within the build environment in order to function correctly.
When using Qt Installer, the script QTDIR/bin/qtenv2.bat should be used to set it up.

Basically you need to run windeployqt.exe as a custom command within the correct environment.

# Retrieve the absolute path to qmake and then use that path to find
# the windeployqt executable
get_target_property(QMAKE_EXE Qt6::qmake IMPORTED_LOCATION)
get_filename_component(QT_BIN_DIR"${QMAKE_EXE }" DIRECTORY)

find_program(WINDEPLOYQT_ENV_SETUP qtenv2.bat HINTS "${QT_BIN_DIR}")
find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${QT_BIN_DIR}")

# Run windeployqt immediately after build
add_custom_command(TARGET perfecttin-gui
    POST_BUILD
    COMMAND "${WINDEPLOYQT_ENV_SETUP}" && "${WINDEPLOYQT_EXECUTABLE}" \"$<TARGET_FILE:perfectin-gui>\"
)
Troyseph
  • 4,960
  • 3
  • 38
  • 61
1

"Since version 6.3, Qt offers a way to define additional deployment steps that are executed on installation."
- https://www.qt.io/blog/cmake-deployment-api

Among other things there is now a *deploy utility script generator already included.

install(TARGETS perfecttin-gui DESTINATION bin)

qt_generate_deploy_app_script(
    TARGET perfecttin-gui
    FILENAME_VARIABLE deploy_script
    NO_UNSUPPORTED_PLATFORM_ERROR
)
install(SCRIPT ${deploy_script})

Documentation is at: generate-deploy-app-script
According to the docs so far this doesn't work on Linux

For some more options one could use the function which the above convenience version actually invokes: qt-deploy-runtime-dependencies

For example qt_generate_deploy_app_script() puts the Qt plugins outside of the destination 'bin' directory, each in their own folder, which I don't like (I want them all under 'bin'). So I had to use the full version (this is all basically from the Qt docs linked above):

set(MY_APP perfecttin-gui)

if(APPLE)
  set(executable_path "$<TARGET_FILE_NAME:${MY_APP}>.app")
else()
  set(executable_path "\${QT_DEPLOY_BIN_DIR}/$<TARGET_FILE_NAME:${MY_APP}>")
endif()

set(deploy_script "${CMAKE_CURRENT_BINARY_DIR}/deploy_${MY_APP}.cmake")

file(GENERATE OUTPUT ${deploy_script} CONTENT "
include(\"${QT_DEPLOY_SUPPORT}\")
qt_deploy_runtime_dependencies(
    EXECUTABLE \"${executable_path}\"
    PLUGINS_DIR bin
)"
)

install(TARGETS ${MY_APP} DESTINATION bin)
install(SCRIPT ${deploy_script})

See https://www.qt.io/blog/cmake-deployment-api for more details about deployment in general

ADDED:
Found the source for qt_deploy_runtime_dependencies().
Looks like the next version adds a NO_TRANSLATIONS option, but it's not in my 6.4.1.
But I did find a hack to pass additional options to the deploy tool...
If you search that code for __qt_deploy_tool_extra_options you'll see the warning about how it may go away. But as a workaround...

My file(GENERATE OUTPUT) now looks like this (likely needs adjusting per platform):

file(GENERATE OUTPUT ${deploy_script} CONTENT "
include(\"${QT_DEPLOY_SUPPORT}\")
set(__qt_deploy_tool_extra_options --no-compiler-runtime --no-translations)
qt_deploy_runtime_dependencies(
    EXECUTABLE \"${executable_path}\"
    PLUGINS_DIR bin
#    NO_TRANSLATIONS
)
")

Also the qt_generate_deploy_app_script() source if anyone is interested.

Maxim Paperno
  • 4,485
  • 2
  • 18
  • 22