CMake + GoogleTest - cmake

I just downloaded googletest, generated its makefile with CMake and built it. Now, I need to use it in my testing project.
With CMake, I have been advised not pointing to gtest libraries directly (using include _directories or link_directories) but use find_package() instead.
The problem is, there is no install target for the gtest makefile generated. I cannot understand how find_package(GTest REQUIRED) could work without some kind of installation. Also, putting the gtest folder as a subfolder in my project is not possible.
Thanks for any help.

This is an unusual case; most projects specify install rules.
CMake's ExternalProject_Add module is maybe the best tool for this job. This allows you to download, configure and build gtest from within your project, and then link to the gtest libraries.
I've tested the following CMakeLists.txt on Windows with Visual Studio 10 and 11, and on Ubuntu using GCC 4.8 and Clang 3.2 - it might need adjusted for other platforms/compilers:
cmake_minimum_required(VERSION 2.8.7 FATAL_ERROR)
project(Test)
# Create main.cpp which uses gtest
file(WRITE src/main.cpp "#include \"gtest/gtest.h\"\n\n")
file(APPEND src/main.cpp "TEST(A, B) { SUCCEED(); }\n")
file(APPEND src/main.cpp "int main(int argc, char **argv) {\n")
file(APPEND src/main.cpp " testing::InitGoogleTest(&argc, argv);\n")
file(APPEND src/main.cpp " return RUN_ALL_TESTS();\n")
file(APPEND src/main.cpp "}\n")
# Create patch file for gtest with MSVC 2012
if(MSVC_VERSION EQUAL 1700)
file(WRITE gtest.patch "Index: cmake/internal_utils.cmake\n")
file(APPEND gtest.patch "===================================================================\n")
file(APPEND gtest.patch "--- cmake/internal_utils.cmake (revision 660)\n")
file(APPEND gtest.patch "+++ cmake/internal_utils.cmake (working copy)\n")
file(APPEND gtest.patch "## -66,6 +66,9 ##\n")
file(APPEND gtest.patch " # Resolved overload was found by argument-dependent lookup.\n")
file(APPEND gtest.patch " set(cxx_base_flags \"\${cxx_base_flags} -wd4675\")\n")
file(APPEND gtest.patch " endif()\n")
file(APPEND gtest.patch "+ if (MSVC_VERSION EQUAL 1700)\n")
file(APPEND gtest.patch "+ set(cxx_base_flags \"\${cxx_base_flags} -D_VARIADIC_MAX=10\")\n")
file(APPEND gtest.patch "+ endif ()\n")
file(APPEND gtest.patch " set(cxx_base_flags \"\${cxx_base_flags} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32\")\n")
file(APPEND gtest.patch " set(cxx_base_flags \"\${cxx_base_flags} -DSTRICT -DWIN32_LEAN_AND_MEAN\")\n")
file(APPEND gtest.patch " set(cxx_exception_flags \"-EHsc -D_HAS_EXCEPTIONS=1\")\n")
else()
file(WRITE gtest.patch "")
endif()
# Enable ExternalProject CMake module
include(ExternalProject)
# Set the build type if it isn't already
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# Set default ExternalProject root directory
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/ThirdParty)
# Add gtest
ExternalProject_Add(
googletest
SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk/
SVN_REVISION -r 660
TIMEOUT 10
PATCH_COMMAND svn patch ${CMAKE_SOURCE_DIR}/gtest.patch ${CMAKE_BINARY_DIR}/ThirdParty/src/googletest
# Force separate output paths for debug and release builds to allow easy
# identification of correct lib in subsequent TARGET_LINK_LIBRARIES commands
CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs
-Dgtest_force_shared_crt=ON
# Disable install step
INSTALL_COMMAND ""
# Wrap download, configure and build steps in a script to log output
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON)
# Specify include dir
ExternalProject_Get_Property(googletest source_dir)
include_directories(${source_dir}/include)
# Add compiler flag for MSVC 2012
if(MSVC_VERSION EQUAL 1700)
add_definitions(-D_VARIADIC_MAX=10)
endif()
# Add test executable target
add_executable(MainTest ${PROJECT_SOURCE_DIR}/src/main.cpp)
# Create dependency of MainTest on googletest
add_dependencies(MainTest googletest)
# Specify MainTest's link libraries
ExternalProject_Get_Property(googletest binary_dir)
if(MSVC)
set(Suffix ".lib")
else()
set(Suffix ".a")
set(Pthread "-pthread")
endif()
target_link_libraries(
MainTest
debug ${binary_dir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gtest${Suffix}
optimized ${binary_dir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}gtest${Suffix}
${Pthread})
If you create this as CMakeLists.txt in an empty directory (say MyTest), then:
cd MyTest
mkdir build
cd build
cmake ..
This should create a basic main.cpp in MyTest/src and create a project file (MyTest/build/Test.sln on Windows)
When you build the project, it should download the gtest sources to MyTest/build/ThirdParty/src/googletest, and build them in MyTest/build/ThirdParty/src/googletest-build. You should then be able to run the MainTest target successfully.

It is long past when the original question being asked, but for the benefit of others, it is possible to use ExternalProject to download the gtest source and then use add_subdirectory() to add it to your build. This has the following advantages:
gtest is built as part of your main build, so it uses the same compiler flags, etc. and doesn't need to be installed anywhere.
There's no need to add the gtest sources to your own source tree.
Used in the normal way, ExternalProject won't do the download and unpacking at configure time (i.e. when CMake is run), but you can get it to do so. I've written a blog post on how to do this which also includes a generalised implementation which works for any external project which uses CMake as its build system, not just gtest. You can find it here:
https://crascit.com/2015/07/25/cmake-gtest/
Update: The approach described above is now also part of the googletest documentation.

My answer is based on the answer from firegurafiku. I modified it in the following ways:
added CMAKE_ARGS to the ExternalProject_Add call so it works with msvc.
gets the gtest source from a file location rather than downloading
added portable (for MSVC and non-MSVC) definition and usage of IMPORTED_LOCATION
Addressed the problem with the call to set_target_properties not working at configure time when the INTERFACE_INCLUDE_DIRECTORIES does not yet exist because the external project has not yet been built.
I prefer keeping gtest as an external project rather than adding its source directly to my project. One reason is because I do not like having the gtest source code included when I am searching my code. Any special build flags that are needed by my code that should also be used when building gtest can be added to the definition of CMAKE_ARGS in the call to ExternalProject_Add
Here is my modified approach:
include(ExternalProject)
# variables to help keep track of gtest paths
set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest")
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build")
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include")
# external project download and build (no install for gtest)
ExternalProject_Add(GTestExternal
URL ${CMAKE_CURRENT_SOURCE_DIR}/../googletest
PREFIX "${GTEST_PREFIX}"
# cmake arguments
CMAKE_ARGS -Dgtest_force_shared_crt=ON
# Disable install step
INSTALL_COMMAND ""
# Wrap download, configure and build steps in a script to log output
LOG_DOWNLOAD ON
LOG_CONFIGURE ON
LOG_BUILD ON
)
# variables defining the import location properties for the generated gtest and
# gtestmain libraries
if (MSVC)
set(GTEST_IMPORTED_LOCATION
IMPORTED_LOCATION_DEBUG "${GTEST_LOCATION}/Debug/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}"
IMPORTED_LOCATION_RELEASE "${GTEST_LOCATION}/Release/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}"
)
set(GTESTMAIN_IMPORTED_LOCATION
IMPORTED_LOCATION_DEBUG "${GTEST_LOCATION}/Debug/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}"
IMPORTED_LOCATION_RELEASE "${GTEST_LOCATION}/Release/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}"
)
else()
set(GTEST_IMPORTED_LOCATION
IMPORTED_LOCATION "${GTEST_LOCATION}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTESTMAIN_IMPORTED_LOCATION
IMPORTED_LOCATION "${GTEST_LOCATION}/${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}")
endif()
# the gtest include directory exists only after it is build, but it is used/needed
# for the set_target_properties call below, so make it to avoid an error
file(MAKE_DIRECTORY ${GTEST_INCLUDES})
# define imported library GTest
add_library(GTest IMPORTED STATIC GLOBAL)
set_target_properties(GTest PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDES}"
IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}"
${GTEST_IMPORTED_LOCATION}
)
# define imported library GTestMain
add_library(GTestMain IMPORTED STATIC GLOBAL)
set_target_properties(GTestMain PROPERTIES
IMPORTED_LINK_INTERFACE_LIBRARIES GTest
${GTESTMAIN_IMPORTED_LOCATION}
)
# make GTest depend on GTestExternal
add_dependencies(GTest GTestExternal)
#
# My targets
#
project(test_pipeline)
add_executable(${PROJECT_NAME} test_pipeline.cpp)
set_target_properties(${PROJECT_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
target_link_libraries(${PROJECT_NAME} ${TBB_LIBRARIES})
target_link_libraries(${PROJECT_NAME} GTest)

There is a bit less complex solution using ExternalProject module and imported libraries feature of cmake. It checks out code from repository, builds it and creates target from built static libraries (they're libgtest.a and libgtest_main.a on my system).
find_package(Threads REQUIRED)
include(ExternalProject)
set(GTEST_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/gtest")
ExternalProject_Add(GTestExternal
SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk
SVN_REVISION -r HEAD
TIMEOUT 10
PREFIX "${GTEST_PREFIX}"
INSTALL_COMMAND "")
set(LIBPREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}")
set(LIBSUFFIX "${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(GTEST_LOCATION "${GTEST_PREFIX}/src/GTestExternal-build")
set(GTEST_INCLUDES "${GTEST_PREFIX}/src/GTestExternal/include")
set(GTEST_LIBRARY "${GTEST_LOCATION}/${LIBPREFIX}gtest${LIBSUFFIX}")
set(GTEST_MAINLIB "${GTEST_LOCATION}/${LIBPREFIX}gtest_main${LIBSUFFIX}")
add_library(GTest IMPORTED STATIC GLOBAL)
set_target_properties(GTest PROPERTIES
IMPORTED_LOCATION "${GTEST_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDES}"
IMPORTED_LINK_INTERFACE_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}")
add_library(GTestMain IMPORTED STATIC GLOBAL)
set_target_properties(GTestMain PROPERTIES
IMPORTED_LOCATION "${GTEST_MAINLIB}"
IMPORTED_LINK_INTERFACE_LIBRARIES
"${GTEST_LIBRARY};${CMAKE_THREAD_LIBS_INIT}")
add_dependencies(GTest GTestExternal)
You may want to replace SVN_REVISION or add LOG_CONFIGURE and LOG_BUILD options here. After GTest and GTestMain targets are created, they can be used like this:
add_executable(Test
test1.cc
test2.cc)
target_link_libraries(Test GTestMain)
or, if you have your own main() function:
add_executable(Test
main.cc
test1.cc
test2.cc)
target_link_libraries(Test GTest)

The topic is a bit old, but there appeared a new way of including external libraries in CMake.
#Requires CMake 3.16+
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
)
FetchContent_MakeAvailable(googletest)
If you want to support the earlier versions of cmake:
# Requires CMake 3.11+
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
)
FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
FetchContent_Populate(googletest)
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
endif()
Then you just add
enable_testing()
add_executable(test ${SOURCES} )
target_link_libraries(test gtest_main ${YOUR_LIBS})
add_test(NAME tests COMMAND test)
Further reading: https://cmake.org/cmake/help/latest/module/FetchContent.html

When you get the libgtest-dev package via
sudo apt install libgtest-dev
The source is stored in location /usr/src/googletest
You can simply point your CMakeLists.txt to that directory so that it can find the necessary dependencies
Something like the following
add_subdirectory(/usr/src/googletest gtest)
target_link_libraries(your_executable gtest)

Related

CMake - How to get include directories of external project?

I tried to used https://github.com/julianxhokaxhiu/SteamworksSDKCI to use steam api on a simple SFML application (helloworld).
I wanted to use cmake to learn it, but I am struggling to understand how the provided CMakeLists and Find*.cmake file are expected to be used.
Currently, I have modified the CMakeLists to change the INSTALL_DIR
INSTALL_DIR "${CMAKE_BINARY_DIR}/../../vendor"
and my CMakeLists is :
cmake_minimum_required(VERSION 3.19)
project(SfmlWithCMake VERSION 1.0)
include(FetchContent)
set (CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake_steam")
# Configure external project
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/cmake_steam)
execute_process(
COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR}/cmake_steam
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/cmake_steam
)
# Build external project
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/cmake_steam
)
set(BUILD_SHARED_LIBS OFF)
FetchContent_Declare(
SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG 2.5.1
)
FetchContent_MakeAvailable(SFML)
find_package(STEAMWORKSSDK REQUIRED)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED true)
# Generate config.h
configure_file(config.h.in config.h)
add_executable(
SfmlWithCMake
main.cpp
)
get_target_property(STEAMSDK STEAMWORKSSDK::STEAMWORKSSDK INCLUDE_DIRECTORIES)
target_include_directories(
SfmlWithCMake
PRIVATE
"${PROJECT_BINARY_DIR}"
"${STEAMSDK}"
)
target_link_libraries(
SfmlWithCMake
sfml-graphics
STEAMWORKSSDK::STEAMWORKSSDK
-static gcc stdc++ winpthread -dynamic
)
install(TARGETS SfmlWithCMake DESTINATION bin)
How to get include directories?
I do not succeed to add the steam include to the target_include_directories.
Here the ${STEAMSDK} is my last attempt to get the directory.
If I replace this by ${PROJECT_BINARY_DIR}/vendor/include, everything works.
Also, why does the SFML include are automatically added to my target include directories and not the steam one?
Am I using the Find*.cmake file the right way?
I understood that ExternalProject_Add was performed at build time and thus, as the find_package is needed at configue time, I added the two "execute_process". But, the readme on github only says to do the find package and add the target to target_link_libraries...
Thanks.

