How to get include directories from a target for use in add_custom_target?
Asked Answered
W

2

14

I'm modeling dependencies with target_link_libraries, as is done in this blog post.

target_link_libraries(Foo
    LibraryA
    LibraryB
)

This is working great, but for various reasons I need to use add_custom_target to preprocess to a file through a custom command. The problem is, this custom target depends on the includes of LibraryA and LibraryB. I was really hoping to do the following like how target_link_libraries works (see the LibraryA and LibraryB bit):

add_custom_target(Bar ALL
    COMMAND ${CMAKE_C_COMPILER} thing.cpp LibraryA LibraryB /P
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Path/Here
    COMMENT "Preprocessing to a file"
    VERBATIM
)

However, this doesn't work. LibraryA and LibraryB are put in as they appear. Even if it did work, I imagine I would get more than the includes, since I think the targets include the library as well. Maybe this is not a good approach.

So, what can I do here? How can I extract the include directories from each target, for use in the custom command? I found if I find_package(Foo REQUIRED) I get access to Foo_DIR, but that points to the build directory and not the source directory where the includes are.

Walters answered 8/11, 2019 at 2:43 Comment(4)
To be clear, is your goal to extract the include directories from the targets LibraryA and LibraryB, and use these directories in the custom command?Furlana
@squareskittles Correct. I just need the headers from LibraryA and LibraryB.Walters
How are you using them in the custom command? If CMAKE_C_COMPILER is gcc do you intend to pass these as include directories to the compiler? Using the -I flag?Furlana
That's part of what I'm trying to find out. If I had a variable that could provide the path I could use the Include flag to get them into the custom command. If I can't get that because I'm modeling dependencies with targets, maybe there's something I can do. I just don't know what.Walters
F
19

You can extract the include directories from each target using get_target_property(). A target's INCLUDE_DIRECTORIES property contains the include directories for that target. Since you have two targets, LibraryA and LibraryB, we have to call it twice. Then, we can concatenate the list of include directories together using foreach(). If you are using these as include directories in a compiler command (such as MSVC), you can append the /I compiler option to each directory in the loop also:

# Get the include directories for the target.
get_target_property(LIBA_INCLUDES LibraryA INCLUDE_DIRECTORIES)
get_target_property(LIBB_INCLUDES LibraryB INCLUDE_DIRECTORIES)

# Construct the compiler string for the include directories.
foreach(dir ${LIBA_INCLUDES} ${LIBB_INCLUDES})
    string(APPEND INCLUDE_COMPILER_STRING "/I${dir} ")
endforeach()

Then, you can call the custom target command using the constructed INCLUDE_COMPILER_STRING variable:

add_custom_target(Bar ALL
    COMMAND ${CMAKE_C_COMPILER} thing.cpp ${INCLUDE_COMPILER_STRING} /P
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Path/Here
    COMMENT "Preprocessing to a file"
    VERBATIM
)

If you wanted something more concise, you could use the generator expression example here, which gets the targets' include directories and expands them inline, within your custom target command. Something like this could work also:

add_custom_target(Bar ALL
    COMMAND ${CMAKE_C_COMPILER} thing.cpp 
        "/I$<JOIN:$<TARGET_PROPERTY:LibraryA,INCLUDE_DIRECTORIES>,;/I>"
        "/I$<JOIN:$<TARGET_PROPERTY:LibraryB,INCLUDE_DIRECTORIES>,;/I>"
        /P
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Path/Here
    COMMENT "Preprocessing to a file"
    VERBATIM
    COMMAND_EXPAND_LISTS
)
Furlana answered 8/11, 2019 at 13:53 Comment(5)
I'm getting LIBA_INCLUDES-NOTFOUND in the above example, and I was only able to get that by using find_package(LibraryA REQUIRED). If I don't use find_package() then it can't find the target which results in a CMake error. I'm using a LibraryAConfig.cmake.in file where I call find_dependency(LibraryA REQUIRED). I tried setting the variable in there and printing it from the CMakeLists but the variable either wasn't set or didn't propagate because the test message I had was blank.Walters
I had a bit more luck with the concise version, but the problem is using the target brings in multiple include directories and they are put inside a single set of quotations. Naturally, the compiler is treating that as one large include path instead of three individual paths. I've experimented with just trying to put the quotes around the JOIN command, but the JOIN result becomes garbled.Walters
Fixed it! Your line should be "/I$<JOIN:$<TARGET_PROPERTY:LibraryA,INCLUDE_DIRECTORIES>,;/I>" The semi-colon is very important. I also added COMMAND_EXPAND_LISTS. Please update your answer! Thank you for the help, this is great!Walters
@Stradigos I was not aware you were using find_package() for those libraries, so yes, get_target_property() may not work as expected in that case. And apologies! I forgot to add the COMMAND_EXPAND_LISTS list portion to that last example. I updated the answer based on your feedback. Glad it's working!Furlana
This does not handle transitive dependencies.Pontiac
S
2

As the comment, the current accepted answer does not handle transitive dependencies. And this question has been confusing me all day, so I'll sort it out now.

I'm in build the LibraryLinkUtilities here. This is my CMakeLists.txt used in project:

cmake_minimum_required(VERSION 3.15.0)

project ("CMakeProject1")

set(LLU_ROOT "D:/test/LibraryLinkUtilities/install")
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)

find_package(LLU NO_MODULE PATH_SUFFIXES LLU)

add_library(${PROJECT_NAME} SHARED ${PROJECT_NAME}.cpp)

target_link_libraries(${PROJECT_NAME} PRIVATE LLU::LLU)

When I open the .sln with Visual Studio, It work well, I mean I can build it in any build type. But I find the include directories is empty in Configuation. This make me crazy, because I want to know the project have include which directory exactly. Then I use the function print_target_properties fixed here to print all properties about imported target:

function(print_target_properties target)
    if(NOT TARGET ${target})
      message(STATUS "There is no target named '${target}'")
      return()
    endif()

    foreach(property ${CMAKE_PROPERTY_LIST})
        string(REPLACE "<CONFIG>" "DEBUG" property ${property})

        get_property(was_set TARGET ${target} PROPERTY ${property} SET)
        if(was_set)
            get_target_property(value ${target} ${property})
            message("${target} ${property} = ${value}")
        endif()
    endforeach()
endfunction()

print_target_properties(LLU::LLU)

enter image description here

Note the red line place, the LLU::LLU dependent with WSTP::WSTP and WolframLibrary::WolframLibrary. So I use this code to print all include directories:

include(CMakePrintHelpers)
get_target_property(LLUDEPENDS LLU::LLU INTERFACE_LINK_LIBRARIES)
cmake_print_properties(TARGETS LLU::LLU ${LLUDEPENDS} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES)

enter image description here

Signesignet answered 2/9, 2022 at 17:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.