4

Our CMakeList.txt had a target_include_directories creep in. It broke our downlevel clients, like Ubuntu LTS, CentOS and Solaris.

I know I can guard it with the following (thanks ZW), but I need something for Cmake versions 2.8.11 and earlier (the ???).

cmake_minimum_required(VERSION 2.8.5 FATAL_ERROR)
...

if (NOT CMAKE_VERSION VERSION_LESS 2.8.12)
  target_include_directories(...)
else()
  ???
endif()

Our directory structure is rather simple. There's one directory with header files in them, and the same directory has the source files in them. This is by design so users don't have troubles under a debugger.

Is it OK to use the following given our configuration (its not clear to me if @steveire answer applies to us):

if (NOT CMAKE_VERSION VERSION_LESS 2.8.12)
  target_include_directories(...)
else()
  include_directories("${ROOT_SOURCE_DIR}")
endif()

If not, what should be used in place of target_include_directories for 2.8.11 and earlier?

Community
  • 1
  • 1
jww
  • 97,681
  • 90
  • 411
  • 885
  • `Is it OK to use the following given our configuration` - if it is work for you, then it is OK. We don't know about your project. Version check is correct, as shown in the question your refer to. `There's one directory with header files in them` - so use `include_directories` unconditionally, and do not bother with conditionals. – Tsyvarev Jun 18 '16 at 20:29
  • *if it is work for you, then it is OK"* - I don't want to check it in unless its known to work, or strongly believed to work. The problem is, I am not a Cmake user, so I am not in a position to make the determination. We accepted the patches as a courtesy to our users with the understanding the Cmake users would own it and maintain it. There's room for improvement in the *maintain it"* part as they are not living up to their end of the bargain. Hence the reason I am here with these questions. – jww Jun 19 '16 at 02:07
  • 1
    Again, **we don't have a code of your project**, how can we conclude that it will work or not? According to your description of the project, it **seems** it will work. BTW, you know that version check is correct, you know meaning of `target_include_directories()` and `include_directories()` commands (if not - see their documentation). Why do you think it won't work? – Tsyvarev Jun 19 '16 at 08:41
  • @Tsyvarev - my apologies. I though the necessary information was available or you have the requisite Cmake experience. Sorry about that. – jww Jun 19 '16 at 09:45

1 Answers1

5

Problem

The target_include_directories() command has extended functionality compared to include_directories(). So I agree with @Tsyvarev that it mainly depends on how you use target_include_directories().

Solution

If you use target_include_directories(my_target PRIVATE ...) the equivalent would be set_property(TARGET my_target APPEND PROPERTY INCLUDE_DIRECTORIES ...). But include_directories() in contrast does set those directories for all targets in the current CMakeLists.txt and all of it's add_subdirectory() siblings.

If you use target_include_directories(my_target INTERFACE/PUBLIC ...) there is no such functionality in CMake prior to version 2.8.11. You can simulate the behavior of self-propagating include directories, but that's much more complex.

Recommendation

Don't use the new command if you still want to support older versions of CMake. Because this not just target_include_directories() (see CMake Version Compatibility Matrix/Commands).

References

Backward Compatible target_include_directories()

The following code shows what older CMake versions would have to do to simulate target_include_directories():

function(target_include_directories _target)
    set_property(GLOBAL APPEND PROPERTY GLOBAL_TARGETS "${_target}")
    set(_mode "PRIVATE")
    foreach(_arg ${ARGN})
        if (_arg MATCHES "SYSTEM|BEFORE")
            message(FATAL_ERROR "target_include_directories: SYSTEM or BEFORE not supported")
        endif()
        if (_arg MATCHES "INTERFACE|PUBLIC|PRIVATE")
            set(_mode "${_arg}")
        else()
            get_filename_component(_inc_dir "${_arg}" ABSOLUTE)
            if (_mode MATCHES "PUBLIC|PRIVATE")
                set_property(TARGET ${_target} APPEND PROPERTY INCLUDE_DIRECTORIES "${_inc_dir}")
            endif()            
            if (_mode MATCHES "INTERFACE|PUBLIC")
                set_property(TARGET ${_target} APPEND PROPERTY MY_INTERFACE_INCLUDE_DIRECTORIES "${_inc_dir}")
            endif()            
        endif()
    endforeach()
endfunction(target_include_directories)

function(target_link_libraries _target)
    set_property(GLOBAL APPEND PROPERTY GLOBAL_TARGETS "${_target}")
    set(_mode "PUBLIC")
    foreach(_arg ${ARGN})
        if (_arg MATCHES "INTERFACE|PUBLIC|PRIVATE|LINK_PRIVATE|LINK_PUBLIC|LINK_INTERFACE_LIBRARIES")
            set(_mode "${_arg}")
        else()
            if (NOT _arg MATCHES "debug|optimized|general")
                set_property(TARGET ${_target} APPEND PROPERTY MY_LINK_LIBARIES "${_arg}")
            endif()
        endif()
    endforeach()
    _target_link_libraries(${_target} ${ARGN})
endfunction(target_link_libraries)

function(my_update_depending_inc_dirs _targets _target _dep_target)
    get_property(_libs TARGET ${_dep_target} PROPERTY MY_LINK_LIBARIES)
    if (NOT _libs)
        return()
    endif()
    foreach(_lib ${_libs})
        list(FIND _targets "${_lib}" _idx)
        if (NOT _idx EQUAL -1)
            get_property(_inc_dirs TARGET ${_lib} PROPERTY MY_INTERFACE_INCLUDE_DIRECTORIES)
            set_property(TARGET ${_target} APPEND PROPERTY INCLUDE_DIRECTORIES "${_inc_dirs}")
            # to prevent cyclic dependencies getting us into an endless loop
            # remove the target we already processed from the list
            list(REMOVE_AT _targets ${_idx})
            my_update_depending_inc_dirs("${_targets}" "${_target}" "${_lib}")
        endif()
    endforeach()
endfunction(my_update_depending_inc_dirs)

function(my_update_inc_dirs)
    get_property(_targets GLOBAL PROPERTY GLOBAL_TARGETS)
    list(REMOVE_DUPLICATES _targets)
    foreach(_target ${_targets})
        my_update_depending_inc_dirs("${_targets}" "${_target}" "${_target}")
    endforeach()
endfunction(my_update_inc_dirs)

Tested with the following code (NOTE: needs the my_update_inc_dirs() call at the end):

CMakeLists.txt

cmake_minimum_required(VERSION 2.6)

project(TargetIncludeDirectories)

...

add_library(PLib plib/source/plib.cpp)
target_include_directories(PLib PRIVATE plib/source PUBLIC plib/include)

add_library(Lib lib/source/lib.cpp)
target_include_directories(Lib PRIVATE lib/source PUBLIC lib/include)
target_link_libraries(Lib PRIVATE PLib)

add_executable(App main.cpp)
target_include_directories(App PRIVATE . PUBLIC include)
target_link_libraries(App PRIVATE Lib)

my_update_inc_dirs()
Community
  • 1
  • 1
Florian
  • 39,996
  • 9
  • 133
  • 149
  • *"So I agree ... that it mainly depends on how you use `target_include_directories()`"* - Thanks Florian. I guess I don't quite understand what goes on behind the scenes with Cmake. As a C/C++ guy who writes his own makefiles, I guess I take for granted what `-I` does. I kind of regret accepting Cmake into the project. Its causing too many problems for us. – jww Jun 20 '16 at 08:10
  • @jww I admit I'm a big fan of CMake & Ninja for cross-platform projects, the only thing is still the documentation. I'm looking forward having [SO Documentation](http://docs-beta.stackexchange.com/documentation) coming out of private beta to add a lot of CMake examples. Do you still need the code for backward compatibility? Give me a few days, then I could come up with something. It would also demonstrate what big improvement `target_include_directories()` is over `include_directories()`. – Florian Jun 20 '16 at 09:12
  • Consider this... The project is [Crypto++](http://www.cryptopp.com/), and I donate my time to it. Others donate time, too. None of us have the requisite Cmake experience to support it. Would you consider a paid engagement to supply a `CMakeList.txt` file to our specification? If so, please contact me off-list. *noloader*, gmail account. – jww Jun 20 '16 at 09:23
  • @jww I'm not sure if my email to your gmail account did reach you, but anyway I'm sorry to have to decline your kind offer. I'm doing this kind of helping with CMake problems in my spare time. One thing came to mind when looking at Crypto++ `CMakeLists.txt`: you could consider posting a minimal version of it on [SE Code Review](https://codereview.stackexchange.com/help/how-to-ask). I think it makes a good example of cross-platform code with some special handling for certain platforms. – Florian Jun 22 '16 at 19:07
  • @jww I've added the code to simulate `target_include_directories()` with older versions of CMake. Still I would recommend to keep your `include_directories()` calls. – Florian Jun 26 '16 at 19:40