cmake transitive private include directory - cmake

Given this cmake sample project:
lib1/CMakeLists.txt:
add_library(lib1 src1.cpp)
target_include_directories(lib1 PUBLIC include)
lib2/CMakeLists.txt:
add_library(lib2 src2.cpp)
target_include_directories(lib2 PUBLIC include)
target_link_libraries(lib2 PRIVATE lib1)
lib3/CMakeLists.txt:
add_library(lib3 src3.cpp)
target_include_directories(lib3 PUBLIC include)
target_link_libraries(lib3 PRIVATE lib2)
All .cpp and .h files are dummy files with no dependency between them.
From what I understood from the documentation, adding a "PRIVATE" target library from lib2 to lib1 means that lib1 include directory should not be added when compiling lib3. However, when launching compiling this using cmake (3.3.2) and "make VERBOSE=1", command line to compile "src3.cpp" contains "-I/.../lib1/include":
c++ -I/.../lib3/include -I/.../lib2/include -I/.../lib1/include -o .../src3.cpp.o -c /.../lib3/src3.cpp
What did I get wrong ?

From what I understood from the documentation, adding a "PRIVATE"
target library from lib2 to lib1 means that lib1 include directory
should not be added when compiling lib3.
Your assumptions are correct.
However, due to legacy reasons in how properties propagate between dependent targets, this correct behavior is deactivated when specifying an old version in cmake_minimum_required.
By changing the version to 3.0 or later, you will get the correct behavior:
cmake_minimum_required(VERSION 3.0)
...
See also this thread on the CMake mailing list.

Related

Cannot specify compile options for imported target "..."

