CMake - Copy DLLs to the runtime output directory - qt

I am trying to create a simple CMake that retrieves the DLLs of Qt and copy it in the directory in which cmake creates my executable.
It works great using g++ or clang, but MSVC (Visual Studio 2017) creates a Debug or Release directory.
I can't find a way to retrieve the path to the real directory in which the executable is written (${CMAKE_CURRENT_BINARY_DIR} returns the directory parent of Release or Debug).
I've seen people using the target property RUNTIME_OUTPUT_DIRECTORY but it is empty when I use it.
Any idea how I can do this ? I do not want to change the output directory, I just want to know its path (so I do not want to change the value of RUNTIME_OUTPUT_DIRECTORY)
Thanks!

In Visual Studio, during configuration step (when CMakeLists.txt files are processed) build type is not set, so no build-type-dependent variable or property is usable directly. Instead, generator-expressions should be used.
E.g., output directory for executable or library target can be extracted with $<TARGET_FILE_DIR:tgt> generator expression. It will return full directory, with "Release/" or "Debug/" already appended.
Note, that generator expressions can be used only when their usage is explicitly allowed in documentation. E.g., they cannot be used in message() command.
Alternatively, you may explicitely set variable CMAKE_RUNTIME_OUTPUT_DIRECTORY, so for every build type will just append appropriate subdirectory to it. But for extracting this subdirectory, you should again resort to generator expressions: $<CONFIG>.
Technically, it is possible to set the same output directory for any build type. But this is not recommended, because a file from one build type will be overwritten by the file from another build type.

Example for Visual Studio 2022 and CMake.
Place this at the end of CMakeLists.txt:
if (WIN32)
add_custom_command(
TARGET qcpp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PROJECT_SOURCE_DIR}/include/external/c.dll" "${CMAKE_BINARY_DIR}"
COMMAND_EXPAND_LISTS
)
endif()
See list of CMake variables.
Note the "qcpp" in the preceding command. This is the project name and should match this line at the start:
project ("qcpp")
Appendix A - Testing
To verify, if you generate a Visual Studio Project using mkdir x && cd x && cmake .., you can see that CMake has added a post-build step to the solution config:

As of CMake 3.21+, the $<TARGET_RUNTIME_DLLS:tgt> generator expression can help copy dependent DLLs to the build tree. Quoting the documentation:
List of DLLs that the target depends on at runtime. This is determined by the locations of all the SHARED and MODULE targets in the target's transitive dependencies. Using this generator expression on targets other than executables, SHARED libraries, and MODULE libraries is an error. On non-DLL platforms, it evaluates to an empty string.
This generator expression can be used to copy all of the DLLs that a target depends on into its output directory in a POST_BUILD custom command.
Docs link: https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:TARGET_RUNTIME_DLLS
An example of how to use this (adapted from the docs) follows:
find_package(foo REQUIRED)
add_executable(main main.cpp)
target_link_libraries(main PRIVATE foo::foo)
if (WIN32)
add_custom_command(
TARGET main POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:main> $<TARGET_FILE_DIR:main>
COMMAND_EXPAND_LISTS
)
endif ()
The if (WIN32) check ensures that $<TARGET_RUNTIME_DLLS:main> won't be empty, which would cause the command to fail (rather than do nothing). COMMAND_EXPAND_LISTS makes sure that the semicolon-delimited list returned by $<TARGET_RUNTIME_DLLS:main> will be split into multiple arguments, rather than passed as a single argument with (escaped) semicolons in it.
Note also that UNKNOWN libraries will be ignored by this generator expression. These are common when using the built-in Find modules, rather than using a library's first-party CMake config-mode package. In these cases, you will have to manually inspect the module variables to find the library paths and add custom commands for each one yourself.
For Qt specifically, I would expect the newer CMake integration in Qt6 to "just work", though I haven't tested it. It might also work in Qt5, but again I haven't tested it.

Related

Import CMake Project into an existing QT Project .pro

