@@ -914,7 +914,27 @@ def get_tags(self, exclude_dirs: Optional[List[str]]=None) -> SPDXTags:
914914 tags |= package .tags
915915 return tags
916916
917+ def get_reachable_components (self , names : List [str ]) -> Set [str ]:
918+ # Return a set of all components that can be reached directly or
919+ # indirectly through requirements from the components in the names
920+ # list, including the names themselves.
921+ build_components = self .proj_desc ['build_component_info' ]
922+ reachable = set ()
923+ seen = set ()
924+ todo = set (names )
925+ while todo :
926+ name = todo .pop ()
927+ seen .add (name )
928+ if not self ._component_used (build_components [name ]):
929+ continue
930+ reachable .add (name )
931+ info = build_components [name ]
932+ reqs = set (info ['reqs' ] + info ['priv_reqs' ] + info ['managed_reqs' ] + info ['managed_priv_reqs' ])
933+ todo |= reqs - seen
934+ return reachable
935+
917936 def add_relationships (self ) -> None :
937+
918938 if not self .manifest ['cpe' ]:
919939 # CPE for whole espressif:esp-idf.
920940 ver = self .proj_desc ['git_revision' ]
@@ -926,10 +946,18 @@ def add_relationships(self) -> None:
926946 # Dependency on toolchain.
927947 self ['Relationship' ] += [f'{ self ["SPDXID" ][0 ]} DEPENDS_ON { self .toolchain ["SPDXID" ][0 ]} ' ]
928948
929- # Dependencies on components. Only components, which are not required by other
930- # components are added as direct dependency for the project binary.
949+ # 1. Gather all component requirements into a `requirements set`.
950+ # 2. Gather components not present in the `requirements set` into the
951+ # `project requirements set`.
952+ # 3. Create a `reachable set` by going through direct and indirect dependencies
953+ # reachable from the `project requirements set`.
954+ # 4. For each component, verify if it is in the `reachable set`. If it is
955+ # not, add it to the `project requirements set` and include its direct
956+ # and indirect dependencies in the `reachable set`.
957+
931958 build_components = self .proj_desc ['build_component_info' ]
932- reqs = set ()
959+
960+ reqs : Set [str ] = set ()
933961 for name , info in build_components .items ():
934962 if not self ._component_used (build_components [name ]):
935963 # Do not include requirements from a component that the project
@@ -942,15 +970,43 @@ def add_relationships(self) -> None:
942970 # If nvs_flash is not linked, newlib is not included in the SPDX
943971 # project package relationships.
944972 continue
945- tmp = info ['reqs' ] + info ['priv_reqs' ] + info ['managed_reqs' ] + info ['managed_priv_reqs' ]
946- reqs |= set (tmp )
973+ reqs |= set (info ['reqs' ] + info ['priv_reqs' ] + info ['managed_reqs' ] + info ['managed_priv_reqs' ])
947974
975+ # Only components, which are not required by other components are added as direct
976+ # dependency for the project binary.
977+ proj_reqs : List [str ] = []
948978 for name , info in build_components .items ():
949979 if name in reqs :
950980 continue
951981 if not self ._component_used (build_components [name ]):
952982 continue
953- self ['Relationship' ] += [f'{ self ["SPDXID" ][0 ]} DEPENDS_ON { self .components [name ]["SPDXID" ][0 ]} ' ]
983+ proj_reqs .append (name )
984+
985+ # Get all used components reachable from the immediate project dependencies.
986+ reachable = self .get_reachable_components (proj_reqs )
987+
988+ # Ensure that all components included in the project can be reached from the
989+ # project's SPDX package dependency tree. It's possible for a component to be
990+ # required by another component yet still not be reachable from the project
991+ # package. This situation can occur if the project doesn't explicitly define
992+ # its dependencies and instead depends on the build system's current behavior
993+ # which includes all discovered components in the build. For instance, esp_wifi
994+ # depends on wpa_supplicant and vice versa, but if the main component doesn't
995+ # specify a dependency on, say, esp_wifi, neither wpa_supplicant nor esp_wifi
996+ # will be included in the SPDX package dependencies.
997+ for name , info in build_components .items ():
998+ if name in reachable :
999+ continue
1000+ if not self ._component_used (build_components [name ]):
1001+ continue
1002+ # The component is not reachable, so include it as a direct dependency in the
1003+ # SPDX project package. Additionally, add all its direct and indirect
1004+ # dependencies into the set of reachable components.
1005+ proj_reqs .append (name )
1006+ reachable |= self .get_reachable_components ([name ])
1007+
1008+ for req in proj_reqs :
1009+ self ['Relationship' ] += [f'{ self ["SPDXID" ][0 ]} DEPENDS_ON { self .components [req ]["SPDXID" ][0 ]} ' ]
9541010
9551011 def dump (self ) -> str :
9561012 out = super ().dump ()
0 commit comments