hbobenicio
hbobenicio

Reputation: 1110

CMake: How to reuse source file definitions between targets without duplication?

In my CMake project, I have 2 targets (a static library and a shared library) that share the same source code files, like this:

add_library("${PROJECT_NAME}" STATIC
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp"
    "${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp"
)
add_library("${PROJECT_NAME}-shared" SHARED
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp"
    "${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp"
)

Obviously, there is a problem here: the sources definition is duplicated. It's hard to maintain and it's also error prone.

In order to avoid that, I'd like to create a CMake list variable so that the sources definition could be reused in both targets.

I've tried this but it doesn't work:

set(CALCULATOR_CORE_SOURCES_LIST
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp"
    "${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp"
)
string(REPLACE ";" " " CALCULATOR_CORE_SOURCES "${CALCULATOR_CORE_SOURCES_LIST}")

add_library("${PROJECT_NAME}" STATIC ${CALCULATOR_CORE_SOURCES})
add_library("${PROJECT_NAME}-shared" SHARED ${CALCULATOR_CORE_SOURCES})

It fails with the error: Cannot find source file.

So... how could I reuse source file definitions between targets without this duplication? Is it possible to do this using lists or is there a better approach to solve this issue?

PS: I'm using CMake 3.15

Upvotes: 4

Views: 488

Answers (3)

Ehsan Mosadeq
Ehsan Mosadeq

Reputation: 1

It would be a better practice to not add headers as source files in CMake. target_include_directories will do it and you can also specify the way the other targets are supposed to be linked to this library.

e.g. you can write:

set(CALCULATOR_CORE_SOURCES_LIST
        ${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp
        ${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp
    )
    
add_library(${PROJECT_NAME} STATIC ${CALCULATOR_CORE_SOURCES_LIST})
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src/calculator/core)
    
add_library(${PROJECT_NAME}-shared SHARED ${CALCULATOR_CORE_SOURCES_LIST})
target_include_directories(${PROJECT_NAME}-shared PRIVATE ${PROJECT_SOURCE_DIR}/src/calculator/core)

more details in: CMake/target_include_directories

Upvotes: 0

John McFarlane
John McFarlane

Reputation: 6087

Obviously, there is a problem here: the sources definition is duplicated. It's hard to maintain and it's also error prone.

Correct. Not only that, but the sources are built twice.

So... how could I reuse source file definitions between targets without this duplication?

Think in terms of targets, rather than source files. Instead of trying to reuse source files in multiple targets, use the source files in a single target, and reuse that.

Normally you'd do this by creating a common library, core, containing the sources, then linking this to your static and shared project libraries like so:

add_library(core
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp"
    "${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp"
)

add_library("${PROJECT_NAME}" STATIC)
target_link_libraries("${PROJECT_NAME}" core)

add_library("${PROJECT_NAME}-shared" SHARED)
target_link_libraries("${PROJECT_NAME}-shared" core)

Unfortunately, you cannot create a static or shared library without source files! One workaround is to use an object library:

add_library(core OBJECT
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp"
    "${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp"
)

add_library("${PROJECT_NAME}" STATIC $<TARGET_OBJECTS:core>)
add_library("${PROJECT_NAME}-shared" SHARED $<TARGET_OBJECTS:core>)

But workarounds are often an indication that what you're trying to do ought not to be done at all.

is there a better approach to solve this issue?

Rephrase the problem - not as a build issue, but - as a build configuration issue. You are not trying to build two different libraries, but rather one library under two different configurations.

Your users are unlikely to need both a static, and shared version. They probably want one or the other. Keep things simple and let them choose!

Use CMake to concisely describe the library that you want to build

add_library("${PROJECT_NAME}"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp"
    "${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h"
    "${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp")

then choose whether this library is a shared, or a static library during configuration.

To build a static library:

cmake -DBUILD_SHARED_LIBS=OFF ..

To build a shared library:

cmake -DBUILD_SHARED_LIBS=ON ..

Upvotes: 1

Kevin
Kevin

Reputation: 18243

Your code is close; you can simply use the CALCULATOR_CORE_SOURCES_LIST variable to add the group of sources/headers to both add_library() calls:

set(CALCULATOR_CORE_SOURCES_LIST
    ${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.h
    ${PROJECT_SOURCE_DIR}/src/calculator/core/ShuntingYard.cpp
    ${PROJECT_SOURCE_DIR}/include/calculator/core/Calculator.h
    ${PROJECT_SOURCE_DIR}/src/calculator/core/Calculator.cpp
)

add_library(${PROJECT_NAME} STATIC ${CALCULATOR_CORE_SOURCES_LIST})
add_library(${PROJECT_NAME}-shared SHARED ${CALCULATOR_CORE_SOURCES_LIST})

If the group of sources is the same for both libraries, you should only need to define the list once. From the documentation, the set command will do the following:

Multiple arguments will be joined as a semicolon-separated list to form the actual variable value to be set.

Thus, this list can be passed to the add_library() calls. You also shouldn't need the quotes (") around each file.

Upvotes: 5

Related Questions