Unix make file fails with cmakejs when adding a dependency

I successfully created a native node addon with napi and cmakejs. But when adding a simple library the unix make file, generated by cmake-js explodes with
[ 50%] Linking CXX static library liblib_name.a
[ 50%] Built target lib_name
CMakeFiles/spielwiese.dir/flags.make:10: * missing separator. Stop.
make[1]: * [CMakeFiles/Makefile2:72: CMakeFiles/spielwiese.dir/all] Error 2
make: *** [Makefile:130: all] Error 2
ERR! OMG Process terminated: 2
Minimized example project: https://github.com/Superlokkus/spielwiese/tree/napi
The root CMakeLists should be close or intented to be close to cmakejs example boilerplate version, just with an additional function PARSE_CMAKEJS_PROPERTIES to also build it via a cmake CLI command, for nice developing with IDEs like CLion. However the problem persisits when removing the PARSE_CMAKEJS_PROPERTIES function.
I added the library with a add_subdirectory, if you remove https://github.com/Superlokkus/spielwiese/blob/napi/CMakeLists.txt#L47 aka add_subdirectory(src/lib_name) and change https://github.com/Superlokkus/spielwiese/blob/napi/CMakeLists.txt#L63 aka target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_JS_LIB} lib_name) to target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_JS_LIB}) and also remove
https://github.com/Superlokkus/spielwiese/blob/napi/src/spielwiese.cpp#L3 aka #include <lib_name/lib_name.hpp>,
the project builds again incl. the mocha test. However add the example library and you get the error again.
Root CMakeLists:
cmake_minimum_required(VERSION 3.2)
# The following function is just for nice CLion IDE support with cmake-js
function(PARSE_CMAKEJS_PROPERTIES)
function(GET_VARIABLE INPUT_STRING VARIABLE_TO_SELECT OUTPUT_VARIABLE)
set(SEARCH_STRING "${VARIABLE_TO_SELECT}=\"")
string(LENGTH "${SEARCH_STRING}" SEARCH_STRING_LENGTH)
string(LENGTH "${INPUT_STRING}" INPUT_STRING_LENGTH)
string(FIND "${INPUT_STRING}" "${VARIABLE_TO_SELECT}=\"" SEARCH_STRING_INDEX)
math(EXPR SEARCH_STRING_INDEX "${SEARCH_STRING_INDEX}+${SEARCH_STRING_LENGTH}")
string(SUBSTRING "${INPUT_STRING}" ${SEARCH_STRING_INDEX} ${INPUT_STRING_LENGTH} AFTER_SEARCH_STRING)
string(FIND "${AFTER_SEARCH_STRING}" "\"" QUOTE_INDEX)
string(SUBSTRING "${AFTER_SEARCH_STRING}" "0" "${QUOTE_INDEX}" RESULT_STRING)
set("${OUTPUT_VARIABLE}" "${RESULT_STRING}" PARENT_SCOPE)
endfunction(GET_VARIABLE)
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
if (CMAKE_BUILD_TYPE_LOWER STREQUAL "debug")
exec_program(./node_modules/.bin/cmake-js ${CMAKE_CURRENT_SOURCE_DIR}
ARGS print-configure --debug
OUTPUT_VARIABLE CMAKE_JS_OUTPUT
)
else ()
exec_program(./node_modules/.bin/cmake-js ${CMAKE_CURRENT_SOURCE_DIR}
ARGS print-configure
OUTPUT_VARIABLE CMAKE_JS_OUTPUT
)
endif ()
get_variable("${CMAKE_JS_OUTPUT}" "CMAKE_JS_INC" CMAKE_JS_INC)
set(CMAKE_JS_INC "${CMAKE_JS_INC}" PARENT_SCOPE)
get_variable("${CMAKE_JS_OUTPUT}" "CMAKE_LIBRARY_OUTPUT_DIRECTORY" CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}" PARENT_SCOPE)
endfunction(PARSE_CMAKEJS_PROPERTIES)
# Name of the project (will be the name of the plugin)
project(spielwiese VERSION 1.0)
if (NOT CMAKE_JS_INC)
parse_cmakejs_properties()
endif ()
add_subdirectory(src/lib_name)
# Essential include files to build a node addon,
# you should add this line in every CMake.js based project.
include_directories(${CMAKE_JS_INC})
# Declare the location of the source files
file(GLOB SOURCE_FILES "src/*.cpp")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
# This line will give our library file a .node extension without any "lib" prefix
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
# Essential library files to link to a node addon,
# you should add this line in every CMake.js based project.
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_JS_LIB} lib_name)
# Include N-API wrappers
execute_process(COMMAND node -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
Libnames CMakeLists
cmake_minimum_required(VERSION 3.0)
project(lib_name VERSION 1.0)
string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPER_CASE)
configure_file(
${PROJECT_NAME}_version.hpp.in
${PROJECT_SOURCE_DIR}/include/${PROJECT_NAME}/${PROJECT_NAME}_version.hpp
)
set(${PROJECT_NAME}_implementation_files
src/lib.cpp
)
add_library(${PROJECT_NAME} ${${PROJECT_NAME}_implementation_files})
set_property(TARGET ${PROJECT_NAME} PROPERTY LINKER_LANGUAGE CXX)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17)
target_include_directories(${PROJECT_NAME}
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
install(TARGETS ${PROJECT_NAME}
ARCHIVE
DESTINATION lib)
install(TARGETS ${PROJECT_NAME}
PUBLIC_HEADER
DESTINATION include)
Update
I did some experimentation and it seems
target_include_directories(${PROJECT_NAME}
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
is doing the trouble, so it looks like cmakejs has some problem with that command?!
Turns out the snippet taken from the official cmake-js documentation
execute_process(COMMAND node -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
is invoking node to get the path to the node headers. But the output of node might contain a newline, which will ruin the day of all includes after that. So the correct part of the root CMakeLists.txt would be
# Include N-API wrappers
execute_process(COMMAND node -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
So a addition of string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) to get rid of the newline fixed the problem.
Thanks #fred for the help to find the problem
Already created a pull request to fix the cmake-js documentation: https://github.com/cmake-js/cmake-js/issues/175

Strange issue with variables in a config-file cmake package

We can use a cmake config file to import targets.
For example given machinary including foobarConfig.cmake.in
set(FOOBAR_VERSION #VERSION#)
#PACKAGE_INIT#
set_and_check(FOOBAR_INCLUDE_DIR "#PACKAGE_INCLUDE_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
set_and_check(FOOBAR_LIBRARY "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
set_and_check(FOOBAR_STATIC_LIBRARY #PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.a")
include("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
message(STATUS "foobar version: ${FOOBAR_VERSION}")
message(STATUS "foobar include location: ${FOOBAR_INCLUDE_DIR}")
message(STATUS "foobar library location: ${FOOBAR_LIBRARY_DIR}")
for an exported target foobar
We can do:
find_package(foobar)
add_executable(usesfoo
usesfoo.cpp)
target_link_libraries(usesfoo
${FOOBAR_LIBRARY})
target_include_directories(usesfoo PUBLIC
${FOOBAR_INCLUDE_DIR})
and it normally just works.
However, I have a strage case where variables set in the Config.cmake are not available after find_package.
For example given:
find_package(foobar REQUIRED)
if (foobar_FOUND)
message(STATUS "found foobar")
endif()
message(STATUS "foobar include location2: ${FOOBAR_INCLUDE_DIR}")
message(STATUS "foobar library location2: ${FOOBAR_LIBRARY_DIR}")
The output is:
foobar include location: /test-import/opt/foobar/include
foobar library location: /test-import/opt/foobar/lib
found foobar
foobar include location2:
foobar library location2:
What could be going on here?
How can I:
Find this problem?
Avoid similar problems in the future?
Create these files in a safe and canonical way?
I got very confused trying to debug this and started to question how Config packages are supposed to work.
Should I be using properties of imported targets instead of variables?
What scope does find_package run in? I thought it was like an include() rather than an add_subdirectory() - which introduces its own scope.
How can these variables become unset?
What is find_package doing under the hood?
See also correctly set the location of imported cmake targets for an installed package.
That question contains code to reproduce that problem which is similar to the code for this problem.
Complete set of files to reproduce the problem:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.7)
set(VERSION 1.3.3)
project(FoobarLib VERSION "${VERSION}" LANGUAGES CXX)
SET(CMAKE_INSTALL_PREFIX "/opt/foo")
set(INSTALL_LIB_DIR lib)
add_library(foobar SHARED
foobar.cpp
)
# Create the distribution package(s)
set(CPACK_PACKAGE_VERSION ${VERSION})
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_PACKAGE_NAME "foobar")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
set(LIBRARY_INSTALL_DIR lib)
set(INCLUDE_INSTALL_DIR include)
INSTALL(TARGETS foobar
EXPORT FoobarLibTargets
LIBRARY DESTINATION ${LIBRARY_INSTALL_DIR}
ARCHIVE DESTINATION ${LIBRARY_INSTALL_DIR}
INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR})
include(CMakePackageConfigHelpers)
set(ConfigFileInstallDir lib/cmake/FoobarLib)
set(INCLUDE_INSTALL_DIR include CACHE PATH "install path for include files")
set(LIBRARY_INSTALL_DIR lib CACHE PATH "install path for libraries")
configure_package_config_file(FoobarLibConfig.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfig.cmake"
INSTALL_DESTINATION "${ConfigFileInstallDir}"
PATH_VARS INCLUDE_INSTALL_DIR LIBRARY_INSTALL_DIR
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfigVersion.cmake"
VERSION "${VERSION}"
COMPATIBILITY SameMajorVersion)
EXPORT(EXPORT FoobarLibTargets
FILE FoobarLibTargets.cmake)
INSTALL(FILES
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibConfigVersion.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/FoobarLibTargets.cmake"
DESTINATION "${ConfigFileInstallDir}")
include(CPack)
FoobarLibConfig.cmake.in:
set(FoobarLib_VERSION #VERSION#)
#PACKAGE_INIT#
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
SET_AND_CHECK(FoobarLib_LIB_DIR "#PACKAGE_LIBRARY_INSTALL_DIR#")
message(STATUS "Foobar library version: ${FoobarLib_VERSION}")
message(STATUS "Foobar library location: ${FoobarLib_LIB_DIR}")
# workaround incorrect setting of location for import targets when package is installed
# see https://stackoverflow.com/q/56135785/1569204
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
# IMPORTED_LOCATION_RELEASE "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so"
# IMPORTED_LOCATION_DEBUG "#PACKAGE_LIBRARY_INSTALL_DIR#/libfoobar.so")
check_required_components(FoobarLib)
run.sh:
#!/bin/sh
SRC=`pwd`
mkdir -p ./target/debug && \
cd ./target/debug &&
cmake -DCMAKE_BUILD_TYPE=Debug ../../ &&
make &&
cpack -G TGZ
cd ../..
rm -rf foo
mkdir foo
TGZ=`pwd`/target/debug/foobar-1.3.3.tar.gz
cd foo
tar -xvzf $TGZ
cat - >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
find_package(FoobarLib ${MIN_FOOBARLIB_VERSION}
HINTS "${WSDIR}/opt/foo"
PATHS /opt/foo
REQUIRED)
message(STATUS "Foobar library version: ${FOOBARLIB_VERSION}")
message(STATUS "Foobar library location: ${FOOBARLIB_LIB_DIR}")
message(STATUS "FoobarLib_FOUND=${FoobarLib_FOUND}")
message(STATUS "FoobarLib_PATH=${FOOBARLIB_PATH}")
message(STATUS "FoobarLib_DIR=${FoobarLib_DIR}")
message(STATUS "FOOBARLIB_FOUND=${FoobarLib_FOUND}")
message(STATUS "FOOBARLIB_PATH=${FOOBARLIB_PATH}")
message(STATUS "FOOBARLIB_DIR=${FoobarLib_DIR}")
file(GENERATE OUTPUT foobar-loc CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
EOF
export CMAKE_PREFIX_PATH=`pwd`/opt/foo/lib/cmake:`pwd`/opt/foo/lib/cmake/
cmake . && make VERBOSE=1
echo pwd=`pwd`
# critical - check the location of the target is relative to the installation
grep $WSDIR/opt/foo/lib/libfoobar.so foobar-loc
if [ $? -ne 0 ]; then
echo "FAIL: location of imported target 'foobar' is incorect" >&2
cat foobar-loc >&2
exit 1
fi
Here is the generated Config.cmake as requested by #havogt I don't think it helps as it is the standard generated code:
# CMake configuration file for the FoobarLib package
# Use with the find_package command in config-mode to find information about
# the FoobarLib package.
#
set(FoobarLib_VERSION 1.3.3)
####### Expanded from #PACKAGE_INIT# by configure_package_config_file() #######
####### Any changes to this file will be overwritten by the next CMake run ####
####### The input file was FoobarLibConfig.cmake.in ########
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()
####################################################################################
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/FoobarLibTargets.cmake")
SET_AND_CHECK(FoobarLib_LIB_DIR "${PACKAGE_PREFIX_DIR}/lib")
message(STATUS "Foobar library version: ${FoobarLib_VERSION}")
message(STATUS "Foobar library location: ${FoobarLib_LIB_DIR}")
# workaround incorrect setting of location for import targets when package is installed
# see https://stackoverflow.com/q/56135785/1569204
#set_target_properties(foobar PROPERTIES
# IMPORTED_LOCATION_NOCONFIG "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so"
# IMPORTED_LOCATION_RELEASE "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so"
# IMPORTED_LOCATION_DEBUG "${PACKAGE_PREFIX_DIR}/lib/libfoobar.so")
check_required_components(FoobarLib)
'package'_FOUND is set by the implementation of find_package() not by the Config.cmake that it loads. Adding check_required_components() is good practice for other reasons (picking up that someone thinks the package is componentised when it isn't) but is not relevant to this issue.
Oops. This is embarrassing. I'd moved the generation code into a shell script and forgot to escape the variables!
cat - >CMakeLists.txt <<EOF
cmake_minimum_required(VERSION 3.7)
project(useFoo VERSION 1.2.3)
find_package(FoobarLib ${MIN_FOOBARLIB_VERSION}
HINTS "${WSDIR}/opt/foo"
PATHS /opt/foo
REQUIRED)
message(STATUS "Foobar library version: ${FOOBARLIB_VERSION}")
message(STATUS "Foobar library location: ${FOOBARLIB_LIB_DIR}")
message(STATUS "FoobarLib_FOUND=${FoobarLib_FOUND}")
message(STATUS "FoobarLib_PATH=${FOOBARLIB_PATH}")
message(STATUS "FoobarLib_DIR=${FoobarLib_DIR}")
message(STATUS "FOOBARLIB_FOUND=${FoobarLib_FOUND}")
message(STATUS "FOOBARLIB_PATH=${FOOBARLIB_PATH}")
message(STATUS "FOOBARLIB_DIR=${FoobarLib_DIR}")
file(GENERATE OUTPUT foobar-loc CONTENT "<TARGET_FILE:foobar>=$<TARGET_FILE:foobar>\n")
EOF
The question is still useful for providing source for the related question though.
To answer my own questions:
How can I find this problem?
Avoid similar problems in the future?
Create these files in a safe and canonical way?
https://en.wikipedia.org/wiki/Rubber_duck_debugging
Reduce the problem to a minimum reproducible example (preferably before posting on stack overflow)
Avoid (or at least take extra care) generating code from shell scripts
Reduce stress and get more sleep
check_required_components(Foobar) should be called at the end in the case. The docs.
check_required_components() should be called at the end
of the FooConfig.cmake file. This macro checks whether all requested,
non-optional components have been found, and if this is not the case,
sets the Foo_FOUND variable to FALSE, so that the package is
considered to be not found. It does that by testing the
Foo__FOUND variables for all requested required components.
This macro should be called even if the package doesn’t provide any
components to make sure users are not specifying components
erroneously. When using the NO_CHECK_REQUIRED_COMPONENTS_MACRO option,
this macro is not generated into the FooConfig.cmake file.

PROTOBUF_GENERATE_CPP not generating src and header files

My cmake configuration is not generating any protobuf src and header files.
I've already checked if the proto files can be found.
Cmakelists.txt
cmake_minimum_required(VERSION 3.0.2)
..
include(FindProtobuf REQUIRED)
file(GLOB PROTO_DEF "${CMAKE_CURRENT_SOURCE_DIR}/protobuf/*/*.proto")
foreach(file ${PROTO_DEF})
if(EXISTS ${file})
MESSAGE("YES")
else()
MESSAGE("NO")
endif()
endforeach()
SET(PROTOBUF_GENERATE_CPP_APPEND_PATH PROTOBUF)
SET(PROTOBUF_PROTOC_EXECUTABLE protoc.exe)
..
PROTOBUF_GENERATE_CPP(PROTO_SRC PROTO_INCL ${PROTO_DEF})
..
add_library(${PROJECT_NAME} STATIC ${INCLUDES} ${INTERNAL_INCLUDES} ${SRC} ${PROTO_SRC} ${PROTO_INCL})
target_link_libraries(${PROJECT_NAME} ${PROTOBUF_LIBRARIES})
I have checked the FindProtobuf.cmake and half way through:
foreach(FIL ${ARGN})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(FIL_WE ${FIL} NAME_WE)
list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc")
list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h")
MESSAGE(1 ${CMAKE_CURRENT_BINARY_DIR})
MESSAGE(2 ${_protobuf_include_path})
MESSAGE(3 ${ABS_FIL})
MESSAGE(4 ${PROTOBUF_PROTOC_EXECUTABLE})
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc"
"${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h"
COMMAND ${PROTOBUF_PROTOC_EXECUTABLE}
ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL}
DEPENDS ${ABS_FIL}
COMMENT "Running C++ protocol buffer compiler on ${FIL}"
VERBATIM )
endforeach()
You can see i've added the 4 message commands, the script reaches this point and variables show good values.
The proto files do have a depencency to the library, thus the command should get executed !?
Any ideas on this problem?
update
replacing the add_custom_command with
EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --cpp_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL}
does generate the source and header files, must i manually activate the custom_commmands?
Regards Auke
The add_custom_command is fired (for my project) during compilation time.
Adding
SET_SOURCE_FILES_PROPERTIES(${PROTO_SRC} ${PROTO_INCL} PROPERTIES GENERATED TRUE)
gives cmake info that the files will be generated.
Alternative if you have an external proto folder:
file(GLOB PROTOBUF_FILELIST ${PROTO_INCLUDE_DIR}/*.proto)
foreach( proto_file ${PROTOBUF_FILELIST} )
get_filename_component(proto_name ${proto_file} NAME_WE)
get_filename_component(proto_path ${PROTO_INCLUDE_DIR} ABSOLUTE)
set_source_files_properties("${proto_path}/${proto_name}.pb.cc"
"${proto_path}/${proto_name}.pb.h"
PROPERTIES GENERATED TRUE)
endforeach()
See that:
https://cmake.org/cmake/help/v3.20/module/FindProtobuf.html
Example:
find_package(Protobuf REQUIRED)
include_directories(${Protobuf_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS EXPORT_MACRO DLL_EXPORT foo.proto)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS DESCRIPTORS PROTO_DESCS foo.proto)
protobuf_generate_python(PROTO_PY foo.proto)
add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(bar ${Protobuf_LIBRARIES})
Note The protobuf_generate_cpp and protobuf_generate_python functions and add_executable() or add_library() calls only work properly within the same directory.
This problem only happens when the target is a library (using add_library, add_executable seems to be fine). The PROTOBUF_GENERATE_CPP will defer the generation of protobuf sources and headers when building the library. A workaround is to define a custom target and add it as a dependency to the lib target. e.g.
add_custom_target(proto_dep DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
add_library(${PROJECT_NAME} STATIC ${OTHER_SRCS})
add_dependencies(${PROJECT_NAME} proto_dep)
target_link_libraries(${PROJECT_NAME} PRIVATE gRPC::grpc++ gRPC::grpc++_reflection protobuf::libprotobuf)

How to check if find_package found the package (boost)

I want to not add boost.cxx if cmake find_package found no boost installed. Does find_package return something that I can wrap in condition to compile boost.cxx or not. Here is my current cmake file:
add_executable (complex complex.cxx lexer.cxx boost.cxx ../../src/lili.cxx ../../src/lilu.cxx)
# Make sure the compiler can find all include files
include_directories (../../src)
include_directories (.)
# Make sure the linker can find all needed libraries
# rt: clock_gettime()
target_link_libraries(complex rt)
# Install example application
install (TARGETS complex
RUNTIME DESTINATION bin)
IF(UNIX)
find_package(Boost COMPONENTS system filesystem REQUIRED)
## Compiler flags
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-O2")
set(CMAKE_EXE_LINKER_FLAGS "-lsqlite3 -lrt -lpthread")
endif()
target_link_libraries(complex
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
#${PROTOBUF_LIBRARY}
)
ENDIF(UNIX)
The FindXXX scripts are supposed to set a variable <Packagename>_FOUND to TRUEif the package was found. So in your case, it will set Boost_FOUND if boost was found.
When compiling your Boost.cxx, I assume that you will need Boost headers as well, so you should adjust your include directories as well.*
look for Boost before creating your executable. Furhtermore, you need to set your include directories before adding the executable.
IF(UNIX)
find_package(Boost COMPONENTS system filesystem REQUIRED)
# IF( Boost_FOUND ) # checking this variable isnt even necessary, since you added
# REQUIRED to your call to FIND_PACKAGE
SET( BOOST_SRC_FILES boost.cxx )
INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIRS} ) # you could move this down as well
# as ${Boost_INCLUDE_DIRS} will be
# empty if Boost was not found
# ENDIF()
ENDIF()
add_executable (complex complex.cxx lexer.cxx ${BOOST_SRC_FILES} ../../src/lili.cxx ../../src/lilu.cxx)
# Make sure the compiler can find all include files
include_directories (../../src)
include_directories (.)
# INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIRS} ) # alternative location to
# add include dirs, see above
# Make sure the linker can find all needed libraries
# rt: clock_gettime()
target_link_libraries(complex rt)
# Install example application
install (TARGETS complex
RUNTIME DESTINATION bin)
IF(UNIX)
## Compiler flags
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-O2")
set(CMAKE_EXE_LINKER_FLAGS "-lsqlite3 -lrt -lpthread")
endif()
target_link_libraries(complex
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
#${PROTOBUF_LIBRARY}
)
ENDIF(UNIX)
Afternote: Since you use the REQUIRED flag when looking for Boost (since you only need it on Unix platform) it is even sufficient to use the optional-source-files-in-a-variable trick.
(*) Thanks to your question, I just found out that it doesn't matter whether include_directories(...) is called before or after creating the target with ADD_EXECUTABLE or ADD_LIBRARY since the directories are added to all targets in the same project.
Yes, if the find_package(Boost COMPONENTS system filesystem REQUIRED) succeeds, Boost_FOUND will be true.
Also, there will be component-specific versions, so Boost_date_time_FOUND, Boost_filesystem_FOUND, etc.
For further info, run
cmake --help-module FindBoost
Yes, it sets variable Boost_FOUND. Example from FindBoost.cmake:
== Using actual libraries from within Boost: ==
#
# set(Boost_USE_STATIC_LIBS ON)
# set(Boost_USE_MULTITHREADED ON)
# set(Boost_USE_STATIC_RUNTIME OFF)
# find_package( Boost 1.36.0 COMPONENTS date_time filesystem system ... )
#
# if(Boost_FOUND)
# include_directories(${Boost_INCLUDE_DIRS})
# add_executable(foo foo.cc)
# target_link_libraries(foo ${Boost_LIBRARIES})
# endif()