Unable to use locally installed Protocol Buffers inside ROS - c++

I have installed Protocol Buffers locally. Below is the directory structure of ROS package:
.
├── CMakeLists.txt
├── package.xml
├── include
│   ├── addressbook.pb.cc
│   ├── addressbook.pb.h
│   └── addressbook.proto
├── lib
│   └── protobuf-3.5.0
└── src
└── main.cpp
Protocol Buffers was installed locally by using ./configure --prefix=$PWD inside lib/protobuf-3.5.0 directory.
While compiling the ROS package using catkin_make, it seems referring to an old installation of Protocol Buffers and showing version incompatibility errors.
[ 0%] Building CXX object local_protobuf_ros_example/CMakeFiles/addressbook_protobuf.dir/include/addressbook.pb.cc.o
In file included from /home/bgplvm/ros_ws/src/local_protobuf_ros_example/include/addressbook.pb.cc:4:0:
/home/bgplvm/ros_ws/src/local_protobuf_ros_example/include/addressbook.pb.h:12:2: error: #error This file was generated by a newer version of protoc which is
#error This file was generated by a newer version of protoc which is
^
/home/bgplvm/ros_ws/src/local_protobuf_ros_example/include/addressbook.pb.h:13:2: error: #error incompatible with your Protocol Buffer headers. Please update
#error incompatible with your Protocol Buffer headers. Please update
^
/home/bgplvm/ros_ws/src/local_protobuf_ros_example/include/addressbook.pb.h:14:2: error: #error your headers.
#error your headers.
^
In file included from /home/bgplvm/ros_ws/src/local_protobuf_ros_example/include/addressbook.pb.cc:4:0:
/home/bgplvm/ros_ws/src/local_protobuf_ros_example/include/addressbook.pb.h:23:35: fatal error: google/protobuf/arena.h: No such file or directory
#include <google/protobuf/arena.h>
^
compilation terminated.
Although, I put message(STATUS "Using Protocol Buffers ${Protobuf_VERSION}") statement inside CMakeLists.txt and found that it is using Protocol Buffers 3.5.0. See below the CMakeLists.txt:
cmake_minimum_required(VERSION 2.8.3)
project(local_protobuf_ros_example)
find_package(catkin REQUIRED COMPONENTS roscpp)
set(PROTOBUF_FOLDER ${PROJECT_SOURCE_DIR}/lib/protobuf-3.5.0)
set(CMAKE_PREFIX_PATH
${CMAKE_PREFIX_PATH}
"${PROTOBUF_FOLDER}/cmake/build/release/lib/x86_64-linux-gnu/cmake/protobuf"
)
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using Protocol Buffers ${Protobuf_VERSION}")
catkin_package()
include_directories(include ${catkin_INCLUDE_DIRS} ${PROTOBUF_INCLUDE_DIRS})
add_library(addressbook_protobuf include/addressbook.pb.cc)
add_executable(main src/main.cpp)
target_link_libraries(main ${catkin_LIBRARIES} addressbook_protobuf ${PROTOBUF_LIBRARIES})
What is missing here? Since I am referring to the local installation, it should work without showing errors. Isn't it?

Your set of CMAKE_PREFIX_PATH is incorrect. It should point to the prefix in which protobuf was installed, not the location of the cmake config file for the package.
So try changing:
set(CMAKE_PREFIX_PATH
${CMAKE_PREFIX_PATH}
${PROTOBUF_FOLDER}/cmake/build/release/lib/x86_64-linux-gnu/cmake/protobuf
)
To this:
set(CMAKE_PREFIX_PATH
${CMAKE_PREFIX_PATH}
${PROTOBUF_FOLDER}
)
That is, assuming ${PROTOBUF_FOLDER} points to the same location given to the --prefix argument of ./configure --prefix=<INSTALL_PREFIX>
See the search paths documentation for find_package here. (about half way down the page)
I believe if you were on Windows what you had would have worked as <prefix>/ is one of the search paths for that platform. But on Unix based OS'es you have these as the search paths (per the documentation on above link):
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/
<prefix>/(lib/<arch>|lib|share)/<name>*/
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/
You also have to be careful about case sensitivity here. Note that in the above paths the last path element they search is <name>*. From what I can see in your question it looks like protobuf installs itself under the name of
'protobuf', but your find_package call is asking for 'Protobuf'. So, try also changing your call to:
find_package(protobuf CONFIG REQUIRED)
And finally, as far as I can tell, protobuf doesn't install CMake configs when doing a build via ./configure .... To get the CMake configs installed I had to build via CMake by doing:
cd protobuf-3.5/cmake
cmake -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> .
make
make install

