| #!/usr/bin/env python3 | |
| # Copyright (C) 2024-2025 Free Software Foundation, Inc. | |
| # | |
| # This file is part of GDB. | |
| # | |
| # This program is free software; you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation; either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| # This is intended to be run from pre-commit. You can also run it by | |
| # hand by passing repository-relative filenames to it, like: | |
| # ./gdb/check-include-guards.py [--update] gdb/*.h | |
| # When --update is used, rewrite the files in place as needed. | |
| import re | |
| import sys | |
| from typing import List | |
| DEF = re.compile("^#ifndef ([A-Za-z0-9_]+)\n") | |
| OLDDEF = re.compile("^#if !defined *\\(([A-Za-z0-9_]+)\\)\n") | |
| # Some headers -- in particular, ones that aren't maintained by gdb -- | |
| # should be excluded from the checks. | |
| EXCLUDED = frozenset(["gdbsupport/unordered_dense.h"]) | |
| # See if | |
| write_files = False | |
| args = sys.argv[1:] | |
| if len(args) > 0 and args[0] == "--update": | |
| write_files = True | |
| args = args[1:] | |
| def failure(filename: str, ndx: int, text: str): | |
| print(filename + ":" + str(ndx + 1) + ": " + text, file=sys.stderr) | |
| sys.exit(1) | |
| def skip_comments_and_blanks(ndx: int, contents: List[str]): | |
| while ndx < len(contents) and contents[ndx].startswith("/*"): | |
| while ndx < len(contents): | |
| ndx += 1 | |
| if contents[ndx - 1].endswith("*/\n"): | |
| break | |
| # Skip blank lines. | |
| while ndx < len(contents): | |
| if contents[ndx].strip() != "": | |
| break | |
| ndx += 1 | |
| return ndx | |
| def write_header(filename: str, contents: List[str]): | |
| with open(filename, "w") as f: | |
| f.writelines(contents) | |
| def check_header(filename: str): | |
| if filename in EXCLUDED: | |
| return | |
| # Turn x/y-z.h into X_Y_Z_H. | |
| assert filename.endswith(".h") | |
| expected = filename.replace("-", "_") | |
| expected = expected.replace(".", "_") | |
| expected = expected.replace("/", "_") | |
| expected = expected.upper() | |
| with open(filename) as f: | |
| contents = list(f) | |
| if len(contents) == 0: | |
| # Empty file -- pathological but we can just ignore rather | |
| # than crashing. | |
| return | |
| if "THIS FILE IS GENERATED" in contents[0]: | |
| # Ignore. | |
| return | |
| if not contents[0].startswith("/*"): | |
| failure(filename, 0, "header should start with comment") | |
| i = skip_comments_and_blanks(0, contents) | |
| if i == len(contents): | |
| failure(filename, i, "unterminated intro comment or missing body") | |
| m = DEF.match(contents[i]) | |
| force_rewrite = False | |
| if not m: | |
| m = OLDDEF.match(contents[i]) | |
| if not m: | |
| failure(filename, i, "no header guard") | |
| force_rewrite = True | |
| symbol = m.group(1) | |
| # Either None or a tuple like (LINE, TEXT) that describes a needed | |
| # update. | |
| updated = None | |
| if symbol != expected: | |
| force_rewrite = True | |
| if force_rewrite: | |
| contents[i] = "#ifndef " + expected + "\n" | |
| updated = (i, "wrong symbol in ifndef") | |
| i += 1 | |
| if i == len(contents): | |
| failure(filename, i, "premature EOF") | |
| if not contents[i].startswith("#define "): | |
| failure(filename, i, "no define of header guard") | |
| if contents[i] != "#define " + expected + "\n": | |
| contents[i] = "#define " + expected + "\n" | |
| if updated is None: | |
| updated = (i, "wrong symbol in define") | |
| i = len(contents) - 1 | |
| if not contents[i].startswith("#endif"): | |
| failure(filename, i, "no trailing endif") | |
| if contents[i] != "#endif /* " + expected + " */\n": | |
| contents[i] = "#endif /* " + expected + " */\n" | |
| if updated is None: | |
| updated = (i, "wrong endif line") | |
| if updated is not None: | |
| if write_files: | |
| write_header(filename, contents) | |
| else: | |
| failure(filename, *updated) | |
| for filename in args: | |
| check_header(filename) |