I want to provide the users of my library with two targets: one that specifies the include path etc., and one that carries useful extra compile options. However, for the extra target some of my users are getting the error
Cannot specify compile options for imported target "myproject::extra"
so it seems on older CMake versions.
I tested with CMake 3.9.2. The test project, including CI is on GitHub, with failing build here.
(How) can my approach be rendered robust for all CMake versions?
The project's main CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project(myproject)
add_library(myproject INTERFACE)
set(MYPROJECT_VERSION "1.0.0")
target_include_directories(myproject INTERFACE
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" DESTINATION include)
install(TARGETS myproject EXPORT myproject-targets)
install(EXPORT myproject-targets FILE myprojectTargets.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" VERSION ${MYPROJECT_VERSION} COMPATIBILITY AnyNewerVersion)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/myprojectConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/myprojectConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/myproject")
The project's myprojectConfig.cmake:
include(CMakeFindDependencyMacro)
if(NOT TARGET myproject)
include("${CMAKE_CURRENT_LIST_DIR}/myprojectTargets.cmake")
endif()
if(NOT TARGET myproject::extra)
add_library(myproject::extra INTERFACE IMPORTED)
if(MSVC)
target_compile_options(myproject::extra INTERFACE /W4)
else()
target_compile_options(myproject::extra INTERFACE -Wall)
endif()
endif()
The user's project CMakeLists.txt could then look as follows:
cmake_minimum_required(VERSION 3.0)
project(myexec)
find_package(myproject REQUIRED)
add_executable(myexec main.cpp)
target_link_libraries(myexec PRIVATE myproject myproject::extra)
List of functions applicable for IMPORTED and INTERFACE targets changes as CMake evolves.
Most of such functions affects only on specific target properties. So, instead of calling a function, you may set the property directly. This will work in any CMake version:
# Works only in new CMake versions
target_compile_options(myproject::extra INTERFACE /W4)
# Equivalent which works in any CMake version
set_property(TARGET myproject::extra PROPERTY INTERFACE_COMPILE_OPTIONS /W4)

Linking against GTest fails

I have the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)
# adding library
set(ST_SRC simple_tree.cpp)
add_library(st ${ST_SRC})
target_include_directories(st PUBLIC ${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree/)
# adding googletest
set(GOOGLETEST_PATH ~/local/googletest)
set(GTEST_INCLUDE_DIR ~/local/include/)
set(GTEST_LIBRARY ~/local/lib/)
set(GTEST_MAIN_LIBRARY ~/local/lib/)
find_package(GTest REQUIRED)
# adding tests
set(TEST_TARGET test_simple_tree)
add_executable(${TEST_TARGET} test_simple_tree.cpp)
target_include_directories(${TEST_TARGET}
PUBLIC
${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree
${GOOGLETEST_PATH}
${GTEST_INCLUDE_DIR})
target_link_libraries(${TEST_TARGET} PUBLIC st)
target_link_libraries(${TEST_TARGET} PUBLIC gtest gtest_main)
Basically, I've installed googletest into my home directory rather than system-wide.
The find_package() command apparently succeeds. However, trying to build test_simple_tree fails with:
/usr/bin/ld: cannot find -lgtest
/usr/bin/ld: cannot find -lgtest_main
Inside this CMakeLists.txt, how else can I tell the linker to look elsewhere
for the gtest?
EDIT: After reading the docs, I've fixed the Gtest issue as described below. However, the following issue cropped up: CMake imported target includes non-existent path
If find_package() was successful in finding the GTest includes/libraries, it should populate targets for you, per the CMake FindGTest documentation:
This module defines the following IMPORTED targets:
GTest::GTest:
The Google Test gtest library, if found; adds Thread::Thread automatically
GTest::Main:
The Google Test gtest_main library, if found
You should use these in your target_link_libraries() command instead. Also, CMake will populate GTEST_INCLUDE_DIRS, but the GTest include directories should be pulled in from the imported targets mentioned above.
Another important note: I'm not sure if you posted all of your CMake code, but I don't see a project() call in your code. As a result, the ${PROJECT_SOURCE_DIR} variable may not be what you expect. In general, it is good practice to declare your project with project() at the top of your CMake.
Your CMake file with these modifications could look something like this:
cmake_minimum_required(VERSION 3.15)
project(simple_tree_example)
set(CMAKE_CXX_STANDARD 17)
# adding library
set(ST_SRC simple_tree.cpp)
add_library(st ${ST_SRC})
target_include_directories(st PUBLIC
${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree/)
# adding googletest
set(GOOGLETEST_PATH ~/local/googletest)
set(GTEST_INCLUDE_DIR ~/local/include/)
set(GTEST_LIBRARY ~/local/lib/)
set(GTEST_MAIN_LIBRARY ~/local/lib/)
find_package(GTest REQUIRED)
# adding tests
set(TEST_TARGET test_simple_tree)
add_executable(${TEST_TARGET} test_simple_tree.cpp)
target_include_directories(${TEST_TARGET}
PUBLIC
${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree
${GOOGLETEST_PATH})
target_link_libraries(${TEST_TARGET} PUBLIC st GTest::GTest GTest::Main)
Targets gtest and gtest_main are created only when GTest is used via add_subdirectory() approach.
When use GTest via find_package(), one need to use either IMPORTED targets GTest::GTest and GTest::Main, or variables GTEST_LIBRARIES and GTEST_MAIN_LIBRARIES correspondingly. This is described in the documentation:
find_package(GTest REQUIRED)
...
target_link_libraries(test_simple_tree PUBLIC GTest::GTest gtest_main)
or
find_package(GTest REQUIRED)
...
# That case we need to add include directories
target_include_directories(test_simple_tree ${GTEST_INCLUDE_DIRS})
target_link_libraries(test_simple_tree PUBLIC ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})

Using target_include_directories with object libraries

I have a CMake project that builds a static library which depends on another static library. I would like to turn this static library into an object libraries. When I do that I get a compiler error and I imagine that there is something I don't get about object libraries.
Here is an example of what I am trying to achieve. MyLib and MyLib2 are both static libraries and MyLib uses a function defined and declared in MyLib2.
MyLib
CMakeList.txt
MyLib.h
MyLib.cpp
MyLib2
CMakeList.txt
MyLib2.h
MyLib2.cpp
MyLib2/CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project (MyLib2)
add_library(${PROJECT_NAME} OBJECT MyLib2.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
MyLib/CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project (MyLib)
add_subdirectory(MyLib2)
add_library(${PROJECT_NAME} STATIC MyLib.cpp MyLib.h)
target_link_libraries(${PROJECT_NAME} MyLib2)
MyLib.h includes MyLib2.h to use the function it declares.
#ifndef MyLib
#define MyLib
#include "MyLib2.h"
#endif
When MyLib2 is built as a static library I can build the code without any problems (I am using make and clang on Mac). However when I turn MyLib2 into an object library I get a compile error saying that MyLib2.h cannot be found.
MyLib.h:4:10: fatal error: 'MyLib2.h'file not found
Here are the content of the CMake files when MyLib2 is an object library.
MyLib2/CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project (MyLib2)
add_library(${PROJECT_NAME} OBJECT MyLib2.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
MyLib/CMakeLists.txt:
cmake_minimum_required(VERSION 3.0)
project (MyLib)
add_subdirectory(MyLib2)
add_library(${PROJECT_NAME} STATIC MyLib.cpp $<TARGET_OBJECTS:MyLib2>)
I do not understand why MyLib can no longer MyLib2.h when MyLib2 is an object library. Maybe there is something wrong with the way is use target_include_directories.
This was answered in the comments:
#Clem: When you link with libary target, you consume its INTERFACE_INCLUDE_LIBRARIES property, which contains include directory Mylib2. When you use object library via $, you don't use any target, so don't consume any property. – Tsyvarev

Overlapping dependencies between libraries in CMake

Let's say there's the following directory structure:
projects
|
+--lib1
| |
| +-CMakeFiles.txt
|
+--lib2
| |
| +-CMakeFiles.txt
|
+--test
|
+-CMakeFiles.txt
lib1/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
add_library(lib1 STATIC lib1.cpp)
lib2/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
add_subdirectory(../lib1 ${CMAKE_CURRENT_BINARY_DIR}/lib1)
add_library(lib2 STATIC lib2.cpp)
target_link_libraries(lib2 lib1)
test/CMakeFiles.txt:
cmake_minimum_required(VERSION 2.0)
project(test)
add_subdirectory(../lib1 ${CMAKE_CURRENT_BINARY_DIR}/lib1)
add_subdirectory(../lib2 ${CMAKE_CURRENT_BINARY_DIR}/lib2)
add_executable(test main.cpp)
target_link_libraries(test lib1 lib2)
I.e. lib2 depends on lib1 and test depends on both of them. (I know that technically static libraries don't "link", but that's just an example.)
The problem is that with the current setup, lib1 compiles twice - the first time it is within the "test" build directory, and a second time it is within "test/build_directory/lib2/build_directory". I'd like to avoid that.
I want to be able to add a dependency on lib1, lib2 or both of them (using add_subdirectory) to any project that's located elsewhere. So moving CMakeFiles isn't an option. I also want to avoid compiling any library several times.
How can I do that?
Platform: CMake v. 2.8.4 and Windows XP SP3
A top-level CMakeLists.txt file isn't an option, because I want to keep a clean top-level directory and be able to include libraries in other projects that can be located elsewhere. Because it is Windows, I can't "install package system-wide" - I don't want to lose ability to switch compiler on the fly. Utility libraries built with different compilers will use different C runtime libraries/ABI, and as a result will be incompatible.
One other solution is to add a guard at the top of the subdirectory-CMakeLists.txt:
if(TARGET targetname)
return()
endif(TARGET targetname)
Which will cause cmake to do nothing the second time the subdirectory is added (if targetname is being defined in that file, of course).
This will lead to the lib beeing build in an sort-of-arbitrary place (depending on which module added it first) in the build/ tree, but it will be built only once and linked everywhere.
In your example, you would add
if(TARGET lib1)
return()
endif(TARGET lib1)
at the top of lib1/CMakeFiles.txt
With CMake, library dependencies are transitive, so you shouldn't call add_subdirectory twice in test/CMakeFiles.txt (nor do you need to list lib1 as a dependency of test since it is already a dependency of lib2's).
So you could modify test's CMakeFiles.txt to:
cmake_minimum_required(VERSION 2.8.7) # Prefer the most current version possible
project(test)
add_subdirectory(../lib2 ${CMAKE_CURRENT_BINARY_DIR}/lib2)
add_executable(test main.cpp)
target_link_libraries(test lib2)
Also, you should probably remove the cmake_minimum_required calls from your non-project CMakeFiles.txt files (the lib ones). For further info, run:
cmake --help-policy CMP0000
This setup will still cause all libs to be recompiled if you add a similar test2 subdirectory and project which depends on lib1 and lib2. If you really don't want to have a top-level CMakeFiles.txt in projects/, then you're stuck with what you're doing, or you could use either the export or install command.
export would create a file which could be included by other projects and which imports the targets into the project which calls include.
install could install the libraries to another common subdirectory of projects/. Depending on your source directory structure, this could have the benefit of only making the intended library API headers available to dependent projects.
However, both of these options require the dependent library projects to be rebuilt (and installed) if they are modified, whereas your current setup includes all the dependent targets in your project, so any change to a source file in a dependent library will cause your test target to go out of date.
For further details about export and install, run:
cmake --help-command export
cmake --help-command install
Perhaps add a top-level CMakeLists.txt in your projects dir. Something like:
project( YourProjects )
add_subdirectory( lib1 )
add_subdirectory( lib2 )
add_subdirectory( test )
This should be sufficient and will give you a solution-file or makefile in your top-level build-dir. You should then remove the add_subdirectory( ../lib1 ... from your lib1 and lib2 projects, but instead simply link to them. CMake will know how to find lib1 and lib2 when compiling test.
I.e. in lib2:
project( lib2)
add_library(lib2 STATIC lib2.cpp)
target_link_libraries(lib2 lib1)
and in test:
project( test )
add_executable(test main.cpp)
target_link_libraries(test lib1 lib2)
Added bonus: you will get makefiles/solutionfiles for building lib2 (with dependent lib1) in the lib2 directory...

CMAKE - resolving dependencies between libraries in project

I am using CMAKE to build a quite large project consisting of many libraries and executables. There is something wrong with how I specify library-library dependencies, and things do not work fully as wanted. Schematically my project looks like this:
CMakeLists.txt
lib1/src/CMakeLists.txt
lib2/src/CMakeLists.txt
app/src/CMakeLists.txt
I.e. I have two libraries lib1 and lib2 where lib2 depends on lib1 and app depends on both lib1 and lib2. I build libraries using both shared and static linking:
add_library(lib1_static STATIC lib1_src)
add_library(lib1_shared SHARED lib1_src)
set_target_properties( lib1_static PROPERTIES OUTPUT_NAME lib1)
set_target_properties( lib1_shared PROPERTIES OUTPUT_NAME lib2)
To ensure that the dependies are satisfied I have target_link_libraries() as:
#lib2/src/CMakeLists.txt:
target_link_libraries( lib2_shared lib1_shared )
target_link_libraries( lib2_static lib1_static )
And for the app:
#app/src/CMakeLists.txt
target_link_libraries( app_static lib2_static ) # <- No explicit dependance on lib1
target_link_libraries( app_shared lib2_shared )
Now - the problem is that when I make a fresh build it compiles for quite a long time, but when creating the liblib2.so file the error message:
make[2]: *** No rule to make target 'lib1/src/liblib1.so' needed by 'lib2/src/liblib2.so'. Stop.
appears. If I then just issue a new make command - things will build successfully. So it seems I have not managed to configure the dependencies correctly? Note that the make output from the first build attempt shows:
Linking C shared library liblib1.so
So the build itself has suceeded - but it seems like the build will not use the liblib1.s0 file created during this build instance to resolve the lib2 dependencies?
I have tried - and removed again - several varietes of link_directories() and target_depends() without success.
It seems, CMake got confused by usage of
set_target_properties( lib1_static PROPERTIES OUTPUT_NAME lib1)
Try this:
target_link_libraries( lib2_shared lib1 )
or remove these property settings.
I was using CMake version 2.6 - I upgraded to CMake 2.8 and then it worked for me.