Files
esp-idf/tools/cmakev2/component_validation.cmake
Frantisek Hrbata 368b90245b fix(cmakev2/component_validation): accelerate component validation
The commit f951ae5b18 ("feat(cmakev2): Added component validation
checks for ...") introduced support for validating component sources and
include directories. It heavily relies on iterating through the
registered components to obtain their properties. For instance, it
examines all components for each component source file to ensure that
the source files do not originate from other components. However, it
turns out that `idf_component_get_property` is a bottleneck in this
process, causing reconfiguration to take 3-4 times longer than before
the introduction of this check. Since we know of all the component
interfaces, we can bypass the checks performed by
`idf_component_get_property` and instead use the raw version,
`__idf_component_get_property_unchecked`, which operates much faster.

Following are hello_world example reconfigure times before and after.

before:

-- Configuring done (11.1s)
-- Generating done (0.2s)

after:

-- Configuring done (3.9s)
-- Generating done (0.2s)

A new library property LIBRARY_COMPONENT_INTERFACES_LINKED is added that
keeps component interfaces linked to the library.

Fixes: f951ae5b18 ("feat(cmakev2): Added component validation checks for ..")
Signed-off-by: Frantisek Hrbata <frantisek.hrbata@espressif.com>
2026-01-13 16:47:25 +01:00

177 lines
7.6 KiB
CMake

# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# Component validation checks
#
# This module contains checks that validate component source files and include directories
# to ensure they belong to the correct component. These checks run after all components
# have been discovered and processed.
#[[
__component_validation_get_component_for_path(<var> <path>)
*var[out]*
Output variable to store the component name.
*path[in]*
File or directory path to check.
Determine which component a given path belongs to. Returns the component name in ``var``.
#]]
function(__component_validation_get_component_for_path var path)
# Determine the starting directory to check: use the path itself if it's a directory,
# otherwise use its containing directory
set(current_dir "${path}")
if(NOT IS_DIRECTORY "${current_dir}")
get_filename_component(current_dir "${path}" DIRECTORY)
endif()
# Get all component names
idf_build_get_property(component_interfaces COMPONENT_INTERFACES)
# Walk up the directory tree from the deepest path towards root and return
# the first component whose COMPONENT_DIR matches exactly. This guarantees
# selecting the deepest matching component without extra heuristics.
while(NOT "${current_dir}" STREQUAL "" AND
NOT "${current_dir}" STREQUAL "/" AND
NOT "${current_dir}" MATCHES "^[A-Za-z]:/$")
foreach(component_interface ${component_interfaces})
__idf_component_get_property_unchecked(component_dir ${component_interface} COMPONENT_DIR)
if(current_dir STREQUAL component_dir)
__idf_component_get_property_unchecked(component_name ${component_interface} COMPONENT_NAME)
set(${var} ${component_name} PARENT_SCOPE)
return()
endif()
endforeach()
get_filename_component(current_dir "${current_dir}" DIRECTORY)
endwhile()
# If no component found, return empty
set(${var} "" PARENT_SCOPE)
endfunction()
#[[
__component_validation_check_sources(<component_interface>)
*component_interface[in]*
Component interface to validate.
Validate that all source files of the specified component belong to that
component's directory tree. Issues warnings if source files belong to other
components.
#]]
function(__component_validation_check_sources component_interface)
__idf_component_get_property_unchecked(component_name ${component_interface} COMPONENT_NAME)
__idf_component_get_property_unchecked(sources ${component_interface} SRCS)
# Skip validation if component does not have any sources
if(NOT sources)
return()
endif()
foreach(src ${sources})
# Check if this source file belongs to another component
__component_validation_get_component_for_path(owner_component ${src})
if(owner_component AND NOT owner_component STREQUAL component_name)
idf_warn(
"Source file '${src}' belongs to component ${owner_component} but is being built by "
"component ${component_name}. It is recommended to build source files by "
"defining component dependencies for ${component_name} "
"via using idf_component_register(REQUIRES ${owner_component}) "
"or idf_component_register(PRIV_REQUIRES ${owner_component}) in the CMakeLists.txt of "
"${component_name}.")
endif()
endforeach()
endfunction()
#[[
__component_validation_check_include_dirs(<component_name>)
*component_name[in]*
Component name to validate.
Validate that all include directories (INCLUDE_DIRS and PRIV_INCLUDE_DIRS
properties) of the specified component belong to that component's directory
tree. Issues warnings if include directories belong to other components.
#]]
function(__component_validation_check_include_dirs component_interface)
__idf_component_get_property_unchecked(component_name ${component_interface} COMPONENT_NAME)
__idf_component_get_property_unchecked(include_dirs ${component_interface} INCLUDE_DIRS)
__idf_component_get_property_unchecked(priv_include_dirs ${component_interface} PRIV_INCLUDE_DIRS)
__idf_component_get_property_unchecked(component_dir ${component_interface} COMPONENT_DIR)
# Check public include directories
foreach(dir ${include_dirs})
# Check if this include directory belongs to another component
# Normalize to absolute path relative to this component directory
get_filename_component(abs_dir ${dir} ABSOLUTE BASE_DIR ${component_dir})
__component_validation_get_component_for_path(owner_component ${abs_dir})
if(owner_component AND NOT owner_component STREQUAL component_name)
idf_warn(
"Include directory '${abs_dir}' belongs to component ${owner_component} but is being "
"used by component ${component_name}. It is recommended to define the "
"component dependency for '${component_name}' on the component ${owner_component}, "
"i.e. 'idf_component_register(... REQUIRES ${owner_component})' in the "
"CMakeLists.txt of ${component_name}, and specify the included directory "
"as idf_component_register(... INCLUDE_DIRS <dir relative to component>) "
"in the CMakeLists.txt of component ${owner_component}.")
endif()
endforeach()
# Check private include directories
foreach(dir ${priv_include_dirs})
# Check if this include directory belongs to another component
# Normalize to absolute path relative to this component directory
get_filename_component(abs_dir ${dir} ABSOLUTE BASE_DIR ${component_dir})
__component_validation_get_component_for_path(owner_component ${abs_dir})
if(owner_component AND NOT owner_component STREQUAL component_name)
idf_warn(
"Private include directory '${abs_dir}' belongs to component ${owner_component} but "
"is being used by component ${component_name}. "
"It is recommended to define the component dependency for ${component_name} "
"on the component ${owner_component}, "
"i.e. 'idf_component_register(... PRIV_REQUIRES ${owner_component})' in the "
"CMakeLists.txt of ${component_name}, "
"and specify the included directory as "
"idf_component_register(... PRIV_INCLUDE_DIRS <dir relative to component>) "
"in the CMakeLists.txt of component ${owner_component}.")
endif()
endforeach()
endfunction()
#[[
__component_validation_run_checks(LIBRARY <library>)
*library[in]*
Library name for which to run validation checks.
Run validation checks for all components linked to the specified library.
#]]
function(__component_validation_run_checks)
set(options)
set(one_value LIBRARY)
set(multi_value)
cmake_parse_arguments(ARG "${options}" "${one_value}" "${multi_value}" ${ARGN})
if(NOT DEFINED ARG_LIBRARY)
idf_die("LIBRARY option is required for __component_validation_run_checks")
endif()
# Validate only components linked to the specified library
idf_library_get_property(component_interfaces "${ARG_LIBRARY}" LIBRARY_COMPONENT_INTERFACES_LINKED)
# Run validation checks for each component
foreach(component_interface ${component_interfaces})
__component_validation_check_sources(${component_interface})
__component_validation_check_include_dirs(${component_interface})
endforeach()
endfunction()