check_soc_headers_leak.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. # This check script is used to ensure the public APIs won't expose the unstable soc files like register files
  4. # public API header files are those taken by doxygen and have full documented docs
  5. import fnmatch
  6. import os
  7. import re
  8. import sys
  9. import typing
  10. from string import Template
  11. # The following header files in soc component is treated as stable, so is allowed to be used in any public header files
  12. allowed_soc_headers = (
  13. 'soc/soc_caps.h',
  14. 'soc/gpio_num.h',
  15. 'soc/reset_reasons.h',
  16. 'soc/reg_base.h',
  17. 'soc/clk_tree_defs.h',
  18. )
  19. include_header_pattern = re.compile(r'[\s]*#[\s]*include ["<](.*)[">].*')
  20. doxyfile_target_pattern = re.compile(r'Doxyfile_(.*)')
  21. class PublicAPIVisits:
  22. def __init__(self, doxyfile_path: str, idf_path: str, target: str) -> None:
  23. self.doxyfile_path = doxyfile_path
  24. self._target = target
  25. self._idf_path = idf_path
  26. def __iter__(self) -> typing.Generator:
  27. with open(self.doxyfile_path, 'r', encoding='utf8') as f:
  28. for line in f:
  29. line = line.strip()
  30. if line.startswith('$(PROJECT_PATH)'):
  31. # $(PROJECT_PATH)/components/soc/$(IDF_TARGET)/include/soc/uart_channel.h \
  32. # -> ${PROJECT_PATH}/components/soc/${IDF_TARGET}/include/soc/uart_channel.h
  33. line = line.replace('(', '{').replace(')', '}').rstrip('\\ ')
  34. file_path = Template(line).substitute(
  35. PROJECT_PATH=self._idf_path, IDF_TARGET=self._target
  36. )
  37. yield file_path
  38. def check_soc_not_in(
  39. idf_path: str,
  40. target: str,
  41. doxyfile_path: str,
  42. violation_dict: typing.Dict[str, set],
  43. ) -> None:
  44. for file_path in PublicAPIVisits(
  45. os.path.join(idf_path, doxyfile_path), idf_path, target
  46. ):
  47. with open(file_path, 'r', encoding='utf8') as f:
  48. for line in f:
  49. match_data = re.match(include_header_pattern, line)
  50. if match_data:
  51. header = match_data.group(1)
  52. if header.startswith('soc') and header not in allowed_soc_headers:
  53. if file_path not in violation_dict:
  54. violation_dict[file_path] = set()
  55. violation_dict[file_path].add(header)
  56. def main() -> None:
  57. idf_path = os.environ.get('IDF_PATH', None)
  58. if idf_path is None:
  59. print('IDF_PATH must be set before running this script', file=sys.stderr)
  60. sys.exit(1)
  61. # list all doxyfiles
  62. doxyfiles = fnmatch.filter(
  63. os.listdir(os.path.join(idf_path, 'docs/doxygen')), 'Doxyfile*'
  64. )
  65. print(f'Found Doxyfiles:{doxyfiles}')
  66. # targets are judged from Doxyfile name
  67. targets = []
  68. for file in doxyfiles:
  69. res = doxyfile_target_pattern.match(file)
  70. if res:
  71. targets.append(res.group(1))
  72. if not targets:
  73. print('No targets found', file=sys.stderr)
  74. sys.exit(1)
  75. soc_violation_dict: typing.Dict[str, set] = {}
  76. for target in targets:
  77. check_soc_not_in(
  78. idf_path,
  79. target,
  80. os.path.join(idf_path, 'docs/doxygen/Doxyfile'),
  81. soc_violation_dict,
  82. )
  83. check_soc_not_in(
  84. idf_path,
  85. target,
  86. os.path.join(idf_path, f'docs/doxygen/Doxyfile_{target}'),
  87. soc_violation_dict,
  88. )
  89. if len(soc_violation_dict) > 0:
  90. for file_path, headers_set in soc_violation_dict.items():
  91. print(f'{file_path} includes non-public soc header file: {headers_set}')
  92. sys.exit(1)
  93. else:
  94. print('No violation found')
  95. if __name__ == '__main__':
  96. main()