Преглед изворни кода

Merge branch 'feat/all_components' into 'master'

feat(tools): display hints for projects with trimmed down list of components

Closes IDF-8005 and IDF-8316

See merge request espressif/esp-idf!26297
Roland Dobai пре 2 година
родитељ
комит
51b104deac

+ 59 - 2
tools/cmake/project.cmake

@@ -119,7 +119,7 @@ function(paths_with_spaces_to_list variable_name)
     endif()
 endfunction()
 
-function(__component_info components output)
+function(__build_component_info components output)
     set(components_json "")
     foreach(name ${components})
         __component_get_target(target ${name})
@@ -187,6 +187,62 @@ function(__component_info components output)
     set(${output} "${components_json}" PARENT_SCOPE)
 endfunction()
 
+function(__all_component_info output)
+    set(components_json "")
+
+    idf_build_get_property(build_prefix __PREFIX)
+    idf_build_get_property(component_targets __COMPONENT_TARGETS)
+
+    foreach(target ${component_targets})
+        __component_get_property(name ${target} COMPONENT_NAME)
+        __component_get_property(alias ${target} COMPONENT_ALIAS)
+        __component_get_property(prefix ${target} __PREFIX)
+        __component_get_property(dir ${target} COMPONENT_DIR)
+        __component_get_property(type ${target} COMPONENT_TYPE)
+        __component_get_property(lib ${target} COMPONENT_LIB)
+        __component_get_property(reqs ${target} REQUIRES)
+        __component_get_property(include_dirs ${target} INCLUDE_DIRS)
+        __component_get_property(priv_reqs ${target} PRIV_REQUIRES)
+        __component_get_property(managed_reqs ${target} MANAGED_REQUIRES)
+        __component_get_property(managed_priv_reqs ${target} MANAGED_PRIV_REQUIRES)
+
+        if(prefix STREQUAL build_prefix)
+            set(name ${name})
+        else()
+            set(name ${alias})
+        endif()
+
+        make_json_list("${reqs}" reqs)
+        make_json_list("${priv_reqs}" priv_reqs)
+        make_json_list("${managed_reqs}" managed_reqs)
+        make_json_list("${managed_priv_reqs}" managed_priv_reqs)
+        make_json_list("${include_dirs}" include_dirs)
+
+        string(JOIN "\n" component_json
+            "        \"${name}\": {"
+            "            \"alias\": \"${alias}\","
+            "            \"target\": \"${target}\","
+            "            \"prefix\": \"${prefix}\","
+            "            \"dir\": \"${dir}\","
+            "            \"lib\": \"${lib}\","
+            "            \"reqs\": ${reqs},"
+            "            \"priv_reqs\": ${priv_reqs},"
+            "            \"managed_reqs\": ${managed_reqs},"
+            "            \"managed_priv_reqs\": ${managed_priv_reqs},"
+            "            \"include_dirs\": ${include_dirs}"
+            "        }"
+        )
+        string(CONFIGURE "${component_json}" component_json)
+        if(NOT "${components_json}" STREQUAL "")
+            string(APPEND components_json ",\n")
+        endif()
+        string(APPEND components_json "${component_json}")
+    endforeach()
+    string(PREPEND components_json "{\n")
+    string(APPEND components_json "\n    }")
+    set(${output} "${components_json}" PARENT_SCOPE)
+endfunction()
+
 #
 # Output the built components to the user. Generates files for invoking esp_idf_monitor
 # that doubles as an overview of some of the more important build properties.
@@ -251,7 +307,8 @@ function(__project_info test_components)
     make_json_list("${build_component_paths};${test_component_paths}" build_component_paths_json)
     make_json_list("${common_component_reqs}" common_component_reqs_json)
 
-    __component_info("${build_components};${test_components}" build_component_info_json)
+    __build_component_info("${build_components};${test_components}" build_component_info_json)
+    __all_component_info(all_component_info_json)
 
     # The configure_file function doesn't process generator expressions, which are needed
     # e.g. to get component target library(TARGET_LINKER_FILE), so the project_description

+ 2 - 1
tools/cmake/project_description.json.in

@@ -1,5 +1,5 @@
 {
-    "version":            "1",
+    "version":            "1.1",
     "project_name":       "${PROJECT_NAME}",
     "project_version":    "${PROJECT_VER}",
     "project_path":       "${PROJECT_PATH}",
@@ -28,5 +28,6 @@
     "build_components" : ${build_components_json},
     "build_component_paths" : ${build_component_paths_json},
     "build_component_info" : ${build_component_info_json},
+    "all_component_info" : ${all_component_info_json},
     "debug_prefix_map_gdbinit": "${debug_prefix_map_gdbinit}"
 }

+ 5 - 1
tools/cmake/scripts/component_get_requirements.cmake

@@ -76,6 +76,7 @@ macro(idf_component_register)
     set(__component_requires "${__REQUIRES}")
     set(__component_kconfig "${__KCONFIG}")
     set(__component_kconfig_projbuild "${__KCONFIG_PROJBUILD}")
+    set(__component_include_dirs "${__INCLUDE_DIRS}")
     set(__component_registered 1)
     return()
 endmacro()
