check_includes.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2022 Project CHIP Authors
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. """Check #include files.
  18. Reads from standard input the output of a `grep -n` for `#include`s;
  19. see `check_includes.sh`.
  20. Uses the conditions defined in `check_includes_config.py`.
  21. """
  22. import re
  23. import sys
  24. from typing import Iterable, Pattern
  25. import check_includes_config as config
  26. # The input comes from `grep -n` and has the form
  27. # filename:line:include-directive
  28. #
  29. # This RE does not handle C-style comments before the file name.
  30. # So don't do that.
  31. _MATCH_SPLIT_RE = re.compile(r"""
  32. (?P<file>.+)
  33. : (?P<line>\d+)
  34. : (?P<directive>
  35. \s* \# \s* include \s* (?P<type>[<"]) (?P<include>[^">]+) [">])
  36. (?P<trailing> .*)
  37. """, re.VERBOSE)
  38. # Allow a temporary override so that a PR will not be blocked
  39. # on first updating `check_includes_config.py`.
  40. OVERRIDE = 'T' + 'ODO: update check_includes_config.py'
  41. def any_re(res: Iterable[str]) -> Pattern:
  42. """Given a list of RE strings, return an RE to match any of them."""
  43. return re.compile('|'.join((f'({i})' for i in res)))
  44. def main():
  45. ignore_re = any_re(config.IGNORE)
  46. n = 0
  47. for line in sys.stdin:
  48. s = line.strip()
  49. m = _MATCH_SPLIT_RE.fullmatch(s)
  50. if not m:
  51. print(f"Unrecognized input '{s}'", file=sys.stderr)
  52. return 2
  53. if OVERRIDE in m.group('trailing'):
  54. continue
  55. filename, include = m.group('file', 'include')
  56. if ignore_re.search(filename):
  57. continue
  58. if include not in config.DENY:
  59. continue
  60. if include in config.ALLOW.get(filename, []):
  61. continue
  62. n += 1
  63. if n == 1:
  64. print('Disallowed:\n')
  65. line_number, directive = m.group('line', 'directive')
  66. print(f' {filename}:{line_number}: {directive}')
  67. if n > 0:
  68. print('\nIf a disallowed #include is legitimate, add an ALLOW rule to')
  69. print(f' {config.__file__}')
  70. print('and/or temporarily suppress this error by adding exactly')
  71. print(f' {OVERRIDE}')
  72. print('in a comment at the end of the #include.')
  73. return 1
  74. return 0
  75. if __name__ == '__main__':
  76. sys.exit(main())