How to Idiomatically Package Dependencies for a Qt Application Using Cpack

How to tell cmake/cpack (NSIS) to deploy the required libs?

If you don't want to hardcode paths, then you should write your own CMake module, which finds those libraries and sets variables for you. Check out CMake Wiki.

Application deployment with 3rd-party dependencies for both Linux and Windows, using CMake and Conan

First, Conan is a package manager for development, not for distribution, that's why you didn't find an easy way to solve your problem. Second, most of discussions are made at Conan issue, including bugs and questions. There you will find a big community + Conan devs which are very helpful.

with needed system/compiler libraries

This is not part of Conan. Why you don't use static linkage for system libraries?

Talking about CPack, we have an open discussion about it usage with Conan: https://github.com/conan-io/conan/issues/5655
Please, join us.

I see few options for your case:

  • on package method, run self.copy and all dependencies from self.cpp_deps, which includes all libraries, so you can run Cpack
  • Use Conan deploy generator to deploy all artifacts, and using a hook you can run cpack or any other installer tool

Out friend SSE4 is writing a new blog post about Deployment + Conan, I think it can help you a lot. You can read a preview here.

Regards!

CMake/CPack: Preferred package generators for different platforms

There are multiple common practices on each of the different platforms. Which one is best for you will depend on a variety of factors, but the following should at least help choose among the more popular formats that CMake/CPack has direct support for. I'm assuming you are using CPack via CMake (i.e. via the CPack module, possibly with package components using the CPackComponent module as well).

Windows:

  • The NSIS package generator produces executable installers which average users are well accustomed to using. These support component-based installs, so you could provide the source as an optional component. CMake's support for this package generator is fairly mature, but it is perhaps becoming a less preferred method in recent times.
  • The WIX package generator produces MSI installers. Support for this is newer and seems to be more active in terms of feature development, etc. It also supports component-based installs and seems to be becoming the preferred format over NSIS.

Mac

There are a number of options to choose from for Mac, but which one is most appropriate depends on what you want to package up. If you just want to provide a single app bundle, the DMG package generator (also sometimes referred to as the DragNDrop generator) is probably what you want. Users are well acquainted with these and they are easy to use. Avoid the Bundle generator, it is older and more limited in what it supports, the DMG generator should be preferred instead.

For packages containing more than a single bundle, the DMG generator is still potentially suitable, but a proper installer may be more appropriate. Until recent years, the PackageMaker generator was the go-to generator for that, but it has been superseded by the ProductBuild generator (supported by CMake since version 3.7).

Linux

On RedHat-based systems, RPM is usually the package format of choice (use the RPM generator), whereas for Debian-based systems the DEB format is preferred (use the DEB generator). Debian-based systems can support RPM using tools like alien, but users almost always prefer a native DEB format. If you're happy to provide both, you can keep both camps happy, but note that you will have to pay careful attention to binary compatibility. Simple packages used to be able to build against the LSB (Linux Standards Base) to produce a single RPM that would work on all major Linux distributions (even Debian-based ones), but the LSB hasn't really kept up with recent developments and it never really supported the full set of functionality most complex apps needed (or the versions of packages they provided were too old). The LSB does, however, provide very useful tools like the app checker for assessing whether packages you've built (by any means) will be missing symbols, etc. across various Linux distributions.

Note that for Linux, you should distinguish between whether you are targeting packaging for inclusion in Linux distributions themselves or whether you expect users to download and install the packages outside of the distribution's packaging system. Larger independent commercial software products will tend to be distributed as standalone packages, including the relevant libraries, etc. and installing under /opt by default (if they follow guidelines like those advocated by the LSB and Filesystem Hierarchy Standard - FHS (PDF)). Ideally, you would make your packages relocatable so that distribution maintainers have an easier time adapting your packaging method to their distribution's requirements.

RPM and DEB both support source packages to some degree.

Cross-platform

  • The IFW package generator is favoured by some as a way to produce installers which have a similar look and feel across all platforms. It is also quite progressive in offering support for features like downloadable components. If having an easy-to-use graphical installer across all platforms is of interest, then this one is probably what you're looking for.
  • The Archive package generator provides support for archives like ZIP, tarballs, 7z and more. These are very basic formats that simply lump your files together into a single archive. These don't have the useful features like desktop integration, pre-/post-install and uninstall, but they are handy as a second alternative package format to one of the above. In particular, they can be useful for users who don't have admin access on their systems and want to simply unpack to a convenient location.

