From 6c2ad89e4cc07cc5c332c215a516f664e8c5b7ff Mon Sep 17 00:00:00 2001 From: Grzegorz Swiderski Date: Fri, 26 May 2023 15:44:00 +0200 Subject: [PATCH] cmake: extensions: Add zephyr_topological_sort() This function performs topological sorting of CMake targets. Its initial use case in Zephyr will be for implementing sysbuild image dependencies, i.e., specifying an image order for CMake configuration and flashing. Sourced from a comment on PR #57884 (anchor: #discussion_r1206807021) Authored-by: Torsten Rasmussen Signed-off-by: Grzegorz Swiderski --- cmake/modules/extensions.cmake | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/cmake/modules/extensions.cmake b/cmake/modules/extensions.cmake index 90a036e94ef190..1e38ae7ba727d3 100644 --- a/cmake/modules/extensions.cmake +++ b/cmake/modules/extensions.cmake @@ -3005,6 +3005,76 @@ function(target_byproducts) ) endfunction() +# Usage: +# topological_sort(TARGETS [ ...] +# PROPERTY_NAME +# RESULT ) +# +# This function performs topological sorting of CMake targets using a specific +# , which dictates target dependencies. A fatal error occurs if the +# provided dependencies cannot be met, e.g., if they contain cycles. +# +# TARGETS: List of target names. +# PROPERTY_NAME: Name of the target property to be used when sorting. For every +# target listed in TARGETS, this property must contain a list +# (possibly empty) of other targets, which this target depends on +# for a particular purpose. The property must not contain any +# target which is not also found in TARGETS. +# RESULT: Output variable, where the topologically sorted list of target +# names will be returned. +# +function(topological_sort) + cmake_parse_arguments(TS "" "RESULT;PROPERTY_NAME" "TARGETS" ${ARGN}) + + set(dep_targets) + set(start_targets) + set(sorted_targets) + + foreach(target ${TS_TARGETS}) + get_target_property(${target}_dependencies ${target} ${TS_PROPERTY_NAME}) + + if(${target}_dependencies) + list(APPEND dep_targets ${target}) + else() + list(APPEND start_targets ${target}) + endif() + endforeach() + + while(TRUE) + list(POP_FRONT start_targets node) + list(APPEND sorted_targets ${node}) + set(to_remove) + foreach(target ${dep_targets}) + if("${node}" IN_LIST ${target}_dependencies) + list(REMOVE_ITEM ${target}_dependencies ${node}) + if(NOT ${target}_dependencies) + list(APPEND start_targets ${target}) + list(APPEND to_remove ${target}) + endif() + endif() + endforeach() + + foreach(target ${to_remove}) + list(REMOVE_ITEM dep_targets ${target}) + endforeach() + if(NOT start_targets) + break() + endif() + endwhile() + + if(dep_targets) + foreach(target ${dep_targets}) + get_target_property(deps ${target} ${TS_PROPERTY_NAME}) + list(JOIN deps " " deps) + list(APPEND dep_string "${target} depends on: ${deps}") + endforeach() + list(JOIN dep_string "\n" dep_string) + message(FATAL_ERROR "Unmet or cyclic dependencies:\n${dep_string}") + endif() + + set(${TS_RESULT} "${sorted_targets}" PARENT_SCOPE) +endfunction() + ######################################################## # 4. Devicetree extensions ########################################################