Compile several test source files as a single merged file with CMake - c++

I have a library that has many different cpp test files.
They look like this, A.cpp, B.cpp, C.pp, etc.
A.cpp:
#define BOOST_TEST_MODULE "C++ Unit Tests A"
#include<boost/test/unit_test.hpp>
#include <multi/array.hpp>
BOOST_AUTO_TEST_CASE(test_case_A1) {
...
}
B.cpp:
#define BOOST_TEST_MODULE "C++ Unit Tests B"
#include<boost/test/unit_test.hpp>
#include <multi/array.hpp>
BOOST_AUTO_TEST_CASE(test_case_B1) {
...
}
I have the following Cmake code to compile and test
include(CTest)
file(
GLOB TEST_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.cpp
)
foreach(TEST_FILE ${TEST_SRCS})
set(TEST_EXE "${TEST_FILE}.x")
add_executable(${TEST_EXE} ${TEST_FILE})
target_link_libraries(${TEST_EXE} PRIVATE multi)
target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS} )
target_link_libraries (${TEST_EXE} PRIVATE Boost::unit_test_framework )
endforeach()
The problem with this is that each test takes sometime to compile.
I know that if I merge all these cpp files it will compile much faster overall.
(For example, I have 40 cpp test files, each takes 10 second, total compilation is 400 second. If I merge all it takes perhaps 20 seconds. Even if I could parallelize the build of individual files a 20x factor is hard to achieve. Obviously the compiler is doing a lot of repeated work. Supposedly gch pre-compiled headers would help but I never figured out how to use them with cmake).
Is there a way to force the compilation of these test to work on a single merged file?
I tried replacing the loop with this:
add_executable(multi_test ${TEST_SRCS})
add_test(NAME multi_test COMMAND ./multi_test)
target_include_directories(multi_test PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_include_directories(multi_test SYSTEM PRIVATE ${Boost_INCLUDE_DIRS} )
target_link_libraries (multi_test PRIVATE Boost::unit_test_framework )
However, it still has two problems:
First, it is still slow to compile because each cpp is used to generate individual .o files (later link into the same executable).
The second problem is that each .o will contain a main function (defined by Boost.Test) and therefore there will be a linker error.
Is there a change I can make to make to compile several cpp files as if it was a single cpp file? (I would like to avoid generating a temporary merged file manually)
Is there a Boost.Test flag that can help me with this?
I would like a solution where each test source file can still be compiled into its own executable.
As an illustration, I was able to do this process to obtain a single file.
echo '#define BOOST_TEST_MODULE "C++ Unit Tests for Multi, All in one"' > ../test/all.cpp # Boost Test need a module name
cat ../test/*.cpp | grep -v BOOST_TEST_MODULE >> ../test/all.cpp # filter macros to avoid warnings

You can do the #define BOOST_TEST_MODULE "C++ Unit Tests <...>"s via the target_compile_definitions command, which is useful here since you want to give the user the option of whether to build tests to individual executables, or one single executable together.
The problem with this is that each test takes some time to compile. I know that if I merge all these cpp files it will compile much faster overall. [...] Supposedly gch pre-compiled headers would help but I never figured out how to use them with CMake).
For using precompiled headers in CMake, see the target_precompile_headers command. But what you're looking for with concatenating source files to compile as if they were a single source file is a thing and has a name: "Unity Build". CMake even comes with a feature to support it: its UNITY_BUILD target property. Do read the docs and learn about how it is suggested to be used properly, and what general caveats there are with respect to possible ODR violations when using the unity build technique with any build tool.
The solution might look something like this (in block quotes because it's mostly taken from your question post and what you wrote in chat):
include(CTest)
file(
GLOB TEST_SRCS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.cpp
)
option(
TEST_UNITY_BUILD
"whether to create a single executable for all tests (instead of one per test source file)"
<default> # replace <default> with desired default value
)
if("${TEST_UNITY_BUILD}")
foreach(TEST_FILE ${TEST_SRCS})
set(TEST_EXE "${TEST_FILE}.x")
add_executable(${TEST_EXE} ${TEST_FILE})
target_link_libraries(${TEST_EXE} PRIVATE multi)
target_include_directories(${TEST_EXE} PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_include_directories(${TEST_EXE} SYSTEM PRIVATE ${Boost_INCLUDE_DIRS} )
target_link_libraries (${TEST_EXE} PRIVATE Boost::unit_test_framework )
target_compile_definitions(${TEST_EXE} PRIVATE BOOST_TEST_MODULE="C++ Unit Tests for ${TEST_FILE}")
endforeach()
else()
add_executable(multi_test ${TEST_SRCS})
set_property(TARGET multi_test PROPERTY UNITY_BUILD ON)
target_include_directories(multi_test PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_include_directories(multi_test SYSTEM PRIVATE ${Boost_INCLUDE_DIRS} )
target_link_libraries (multi_test PRIVATE Boost::unit_test_framework )
target_compile_definitions(multi_test PRIVATE BOOST_TEST_MODULE="C++ Unit Tests for Multi GLOBAL TEST")
add_test(NAME multi_test COMMAND ./multi_test)
endif()
Note that file(GLOB) is discouraged for use by the maintainers of CMake, and by various long-time users of CMake on Stack Overflow, as seen here and here. You can instead define the list of files manually like set(TEST_SRCS A.cpp B.cpp C.cpp ...).
Also note that if you use file(GLOB), the order files in the result variable in unspecified for versions older than CMake v3.6, and specified as lexicographical for versions 3.6 and above. (see the file(GLOB) docs), which has relevance to CMake's unity build feature. Quoting from the UNITY_BUILD docs:
The order of source files added to the target via commands like add_library(), add_executable() or target_sources() will be preserved in the generated unity source files. This can be used to manually enforce a specific grouping based on the UNITY_BUILD_BATCH_SIZE target property.

Related

Forcing cmake to call external executable before reading source files [duplicate]

Example source of a binary I want to run before each build, once per add_executable:
#include <stdio.h>
int main(int argc, char *argv[]) {
for(int i=0; i<argc; ++i)
printf("argv[%d] = %s\n", i, argv[i]);
fclose(fopen("foo.hh", "a"));
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(foo_proj)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
set(SOURCE_FILES main.cpp)
# ---- this line changes ----
add_executable(foo_proj ${SOURCE_FILES})
Attempts:
add_custom_target(create_foo_hh COMMAND /tmp/bin/create_foo_hh)
add_dependencies(${SOURCE_FILES} create_foo_hh)
Error:Cannot add target-level dependencies to non-existent target "main.cpp".
The add_dependencies works for top-level logical targets created by the add_executable, add_library, or add_custom_target commands. If you want to add file-level dependencies see the DEPENDS option of the add_custom_target and add_custom_command commands.
execute_process(COMMAND /tmp/bin/create_foo_hh main.cpp)
No error, but foo.hh isn't created.
How do I automate the running of this command?
execute_process() is invoked at configuration time.
You can use add_custom_command():
add_custom_command(
OUTPUT foo.hh
COMMAND /tmp/bin/create_foo_h main.cpp
DEPENDS ${SOURCE_FILES} /tmp/bin/create_foo_hh main.cpp
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_executable(foo_proj ${SOURCE_FILES} foo.hh)
That way, foo.hh is a dependency of foo_proj: and your command will be invoked when building foo_proj. It depends on ${SOURCE_FILES} and /tmp/bin/create_foo_hh main.cpp so that it is generated again if one of those files changes.
Regarding paths, add_custom_command() is configured to run in the current build directory to generate the file there, and include_directories() is used to add the build directory to the include dirs.
You probably don't want the custom target to depend on your source files (because they aren't targets themselves and are therefore never "run"), but on the target you create with them:
target_add_dependencies(foo_proj create_foo_hh)
I think that the cleanest is to add two new project() (targets) and then add the resulting file to your final executable. This is how cmake can build a valid dependency tree so when your source files change they get recompiled, the command run, as necessary to get everything up to date.
Build Executable
First, as you do in your example, I create an executable from some .cpp file:
(example extracted from the as2js project)
project(unicode-characters)
add_executable(${PROJECT_NAME}
unicode_characters.cpp
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${ICU_INCLUDE_DIRS}
${SNAPDEV_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME}
${ICU_LIBRARIES}
${ICU_I18N_LIBRARIES}
)
As we can see,m we can add specific include paths (-I) and library paths (-L). It is specific to that one target so you can have a set of paths that is different from the one used with your other executables.
Generate Additional File
Next, you create a custom command to run your executable like so:
project(unicode-character-types)
set(UNICODE_CHARACTER_TYPES_CI ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.ci)
add_custom_command(
OUTPUT
${UNICODE_CHARACTER_TYPES_CI}
COMMAND
unicode-characters >${UNICODE_CHARACTER_TYPES_CI}
WORKING_DIRECTORY
${PROJECT_BINARY_DIR}
DEPENDS
unicode-characters
)
add_custom_target(${PROJECT_NAME}
DEPENDS
${UNICODE_CHARACTER_TYPES_CI}
)
Notice a couple of things:
I set a variable (UNICODE_CHARACTER_TYPES_CI) because I am going to reference that one file multiple times
a. Notice how I put the destination in the binary (cmake output folder) using the ${PROJECT_BINARY_DIR}/... prefix. This is best to avoid generating those files in your source tree (and possibly ending up adding that file to your source tracking system like svn or git).
b. An important aspect of the add_custom_command() is the DEPENDS section which includes the name of your special command, the one we defined in the previous step.
The add_custom_target() is what allows cmake to find your target and execute the corresponding command whenever one of the source files (a.k.a. dependency) changes; notice the DEPENDS definition.
Use the Output
Finally, here is the main project (a library in my case) that makes use of the file we generated in the step above.
Notice that I reference that file using the variable I defined in the previous step. That way, when I feel like changing that name, I can do it by simply editing that one variable.
project(as2js)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version.h.in
${CMAKE_CURRENT_BINARY_DIR}/version.h
)
add_library(${PROJECT_NAME} SHARED
compiler/compiler.cpp
...
parser/parser_variable.cpp
${UNICODE_CHARACTER_TYPES_CI}
file/database.cpp
...
)
(Note: the ... represent a list of files, shorten for display here as these are not important, the link above will take you to the file with the complete list.)
By having the filename inside the list of files defined in the add_library() (or the add_executable() in your case), you create a dependency which will find your custom_target(), because of the filename defined in the OUTPUT section of the add_custom_command()¹.
¹ It is possible to defined multiple outputs for an add_custom_command(). For example, some of my generators output a .cpp and a .h. In that case, I simply define both files in the OUTPUT section.
Results
Important points about the final results with this solution:
the output files of your generator are saved in the binary output path instead of your current working directory
the Makefile generated by cmake includes all the necessary targets/dependencies which means changing any of the input files regenerate everything as expected (even if you just update a comment)
if the generator fails, the build fails as expected
the files are generated by the build step (make time) instead of the generation step (cmake time, like the execute_process() would do)

CMake: How to reuse the same test_main.cpp for each test

I would like to use the test framework Catch2 in a monorepo in which there will be many components, each with their own tests. I'd like to use CMake to define all build targets.
Catch2 provides a simple means to generate a common main() function that will run all tests linked into the application. The source file (which I am calling test_main.cpp) is just these two lines:
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
I'd like to have just one copy of test_main.cpp in my source tree, and refer to it from each component directory that has tests (which will end up being on the order of hundreds of directories).
I can see how to set up CMake to work if I duplicated the test_main.cpp so that a redundant copy existed in each directory with tests, but I haven't figured out how to have just one copy. Is there a simple trick I've overlooked?
Try integrating Catch2 as a CMake interface library in the following way:
Add the single header file catch.hpp and test_main.cpp to a directory of their own (e.g., Catch2) along with the following CMakeLists.txt:
add_library(catch INTERFACE)
target_include_directories(catch INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_sources(catch INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp")
The in your outermost CMakeLists.txt include Catch2 with add_subdirectory:
add_subdirectory(Catch2)
You can then set up test executables anywhere in the project in the following way:
add_executable(
project_tests EXCLUDE_FROM_ALL
test_case1.cpp
test_case2.cpp
...
)
target_link_libraries(project_tests catch)
Because test_main.cpp has been added to catch as an interface source file, it will be compiled as part of the test executable.

Cmake and Gtest

I want to use Google C++ Testing and I am completely beginner to cmake and gtest.
I have a class called Filter which uses a 3d party library called jane.
For this case I have a cmakeFile which builds my project nicely as follows:
cmake_minimum_required(VERSION 3.1.2)
project(Filter)
include(../../../cmake/CMakeMacros.txt)
set_variables()
#add 3rdparty libraries
add_jane()
#add framework libraries
add_framework_libs(
ip/Image
)
include_directories(
../include
${FW_INCLUDE_DIRS}
)
#set project's source and include files
set(INCS
../include/${PROJECT_NAME}.h
../include/${PROJECT_NAME}.tpp
../include/FilterMask.h
)
set(SRCS
../src/${PROJECT_NAME}.cpp
../src/FilterMask.cpp
)
#set link directories
link_directories(
${FW_LIBRARY_DIRS}
)
#build project as static library (*.lib)
add_library(${PROJECT_NAME} STATIC
${INCS}
${SRCS}
)
#link libraries against project
target_link_libraries( ${PROJECT_NAME}
${FW_LIBRARIES}
)
#if a test executable should be build
if(Test_BUILD_EXAMPLES)
#build test executable
add_executable(${PROJECT_NAME}Test
../src/main.cpp
)
#link library against executable
target_link_libraries(${PROJECT_NAME}Test
${PROJECT_NAME}
)
endif(Test_BUILD_EXAMPLES)
and also I have read this simple tutorial on https://github.com/snikulov/google-test-examples with this cmake file https://github.com/snikulov/google-test-examples/blob/master/CMakeLists.txt and tried to build my project again to combine these cmake files together (may be in very silly way) but I can not achieve it since days.
The problem is that when I want to test a simple project with just a header file I can use this cmake file but as soon as I try to test my project containing a 3rd party library I run into different errors.
Can someone please tell me how I can edit a correct cmake file to test my project with googleTest using a cmake file !?
If you want to link against a 3rd party library you typically first:
find_package() or use the pkg config support to check the library is available on the build host and pick up a reference to it.
Include the reference from step #1 in target_link_libraries()
So that's what you need to do for your 3rd party lib. For your own code which you want to bring under test you probably want to put it all inside your own libraries and then link your tests against those.
If you have multiple test executables to separate & isolate each test suite into its own binaries you probably want an alternative technique to avoid over linking and limit the code inside the test suite to the actual unit under test. (This is also quite useful when your code base is in flux and builds only partially but you still wish to check that what builds continues to pass relevant tests.)
In that case you may want to define your units under test as OBJECT type libraries and then instead of doing target_link_libraries() against those object libs you include the objects as part of the sources for the executable using this syntax: $<TARGET_OBJECTS:NameOfObjLibHere> (cmake generator expressions).
So in the case of units which depend on a 3rd party lib, say, Qt5 Core, you'd have snippets like this:
# define the dependency on 3rd party project Qt5, (sub)component: Core, Test)
set(MY_QT_VERSION "5.4.0")
find_package(Qt5 ${MY_QT_VERSION} REQUIRED COMPONENTS Core CONFIG)
# define the object lib for a unit to be tested (Item1)
set(item1_srcs item1.cpp util1.cpp)
add_library(Item1 TYPE OBJECT ${item1_srcs})
# ensure that necessary compiler flags are passed
# when building "Item1" separately
# note that PRIVATE may also be INTERFACE or PUBLIC
# read the cmake docs on target_include_*** to determine which applies.
# you probably want to hide this behind a convenience macro.
target_include_directories(Item1 PRIVATE $<TARGET_PROPERTY:Qt5::Core,INTERFACE_INCLUDE_DIRECTORIES>)
target_compile_options(Item1 PRIVATE $<TARGET_PROPERTY:Qt5::Core,INTERFACE_COMPILE_OPTIONS>)
# find the unit testing framework (dependency)
# this sample uses Qt5 Test (Qt5::Test) but you could use GTest, too
find_package(Qt5 ${MY_QT_VERSION} REQUIRED COMPONENTS Test CONFIG)
# define an executable which contains test sources + unit under test
# link against the testing framework (obviously) as per normal
# note the Qt5::Core dependency here: remember Item1 depends on Qt5::Core (!)
set(test_item1_srcs, test_item1.cpp $<TARGET_OBJECTS:Item1>)
add_executable(test_item1 ${test_item1_srcs)
target_link_libraries(test_item1 Qt5::Core Qt5::Test)
# inform cmake/ctest integration about our test
# so it knows to execute it during `make test` phase.
# and other cmake/ctest integration falls into place as well, possibly
add_test(test_item1 test_item1)

CMake run custom command before build?

Example source of a binary I want to run before each build, once per add_executable:
#include <stdio.h>
int main(int argc, char *argv[]) {
for(int i=0; i<argc; ++i)
printf("argv[%d] = %s\n", i, argv[i]);
fclose(fopen("foo.hh", "a"));
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(foo_proj)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
set(SOURCE_FILES main.cpp)
# ---- this line changes ----
add_executable(foo_proj ${SOURCE_FILES})
Attempts:
add_custom_target(create_foo_hh COMMAND /tmp/bin/create_foo_hh)
add_dependencies(${SOURCE_FILES} create_foo_hh)
Error:Cannot add target-level dependencies to non-existent target "main.cpp".
The add_dependencies works for top-level logical targets created by the add_executable, add_library, or add_custom_target commands. If you want to add file-level dependencies see the DEPENDS option of the add_custom_target and add_custom_command commands.
execute_process(COMMAND /tmp/bin/create_foo_hh main.cpp)
No error, but foo.hh isn't created.
How do I automate the running of this command?
execute_process() is invoked at configuration time.
You can use add_custom_command():
add_custom_command(
OUTPUT foo.hh
COMMAND /tmp/bin/create_foo_h main.cpp
DEPENDS ${SOURCE_FILES} /tmp/bin/create_foo_hh main.cpp
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
add_executable(foo_proj ${SOURCE_FILES} foo.hh)
That way, foo.hh is a dependency of foo_proj: and your command will be invoked when building foo_proj. It depends on ${SOURCE_FILES} and /tmp/bin/create_foo_hh main.cpp so that it is generated again if one of those files changes.
Regarding paths, add_custom_command() is configured to run in the current build directory to generate the file there, and include_directories() is used to add the build directory to the include dirs.
You probably don't want the custom target to depend on your source files (because they aren't targets themselves and are therefore never "run"), but on the target you create with them:
target_add_dependencies(foo_proj create_foo_hh)
I think that the cleanest is to add two new project() (targets) and then add the resulting file to your final executable. This is how cmake can build a valid dependency tree so when your source files change they get recompiled, the command run, as necessary to get everything up to date.
Build Executable
First, as you do in your example, I create an executable from some .cpp file:
(example extracted from the as2js project)
project(unicode-characters)
add_executable(${PROJECT_NAME}
unicode_characters.cpp
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${ICU_INCLUDE_DIRS}
${SNAPDEV_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME}
${ICU_LIBRARIES}
${ICU_I18N_LIBRARIES}
)
As we can see,m we can add specific include paths (-I) and library paths (-L). It is specific to that one target so you can have a set of paths that is different from the one used with your other executables.
Generate Additional File
Next, you create a custom command to run your executable like so:
project(unicode-character-types)
set(UNICODE_CHARACTER_TYPES_CI ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.ci)
add_custom_command(
OUTPUT
${UNICODE_CHARACTER_TYPES_CI}
COMMAND
unicode-characters >${UNICODE_CHARACTER_TYPES_CI}
WORKING_DIRECTORY
${PROJECT_BINARY_DIR}
DEPENDS
unicode-characters
)
add_custom_target(${PROJECT_NAME}
DEPENDS
${UNICODE_CHARACTER_TYPES_CI}
)
Notice a couple of things:
I set a variable (UNICODE_CHARACTER_TYPES_CI) because I am going to reference that one file multiple times
a. Notice how I put the destination in the binary (cmake output folder) using the ${PROJECT_BINARY_DIR}/... prefix. This is best to avoid generating those files in your source tree (and possibly ending up adding that file to your source tracking system like svn or git).
b. An important aspect of the add_custom_command() is the DEPENDS section which includes the name of your special command, the one we defined in the previous step.
The add_custom_target() is what allows cmake to find your target and execute the corresponding command whenever one of the source files (a.k.a. dependency) changes; notice the DEPENDS definition.
Use the Output
Finally, here is the main project (a library in my case) that makes use of the file we generated in the step above.
Notice that I reference that file using the variable I defined in the previous step. That way, when I feel like changing that name, I can do it by simply editing that one variable.
project(as2js)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version.h.in
${CMAKE_CURRENT_BINARY_DIR}/version.h
)
add_library(${PROJECT_NAME} SHARED
compiler/compiler.cpp
...
parser/parser_variable.cpp
${UNICODE_CHARACTER_TYPES_CI}
file/database.cpp
...
)
(Note: the ... represent a list of files, shorten for display here as these are not important, the link above will take you to the file with the complete list.)
By having the filename inside the list of files defined in the add_library() (or the add_executable() in your case), you create a dependency which will find your custom_target(), because of the filename defined in the OUTPUT section of the add_custom_command()¹.
¹ It is possible to defined multiple outputs for an add_custom_command(). For example, some of my generators output a .cpp and a .h. In that case, I simply define both files in the OUTPUT section.
Results
Important points about the final results with this solution:
the output files of your generator are saved in the binary output path instead of your current working directory
the Makefile generated by cmake includes all the necessary targets/dependencies which means changing any of the input files regenerate everything as expected (even if you just update a comment)
if the generator fails, the build fails as expected
the files are generated by the build step (make time) instead of the generation step (cmake time, like the execute_process() would do)

Avoiding many "../include" in CMake

I just started getting into CMake with C++ and was wondering how other programmers avoid having to do "../include" in all their CMakeFiles.txt.
One example is here: https://github.com/clab/cnn/blob/master/examples/CMakeLists.txt
They create an executable for each example without having to call INCLUDE_DIRECTORIES(...).
I tried adding the headers when calling ADD_LIBRARY(...), but that didn't seem to work.
Example:
tl/src/CMakeLists.txt:
SET(SRCS "x1.cpp" "x2.cpp")
SET(HDRS "../include/tl/x1.h" "../include/tl/x2.h")
INCLUDE_DIRECTORIES("../include")
ADD_LIBRARY(test_lib ${SRCS} ${HDRS})
tl/CMakeLists.txt:
PROJECT(TEST_LIB VERSION 0.1)
ADD_SUBDIRECTORY("src")
tl/examples/CMakeLists.txt:
INCLUDE_DIRECTORIES("../include")
ADD_EXECUTABLE(e1 e1.cpp)
TARGET_LINK_LIBRARIES(e1 test_lib)
Edit: I believe that INCLUDE_DIRECTORIES(...) is only necessary one per each directory throughout the tree.
Just add the INCLUDE_DIRECTORIES command at the top level. No need to explicitly add included files then.
tl/CMakeLists.txt:
PROJECT(TEST_LIB VERSION 0.1)
INCLUDE_DIRECTORIES("include")
ADD_SUBDIRECTORY("src")
ADD_SUBDIRECTORY("examples")
tl/src/CMakeLists.txt:
ADD_LIBRARY(test_lib "x1.cpp" "x2.cpp")
tl/examples/CMakeLists.txt:
ADD_EXECUTABLE(e1 e1.cpp)
TARGET_LINK_LIBRARIES(e1 test_lib)
How about this? tl/CMakeLists.txt
SET(SRCS "x1.cpp" "x2.cpp")
SET(HDRS "../include/tl/x1.h" "../include/tl/x2.h")
INCLUDE_DIRECTORIES("${CMAKE_PROJECT_DIR}/include")
ADD_LIBRARY(test_lib ${SRCS} ${HDRS})
You do not need to add headers to ADD_LIBRARY or ADD_EXECUTABLE if you want to compile your programs and libs. Only source files "cpp,c,cxx,..." are required because through the include macro #include you tell the compiler where it finds the header files.
With INCLUDE_DIRECTORIES(...) you only add a search path to the compiler where to look for headers. If you have your headers in the same directory as your source you don't need another search path. Also subpaths with include macro like this #include "../../include" is possible. So it really depends on the structure of your source files. Also remember, compiler settings setups know where some of the system headers are found. That is why you also do not need to define them.
And last but not least there are cmake scripts and pkg search files where adding paths to specific libraries is done automatically.
This is how I did it in a project of mine:
file(GLOB_RECURSE SRC
engine/*.cpp
platfoorm/*.cpp
)
file(GLOB_RECURSE INCLUDES
engine/*.h
platform/*.h
)
This generate an internal "hardcoded" list of files that the generated makefil will use, so if you add a new source file you will need to re-run cmake.
Of course you can change and add paths, the one in the example are the one I used in my project.