CMake:根据其依赖关系对目标列表进行排序

时间:2016-01-06 16:31:37

标签: cmake

我有一个包含大量库的巨大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

2 个答案:

答案 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