我有一个包含大量库的巨大CMake
项目。在某些时候,我最终得到一个,让我们说随机排序的库列表。我需要根据它们的依赖关系对它们进行排序(按构建顺序排序:库必然出现在它所依赖的库之后)。
例如,如果我有:
target_link_libraries (lib2 lib1) # lib2 needs lib1
target_link_libraries (lib3 lib1) # lib3 needs lib1
target_link_libraries (lib4 lib2) # lib4 needs lib2, so it also needs lib1
我需要一个使用CMake
本机函数的函数(因为依赖关系存储在Cmake环境中的某个地方),排序:
lib4;lib3;lib2;lib1
进入以下之一(全部作为编译顺序有效):
lib1;lib2;lib3;lib4
lib1;lib2;lib4;lib3
lib1;lib3;lib2;lib4
答案 0 :(得分:2)
直接依赖关系列表存储在LINK_LIBRARIES
目标的属性中。
因此,如果您的项目没有使用generator-expressions,则可以使用此属性的递归遍历来收集所有依赖项的列表。函数compute_links
将所有作为目标的链接存储到LINK_LIBRARIES_ALL
目标的属性中:
define_property(TARGET PROPERTY LINK_LIBRARIES_ALL
BRIEF_DOCS "List of all targets, linked to this one"
FULL_DOCS "List of all targets, linked to this one"
)
# Compute list of all target links (direct and indirect) for given library
# Result is stored in LINK_LIBRARIES_ALL target property.
function(compute_links lib)
if(${lib}_IN_PROGRESS)
message(FATAL_ERROR "Circular dependency for library '${lib}'")
endif()
# Immediately return if output property is already set.
get_property(complete TARGET ${lib} PROPERTY LINK_LIBRARIES_ALL SET)
if(completed)
return()
endif()
# Initialize output property.
set_property(TARGET ${lib} PROPERTY LINK_LIBRARIES_ALL "")
set(${lib}_IN_PROGRESS 1) # Prevent recursion for the same lib
get_target_property(links ${lib} LINK_LIBRARIES)
if(NOT links)
return() # Do not iterate over `-NOTFOUND` value in case of absence of the property.
endif()
# For each direct link append it and its links
foreach(link ${links})
if(TARGET ${link}) # Collect only target links
compute_links(${link})
get_target_property(link_links_all ${link} LINK_LIBRARIES_ALL)
set_property(TARGET ${lib} APPEND PROPERTY
LINK_LIBRARIES_ALL ${link} ${link_links_all}
)
elseif(link MATCHES "$<")
message(STATUS "Library '${lib}' uses link '${link}'.")
message(FATAL_ERROR "Algorithm doesn't work with generator expressions.")
endif()
endforeach(link ${links})
# Remove duplicates
get_target_property(links_all ${lib} LINK_LIBRARIES_ALL)
list(REMOVE_DUPLICATES links_all)
set_property(TARGET ${lib} PROPERTY LINK_LIBRARIES_ALL ${links_all})
endfunction()
结果列表可以按编译顺序用于排序库:
# Sort given list of targets, so for any target its links come before the target itself.
#
# Uses selection sort (stable).
function(sort_links targets_list)
# Special case of empty input list. Futher code assumes list to be non-empty.
if(NOT ${targets_list})
return()
endif()
foreach(link ${${targets_list}})
compute_links(${link})
endforeach()
set(output_list)
set(current_input_list ${${targets_list}})
list(LENGTH current_input_list current_len)
while(NOT current_len EQUAL 1)
# Assume first element as minimal
list(GET current_input_list 0 min_elem)
set(min_index 0)
get_target_property(min_links ${min_elem} LINK_LIBRARIES_ALL)
# Check that given element is actually minimal
set(index 0)
foreach(link ${current_input_list})
if(index) # First iteration should always fail, so skip it.
list(FIND min_links ${link} find_index)
if(NOT find_index EQUAL "-1")
# Choose linked library as new minimal element.
set(min_elem ${link})
set(min_index ${index})
get_target_property(min_links ${min_elem} LINK_LIBRARIES_ALL)
endif(NOT find_index EQUAL "-1")
endif()
math(EXPR index "${index}+1")
endforeach(link ${current_input_list})
# Move minimal element from the input list to the output one.
list(APPEND output_list ${min_elem})
list(REMOVE_AT current_input_list ${min_index})
math(EXPR current_len "${current_len}-1")
endwhile()
# Directly append the only element in the current input list to the resulted variable.
set(${targets_list} ${output_list} ${current_input_list} PARENT_SCOPE)
endfunction(sort_links)
有
target_link_libraries (lib2 lib1) # lib2 needs lib1
target_link_libraries (lib3 lib1) # lib3 needs lib1
target_link_libraries (lib4 lib2) # lib4 needs lib2, so it also needs lib1
set(libs lib4 lib3 lib2 lib1)
message(Before sort: ${libs})
sort_links(libs)
message(After sort: ${libs})
将产生:
Before sort: lib4;lib3;lib2;lib1
After sort: lib1;lib2;lib4;lib3
答案 1 :(得分:0)
在Tsyvarev的帮助下,这是依赖函数排序。
它以增量顺序遍历列表并移动到列表末尾找到的任何库太早出现(取决于列表中较高位置的库)
function( sort_dep_list the_list )
list(LENGTH the_list cur_size)
message( "Unordred list was ${the_list} (size is ${cur_size})")
list( REMOVE_DUPLICATES the_list )
set( index 0 )
while ( 1 )
list(LENGTH the_list cur_size)
if ( ${index} EQUAL ${cur_size} )
break()
endif()
list( GET the_list ${index} currently_checked_lib )
message( "Testing ${currently_checked_lib} dependency order (index ${index})" )
set ( changed_order 0 )
get_target_property( currently_checked_lib_dep_list ${currently_checked_lib} LINK_LIBRARIES )
if ( NOT "${currently_checked_lib_dep_list}" STREQUAL "" )
set ( used_lib_last_index ${index} )
foreach( used IN LISTS currently_checked_lib_dep_list )
list( FIND the_list ${used} used_pos )
#message( "${used} index is ${used_pos}" )
if ( ${used_pos} GREATER ${used_lib_last_index} )
set( used_lib_last_index ${used_pos} )
endif()
endforeach()
message( "${currently_checked_lib} last dependency index is ${used_lib_last_index}" )
if ( ${used_lib_last_index} GREATER ${index} )
message( "${currently_checked_lib} (index ${index}) uses a library at index ${used_lib_last_index}, ${currently_checked_lib} will be moved to end of list" )
list( APPEND the_list ${currently_checked_lib} )
list( REMOVE_AT the_list ${index} )
message( "Modified list is ${the_list} (size is ${cur_size})")
# Flag that index should not be incremented
set ( changed_order 1 )
endif()
endif()
if ( ${changed_order} EQUAL 0 )
# Everything's fine, move to next index:
math( EXPR index ${index}+1 )
endif()
endwhile()
list(LENGTH the_list cur_size)
message( "Ordered list is ${the_list} (size is ${cur_size})" )
endfunction()
使用OP示例,中间体和最终列表将是:
lib4;lib3;lib2;lib1 < original list
lib3;lib2;lib1;lib4 < lib4 moved to tail because it depends on lib2
lib2;lib1;lib4;lib3 < lib3 moved to tail because it depends on lib1
lib1;lib4;lib3;lib2 < lib2 moved to tail because it depends on lib1
lib1;lib3;lib2;lib4 < final: ordered