ci(g1_g0): added CI check for detecting new dependencies in g1/g0

This commit is contained in:
Marius Vikhammer 2024-07-05 09:24:57 +08:00
parent 7f8fac5dc5
commit f1719307d6
3 changed files with 100 additions and 0 deletions

View File

@ -110,3 +110,16 @@ if(NOT "${expected_components}" STREQUAL "${build_components}")
"Expected: ${expected_components}. "
"Actual: ${build_components}")
endif()
set(comp_deps_dot "${CMAKE_BINARY_DIR}/component_deps.dot")
execute_process(
COMMAND ${CMAKE_COMMAND} -E echo "Checking dependency violations"
COMMAND python "${CMAKE_SOURCE_DIR}/check_dependencies.py" --component_deps_file ${comp_deps_dot}
RESULT_VARIABLE result
)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Found unexpected componend dependencies while running check_dependencies.py, "
"please update the dependency list in the script according to the error output.")
endif()

View File

@ -16,6 +16,9 @@ Once all the unwanted dependencies of G1 components are removed, the `extra_comp
Please note, if an extra component B is already added to the build as a dependency of component A (A -> B), the build of this example will not fail when a new dependency onto the same component is added (C -> B). It will only fail when a new component is added to the build. Diff-ing `build/component_deps.dot` (see below) against a known-good one could be an alternative, but it will likely be quite fragile.
In addition the script `check_dependencies.py` will also check that no G1 components get new dependencies to components that do not belong in this group. If an extra component B is already added to the build as a dependency of component A (A -> B), the build of this script will fail when a new dependency onto the same component is added (C -> B).
# Using this test app
To check G1 components using this app, run:
@ -39,6 +42,22 @@ it means that the list of extra components added to G1 build has changed compare
Please try very hard not to increase the number of components in `extra_components_which_shouldnt_be_included`.
If you get the error:
```
Found the following new dependency violations:
['<G1 Component> -> <Non G1 Component>']
```
it means that a new dependency to an extra component that was already in the build was added. If this is unavoidable then you can update the list of `expected_dep_violations` in the script to include this new dependency.
If you get the error:
```
The following dependencies are list as violations, but were not found in the component_deps.dot file:
{'<G1 Component>': ['<Non G1 Component>']}
```
Then it means a previous extra component dependency no longer exists, and we should remove it from the list `expected_dep_violations` in the script.
# Component dependencies graph (`component_deps.dot`)
When this project is configured, `component_deps.dot` file in the build directory is generated. This file contains a Graphviz graph showing the component dependencies. You can visualize this graph (using `dot` tool or online at https://dreampuf.github.io/GraphvizOnline/) to see why an extra component got added. You can also build the project for the base branch, to compare the graph to a known good one.

View File

@ -0,0 +1,68 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import argparse
import logging
from typing import Dict
from typing import List
from typing import Tuple
g1_g0_components = ['hal', 'cxx', 'newlib', 'freertos', 'esp_hw_support', 'heap', 'log', 'soc', 'esp_rom',
'esp_common', 'esp_system', 'xtensa', 'riscv', 'spi_flash', 'esp_mm']
expected_dep_violations = {'esp_system': ['esp_timer', 'bootloader_support', 'esp_pm'],
'spi_flash': ['bootloader_support', 'app_update', 'esp_driver_gpio'],
'esp_hw_support': ['efuse', 'bootloader_support', 'esp_driver_gpio', 'esp_timer', 'esp_pm'],
'cxx': ['pthread']}
def parse_dependencies(file_path: str) -> Tuple[Dict[str, List[str]], List[str]]:
new_dependency_errors = []
with open(file_path, 'r') as file:
for line in file:
line = line.strip(' ;')
if line:
parts = line.split(' -> ')
if (len(parts) >= 2):
source = parts[0]
target = parts[1].split()[0] # Extracting the target component
logging.debug(f'Parsed dependency: {source} -> {target}')
# Check that g1/g0 dependencies are either on the list of expected violations
# or dependencies to other g1/g0 components
if source in g1_g0_components and target not in g1_g0_components:
if target in expected_dep_violations[source]:
logging.debug(f'Removing dependency {target} from {source} in list of expected violations')
expected_dep_violations[source].remove(target)
else:
new_dependency_errors.append(f'{source} -> {target}')
# Any leftover dependencies in the expected_dep_violations are no longer true dependencies and
# can be removed from the list
false_dependencies = {k: v for k, v in expected_dep_violations.items() if len(v) > 0}
return (false_dependencies, new_dependency_errors)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Check G1 dependencies')
parser.add_argument('--component_deps_file', required=True, type=str, help='The path to the component_deps.dot file')
args = parser.parse_args()
(false_dependencies, new_dependency_errors) = parse_dependencies(args.component_deps_file)
if new_dependency_errors:
print('Found the following new dependency violations:')
print(new_dependency_errors)
exit(1)
if false_dependencies:
print('The following dependencies are list as violations, but were not found in the component_deps.dot file:')
print(false_dependencies)
print('Please remove them from the violation list')
exit(1)
print('No new dependency violations found')