From ae7d1fff49f60624b8a0008f21ddf90d4a1512d9 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 1 Dec 2021 23:00:01 +0100 Subject: [PATCH] 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. --- Kconfig | 5 +++ tools/cmake/build.cmake | 3 ++ tools/cmake/component_deps.dot.in | 30 +++++++++++++++ tools/cmake/depgraph.cmake | 61 +++++++++++++++++++++++++++++++ tools/cmake/idf.cmake | 1 + tools/cmake/project.cmake | 3 ++ 6 files changed, 103 insertions(+) create mode 100644 tools/cmake/component_deps.dot.in create mode 100644 tools/cmake/depgraph.cmake diff --git a/Kconfig b/Kconfig index 1325799097..8edda6b8a5 100644 --- a/Kconfig +++ b/Kconfig @@ -22,6 +22,11 @@ mainmenu "Espressif IoT Development Framework Configuration" bool default "n" + config IDF_TARGET_ARCH + string + default "riscv" if IDF_TARGET_ARCH_RISCV + default "xtensa" if IDF_TARGET_ARCH_XTENSA + config IDF_TARGET # 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 diff --git a/tools/cmake/build.cmake b/tools/cmake/build.cmake index 3352238ef6..94a2f8ccc5 100644 --- a/tools/cmake/build.cmake +++ b/tools/cmake/build.cmake @@ -221,13 +221,16 @@ function(__build_expand_requirements component_target) get_property(reqs TARGET ${component_target} PROPERTY REQUIRES) get_property(priv_reqs TARGET ${component_target} PROPERTY PRIV_REQUIRES) + __component_get_property(component_name ${component_target} COMPONENT_NAME) foreach(req ${reqs}) + depgraph_add_edge(${component_name} ${req} REQUIRES) __build_resolve_and_add_req(_component_target ${component_target} ${req} __REQUIRES) __build_expand_requirements(${_component_target}) endforeach() 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_expand_requirements(${_component_target}) endforeach() diff --git a/tools/cmake/component_deps.dot.in b/tools/cmake/component_deps.dot.in new file mode 100644 index 0000000000..fd49b8aad8 --- /dev/null +++ b/tools/cmake/component_deps.dot.in @@ -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; +} + +} diff --git a/tools/cmake/depgraph.cmake b/tools/cmake/depgraph.cmake new file mode 100644 index 0000000000..fc312db7ad --- /dev/null +++ b/tools/cmake/depgraph.cmake @@ -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() diff --git a/tools/cmake/idf.cmake b/tools/cmake/idf.cmake index ea096b96c1..311346b5fe 100644 --- a/tools/cmake/idf.cmake +++ b/tools/cmake/idf.cmake @@ -41,6 +41,7 @@ if(NOT __idf_env_set) include(kconfig) include(component) include(utilities) + include(depgraph) include(targets) include(ldgen) include(dfu) diff --git a/tools/cmake/project.cmake b/tools/cmake/project.cmake index 4f0b33d9dc..2d56126a09 100644 --- a/tools/cmake/project.cmake +++ b/tools/cmake/project.cmake @@ -120,6 +120,9 @@ function(__project_info test_components) configure_file("${idf_path}/tools/cmake/project_description.json.in" "${build_dir}/project_description.json") + # Generate component dependency graph + depgraph_generate("${build_dir}/component_deps.dot") + # We now have the following component-related variables: # # build_components is the list of components to include in the build.