Check for optionally targets in cmake that are not

2020-07-26 02:54发布

i'm currently working on a large software project which uses cmake as build system. But i have a problem to check if another target exists (or will exist).

For example there is root CMakeLists.txt and two modules that can optionally added to the software project as subfolders.

.
├── A
│   └── CMakeLists.txt
├── B
│   └── CMakeLists.txt
└── CMakeLists.txt

In the root CMakeLists these modules are added with the add_subdirectory command:

cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
project(root)
add_subdirectory(./A)
add_subdirectory(./B)

In some cases i want to check in module A if module B exists and add an define to the compile options in module A:

cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
project(A)
add_libary(A a.cpp a.hpp)
if (TARGET B)
    target_compile_definitions(A PUBLIC HAVE_B)
endif()

The

if(TARGET target-name)

command will return false because this will only work if the modules are added in the right order to the root CMakeLists.txt.

Is there another check in cmake that doesn't depend on the order of the targets?

Greetings Perry

标签: c++ cmake
3条回答
等我变得足够好
2楼-- · 2020-07-26 03:09

This can be solved using a macro.

The idea is that each subdirectory must have an associated top-level variable that other subdirectories can check, so they must be declared on the top CMakeLists.txt file. Also all variables must be defined before the start of the add_subdirectory rules.

So our macro will take as parameters the list of subdirectories, first output the list of variable declarations, and then output the list of subdirectories.

Here is the code for such a macro:

macro(declare_modules)
    foreach(module ${ARGN})
        set(TARGET_${module} yes)
    endforeach()

    foreach(module ${ARGN})
        add_subdirectory(./${module})
    endforeach()
endmacro(declare_modules)

It can be used like this:

declare_modules(A B C)

And it will expand into the following code:

foreach(module A B C)
    set(TARGET_${module} yes)
endforeach()

foreach(module A B C)
    add_subdirectory(./${module})
endforeach()

This will result into the variables TARGET_A, TARGET_B, and TARGET_C to be declared and set to yes, and they will be available from all modules.

Adding new modules is just a matter of adding new parameters to the declare_modules call.

查看更多
你好瞎i
3楼-- · 2020-07-26 03:12

Because the root CMakeLists.txt file have to know the project's subdirectories, I normally put my optional settings there:

CMakeLists.txt:

cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)

project(root CXX)

if (EXISTS "B/CMakeLists.txt")
    set(HAVE_B 1)
endif()

add_subdirectory(A)
if (HAVE_B)
    add_subdirectory(B)
endif()

A/CMakeLists.txt:

add_library(A a.cpp a.hpp)
if (HAVE_B)
    target_link_libraries(A B)
endif()

B/CMakeLists.txt:

add_library(B b.cpp b.hpp)
target_compile_definitions(B PUBLIC HAVE_B)
target_include_directories(B PUBLIC .)

There are a lot of possibile places you could set the compiler definitions and necessary include directories. In this example I've choosen to propagade both with target_compile_definitions(... PUBLIC ...) and target_include_directories(... PUBLIC ...). Then you just have to setup the dependency with target_link_libraries() and CMake handles the rest (and target_link_libraries() does accept forward declarations).

查看更多
Root(大扎)
4楼-- · 2020-07-26 03:23

There is no available $<TARGET_EXISTS:...> generator expression where you could write this:

add_libary(A a.cpp a.hpp)
target_compile_definitions(A PUBLIC $<<TARGET_EXISTS:B>:HAVE_B=1>)

Although I am proposing this to cmake, here. As a workaround, you can create shadow targets to track the targets from other directories:

# Custom property to check if target exists
define_property(TARGET PROPERTY "INTERFACE_TARGET_EXISTS"
    BRIEF_DOCS "True if target exists"
    FULL_DOCS "True if target exists"
)

# Create shadow target to notify that the target exists
macro(shadow_notify TARGET)
    if(NOT TARGET _shadow_target_${TARGET})
        add_library(_shadow_target_${TARGET} INTERFACE IMPORTED GLOBAL)
    endif()
    set_target_properties(_shadow_target_${TARGET} PROPERTIES INTERFACE_TARGET_EXISTS 1)
endmacro()
# Check if target exists by querying the shadow target
macro(shadow_exists OUT TARGET)
    if(NOT TARGET _shadow_target_${TARGET})
        add_library(_shadow_target_${TARGET} INTERFACE IMPORTED GLOBAL)
        set_target_properties(_shadow_target_${TARGET} PROPERTIES INTERFACE_TARGET_EXISTS 0)
    endif()
    set(${OUT} "$<TARGET_PROPERTY:_shadow_target_${TARGET},INTERFACE_TARGET_EXISTS>")
endmacro()

This creates two functions, and a custom property to track if the target exists or not. The shadow_notify will create a shadow target if it doesn't exists and then set the INTERFACE_TARGET_EXISTS property to true if it does exists.

The shadow_exists target will create a generator expression to query if the target exists. It does this by creating a new shadow target if doesn't exists with the INTERFACE_TARGET_EXISTS property set to 0. Then it will create a generator expression that will query the INTERFACE_TARGET_EXISTS property on the shadow target. The shadow target will always exists, and if the actual target exists then the property will be set to 1 by shadow_notify.

So then you can write this in your cmake:

add_libary(A a.cpp a.hpp)
# Notify that target A exists
shadow_notify(A)
# Check if target B exists
shadow_exists(HAS_B B)
target_compile_definitions(A PUBLIC $<${HAS_B}:HAVE_B=1>)

When you create library B you will need to call shadow_notify(B) as well.

If you don't like these extra shadow_notify calls, you could also override add_library to do it for you:

macro(add_library LIB)
    _add_library(${LIB} ${ARGN})
    if(NOT TARGET _shadow_target_${TARGET})
        _add_library(_shadow_target_${TARGET} INTERFACE IMPORTED GLOBAL)
    endif()
    set_target_properties(_shadow_target_${TARGET} PROPERTIES INTERFACE_TARGET_EXISTS 1)
endmacro()
查看更多
登录 后发表回答