I found a workaround to use locally installed Protocol Buffers.
I just need to set following two variables to locally installed Protocol Buffers:
Protobuf_INCLUDE_DIRS
Protobuf_LIBRARIES
Please see the CMakeLists.txt below:
cmake_minimum_required(VERSION 2.8.3)
project(local_protobuf_ros_example)
find_package(catkin REQUIRED COMPONENTS roscpp)
set(PROTOBUF_FOLDER ${PROJECT_SOURCE_DIR}/lib/protobuf-3.5.0)
set(Protobuf_INCLUDE_DIRS ${PROTOBUF_FOLDER}/include)
set(Protobuf_LIBRARIES ${PROTOBUF_FOLDER}/lib/libprotobuf.so)
catkin_package()
include_directories(include ${catkin_INCLUDE_DIRS} ${Protobuf_INCLUDE_DIRS})
add_library(addressbook_protobuf include/addressbook.pb.cc)
add_executable(main src/main.cpp)
target_link_libraries(main ${catkin_LIBRARIES} addressbook_protobuf ${Protobuf_LIBRARIES})
It works as of now. However, I am not sure, if it is a good practice or not. Anyone having a better solution is most welcome.

Related

Export custom library which uses Qt via CMake for use in another CMake project (Windows, Mingw-w64)

