Piodo
Piodo

Reputation: 616

How to properly create cmake library for both program and test usage and compile it only once

I have code with one big directory of models which are going to be used in two binaries - the main program and tests. I want to put the models to the library which will compile once and then both main program and tests can use it without recompiling it again.

Here are my current directories:

root/
    CMakeLists.cpp
    src/
        CMakeLists.txt
        main.cpp
        model/
            /*a lot of cpp/hpp files*/
        impl/
            impl.cpp
            impl.hpp (uses models)
    test/
        CMakeLists.txt
        main.cpp
        test.cpp (uses models and impl)

root/CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(server)

set(CMAKE_CXX_STANDARD 14)

file(GLOB MODEL_SOURCES "src/model/*.cpp")
add_library(modelLibrary ${MODEL_SOURCES})

add_subdirectory(src)
add_subdirectory(test)

src/CMakeLists.txt

set(BINARY ${CMAKE_PROJECT_NAME})

file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)

set(SOURCES ${SOURCES})

add_executable(${BINARY} ${SOURCES})

include_directories(impl)

# can't see model files without this line
target_include_directories(${BINARY} PRIVATE ./model)

target_link_libraries(${BINARY} PUBLIC modelLibrary pistache pthread ssl crypto)

test/CMakeLists.txt

set(BINARY ${CMAKE_PROJECT_NAME}_test)

file(GLOB_RECURSE TEST_SOURCES LIST_DIRECTORIES false *.hpp *.cpp)

set(SOURCES ${TEST_SOURCES})

add_executable(${BINARY} ${TEST_SOURCES})

add_test(NAME ${BINARY} COMMAND ${BINARY})

# can't see model files without this line
target_include_directories(${BINARY} PRIVATE ../src/model)

target_link_libraries(${BINARY} modelLibrary gtest gtest_main pthread)

I have failed with using modelLibrary as only source. It still compiles the models two times. I'd like to achieve solution where models are compiled once and they are reachable from both - main program and test.

Upvotes: 0

Views: 697

Answers (1)

mydisplayname
mydisplayname

Reputation: 356

I believe the reason you see the model files compiled twice is due to the recursive glob in src/CMakeLists.txt.

file(GLOB_RECURSE SOURCES LIST_DIRECTORIES true *.h *.cpp)

This recursive glob will also walk into src/model and grab the *.cpp files there. So the model files are getting compiled as part of modelLibrary as well as part of the server executable.

One way to fix this would be to remove the recursive glob and create a separate CMakeLists.txt in src/model with the contents of:

# I wouldn't actually glob, see note below.
file(GLOB MODEL_SOURCES "*.cpp")
add_library(modelLibrary ${MODEL_SOURCES})

target_include_directories(modelLibrary PUBLIC ".")

One would need to make a call add_subdirectory() for this new CMakeLists in either the root or the src/CMakeLists.txt.

Notice the use of target_include_directories in the possible solution. I put that there as I noticed it was being repeated in the test and executable. Using PUBLIC on modelLibrary means that consumers get the include directory just by using target_link_libraries.

Note about globbing: from CMake docs on GLOB

Note We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate. The CONFIGURE_DEPENDS flag may not work reliably on all generators, or if a new generator is added in the future that cannot support it, projects using it will be stuck. Even if CONFIGURE_DEPENDS works reliably, there is still a cost to perform the check on every rebuild.

That note can be a bit hard to grok, but try to fully understand the consequences if you do decide to glob.

Upvotes: 3

Related Questions