|
|
|
|
# CMake has limited support for object libraries.
|
|
|
|
|
#
|
|
|
|
|
# The main limitation of CMake object libraries is the lack
|
|
|
|
|
# of transitive dependency support.
|
|
|
|
|
# The functions here provide a workaround for that.
|
|
|
|
|
#
|
|
|
|
|
# Use `target_link_dependencies` instead of `target_link_libraries`
|
|
|
|
|
#
|
|
|
|
|
# https://gitlab.kitware.com/cmake/cmake/-/issues/18090#note_861617
|
|
|
|
|
#
|
|
|
|
|
# At the end of the main `CMakeLists.txt`, call `resolve_target_link_dependencies()`.
|
|
|
|
|
|
|
|
|
|
# Behaves like target_link_libraries, but propagates OBJECT libraries' objects
|
|
|
|
|
# up to the first non-object library.
|
|
|
|
|
function(target_link_dependencies TARGET)
|
|
|
|
|
# The library we're linking may not have been defined yet,
|
|
|
|
|
# so we record it for now and resolve it later.
|
|
|
|
|
|
|
|
|
|
# CMake <3.19 limits which property names are allowed on INTERFACE targets,
|
|
|
|
|
# so we prefix the name with "INTERFACE_":
|
|
|
|
|
# https://cmake.org/cmake/help/v3.18/manual/cmake-buildsystem.7.html#interface-libraries
|
|
|
|
|
set_property(TARGET ${TARGET} APPEND PROPERTY INTERFACE_LINKED_DEPENDENCIES ${ARGN})
|
|
|
|
|
set_property(GLOBAL APPEND PROPERTY TARGETS_WITH_LINKED_DEPENDENCIES "${TARGET}")
|
|
|
|
|
endfunction()
|
|
|
|
|
|
|
|
|
|
# Transitively collects dependencies in topological order using depth-first search.
|
|
|
|
|
function(_collect_linked_dependencies INITIAL_TARGET)
|
|
|
|
|
set(MODES PUBLIC PRIVATE INTERFACE)
|
|
|
|
|
list(APPEND STACK "${INITIAL_TARGET}")
|
|
|
|
|
while(NOT STACK STREQUAL "")
|
|
|
|
|
list(POP_BACK STACK TARGET)
|
|
|
|
|
if(${TARGET} MATCHES "^\\$")
|
|
|
|
|
set(FINALIZING ON)
|
|
|
|
|
string(SUBSTRING "${TARGET}" 1 -1 TARGET)
|
|
|
|
|
else()
|
|
|
|
|
set(FINALIZING OFF)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
get_target_property(LINKED_DEPENDENCIES ${TARGET} INTERFACE_LINKED_DEPENDENCIES)
|
|
|
|
|
if(LINKED_DEPENDENCIES STREQUAL "LINKED_DEPENDENCIES-NOTFOUND")
|
|
|
|
|
# Not a `target_link_dependencies` target, nothing to do.
|
|
|
|
|
continue()
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
if(NOT FINALIZING)
|
|
|
|
|
get_target_property(LINKED_DEPENDENCIES_COLLECTED ${TARGET} INTERFACE_LINKED_DEPENDENCIES_COLLECTED)
|
|
|
|
|
if(NOT LINKED_DEPENDENCIES_COLLECTED STREQUAL "LINKED_DEPENDENCIES_COLLECTED-NOTFOUND")
|
|
|
|
|
# Already processed.
|
|
|
|
|
continue()
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
list(APPEND STACK "$${TARGET}")
|
|
|
|
|
|
|
|
|
|
get_target_property(LINKED_DEPENDENCIES_COLLECTING ${TARGET} INTERFACE_LINKED_DEPENDENCIES_COLLECTING)
|
|
|
|
|
if(NOT LINKED_DEPENDENCIES_COLLECTING STREQUAL "LINKED_DEPENDENCIES_COLLECTING-NOTFOUND")
|
|
|
|
|
# A cycle.
|
|
|
|
|
message(FATAL_ERROR "Dependency cycle for ${TARGET}: ${STACK}")
|
|
|
|
|
endif()
|
|
|
|
|
set_property(TARGET "${TARGET}" PROPERTY INTERFACE_LINKED_DEPENDENCIES_COLLECTING ON)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
get_target_property(TARGET_TYPE ${TARGET} TYPE)
|
|
|
|
|
get_target_property(LINKED_DEPENDENCIES ${TARGET} INTERFACE_LINKED_DEPENDENCIES)
|
|
|
|
|
set(MODE PUBLIC)
|
|
|
|
|
foreach(ARG ${LINKED_DEPENDENCIES})
|
|
|
|
|
if(ARG IN_LIST MODES)
|
|
|
|
|
set(MODE ${ARG})
|
|
|
|
|
continue()
|
|
|
|
|
endif()
|
|
|
|
|
set(LIBRARY "${ARG}")
|
|
|
|
|
if(TARGET ${LIBRARY})
|
|
|
|
|
if(NOT FINALIZING)
|
|
|
|
|
list(APPEND STACK ${LIBRARY})
|
|
|
|
|
continue()
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
# When linking two OBJECT libraries together, record the input library objects in
|
|
|
|
|
# a custom target property "LINKED_OBJECTS" together with any other existing ones
|
|
|
|
|
# from the input library's LINKED_OBJECTS property.
|
|
|
|
|
# Accumulate LINKED_OBJECTS until reaching a non-object target, and add them as
|
|
|
|
|
# extra sources - this will de-duplicate the list and link it into the target.
|
|
|
|
|
get_target_property(LIBRARY_TYPE ${LIBRARY} TYPE)
|
|
|
|
|
|
|
|
|
|
if(LIBRARY_TYPE STREQUAL "OBJECT_LIBRARY")
|
|
|
|
|
if(TARGET_TYPE STREQUAL "INTERFACE_LIBRARY")
|
|
|
|
|
message(FATAL_ERROR "OBJECT to INTERFACE library linking is not supported.")
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
# All transitive dependencies of this object library:
|
|
|
|
|
get_target_property(LIBRARY_LINKED_OBJECTS ${LIBRARY} LINKED_OBJECTS)
|
|
|
|
|
if(LIBRARY_LINKED_OBJECTS STREQUAL "LIBRARY_LINKED_OBJECTS-NOTFOUND")
|
|
|
|
|
set(LIBRARY_LINKED_OBJECTS)
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
# target_sources deduplicates the list but we also do it here for ease of debugging.
|
|
|
|
|
get_target_property(TARGET_LINKED_OBJECTS ${TARGET} LINKED_OBJECTS)
|
|
|
|
|
if(TARGET_LINKED_OBJECTS STREQUAL "TARGET_LINKED_OBJECTS-NOTFOUND")
|
|
|
|
|
set(TARGET_LINKED_OBJECTS)
|
|
|
|
|
endif()
|
|
|
|
|
list(APPEND TARGET_LINKED_OBJECTS ${LIBRARY_LINKED_OBJECTS} $<TARGET_OBJECTS:${LIBRARY}>)
|
|
|
|
|
list(REMOVE_DUPLICATES TARGET_LINKED_OBJECTS)
|
|
|
|
|
|
|
|
|
|
if(TARGET_TYPE STREQUAL "OBJECT_LIBRARY")
|
|
|
|
|
set_property(TARGET ${TARGET} PROPERTY LINKED_OBJECTS "${TARGET_LINKED_OBJECTS}")
|
|
|
|
|
else()
|
|
|
|
|
target_sources(${TARGET} PRIVATE ${TARGET_LINKED_OBJECTS})
|
|
|
|
|
endif()
|
|
|
|
|
endif()
|
|
|
|
|
endif()
|
|
|
|
|
|
|
|
|
|
if(FINALIZING)
|
|
|
|
|
target_link_libraries(${TARGET} ${MODE} "${LIBRARY}")
|
|
|
|
|
endif()
|
|
|
|
|
endforeach()
|
|
|
|
|
if(FINALIZING)
|
|
|
|
|
set_property(TARGET "${TARGET}" PROPERTY INTERFACE_LINKED_DEPENDENCIES_COLLECTED ON)
|
|
|
|
|
endif()
|
|
|
|
|
endwhile()
|
|
|
|
|
endfunction()
|
|
|
|
|
|
|
|
|
|
# Actually resolves the linked dependencies.
|
|
|
|
|
function(resolve_target_link_dependencies)
|
|
|
|
|
set(MODES PUBLIC PRIVATE INTERFACE)
|
|
|
|
|
get_property(TARGETS GLOBAL PROPERTY TARGETS_WITH_LINKED_DEPENDENCIES)
|
|
|
|
|
foreach(TARGET ${TARGETS})
|
|
|
|
|
_collect_linked_dependencies("${TARGET}" "")
|
|
|
|
|
endforeach()
|
|
|
|
|
set_property(GLOBAL PROPERTY TARGETS_WITH_LINKED_DEPENDENCIES)
|
|
|
|
|
endfunction()
|