cmake: add component dependency graph generation helpers

These optional feature produces a graphviz file showing component
dependencies. It is useful for debugging reasons why certain
components got added to the build.
This commit is contained in:
Ivan Grokhotkov 2021-12-01 23:00:01 +01:00
parent f4d1bb017a
commit ae7d1fff49
6 changed files with 103 additions and 0 deletions

View File

@ -22,6 +22,11 @@ mainmenu "Espressif IoT Development Framework Configuration"
bool bool
default "n" default "n"
config IDF_TARGET_ARCH
string
default "riscv" if IDF_TARGET_ARCH_RISCV
default "xtensa" if IDF_TARGET_ARCH_XTENSA
config IDF_TARGET config IDF_TARGET
# This option records the IDF target when sdkconfig is generated the first time. # This option records the IDF target when sdkconfig is generated the first time.
# It is not updated if environment variable $IDF_TARGET changes later, and # It is not updated if environment variable $IDF_TARGET changes later, and

View File

@ -221,13 +221,16 @@ function(__build_expand_requirements component_target)
get_property(reqs TARGET ${component_target} PROPERTY REQUIRES) get_property(reqs TARGET ${component_target} PROPERTY REQUIRES)
get_property(priv_reqs TARGET ${component_target} PROPERTY PRIV_REQUIRES) get_property(priv_reqs TARGET ${component_target} PROPERTY PRIV_REQUIRES)
__component_get_property(component_name ${component_target} COMPONENT_NAME)
foreach(req ${reqs}) foreach(req ${reqs})
depgraph_add_edge(${component_name} ${req} REQUIRES)
__build_resolve_and_add_req(_component_target ${component_target} ${req} __REQUIRES) __build_resolve_and_add_req(_component_target ${component_target} ${req} __REQUIRES)
__build_expand_requirements(${_component_target}) __build_expand_requirements(${_component_target})
endforeach() endforeach()
foreach(req ${priv_reqs}) foreach(req ${priv_reqs})
depgraph_add_edge(${component_name} ${req} PRIV_REQUIRES)
__build_resolve_and_add_req(_component_target ${component_target} ${req} __PRIV_REQUIRES) __build_resolve_and_add_req(_component_target ${component_target} ${req} __PRIV_REQUIRES)
__build_expand_requirements(${_component_target}) __build_expand_requirements(${_component_target})
endforeach() endforeach()

View File

@ -0,0 +1,30 @@
digraph G {
${depgraph}
subgraph cluster_g0 {
rank = same;
label = "G0";
soc;
hal;
esp_common;
esp_rom;
${CONFIG_IDF_TARGET_ARCH};
${CONFIG_IDF_TARGET};
}
subgraph cluster_g1 {
rank = same;
label = "G1";
spi_flash;
freertos;
log;
heap;
newlib;
esp_system;
esp_hw_support;
}
}

View File

@ -0,0 +1,61 @@
# Component dependency graph generation helpers.
# To enable this functionality, add:
# idf_build_set_property(__BUILD_COMPONENT_DEPGRAPH_ENABLED 1)
# in the project CMakeLists.txt file between include(project.cmake) and project(name) calls.
#
# Graphviz file component_deps.dot will be produced in the build directory.
# To change the template, see component_deps.dot.in.
# depgraph_add_edge
#
# @brief Record an edge in the component dependency graph (dependent -> dependency)
#
# @param[in] dep_from dependent name
# @param[in] dep_to dependency name
# @param[in, optional] REQUIRES if given, record this as "REQUIRES" (public/interface) dependency
# @param[in, optional] PRIV_REQUIRES if given, record this as "PRIV_REQUIRES" (private) dependency
#
function(depgraph_add_edge dep_from dep_to)
cmake_parse_arguments(_ "REQUIRES;PRIV_REQUIRES" "" "" ${ARGN})
idf_build_get_property(depgraph_enabled __BUILD_COMPONENT_DEPGRAPH_ENABLED)
if(NOT depgraph_enabled)
return()
endif()
idf_build_get_property(common_reqs __COMPONENT_REQUIRES_COMMON)
set(attr)
if(__REQUIRES)
set(attr "[class=\"requires\"]")
elseif(__PRIV_REQUIRES)
set(attr "[class=\"priv_requires\" style=\"dotted\"]")
endif()
if(dep_to IN_LIST common_reqs)
# Don't record graph edges leading to "common" components, since every component has these dependencies.
# However, show which components are "common" by adding an edge from a node named "common".
# If necessary, add a new build property to customize this behavior.
if(NOT dep_from IN_LIST common_reqs)
idf_build_set_property(__BUILD_COMPONENT_DEPGRAPH "common -> ${dep_to}" APPEND)
endif()
else()
idf_build_set_property(__BUILD_COMPONENT_DEPGRAPH "${dep_from} -> ${dep_to} ${attr}" APPEND)
endif()
endfunction()
# depgraph_generate
#
# @brief Write the collected component dependency graph to a file. The file is in Graphviz (.dot) format.
#
# @param[in] out_path name of the output file
#
function(depgraph_generate out_path)
idf_build_get_property(depgraph_enabled __BUILD_COMPONENT_DEPGRAPH_ENABLED)
if(NOT depgraph_enabled)
return()
endif()
idf_build_get_property(depgraph __BUILD_COMPONENT_DEPGRAPH)
list(REMOVE_DUPLICATES depgraph)
string(REPLACE ";" ";\n" depgraph "${depgraph}")
configure_file("${IDF_PATH}/tools/cmake/component_deps.dot.in"
"${out_path}")
endfunction()

View File

@ -41,6 +41,7 @@ if(NOT __idf_env_set)
include(kconfig) include(kconfig)
include(component) include(component)
include(utilities) include(utilities)
include(depgraph)
include(targets) include(targets)
include(ldgen) include(ldgen)
include(dfu) include(dfu)

View File

@ -120,6 +120,9 @@ function(__project_info test_components)
configure_file("${idf_path}/tools/cmake/project_description.json.in" configure_file("${idf_path}/tools/cmake/project_description.json.in"
"${build_dir}/project_description.json") "${build_dir}/project_description.json")
# Generate component dependency graph
depgraph_generate("${build_dir}/component_deps.dot")
# We now have the following component-related variables: # We now have the following component-related variables:
# #
# build_components is the list of components to include in the build. # build_components is the list of components to include in the build.