Răsfoiți Sursa

ci: check unstable soc headers wont be leaked by public api

morris 4 ani în urmă
părinte
comite
3b371d2d64

+ 2 - 1
.gitlab/ci/pre_check.yml

@@ -113,13 +113,14 @@ check_public_headers:
   script:
     - python tools/ci/check_public_headers.py --jobs 4 --prefix xtensa-esp32-elf-
 
-check_soc_struct_headers:
+check_soc_component:
   extends:
     - .pre_check_base_template
     - .rules:build
   tags:
     - build
   script:
+    - python tools/ci/check_soc_headers_leak.py
     - find ${IDF_PATH}/components/soc/*/include/soc/ -name "*_struct.h" -print0 | xargs -0 -n1 ./tools/ci/check_soc_struct_headers.py
 
 check_esp_err_to_name:

+ 4 - 0
docs/doxygen/Doxyfile_esp32h2

@@ -0,0 +1,4 @@
+INPUT += \
+         $(PROJECT_PATH)/components/driver/esp32h2/include/driver/temp_sensor.h \
+         $(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32h2/esp_ds.h \
+         $(PROJECT_PATH)/components/esp_hw_support/include/soc/esp32h2/esp_hmac.h

+ 112 - 0
tools/ci/check_soc_headers_leak.py

@@ -0,0 +1,112 @@
+# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
+
+# This check script is used to ensure the public APIs won't expose the unstable soc files like register files
+# public API header files are those taken by doxygen and have full documented docs
+
+import fnmatch
+import os
+import re
+import sys
+import typing
+from string import Template
+
+# The following header files in soc component is treated as stable, so is allowed to be used in any public header files
+allowed_soc_headers = (
+    'soc/soc_caps.h',
+    'soc/reset_reasons.h',
+    'soc/reg_base.h',
+    'soc/efuse_periph.h',  # 'soc/efuse_periph.h' should not be allowed , remove it from the list in IDF-1256
+)
+
+include_header_pattern = re.compile(r'[\s]*#[\s]*include ["<](.*)[">].*')
+doxyfile_target_pattern = re.compile(r'Doxyfile_(.*)')
+
+
+class PublicAPIVisits:
+    def __init__(self, doxyfile_path: str, idf_path: str, target: str) -> None:
+        self.doxyfile_path = doxyfile_path
+        self._target = target
+        self._idf_path = idf_path
+
+    def __iter__(self) -> typing.Generator:
+        with open(self.doxyfile_path, 'r', encoding='utf8') as f:
+            for line in f:
+                line = line.strip()
+                if line.startswith('$(PROJECT_PATH)'):
+                    # $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/uart_channel.h \
+                    # -> ${PROJECT_PATH}/components/soc/${IDF_TARGET}/include/soc/uart_channel.h
+                    line = line.replace('(', '{').replace(')', '}').rstrip('\\ ')
+                    file_path = Template(line).substitute(
+                        PROJECT_PATH=self._idf_path, IDF_TARGET=self._target
+                    )
+                    yield file_path
+
+
+def check_soc_not_in(
+    idf_path: str,
+    target: str,
+    doxyfile_path: str,
+    violation_dict: typing.Dict[str, set],
+) -> None:
+    for file_path in PublicAPIVisits(
+        os.path.join(idf_path, doxyfile_path), idf_path, target
+    ):
+        with open(file_path, 'r', encoding='utf8') as f:
+            for line in f:
+                match_data = re.match(include_header_pattern, line)
+                if match_data:
+                    header = match_data.group(1)
+                    if header.startswith('soc') and header not in allowed_soc_headers:
+                        if file_path not in violation_dict:
+                            violation_dict[file_path] = set()
+                        violation_dict[file_path].add(header)
+
+
+def main() -> None:
+    idf_path = os.environ.get('IDF_PATH', None)
+    if idf_path is None:
+        print('IDF_PATH must be set before running this script', file=sys.stderr)
+        sys.exit(1)
+
+    # list all doxyfiles
+    doxyfiles = fnmatch.filter(
+        os.listdir(os.path.join(idf_path, 'docs/doxygen')), 'Doxyfile*'
+    )
+    print(f'Found Doxyfiles:{doxyfiles}')
+
+    # targets are judged from Doxyfile name
+    targets = []
+    for file in doxyfiles:
+        res = doxyfile_target_pattern.match(file)
+        if res:
+            targets.append(res.group(1))
+    if not targets:
+        print('No targets found', file=sys.stderr)
+        sys.exit(1)
+
+    soc_violation_dict: typing.Dict[str, set] = {}
+    for target in targets:
+        check_soc_not_in(
+            idf_path,
+            target,
+            os.path.join(idf_path, 'docs/doxygen/Doxyfile'),
+            soc_violation_dict,
+        )
+        check_soc_not_in(
+            idf_path,
+            target,
+            os.path.join(idf_path, f'docs/doxygen/Doxyfile_{target}'),
+            soc_violation_dict,
+        )
+
+    if len(soc_violation_dict) > 0:
+        for file_path, headers_set in soc_violation_dict.items():
+            print(f'{file_path} includes non-public soc header file: {headers_set}')
+        sys.exit(1)
+    else:
+        print('No violation found')
+
+
+if __name__ == '__main__':
+    main()