Compile part of code rely on OS in Qt

Use #ifdefs in order to figure out in compilation time the operating system:

#ifdef Q_OS_MAC
// mac code here
#endif

#ifdef Q_OS_LINUX
// linux specific code here
#endif

#ifdef Q_OS_WIN32
// windows code here
#endif

CMake copy dependencies to executable output path

There are two aspects to consider:

  • the build tree
  • the install tree

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:

  • CPack: This allow to create generate packages or archives that can be distributed to users. These includes windows installers, .tar,gz, .dmg, ...
  • macdeployqt: Tools provided by Qt allowing to copy all libraries, plugins, ... required by your application.

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)

dylibs and frameworks in bundle app

You can change by hand all the paths but it is error-prone, long and painful to do.
What I suggest is to use a tool to do it for you: cpack included with cmake.

Anyways since 10.5 you should use: @loader_path. See Dylib.

For MacOS X the following code creates a bundle and copy properly homebrew dylib even with root permissions.
However, you will have to check that every lib compiled with homebrew had the right parameters.

Usually, for deployement it is better to compile by hand the libs. I had some problems with homebrew libs (ffmpeg). But not Qt nor Boost :-).

if( USE_QT5 )
qt5_use_modules( MyApp Core OpenGL Sql Multimedia Concurrent )
endif()

# Install stuff
set( plugin_dest_dir bin )
set( qtconf_dest_dir bin )
set( APPS "\${CMAKE_INSTALL_PREFIX}/bin/MyApp" )
if( APPLE )
set( plugin_dest_dir MyApp.app/Contents/ )
set( qtconf_dest_dir MyApp.app/Contents/Resources )
set( APPS "\${CMAKE_INSTALL_PREFIX}/MyApp.app" )
endif( APPLE )
if( WIN32 )
set( APPS "\${CMAKE_INSTALL_PREFIX}/bin/MyApp.exe" )
endif( WIN32 )

#--------------------------------------------------------------------------------
# Install the MyApp application, on Apple, the bundle is at the root of the
# install tree, and on other platforms it'll go into the bin directory.
install( TARGETS MyApp
BUNDLE DESTINATION . COMPONENT Runtime
RUNTIME DESTINATION bin COMPONENT Runtime
)

#--------------------------------------------------------------------------------
# Install needed Qt plugins by copying directories from the qt installation
# One can cull what gets copied by using 'REGEX "..." EXCLUDE'
install( DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
"${QT_PLUGINS_DIR}/codecs"
"${QT_PLUGINS_DIR}/phonon_backend"
"${QT_PLUGINS_DIR}/sqldrivers"
"${QT_PLUGINS_DIR}/accessible"
"${QT_PLUGINS_DIR}/bearer"
"${QT_PLUGINS_DIR}/graphicssystems"
DESTINATION ${plugin_dest_dir}/PlugIns
COMPONENT Runtime
FILES_MATCHING
PATTERN "*.dylib"
PATTERN "*_debug.dylib" EXCLUDE
)

#--------------------------------------------------------------------------------
# install a qt.conf file
# this inserts some cmake code into the install script to write the file
install( CODE "
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"\")
" COMPONENT Runtime
)

#--------------------------------------------------------------------------------
# Use BundleUtilities to get all other dependencies for the application to work.
# It takes a bundle or executable along with possible plugins and inspects it
# for dependencies. If they are not system dependencies, they are copied.

# directories to look for dependencies
set( DIRS ${QT_LIBRARY_DIRS} ${MYAPP_LIBRARIES} )

# Now the work of copying dependencies into the bundle/package
# The quotes are escaped and variables to use at install time have their $ escaped
# An alternative is the do a configure_file() on a script and use install(SCRIPT ...).
# Note that the image plugins depend on QtSvg and QtXml, and it got those copied
# over.

install( CODE "
file(GLOB_RECURSE QTPLUGINS
\"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/plugins/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
fixup_bundle(\"${APPS}\" \"\${QTPLUGINS}\" \"${DIRS}\")
" COMPONENT Runtime
)

# To Create a package, one can run "cpack -G DragNDrop CPackConfig.cmake" on Mac OS X
# where CPackConfig.cmake is created by including CPack
# And then there's ways to customize this as well
set( CPACK_BINARY_DRAGNDROP ON )
include( CPack )


Related Topics



Leave a reply



Submit