I have the following simple CMake project. It's basically an executable which links dynamically to Qt Widgets (I'm using Qt just as an example). What I'm trying to figure out is whether it is possible to copy all the linked libraries (not only the ones built by the current project) to the executable output directory using CMake.
cmake_minimum_required(VERSION 3.12)
project(MyProject)
set(CMAKE_CXX_STANDARD 14)
set(QT_CMAKE_DIR "/Users/huser/Qt/5.11.1/clang_64/lib/cmake")
set(CMAKE_PREFIX_PATH ${CMAKE_MODULE_PATH} ${QT_CMAKE_DIR})
find_package(Qt5 REQUIRED COMPONENTS Widgets)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_LIST_DIR}/build)
The issue is that the output directory only contains the MyProject executable (which is the expected behaviour). However, if I were to distribute that executable to someone who doesn't have Qt installed, they would not be able to open it. Hence, I would like to bundle only the necessary libraries/ frameworks with the executable.
Running otool -L MyProject lists the dependencies:
MyProject:
@rpath/QtWidgets.framework/Versions/5/QtWidgets
@rpath/QtGui.framework/Versions/5/QtGui
@rpath/QtCore.framework/Versions/5/QtCore
/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib
What I'm looking for is a common way through CMake to get these 3 frameworks copied in the output directory right after the build step. That would result in the following directory structure:
build/
MyProject
QtWidgets.framework
QtGui.framework
QtCore.framework
Any help would be greatly appreciated!
There are two aspects to consider:
The build tree is what you work with as a developer, the install tree is what is "created" after executing the install target or after extracting the content of a package.
To redistribute your Qt5 based project, I suggest you leverage two tools:
.tar,gz, .dmg, ...Using the BundleUtilities would still require you to explicitly identify and install all Qt plugins. For more complex application, with dependencies other than Qt, is is indeed helpful but for a simple application, I would suggest to use the approach described below.
You will find below a modified version of your example including some suggestions regarding the best practices as well as the integration of CPack and macdeployqt.
After configuring and building project, building the Package target will create a MyProject-0.1.1-Darwin.dmg package.
Note that more would need to be done but that should give a good starting point.
Reading the following may also be helpful: https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling
To configure the project, consider passing the variable -DQt5_DIR:PATH=/path/to/lib/cmake/Qt5 instead of hardcoding the path.
Assuming the sources or the project are in a directory named src, you would configure the project with:
mkdir build
cd build
cmake -DQt5_DIR:PATH=/Volumes/Dashboards/Support/Qt5.9.1/5.9.1/clang_64/lib/cmake/Qt5 ../src/
src/CMakeLists.txt:
cmake_minimum_required(VERSION 3.12)
project(MyProject)
set(CMAKE_CXX_STANDARD 14)
# Suggestions:
# (1) EXECUTABLE_OUTPUT_PATH is deprecated, consider
# setting the CMAKE_*_OUTPUT_DIRECTORY variables
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build)
set(CMAKE_MACOSX_BUNDLE 1)
set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks")
# Suggestions:
# (1) Do not hardcode path to Qt installation
# (2) Configure the project specifying -DQt5_DIR
# See https://blog.kitware.com/cmake-finding-qt5-the-right-way/
# (3) By convention, "REQUIRED" is added at the end
find_package(Qt5 COMPONENTS Widgets REQUIRED)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets)
install(TARGETS ${PROJECT_NAME} DESTINATION . COMPONENT Runtime)
# Get reference to deployqt
get_target_property(uic_location Qt5::uic IMPORTED_LOCATION)
get_filename_component( _dir ${uic_location} DIRECTORY)
set(deployqt "${_dir}/macdeployqt")
if(NOT EXISTS ${deployqt})
message(FATAL_ERROR "Failed to locate deployqt executable: [${deployqt}]")
endif()
# Execute deployqt during package creation
# See https://doc.qt.io/qt-5/osx-deployment.html#macdeploy
install(CODE "set(deployqt \"${deployqt}\")" COMPONENT Runtime)
install(CODE [===[
execute_process(COMMAND "${deployqt}" "${CMAKE_INSTALL_PREFIX}/MyProject.app")
]===] COMPONENT Runtime)
set(CPACK_GENERATOR "DragNDrop")
include(CPack)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With