I'm working on the cmake scripts for my project and I've run into a problem:
My project uses a 3rd party library (FreeImage), which has its own Makefile-based build system. I can build FreeImage just fine by simply running "make" (I'm using gnuwin32), which will build FreeImage using MinGW and produce:
FreeImage.lib
FreeImage.dll
Now my problem is twofold:
I want to execute "make" from my cmake script.
I want to link to the import lib (FreeImage.lib), and also make sure the DLL gets copied to the correct place so the EXE will run.
I know how to link to the LIB file, but I'm lost on the rest.
The folder structure is like this:
MyProject # main directory
MyProject/Libs/FreeImage # FreeImage root directory
MyProject/Libs/FreeImage/Dist # This is where FreeImage outputs go (LIB and DLL)
BTW: I'm running on Windows 7. I plan to build my project both with MSVC and MinGW.
Thanks!
EDIT:
I'm now trying to use ExternalProject_Add like so:
ExternalProject_Add(
FreeImage
PREFIX ./Libs/FreeImage
URL ./Libs/FreeImage
BUILD_COMMAND make
)
This gets me part of the way there, but doesn't totally work... it tries to configure things for me and tries to use nmake... ugh
In my opinion, there are two options:
In case you have put your FreeImage sources in your projects' source-tree, the easiest option may be to use the execute_process() command. Assuming FreeImage is in your projects' source-tree in "3rdparty/FreeImage/" you can do something like,
execute_process( COMMAND make WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/3rdParty/FreeImage )
Optionally, you can copy the dll from 3rdParty/FreeImage/bin into you own bin directory. And then you can write a FreeImageConfig.cmake for importing the library:
add_library( FreeImage IMPORTED )
set_target_properties( FreeImage PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/3rdParty/FreeImage/lib )
...
The other option is to make use of the ExternalProject module. You can also take a look at this article from Kitware for an overview of this module. In essence, you specify the full chain of commands needed to get the source, configure the build, build the source and install it. All in your own CMakeLists.txt
Related
There is a static library called revolta which is being built and then installed into a sysroot:
set( CMAKE_INSTALL_PREFIX <path to sysroot> )
# ReVolta c++ library name
set( TARGET_LIBREVOLTA "revolta" )
add_library( ${TARGET_LIBREVOLTA} STATIC )
target_include_directories( ${TARGET_LIBREVOLTA}
PUBLIC
# Once the librevolta targets are being exported, this include directory in which the lib is installed is used
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include>
PRIVATE
# Include directory used privately just to build the library itself
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
)
target_sources( ${TARGET_LIBREVOLTA}
PUBLIC
...
)
Later then once the librevolta is built, it is installed into the sys root using:
# Install all the revolta headers into include directory and copy the built library
install( TARGETS ${TARGET_LIBREVOLTA} EXPORT ${TARGET_LIBREVOLTA}
FILE_SET HEADERS DESTINATION "${CMAKE_INSTALL_PREFIX}/include"
ARCHIVE DESTINATION "${CMAKE_INSTALL_PREFIX}/lib"
)
and the connected custom command:
# Once the librevolta is built, install it to the sysroot as specified by 'install()' commands
add_custom_command( TARGET ${TARGET_LIBREVOLTA} POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS --install . )
So far so good. This works as intended, once CMake builds the "revolta" target, it is built and installed into the sysroot as installed using the ${CMAKE_INSTALL_PREFIX}.
My problem is once I try to add the target as the linked one in other lib/executable, it includes somehow automatically the librevolta source path into includes and links the library using the relative path in the build directory rather than the one installed into sysroot as performed in the step right after the librevolta build.
Some other lib/executable:
target_link_libraries( ${APP_EXECUTABLE}
PRIVATE
revolta
)
Once being built, the include path -I/home/martin/git/revolta/source/librevolta is added (the source location) even though it is stated as PRIVATE in the snipped above:
PRIVATE
# Include directory used privately just to build the library itself
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>
and only the ${CMAKE_INSTALL_PREFIX}/include is made public...
Additionally, the library is taken from the build tree rather than from the location where it is installed:
../../librevolta/librevolta.a
instead of
/home/martin/git/revolta/sysroot/lib/librevolta.a
Could you please advice me how to correctly set the revolta target the way it correctly uses its sources for building itself but once used elsewhere it provides the sysroot installed headers and built library from the same location (respecting the standard locations)?
HINT: I also tried to remove the revolta target from the app completely, specifying only to use the sys root (gcc option --sysroot=/home/martin/git/revolta/sysroot), it works fine correct headers and lib is used BUT once the librevolta is not built and installed, the target is not run prior to app build as the dependency is not defined then...
TL;DR: You need to do what's done here:
How to create a ProjectConfig.cmake file
I see a few issues with these CMakeLists.txt files but they aren't related to your problem, because if I understand correctly what you are trying to do here, then there is no problem and it is used as intended.
Let me clarify:
You have a library project that has it's own CMakeLists.txt, where you define the target revolta
You have an executable project that has it's own CMakeLists.txt, where you define your executable target and then you add the revolta target via add_subdirectory() and target_link_libraries(my_executable revolta)
If that's the case then this is just bad:
# Once the librevolta is built, install it to the sysroot as specified by 'install()' commands
add_custom_command( TARGET ${TARGET_LIBREVOLTA} POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS --install . )
Forcing your build to automatically install this library is not the way to go, ever (you for example, need elevated privileges to build it in the first place, because of this command and that poses a security risk).
That being said what is happening is perfectly fine, because from the perspective of the my_executable's CMakeLists.txt you are still building i.e. you use the BUILD_INTERFACE. It is however something you do not want to do.
What instead you want to do is:
Create generator files for a revoltaConfig.cmake file. For that I will refer you to this tutorial:
How to create a ProjectConfig.cmake file
After you create such file, i.e. after building and installing revolta. You will (in the process) also create a revoltaConfig.cmake file. Which helps you populate the my_executable project via find_package(revolta).
The above is probably what you are interested in.
The generator expressions that you use to distinguish BUILD_INTERFACE and INSTALL_INTERFACE are mainly for header file locations (or other linked libraries). Because when you build the library the header files can have a different structure then when you install it (as you already know). And as such work perfectly fine in your CMakeLists.txt, because when you think about it:
You don't want to copy changes to your library files (into the install directory) just to test ongoing development (features/bugfixes) in your executable.
And during the build of the executable if your building another target then IT IS NOT INSTALLED but rather BEING BUILT. And you are most likely adding it as a built target.
So to sum up what would most likely happen here (using your old CMakeLists.txt) is that
The moment you start building the executable which adds the target library as a dependency via add_subdirectory you are implicitly using BUILD_INTERFACE because you are building.
If you were to then install both the executable and the library it would again use the correct install paths, i.e. you would then implicitly start using INSTALL_INTERFACE.
You could hack it without the projectConfig file using the same generator expressions by mixing them up, but I don't recommend it, because then the CMakeLists.txt wouldn't work without doing some weird steps beforehand.
I have git cloned, built (with MSVC for both Debug and Release) and then installed wxWidgets:
cmake -B build wxWidgets
cmake --build build --config <CONFIG>
cmake --install build --prefix my_install --config <CONFIG>
with <CONFIG> = Debug and <CONFIG> = Release.
Then I used the following CMake script to link against it, as suggested by the wiki:
cmake_minimum_required(VERSION 3.16)
project(Test)
add_executable(Test WIN32 Main.cpp)
# wxWidgets
SET(wxWidgets_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../thirdparty/my_install)
find_package(wxWidgets COMPONENTS core base REQUIRED)
include(${wxWidgets_USE_FILE})
target_link_libraries(Test PRIVATE ${wxWidgets_LIBRARIES})
# Copy runtime DLLs to the directory of the executable.
add_custom_command(TARGET Test POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Runtime Dlls: $<TARGET_RUNTIME_DLLS:Test>"
)
My goal is to automatically copy the DLLs into the directory of the built executable, so that they can be found at runtime. For that I'm using the TARGET_RUNTIME_DLLS generator expression (follwing the sample code in the docs). In the code above, I only print out the expression at build time for testing purposes. The problem is that it is empty.
The approach worked for me before when installing and linking SDL, but SDL provides package configuration files which create imported targets, defining the DLL location(s) via IMPORTED_LOCATION_RELEASE or IMPORTED_LOCATION_DEBUG. For wxWidgets one is apparently supposed to use the FindwxWidgets.cmake script shipped with CMake, which sadly doesn't define the produced binaries. Maybe that's why TARGET_RUNTIME_DLLS isn't populated.
Does anyone know, either how to get TARGET_RUNTIME_DLLS filled or how to obtain the list of built wxWidgets DLLs for the current configuration (Release/Debug) post build copying?
Thanks a lot in advance!
I am dealing with a similar problem.
First sanity checks:
You have to work on windows platform otherwise this feature does not
work.
Your Cmake is 3.21 or above
Next comes fuzzy part. I think the library that you are trying to include have to be a Shared Imported library and you have to set a set_target_properties for IMPORTED_IMPLIB which is a path to a .lib file of sort (dll import library, I think it is called) So you have to make sure that it is all set in the package library that you trying to link with your executable.
If you have those dll avaiable and you just want to use them and not actually build them then you can write your own cmake script that will do just what I said above. Then you can include that cmake file in your project and then link against your app.
Note: I also work on similar issue right now and what I just said have not been working very reliably. I got some dlls to be copied and some do not.
Edit:
Cmake docs give a more detailed explanation on how this library setting should look like if you use find_package feature.
Found here: https://cmake.org/cmake/help/latest/command/add_library.html#imported-libraries
An UNKNOWN library type is typically only used in the implementation
of Find Modules. It allows the path to an imported library (often
found using the find_library() command) to be used without having to
know what type of library it is. This is especially useful on Windows
where a static library and a DLL's import library both have the same
file extension.
I am trying to use the assimp library in a cross platform C++ project. I include the repo as a git submodule, so, effectively, if someone downloads my project they will also download the ASSIMP project.
After I go through the assimp build / CMAKE instructions and (on Linux) type make install and from then on in my project I can use:
target_link_libraries(${PROJECT_NAME} assimp)
However, there is no make install on Windows.
The only other way I have been able to include the library on Linux is to put (in my CmakeLists.txt file):
target_link_libraries(${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/build/assimp/code/libassimp.so)
This is not cross platform as it hardcodes the name and location of the .so file which will not work on Windows.
How can I expose the library so that I can do something like target_link_libraries(${PROJECT_NAME} assimp) on all platforms?
My directory tree looks like:
- src
- include
- assimp
- bin
Where the assimp directory in the include directory is the git submodule
I think you're going about this the wrong way. You don't need to build assimp in a separate step from your project, and you don't need to make install to make it available.
There are a number of ways of handling third party dependencies in Cmake, since you've already chosen to submodule the assimp repository, we'll start there. Assuming assimp is located in the root of your repository in a directory called assimp/ this would be a barebones project including it:
cmake_minimum_required(VERSION 3.0)
project(Project myassimpproj)
# include your directories
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
)
# set any variables you might need to set for your app and assimp
set(BUILD_ASSIMP_TOOLS ON)
set(ASSIMP_BUILD_STATIC_LIB ON)
# add assimp source dir as a subdirectory, effectively making
# assimp's CMakeLists.txt part of your build
add_subdirectory(/path/to/assimp ${CMAKE_BINARY_DIR}/assimp)
add_executable(assimp_target main.cpp)
# be sure to link in assimp, use platform-agnostic syntax for the linker
target_link_libraries(assimp_target assimp)
There may be a better way of phrasing this using generator expressions syntax, but I haven't looked at assimp's CMakeLists.txt to know if it's supported (and this is a more generic way anyway.)
Not every project uses Cmake, so you may not be able to just add_subdirectory(). In those cases, you can effectively "fake" a user call to build them using their build commands on respective platforms. execute_process() runs a command at configure time add_custom_command() and add_custom_target() run commands at build time. You then create a fake target to make integration and cross your fingers they support Cmake someday.
You can also use the ExternalProject commands added to Cmake to create a custom target to drive download, update/patch, configure, build, install and test steps of an external project, but note that this solution and the next download the dependency rather than using the submodule'd source code.
Finally, I prefer to work with prebuilt dependencies, cuts down on build time, and they can be unit tested on their own outside of the project. Conan is an open source, decentralized and multi-platform package manager with very good support for C++ and almost transparent support for Cmake when used the right way. They have grown very stable in the last year. More information on how to use Conan with Cmake can be found here.
Finally, after whole day spent, I've made my CMake based project to successfully compile project on Windows(I've been developing it in Linux) and it even links well together, perfect isn't it? Now I launch the program and boom, it's not able to find dll libraries.
So, The problem:
I have CMake project with library and executable inside, that executable is using project's library. I also have many third party libraries in project. It compiles and links well. When I try to launch it it prints error that project's library wasn't found. I've copied it to the executable's directory. Now it prints that 3rd party library wasn't found, I've copied all of them to exe's dir. Now it prints that gcc's library wasn't found, so I've added C:\MinGW\bin to the path and now it launches fine.
But I feel it's not the proper way. I'd like to spare my teammates(new) of that pain of launching.
Either link everything statically, or add necessary install() calls into your CMakeLists.txt. Install all needed libs into the same directory and run executable from there.
If you want to run executable from CMAKE_BINARY_DIR, you can
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} )
This would make CMake to put linked binaries into the same dir.
As for gcc runtime - there is no other way except copying dll or linking statically.
I have just downloaded the OpenSceneGraph source, unzip it into
"~/OpenSceneGraph-3.0.1" directory and use CMake to create an out-of-source
eclipse make project in "~/OpenSceneGraph-3.0.1-build-eclipse-cdt"
directory. When I execute "make" in
"~/OpenSceneGraph-3.0.1-build-eclipse-cdt" directory, OpenSceneGraph builds
successfully. I have not run "sudo make install" as I do not want to
install OpenSceneGraph tightly into my Ubuntu system.
Now I want to use CMake to create a project using the compiled
OpenSceneGraph libraries. I use the following codes in CMakeLists.txt :
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
PROJECT( test_proj )
FIND_PACKAGE(OpenSceneGraph)
ADD_EXECUTABLE(test test.cpp )
INCLUDE_DIRECTORIES(${OPENSCENEGRAPH_INCLUDE_DIRS})
TARGET_LINK_LIBRARIES(test ${OPENSCENEGRAPH_LIBRARIES} )
But it seems that OpenSceneGraph could not be found by CMake.
Does anyone know how CMake could find the compiled OpenSceneGraph
libraries in the "~/OpenSceneGraph-3.0.1-build-eclipse-cdt" directory and
use it to create projects as if I have tightly installed OpenSceneGraph
using "sudo make install". Thanks for any suggestion.
You don't need to install OpenSceneGraph system-wide. Just choose a CMAKE_INSTALL_PREFIX that suits you (eg. ~/osg).
Using the install command makes sure that everything is correctly in place (i.e. in the correct directory structure) for FindOpenSceneGraph.cmake (the script CMake invokes when you call FIND_PACKAGE( OpenSceneGraph ) ) to find it.
Then, you should point any of OSG_DIR, OSGDIR, or OSG_ROOT as environment variable and point it to your install location, so CMake knows where to look for it.
Edit:
#Hugues: I'll try to make it a bit clearer:
Setup up OpenSceneGraph:
Get OSG source.
When running CMake for it, choose a CMAKE_INSTALL_PREFIX that suits you, eg. ~/osg if you don't want a system-wide installation in (default) /usr/local. Do it either by stating -DCMAKE_INSTALL_PREFIX=/home/hugues/osg on the command-line or by setting it using a gui tool like ccmake or cmake-gui.
Run make install to build and install OSG.
Set the environment variable OSG_DIR to whatever you pointed CMAKE_INSTALL_PREFIX. (export OSG_DIR=<whereever_you_installed_osg>)
Setup your project:
In your CMakeLists.txt, use FIND_PACKAGE( OpenSceneGraph ) (add desired optional arguments as desired).
Use the resulting variables (like ${OpenSceneGraph_LIBRARIES} in the appropriate places in your cmake file.
Run CMake for your project.
Add this below command in your CMakeLists.txt:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
Then put the file FindOpenSceneGraph.cmake in src/cmake dir.
FindOpenSceneGraph.cmake can be found here.