A bit of background on what I am trying to achieve: I already have a project that I have developed in CMake (it is collection of CMake projects: basically a state machine with a few helper libraries). Now, I want to develop a GUI in Qt that this state machine can control (and make it do stuff). However, for the life of me, I cannot figure out how to properly export the library which has the Qt classes and functions to another CMake package's executable. I am on Windows, and using the Mingw-w64 compiler and make program.
I was experimenting with 2 simple CMake projects, one which has a simple Qt form (which I just copied from and modified this repository and the corresponding youtube video given in the README), and the other which has the executable. I was trying to link the first project with the second project, however, whenever I build the autogenerated makefile, it fails in the end, saying that symbols are undefined (For example undefined reference to run_stuff::run_stuff()'). I apologize if the files are dirty, I have been trying out various stuff to no avail.
Things I have tried:
I have followed the answer here very carefully.
I have tried setting CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS to ON, as suggested by many sites (like this one) for running DLLs
I have tried making the library static (hence doing away with the need for DLLs)
I have tried setting the target_include_dirs from private to public and also commenting out that line. (not sure what that does, but okay)
However, no matter what I do, I always end with linking errors in the end, and my executable in the second project is not able to access any functions from the Qt project. Any suggestions on what I should try next? Would be really helpful if you could point out errors I am making in my CMakeLists.txt files (once again apologies for the messy code and CMakeLists)
My project CMake file looks like this:
cmake_minimum_required(VERSION 3.14)
# if (WIN32)
# set(CMAKE_SHARED_LIBRARY_PREFIX "")
# endif ()
set(CMAKE_MAKE_PROGRAM $ENV{MAKE})
set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo" CACHE STRING "" FORCE)
set(CMAKE_SUPPORT_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(WINDOWS_EXPORT_ALL_SYMBOLS ON)
add_subdirectory(QT6CMake)
add_subdirectory(Exec)
The library CMake file:
cmake_minimum_required(VERSION 3.14)
if (WIN32)
project(MY_PROJECT LANGUAGES CXX)
elseif(UNIX)
project(MY_PROJECT)
endif()
set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo" CACHE STRING "" FORCE)
#======================= INCLUSION OF Qt =======================#
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_PREFIX_PATH $ENV{QTDIR})
find_package(Qt6Core REQUIRED)
find_package(Qt6Widgets REQUIRED)
#=================== INCLUSION OF Project Files ====================#
set(FORMS_DIR "${CMAKE_SOURCE_DIR}/forms")
set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include")
set(SOURCE_DIR "${CMAKE_SOURCE_DIR}/src")
include_directories(${FORMS_DIR})
include_directories(${INCLUDE_DIR})
include_directories(${SOURCE_DIR})
file(GLOB_RECURSE SOURCES
"${FORMS_DIR}/*.ui"
"${FORMS_DIR}/*.qrc"
"${INCLUDE_DIR}/*.h"
"${SOURCE_DIR}/*.cpp"
)
#=================== SETUP EXECTUABLE ====================#
# Enable debug logging on RELWITHDEBINFO configuration
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS
$<$<CONFIG:RELWITHDEBINFO>:QT_MESSAGELOGCONTEXT>
)
# Add the forms directory to the AUTOUIC search paths
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_AUTOUIC_SEARCH_PATHS} ${FORMS_DIR})
# Add the executable
if (WIN32)
add_executable(MY_PROJECT WIN32 ${SOURCES})
elseif(UNIX)
add_executable(MY_PROJECT ${SOURCES})
endif()
# Add the target includes for MY_PROJECT
target_include_directories(MY_PROJECT PRIVATE ${FORMS_DIR})
target_include_directories(MY_PROJECT PRIVATE ${INCLUDE_DIR})
target_include_directories(MY_PROJECT PRIVATE ${SOURCE_DIR})
#===================== LINKING LIBRARIES =======================#
target_link_libraries(MY_PROJECT Qt6::Widgets)
The Exec CMake file:
cmake_minimum_required(VERSION 3.14)
project(somexec)
add_definitions(${MY_PROJECT_DEFINITIONS})
include_directories(${MY_PROJECT_INCLUDE_DIRS})
if (WIN32)
add_executable(${PROJECT_NAME} WIN32 main.cpp)
elseif(UNIX)
add_executable(${PROJECT_NAME} main.cpp)
endif()
target_link_libraries(${PROJECT_NAME} MY_PROJECT)
Here is the codebase.
EDIT:
Finally the code seems to be working, courtesy of Guillaume Racicot's multiple edits and fixes. I am going to leave this repository public in case anyone wants to see the codebase. Also, right now, I don't understand all the CMake commands that he has used, will try to understand those as well
The CMake code from the tutorial uses very old fashioned and outdated CMake practices. It works when creating a simple project, but won't work when doing libraries. To share files between projects, you need to export the CMake targets. You can do that by creating this file first:
cmake/yourlibrary-config.cmake
include(CMakeFindDependencyMacro)
# write it like you find_package of your cmake scripts
find_dependency(Qt6 COMPONENTS Core Widgets REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/yourlibrary-targets.cmake")
Then, add this to your main project cmake files. You should have a CMake file that looks like this:
cmake_minimum_required(VERSION 3.21)
project(yourlibrary CXX)
# First, create your library. List all the files one by one. Don't use globs
add_library(yourlibrary src/mainwindow.cpp)
# Then add an alias of your library to enable target syntax
add_library(yourlibrary::yourlibrary ALIAS yourlibrary)
# Automatically generate Qt ui and moc
set_target_properties(yourlibrary PROPERTIES
AUTOUIC ON
AUTOMOC ON
AUTOUIC_SEARCH_PATHS forms
)
# Then, include gnuinstalldir to get the platform's standard directories:
include(GNUInstallDirs)
# Then, carefully add your include directories. All of your `target_include_directories` must look like this
target_include_directories(yourlibrary PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> # include directory in your build tree
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> # include directory when installed
)
# Then, include Qt and link qt
find_package(Qt6 COMPONENTS Core Widgets REQUIRED)
target_link_libraries(yourlibrary PUBLIC Qt6::Core Qt6::Widgets)
# Now, create the install script. We install all header files under `include/yourlibrary` to install them in `<prefix>/include/yourlibrary`:
install(
DIRECTORY include/yourlibrary
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h"
)
# We add `yourlibrary` target into the export set.
# The export set will contain all targets to be imported by the other project.
# It also installs the library to the install script so they are installed:
install(TARGETS yourlibrary EXPORT yourlibrary-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# Now, we install the export set. This will generate a CMake file exporting all the target for other projects to use:
install(EXPORT yourlibrary-targets
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yourlibrary"
NAMESPACE yourlibrary::
)
# Now, we also export the current buildtree. Other project will be able to import the project directly from a build dir:
configure_file(cmake/yourlibrary-config.cmake yourlibrary-config.cmake COPYONLY)
export(
EXPORT yourlibrary-targets
NAMESPACE yourlibrary::
FILE "${PROJECT_BINARY_DIR}/yourlibrary-targets.cmake"
)
# The file we created earlier:
install(
FILES cmake/yourlibrary-config.cmake
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/yourlibrary"
)
I omitted the installation of the generated headers since they are usually considered private for the project.
For the library, have a source tree looking like this:
yourlibrary
├── cmake
│   └── yourlibrary-config.cmake
├── forms
│   └── mainwindow.ui
├── include
│   └── yourlibrary
│   └── mainwindow.h
├── src
│ └── mainwindow.cpp
└── CMakeLists.txt
To use the library, you have two choices.
Either you embed it in your project using add_subdirectory(yourlibrary)
Either you build it separately and use find_package(yourlibrary REQUIRED)
You can't do both.
I usually prefer using find_package, but it require a package manager to avoid manually building all dependencies.
If you use add_subdirectory
Then remove find_package(yourlibrary REQUIRED) from your project. Simply add the directory and have your main project file look like this:
cmake_minimum_required(VERSION 3.21)
project(exec LANGUAGES CXX)
# Embed the project
add_subdirectory(yourlibrary)
add_executable(myexec main.cpp)
target_link_libraries(myexec PUBLIC yourlibrary::yourlibrary)
I assume a project tree like this:
SampleProject
├── yourlibrary
│ └── ...
├── CMakeLists.txt
└── main.cpp
If you build yourlibrary separately
First, build the library and (optionally) install it in a known location.
Now, you can build your library that uses Qt. You will need a CMakeLists.txt that looks like this:
cmake_minimum_required(VERSION 3.21)
project(myexec LANGUAGES CXX)
# also finds Qt and all
find_package(yourlibrary REQUIRED)
add_executable(myexec main.cpp)
target_link_libraries(myexec PUBLIC yourlibrary::yourlibrary)
If you install the yourlibrary library, it will simply work. If you want to use it from its build tree, simply run the CMake command with -DCMAKE_PREFIX_PATH=/path/to/yourlibrary/build. It should point to the build directory of the library, or the installation prefix if you installed it in a custom location.

Why CTest can't find tests with googletest ? Also, can I run a single test function by mouse click in visual studio code like in visual studio? [duplicate]

I have a project with a structure
├── CMakeLists.txt
├── mzl.c
├── mzl.h
└── tests
├── CMakeLists.txt
├── mzl-communication-test.c
├── mzl-setup-test.c
├── mzl-test-errors.c
└── mzl-test-errors.h
Where the top directory CMakeLists.txt file is
project(mzl)
cmake_minimum_required(VERSION 2.8)
add_subdirectory(tests)
# Enable testing for the project
enable_testing()
# Find zmq
find_library(ZMQ_LIB zmq REQUIRED)
message(STATUS "ZMQ Library: ${ZMQ_LIB}")
# Find threading library
set(CMAKE_THREAD_PREFER_PTHREAD ON)
find_package(Threads REQUIRED)
message(STATUS "Threading Library: ${CMAKE_THREAD_LIBS_INIT}")
# Set include directories for headers
set (
MZL_INCLUDE_DIRS
${CMAKE_SOURCE_DIR}
CACHE STRING "MZL Include Directories"
)
include_directories(${MZL_INCLUDE_DIRS})
# Set source files
set (
MZL_SRC_FILES
mzl.c
)
# Add library
add_library(${PROJECT_NAME} STATIC ${MZL_SRC_FILES})
# Link to zmq
target_link_libraries(${PROJECT_NAME} ${ZMQ_LIB} ${CMAKE_THREAD_LIBS_INIT})
and tests/CMakeLists.txt is
# Use MZL Source Directories (mzl headers are in top directory)
include_directories(${CMAKE_SOURCE_DIR})
# Variable from here is empty
message(STATUS "MZL Include Directories: ${MZL_INCLUDE_DIRS}")
# Files common to all tests
set ( TEST_COMMON_SOURCES
mzl-test-errors.c
)
# Library setup/shutdown testing
set(SETUP_TEST_NAME mzl-setup-test)
add_executable(${SETUP_TEST_NAME} ${TEST_COMMON_SOURCES} ${SETUP_TEST_NAME}.c)
target_link_libraries(${SETUP_TEST_NAME} ${PROJECT_NAME} ${ZMQ_LIB})
add_test(${SETUP_TEST_NAME} ${SETUP_TEST_NAME})
# Communcations test
set(COMMUNICATION_TEST_NAME mzl-communication-test)
add_executable(${COMMUNICATION_TEST_NAME} ${TEST_COMMON_SOURCES}
${COMMUNICATION_TEST_NAME}.c)
target_link_libraries(${COMMUNICATION_TEST_NAME} ${PROJECT_NAME}
${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIB})
add_test(${COMMUNICATION_TEST_NAME} ${COMMUNICATION_TEST_NAME})
Everything worked fine before I added the second test mzl-communication-test. After adding it and the lines in the tests/CMakeLists.txt after # Communications test, running ctest did nothing extra -- it still only ran the first test.
After deleting the build directory and running cmake again, I get no errors for the initial CMake run, but running make runs CMake again, resulting in an error with CMake:
CMake Error: Parse error in cache file build/CMakeCache.txt. Offending entry: include
followed by a Make error:
Makefile:203: recipe for target 'cmake_check_build_system' failed
make: *** [cmake_check_build_system] Error 1
Running make again results in everything being built, including the tests; however, running ctest results in
Test project build
No tests were found!!!
The issue seems to be with something that I am doing with CMake, but can't figure out what to do from here as I can't see anything that I'm doing differently than when it was originally working. Even my last commit to git, which was working when I committed it, is no longer working.
You need to move the enable_testing() call to be before you do add_subdirectory(tests)
# Enable testing for the project
enable_testing()
add_subdirectory(tests)

How to include external modules headers when FetchContent_Declare is used?

I want to use modern CMake project structure convention in my new project to follow good practices and to be consistent with many other projects, but I'm facing problem with include path.
My directories structure looks as follows:
.
├── build
├── CMakeLists.txt
├── extern
│ └── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
I want to use FetchContent_Declare instead of git submodules to manage the third-party libraries.
The root CMakeLists.txt:
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(ProjectName "MyApp")
project(${ProjectName})
add_subdirectory(extern)
add_subdirectory(src)
The extern CMakeLists.txt:
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
include(FetchContent)
FetchContent_Declare(
extern_spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.8.2)
FetchContent_GetProperties(extern_spdlog)
if(NOT extern_spdlog_POPULATED)
FetchContent_Populate(extern_spdlog)
add_subdirectory(
${extern_spdlog_SOURCE_DIR}
${extern_spdlog_BINARY_DIR}
)
endif()
The src CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
set(BINARY ${CMAKE_PROJECT_NAME})
file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)
set(SOURCES ${SOURCES})
add_executable(${BINARY}_run ${SOURCES})
add_library(${BINARY}_lib STATIC ${SOURCES})
target_link_libraries(${BINARY}_run extern_spdlog)
And the main.cpp:
#include <spdlog/spdlog.h>
int main(int argc, char* argv[]) {
spdlog::info("Welcome to spdlog!");
return 0;
}
CMake executes and the spdlog library is cloned properly and can be found in the build directory at: build/_deps/extern_spdlog-src.
If I call make in the build directory then I get fatal error: spdlog/spdlog.h: No such file or directory.
How to add include directories for the external libraries used in the application?
I was initially confused about this as well. It doesn't help that the names of the FetchContent targets often match the names of the imported targets. What worked for me was similar (though not identical) to what the first comment states. I used target_link_libraries(target spdlog::spdlog) (if you want to use the pre-compiled library or spdlog::spdlog_header_only if you want to use the header source only version.

CMake ExternalProject does not unpack dependencies

I am building an application which uses tinyxml2 and few other dependencies (namely Irrlicht and IrrKlang) that I provide as .zip files in the Dependency subdirectory of my project:
.
├── CMakeLists.txt
├── Dependencies
│   ├── irrKlang-1.5.0.zip
│   ├── irrKlang-32bit-1.5.0.zip
│   ├── irrKlang-64bit-1.5.0.zip
│   ├── irrlicht-1.8.4.zip
│   └── tinyxml2-master.zip
├── Editor
│   ├── CMakeLists.txt
│   └── Sources
│   └── main.cpp
└── Game
   ├── CMakeLists.txt
   └── Sources
   └── main.cpp
NOTE: for a reference, full sources are available on GitHub, here I cut some corners to make the question shorter.
The top-level CMakeFiles.txt is set up is:
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(shoot-them VERSION 1.0.1 LANGUAGES CXX)
include(ExternalProject)
# few platform-specific variables like MAKE_COMMAND, PLATFORM_ARCH, etc.
# libraries (see below)
add_subdirectory(Game)
add_subdirectory(Editor
Both Irrlicht and IrrKlang come with pre-built libraries for Windows for x86, but not for Windows x64 and not for OSX. Hence I add it as a dependency like this (using the if(NOT IRRLICHT_LIBRARY_PATH) just to separate the code into a block:
if(NOT IRRLICHT_LIBRARY_PATH)
ExternalProject_Add(irrlicht-dep
URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/irrlicht-1.8.4.zip
PREFIX Dependencies/irrlicht
SOURCE_SUBDIR source/Irrlicht
CONFIGURE_COMMAND ""
BUILD_COMMAND "${MAKE_COMMAND}"
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(irrlicht-dep SOURCE_DIR)
set(IRRLICHT_PATH ${SOURCE_DIR})
add_library(irrlicht SHARED IMPORTED GLOBAL)
set_target_properties(
irrlicht PROPERTIES
IMPORTED_LOCATION ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_LINK_LIBRARY_SUFFIX}
IMPORTED_IMPLIB ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_IMPORT_LIBRARY_SUFFIX}
INTERFACE_INCLUDE_DIRECTORIES ${IRRLICHT_PATH}/include
)
endif()
I follow the same principles for IrrKlang. But since tinyxml2 comes as a header and a source file and it comes packed with CMakeLists.txt, I just include it like this:
ExternalProject_Add(tinyxml2-dep
URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/tinyxml2-master.zip
PREFIX Dependencies/tinyxml2
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(tinyxml2-dep SOURCE_DIR)
set(TINYXML2_PATH ${SOURCE_DIR})
add_subdirectory(${TINYXML2_PATH})
I define both Game and Editor sub-projects as follows:
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(Editor VERSION 1.0.1 LANGUAGES CXX)
set(EXECUTABLE_NAME Editor)
set(SOURCES Sources/main.cpp)
# platform-specific variables
set_target_properties(${EXECUTABLE_NAME} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS ON
)
add_dependencies(${EXECUTABLE_NAME} tinyxml2 irrlicht)
target_link_libraries(${EXECUTABLE_NAME} PUBLIC ${LIBRARIES} tinyxml2 irrlicht)
if(NOT APPLE)
# Copy libraries' DLLs
add_custom_command(
TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:tinyxml2> $<TARGET_FILE_DIR:${EXECUTABLE_NAME}>
)
endif()
# TODO: copy DLLs, not LIBs
add_custom_command(
TARGET ${EXECUTABLE_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:irrlicht> $<TARGET_FILE_DIR:${EXECUTABLE_NAME}>
)
The way I use ExternalProject specifically is because
I wanted to fix the versions I build my project with
I did not want to pollute my system with installing the libraries (hence no INSTALL steps, although I may completely misunderstand that concept)
I do not depend on 3rd party repositories being dead (hence shipping the ZIP files with the sources)
I do not rely on unbelievably outdated (and thus potentially non-working) find modules for CMake
To be fair: I am not aware of any best practices of building projects with CMake, so I might very well be completely wrong about all of the above, so please do correct me.
When I build this project in Visual Studio 2019 on Windows, it works like a charm. But whenever I try building the thing on OSX, I get failures:
none of the dependencies gets even unpacked
(because of p.1) the ${TINYXML2_DIR} is never set
(because of p.2) the tinyxml2 directory could not be found and thus added via add_subdirectory()
(because of p.3) the $<TARGET_FILE:tinyxml2> expression does not evaluate
(as a global consequence) the project does not build
The way I build project is rather simple:
cmake -Bbuild -H. && cmake --build build
What am I doing wrong?
Also, what is the right way to handle 3rd party dependencies with CMake?
I am very well aware that CMake is technically just a makefile (roughly speaking, since it is different for every build toolchain) generator, so my question is more about how do I tell CMake to generate the correct build files for each type of dependency that should be built with my project (pre-built, build from sources with CMake, build from sources with a custom command). I thought ExternalProject is supposed to handle just that, but apparently something went horribly wrong along the way.
I have played with both a solution suggested by #Mizux in their comment and had some success with two different approaches.
1. vcpkg
This is arguably the easier of the two. It requires vcpkg installed.
See this commit for example.
Create a manifest file, vcpkg.json in the project root directory, listing all the dependencies used by the project:
{
"name": "PROJECT_NAME",
"version-string": "0.1.0",
"dependencies": [
"irrlicht",
"tinyxml2"
]
}
You can also use CLI to generate the manifest by using vcpkg install command.
Use the find_package from CMake to link libraries to each target - in the child CMakeLists.txt:
find_package(irrlicht CONFIG REQUIRED)
find_package(tinyxml2 CONFIG REQUIRED)
target_link_libraries(${EXECUTABLE_NAME} PUBLIC ${LIBRARIES} tinyxml2::tinyxml2 Irrlicht)
Important: when configuring the project, pass the path to the vcpkg CMake module:
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]/scripts/buildsystems/vcpkg.cmake
2. FetchContent + ExternalProject
Since my project depends on both a library that ships with CMakeLists.txt and a library that does not, this is a nice example.
See this commit for example.
First, as #Mizux mentioned, FetchContent works at configure time - it downloads and unpacks the dependency when you configure the project (call cmake -S . -B build). Then, since irrlicht does not ship with CMakeLists.txt, you either use ExternalProject_Add to build it with custom command (make in my case) or add it as a sub-directory to the project.
FetchContent part:
include(FetchContent)
FetchContent_Declare(irrlicht
URL ${CMAKE_CURRENT_LIST_DIR}/Dependencies/irrlicht-1.8.4.zip
)
FetchContent_GetProperties(irrlicht)
if(NOT irrlicht_POPULATED)
FetchContent_Populate(irrlicht)
endif()
set(IRRLICHT_PATH ${irrlicht_SOURCE_DIR})
ExternalProject part:
include(ExternalProject)
if(NOT WIN32)
ExternalProject_Add(irrlicht-dep
SOURCE_DIR "${IRRLICHT_PATH}/source/Irrlicht"
BUILD_IN_SOURCE true
CONFIGURE_COMMAND ""
BUILD_COMMAND "${MAKE_COMMAND}"
)
endif()
add_library(irrlicht SHARED IMPORTED GLOBAL)
set_target_properties(
irrlicht PROPERTIES
IMPORTED_LOCATION ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_LINK_LIBRARY_SUFFIX}
IMPORTED_IMPLIB ${IRRLICHT_PATH}/lib/${IRRLICHT_PATH_SUFFIX}/Irrlicht${CMAKE_IMPORT_LIBRARY_SUFFIX}
INTERFACE_INCLUDE_DIRECTORIES ${IRRLICHT_PATH}/include
)
Note the important bits in the ExternalProject configuration:
irrlicht_SOURCE_DIR variable is populated by FetchContent
SOURCE_DIR "${irrlicht_SOURCE_DIR}/source/Irrlicht" - tells ExternalProject where the unpacked sources are; if this directory is not empty (which it should be not, since FetchContent should have populated it at project configuration time), ExternalProject will skip the download phase
BUILD_IN_SOURCE true - builds from within the source directory
CONFIGURE_COMMAND "" - skips the dependency configuration phase, so not cmake will be executed for the dependency
BUILD_COMMAND "make" - uses a specific command to build the dependency from sources
if(NOT WIN32) - only uses ExternalProject to build the dependency; since Irrlicht comes with pre-built libraries for Win32, this should build the thing for the other platforms (including Win64)

How to integrate Catch2 as external library with CMake?

I am trying to set up a learning project using Catch2 and I decided that it was
best to clone the repository in a Cpp folder, so I could get updates and use it
for other C++ projects. The installing method is as described here.
The basic folder structure is:
Cpp
├───TestProject
│ ├───main.cpp
│ ├───.vscode
│ └───build
│ ├───CMakeFiles
│ └───Testing
└───Catch2
├─── ...
...
As per Catch2 documentation I put this on my CMake file:
find_package(Catch2 REQUIRED)
target_link_libraries(tests Catch2::Catch2)
However, when I try to configure the project in VS Code, I get the following error message:
[cmake] CMake Error at CMakeLists.txt:5 (target_link_libraries):
[cmake] Cannot specify link libraries for target "tests" which is not built by this
[cmake] project.
main.cpp is just a Hello World file and the complete CMakeLists.txt file contents are:
cmake_minimum_required(VERSION 3.0.0)
project(TestProject VERSION 0.1.0)
find_package(Catch2 REQUIRED)
target_link_libraries(tests Catch2::Catch2)
enable_testing()
add_library(TestProject TestProject.cpp)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
I am unsure why this happens. I am a complete newcomer to CMake, save for very
basic commands I had to use at work. I guess it would be less work to just drop
it as a header file like it's intended to, but this approach made more sense to
me...
Note: I have read this SO question. However his question had to do with
Catch2 as a header file inside the project.
Note 2: desired behaviour is to build the project using Catch2 as an external
library.
(Additional information: CMake --version is 3.13.3, using CMakeTools in VS Code,
OS is Windows 10)
First, since the library has been installed through CMake (same applies for installs using a package manager), it is recommended to flag find_package with CONFIG (read about Config mode here).
This is because even if Catch2 repository is in a parent, common folder to the project, the CMake installation process installs it in your Program Files folder (in Windows); i.e. the repository is just that.
Additionally you should add_executable(tests main.cpp) so CMake has "tests" as a target.
This solves the original problem.
However, for it to completely work, you need to follow these additional steps:
Use catch_discover_tests(tests)
include(CTest) possibly necessary.
The include preprocessor command should be: #include <catch2/catch.hpp> instead of simply #include "catch.hpp".
Also, make sure your editor is aware of the environmental variables created
during the installation of Catch2. That is, if you are having problems, restart
the editor so it re-reads environmental variables.
Full CMakeLists.txt:
cmake_minimum_required(VERSION 3.5.0)
project(TestProject LANGUAGES CXX VERSION 0.1.0)
find_package(Catch2 REQUIRED)
add_executable(tests main.cpp) # solution to the original problem
target_link_libraries(tests Catch2::Catch2)
include(CTest) # not sure if this is 100% necessary
include(Catch)
catch_discover_tests(tests)
enable_testing()
Note: Instead of add_executable, we should use add_library, although no
tests are recognized in library mode for some reason; however that is beyond the
scope of this question as it lies more in the knowledge of use pf Catch2.