@@ -107,11 +108,13 @@ function(__component_get_requirements)
 
     spaces2list(__component_requires)
     spaces2list(__component_priv_requires)
+    spaces2list(__component_include_dirs)
 
     set(__component_requires "${__component_requires}" PARENT_SCOPE)
     set(__component_priv_requires "${__component_priv_requires}" PARENT_SCOPE)
     set(__component_kconfig "${__component_kconfig}" PARENT_SCOPE)
     set(__component_kconfig_projbuild "${__component_kconfig_projbuild}" PARENT_SCOPE)
+    set(__component_include_dirs "${__component_include_dirs}" PARENT_SCOPE)
     set(__component_registered ${__component_registered} PARENT_SCOPE)
 endfunction()
 
@@ -141,7 +144,8 @@ foreach(__component_target ${__component_targets})
     set(__contents
 "__component_set_property(${__component_target} REQUIRES \"${__component_requires}\")
 __component_set_property(${__component_target} PRIV_REQUIRES \"${__component_priv_requires}\")
-__component_set_property(${__component_target} __COMPONENT_REGISTERED ${__component_registered})"
+__component_set_property(${__component_target} __COMPONENT_REGISTERED ${__component_registered})
+__component_set_property(${__component_target} INCLUDE_DIRS \"${__component_include_dirs}\")"
     )
 
     if(__component_kconfig)

+ 4 - 4
tools/idf_py_actions/hint_modules/component_requirements.py

@@ -58,7 +58,7 @@ def generate_hint(output: str) -> Optional[str]:
     # find the source_component that contains the source file
     found_source_component_name = None
     found_source_component_info = None
-    for component_name, component_info in proj_desc['build_component_info'].items():
+    for component_name, component_info in proj_desc['all_component_info'].items():
         # look if the source_filename is within a component directory, not only
         # at component_info['sources'], because the missing file may be included
         # from header file, which is not present in component_info['sources']
@@ -86,7 +86,7 @@ def generate_hint(output: str) -> Optional[str]:
         original_file_match = ORIGINAL_FILE_RE.match(lines[-2])
         if original_file_match:
             original_filename = _get_absolute_path(original_file_match.group(1), proj_desc['build_dir'])
-            for component_name, component_info in proj_desc['build_component_info'].items():
+            for component_name, component_info in proj_desc['all_component_info'].items():
                 component_dir = os.path.normpath(component_info['dir'])
                 if original_filename.startswith(component_dir):
                     found_original_component_name = component_name
@@ -100,7 +100,7 @@ def generate_hint(output: str) -> Optional[str]:
 
     # look for the header file in the public include directories of all components
     found_dep_component_names = []
-    for candidate_component_name, candidate_component_info in proj_desc['build_component_info'].items():
+    for candidate_component_name, candidate_component_info in proj_desc['all_component_info'].items():
         if candidate_component_name == found_source_component_name:
             # skip the component that contains the source file
             continue
@@ -117,7 +117,7 @@ def generate_hint(output: str) -> Optional[str]:
         # directories if we can find the missing header there and notify user about possible
         # missing entry in INCLUDE_DIRS.
         candidate_component_include_dirs = []
-        for component_name, component_info in proj_desc['build_component_info'].items():
+        for component_name, component_info in proj_desc['all_component_info'].items():
             component_dir = os.path.normpath(component_info['dir'])
             for root, _, _ in os.walk(component_dir):
                 full_path = os.path.normpath(os.path.join(root, missing_header))

+ 29 - 0
tools/test_idf_py/test_hints.py

@@ -171,5 +171,34 @@ class TestNestedModuleComponentRequirements(unittest.TestCase):
         self.tmpdir.cleanup()
 
 
+class TestTrimmedModuleComponentRequirements(unittest.TestCase):
+    def setUp(self) -> None:
+        # Set up a dummy project with a trimmed down list of components and main component.
+        # The main component includes "esp_http_client.h", but the esp_http_client
+        # component is not added to main's requirements.
+        self.tmpdir = tempfile.TemporaryDirectory()
+        self.tmpdirpath = Path(self.tmpdir.name)
+
+        self.projectdir = self.tmpdirpath / 'project'
+        self.projectdir.mkdir(parents=True)
+        (self.projectdir / 'CMakeLists.txt').write_text((
+            'cmake_minimum_required(VERSION 3.16)\n'
+            'set(COMPONENTS main)\n'
+            'include($ENV{IDF_PATH}/tools/cmake/project.cmake)\n'
+            'project(foo)'))
+
+        maindir = self.projectdir / 'main'
+        maindir.mkdir()
+        (maindir / 'CMakeLists.txt').write_text('idf_component_register(SRCS "foo.c")')
+        (maindir / 'foo.c').write_text('#include "esp_http_client.h"\nvoid app_main(){}')
+
+    def test_trimmed_component_requirements(self) -> None:
+        output = run_idf(['app'], self.projectdir)
+        self.assertIn('To fix this, add esp_http_client to PRIV_REQUIRES list of idf_component_register call in', output)
+
+    def tearDown(self) -> None:
+        self.tmpdir.cleanup()
+
+
 if __name__ == '__main__':
     unittest.main()