blob: 40f8e757eb6d695ecfccc35d5f336b0fcfaec95e [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright (c) 2022 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Check #include files.
Reads from standard input the output of a `grep -n` for `#include`s;
see `check_includes.sh`.
Uses the conditions defined in `check_includes_config.py`.
"""
import re
import sys
from typing import Iterable, Pattern
import check_includes_config as config
# The input comes from `grep -n` and has the form
# filename:line:include-directive
#
# This RE does not handle C-style comments before the file name.
# So don't do that.
_MATCH_SPLIT_RE = re.compile(r"""
(?P<file>.+)
: (?P<line>\d+)
: (?P<directive>
\s* \# \s* include \s* (?P<type>[<"]) (?P<include>[^">]+) [">])
(?P<trailing> .*)
""", re.VERBOSE)
# Allow a temporary override so that a PR will not be blocked
# on first updating `check_includes_config.py`.
OVERRIDE = 'T' + 'ODO: update check_includes_config.py'
def any_re(res: Iterable[str]) -> Pattern:
"""Given a list of RE strings, return an RE to match any of them."""
return re.compile('|'.join((f'({i})' for i in res)))
def main():
ignore_re = any_re(config.IGNORE)
n = 0
for line in sys.stdin:
s = line.strip()
m = _MATCH_SPLIT_RE.fullmatch(s)
if not m:
print(f"Unrecognized input '{s}'", file=sys.stderr)
return 2
if OVERRIDE in m.group('trailing'):
continue
filename, include = m.group('file', 'include')
if ignore_re.search(filename):
continue
if include not in config.DENY:
continue
if include in config.ALLOW.get(filename, []):
continue
n += 1
if n == 1:
print('Disallowed:\n')
line_number, directive = m.group('line', 'directive')
print(f' {filename}:{line_number}: {directive}')
if n > 0:
print('\nIf a disallowed #include is legitimate, add an ALLOW rule to')
print(f' {config.__file__}')
print('and/or temporarily suppress this error by adding exactly')
print(f' {OVERRIDE}')
print('in a comment at the end of the #include.')
return 1
return 0
if __name__ == '__main__':
sys.exit(main())