I have a QT Project I am working on, it has a couple of sub-modules, which I list in my project's .pro file as:
if(!include(module/my_module.pri)){
error("module my_module not found")
}
What I tried:
Well, I have found a library which I intend to use, which is a CMake project libcrashreporter-qt. I am just linking the project for completeness - there is nothing wrong with this library (to my knowledge)
Problem:
I figured I will try an include the CMakeLists.txt file as I would a .pri file:
if(!include(libcrashreporter-qt/CMakeLists.txt)){
error("module libcrashreporter-qt not found")
}
where the libcrashreporter-qt folder is in the root of my project
Some Code:
In doing so, I get a horde of compiler errors:
$project_dir/libcrashreporter-qt/CMakeLists.txt:1: 'project' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:2: 'cmake_minimum_required' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:4: Extra characters after test expression.
$project_dir/libcrashreporter-qt/CMakeLists.txt:5: 'cmake_policy' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:6: 'endif' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:10: 'find_package' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:12: Opening parenthesis without prior test name.
$project_dir/libcrashreporter-qt/CMakeLists.txt:16: 'string' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:17: 'string' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:18: 'endif' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:20: 'add_subdirectory' is not a recognized test function.
$project_dir/libcrashreporter-qt/CMakeLists.txt:21: 'add_subdirectory' is not a recognized test function.
CMake file content (note, I changed the file slightly trying to resolve build issues by removing the multi-line if-statement and commented the option line):
project(libcrashreporter-qt)
cmake_minimum_required(VERSION 3.1)
if(POLICY CMP0071)
cmake_policy(SET CMP0071 NEW)
endif()
#option(ENABLE_GPL_CODE OFF)
find_package(Qt5 COMPONENTS Core Network Widgets)
if((CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_FLAGS) OR (UNIX AND NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
# Breakpad uses GNU compiler extensions like typeof.
# Luckily these features are not used on OSX, so we can build the
# crashreporter there with the normal C++11 standard.
string(REPLACE "-std=c++11" "-std=gnu++11" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
string(REPLACE "-std=c++14" "-std=gnu++14" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
endif()
add_subdirectory(3rdparty)
add_subdirectory(src)
This leads me to believe there is an issue with Qt .pro file compatibility and CMake projects being used within each other.
Question:
How can access the library functions of this CMake project, and compile it with my project (i.e. w/o changing the CMake project itself), or alternatively how best can I convert the CMake project to a compatible .pro project?
AFAIK CMake and QMake cannot be mixed together in one project, they are two different build systems and if you want to use a CMake project in a QMake project you have "two" options:
if you want to use the source files (.cpp and .h) or even modify them in your project you have to import them to your QMake project (you can easily do that in your QtCreator) and compile all of the files of your project
if the CMake library that you are using doesn't need any change (most of the times it's like this when you're using a third party library) and you just want to import it to your project and link it against your executable, you just run and compile the CMake project (separate from your own QMake project) and then use the output dynamic or static library of that project to link against your QMake project
As far as I remember, in Qt you can specify the custom compiler settings for the extra target, so, if you need your CMake project to be rebuilt just before the target Qt application is built, you can try to use this way
A possible solution is here:
https://stackoverflow.com/a/15766946/2333496

What the right variable to use here, that represents the TARGET field in the .pro file

I would like to add a custom command, that will work on the generated binary file (The target field in *.pro file),
But what should I use here, in the Command arguments
I'm afraid this is not possible. QtCreator only handles source and build directory. The QtCreator documentation says:
The following Qt Creator variables are available:
%{buildDir}
%{sourceDir}
Note that the target even doesn't have to be in the build directory. The build directory is where qmake is ran, typically resulting in the target being put there, because in the .pro file one typically specifies TARGET = projectName.
Further note that the QtCreator build steps configuration only works within QtCreator. This should not be used when your custom build steps are needed for other people working without QtCreator (they should only run qmake and make to build your application).
This being said and assuming that you want to define a post-build step, you should look for a solution to define such in the .pro file (by using the $${TARGET} variable) so that qmake will put your buildstep into the Makefile after the linking step.
If you want to execute a command after linkage, let's say call a custom script (batch script on Windows, otherwise a bourne shell script) with the TARGET as an argument, add the following to your .pro file:
win32 {
poststep.commands = #myScript.bat $${TARGET}
}
!win32 {
poststep.commands = #./myScript.sh $${TARGET}
}
QMAKE_EXTRA_TARGETS += poststep

cmake recursive

I am completely stuck with cmake. I am building big directory of
latex documents, so I want to get Makefile system with following
targets: prepare, build, archive. Main is that they must be recursive:
they present in every directory and run themself in all subdirectories.
But, unfortunally, if I manually say add_custom_target cmake complains
about duplicate targets. If I declare them only in root, then they do not
present in subdirectories. Unfortunately, I cannot reject cmake and start use
plain Makefiles.
Try using this:
http://www.cmake.org/Wiki/CMakeUserUseLATEX

Codeblocks with Qt - something terribly wrong

After creating qt project in codeblocks and running it I'm getting:
Anyone knows how to resolve it?
Thanks
This looks to me like you're building against one version of Qt and linking against another at runtime. Run the QtSDK Maintenance Tool and remove any versions of Qt Desktop that you don't need. You may then need to repoint Codeblocks at the correct headers.
I'm guessing that if you're running from within Codeblocks, you've had to explicitly specify which dlls to use when you run your newly built app. If so, make sure that those are the correct versions (i.e. replace them with dlls from QT INSTALL DIR\Desktop\4.7.x\mingw\bin [though I'm not on my work PC at the moment, so this path may be slightly wrong. Just make sure you're in the correct 4.7.x folder]).
To be honest though, if you're running from Windows, why not use QtCreator? Aside from slightly lacking in terms of GDB integration, it's pretty good and you'd find problems like this are harder to come across.
This is (esp on Windows) a common problem. When installing the Qt SDK, you'll get at least 2 .dll's with the same name but in different versions. It happens, that you link against the intended (dev-)lib but at runtime the version from the Designer/Creator is used.
The easiest way to avoid this, is to deploy the right version of the dll's together with your binaries (.exe and stuff) in a separate folder. This can be achived by modifying your build script. It depends on your build system which is usually qmake/.pro or cmake/CMakeLists.txt.
As for CMake, given an environment variable MYQTDLLDIR containing the path to the files to be deployed you can use something like that:
configure_file($ENV{MYQTDLLDIR}/QtCore4.dll ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
configure_file($ENV{MYQTDLLDIR}/QtGui4.dll ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
documentation stripped from cmake --help-full:
configure_file Copy a file to another location and modify its
contents.
configure_file(
[COPYONLY] [ESCAPE_QUOTES] [#ONLY])
Copies a file to file and substitutes variable
values referenced in the file content. If is a relative
path it is evaluated with respect to the current source directory.
The must be a file, not a directory. If is a
relative path it is evaluated with respect to the current binary
directory. If names an existing directory the input file
is placed in that directory with its original name.
This command replaces any variables in the input file referenced as
${VAR} or #VAR# with their values as determined by CMake. If a
variable is not defined, it will be replaced with nothing. If
COPYONLY is specified, then no variable expansion will take place. If
ESCAPE_QUOTES is specified then any substituted quotes will be C-style
escaped. The file will be configured with the current values of CMake
variables. If #ONLY is specified, only variables of the form #VAR#
will be replaces and ${VAR} will be ignored. This is useful for
configuring scripts that use ${VAR}. Any occurrences of #cmakedefine
VAR will be replaced with either #define VAR or /* #undef VAR */
depending on the setting of VAR in CMake. Any occurrences of
#cmakedefine01 VAR will be replaced with either #define VAR 1 or
#define VAR 0 >depending on whether VAR evaluates to TRUE or FALSE in
CMake
As for qmake you could use INSTALLS (used when make install is called) or execute a "plain command" after linking. Using INSTALLS:
mytarget.path = /output/path
mytarget.files += /path/to/QtCore4.dll
mytarget.files += /path/to/QtGui4.dll
INSTALLS += mytarget
qmake using command execution:
win32 {
EXTRA_BINFILES += \
$${MYQTDLLDIR}/QtCore4.dll \
$${MYQTDLLDIR}/QtGui4.dll
EXTRA_BINFILES_WIN = $${EXTRA_BINFILES}
EXTRA_BINFILES_WIN ~= s,/,\\,g
DESTDIR_WIN = $${DESTDIR}
DESTDIR_WIN ~= s,/,\\,g
for(FILE,EXTRA_BINFILES_WIN){
QMAKE_POST_LINK +=$$quote(cmd /c copy /y $${FILE} $${DESTDIR_WIN}$$escape_expand(\n\t))
}
}

Building a library using autotools from cmake

This is my first try with cmake and I would like to have, if possible, some feedbacks about what I did since some problems remain.
In the CMakeLists.txt of the library folder, I created two makefile targets: configure-antlr3c and antlr3c. The first target runs the autotools configuration shell script, the second one runs the make executable to build the library:
# CMakeLists.txt in libantlr3c-3.1.3
add_custom_target(
configure-antlr3c
${SHELL_EXECUTABLE} configure
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(
antlr3c
${MAKE}
DEPENDS configure-antlr3c
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
The main problem is thatconfigure-antlr3c target is always "out of date", so it will always be executed even if no changes happened. Moreover, I necessarily need to generate my cmake makefiles in a separate directory (not in the root directory of my project) to avoid overriding the autotools Makefile of the library...
Has anyone had this problem (building autotools projects with cmake) ? And if so, what have been your solutions ?
Thank you.
EDIT : Solution
In the root CMakeLists.txt:
include(ExternalProject)
ExternalProject_Add(
libantlr3c
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/libantlr3c-3.1.3
CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lib/libantlr3c-3.1.3/configure --prefix=${CMAKE_CURRENT_SOURCE_DIR}/lib/libantlr3c-3.1.3
PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/lib/libantlr3c-3.1.3
BUILD_COMMAND make
BUILD_IN_SOURCE 1
)
I think that you'd be better off using the ExternalProject feature of cmake. I guess you have your project and have libantrl in a sub directory?
project
+- libantlr
+- mysrc
---- etc ----
If that's the case, you can do something like this in the top level CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
project(test)
include(ExternalProject)
ExternalProject_Add(libantlr
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libantlr
CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/libantlr/configure --prefix=<INSTALL_DIR>
BUILD_COMMAND ${MAKE})
The <INSTALL_DIR> is expanded to something like libantlr-prefix, so things are installed in your build tree rather than in /usr/local, which is what autotools would do without a prefix.
I needed to do something similar but found it surprisingly difficult to get a working solution, despite the example provided here with the accepted answer, and code snippets provided in several other blog posts, the CMake support email listserv archives, etc. For the benefit of others who come across this question, here is my solution.
The external project we wanted to use is libmodbus, though I believe my solution is general enough to work with any project configured with the standard autoconf recipe of ./autoconf.sh && configure.sh && make && make install.
We wanted to add libmodbus as a submodule of our git repository. We added to our repository at the path <root>/opt/libmodbus. The CMake code to configure it is located in <root>/cmake/modbus.cmake, which is included from our root CMakeLists.txt using
# libmodbus
include(cmake/modbus.cmake)
The content of cmake/modbus.cmake is:
include(ExternalProject)
set(MODBUS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/opt/libmodbus)
set(MODBUS_BIN ${CMAKE_CURRENT_BINARY_DIR}/libmodbus)
set(MODBUS_STATIC_LIB ${MODBUS_BIN}/lib/libmodbus.a)
set(MODBUS_INCLUDES ${MODBUS_BIN}/include)
file(MAKE_DIRECTORY ${MODBUS_INCLUDES})
ExternalProject_Add(
libmodbus
PREFIX ${MODBUS_BIN}
SOURCE_DIR ${MODBUS_DIR}
DOWNLOAD_COMMAND cd ${MODBUS_DIR} && git clean -dfX && ${MODBUS_DIR}/autogen.sh
CONFIGURE_COMMAND ${MODBUS_DIR}/configure --srcdir=${MODBUS_DIR} --prefix=${MODBUS_BIN} --enable-static=yes --disable-shared
BUILD_COMMAND make
INSTALL_COMMAND make install
BUILD_BYPRODUCTS ${MODBUS_STATIC_LIB}
)
add_library(modbus STATIC IMPORTED GLOBAL)
add_dependencies(modbus libmodbus)
set_target_properties(modbus PROPERTIES IMPORTED_LOCATION ${MODBUS_STATIC_LIB})
set_target_properties(modbus PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${MODBUS_INCLUDES})
A component that uses libmodbus can declare its dependency as usual:
add_executable(hello_modbus main.cpp)
target_link_libraries(hello_modbus modbus)
A few notes:
This abuses the DOWNLOAD_COMMAND to perform the autogen.sh step. The git clean -dfX is probably not necessary (it is a leftover from an earlier version that used the BUILD_IN_SOURCE option. If you really want to download the code instead of using a git submodule, you'll need to modify this line appropriately.
We go to the trouble to force a static-only build of the library. Adjust your configure command line if you want shared libraries.
The set_target_properties command to set the IMPORTED_LOCATION will fail without the BUILD_BYPRODUCTS ${MODBUS_STATIC_LIB} declaration.
Likewise, the set_target_properties command to set the INTERFACE_INCLUDE_DIRECTORIES will fail without the file(MAKE_DIRECTORY ${MODBUS_INCLUDES}).

Resources