|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# |
| 3 | +# Copyright (c) 2018-2022 The Bitcoin Core developers |
| 4 | +# Distributed under the MIT software license, see the accompanying |
| 5 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 6 | +# |
| 7 | +# Check for duplicate includes. |
| 8 | +# Guard against accidental introduction of new Boost dependencies. |
| 9 | +# Check includes: Check for duplicate includes. Enforce bracket syntax includes. |
| 10 | + |
| 11 | +import os |
| 12 | +import re |
| 13 | +import sys |
| 14 | + |
| 15 | +from subprocess import check_output, CalledProcessError |
| 16 | + |
| 17 | + |
| 18 | +EXCLUDED_DIRS = ["src/leveldb/", |
| 19 | + "src/crc32c/", |
| 20 | + "src/secp256k1/", |
| 21 | + "src/minisketch/", |
| 22 | + "src/univalue/"] |
| 23 | + |
| 24 | +EXPECTED_BOOST_INCLUDES = ["boost/algorithm/string.hpp", |
| 25 | + "boost/algorithm/string/classification.hpp", |
| 26 | + "boost/algorithm/string/replace.hpp", |
| 27 | + "boost/algorithm/string/split.hpp", |
| 28 | + "boost/date_time/posix_time/posix_time.hpp", |
| 29 | + "boost/multi_index/hashed_index.hpp", |
| 30 | + "boost/multi_index/ordered_index.hpp", |
| 31 | + "boost/multi_index/sequenced_index.hpp", |
| 32 | + "boost/multi_index_container.hpp", |
| 33 | + "boost/process.hpp", |
| 34 | + "boost/signals2/connection.hpp", |
| 35 | + "boost/signals2/optional_last_value.hpp", |
| 36 | + "boost/signals2/signal.hpp", |
| 37 | + "boost/test/included/unit_test.hpp", |
| 38 | + "boost/test/unit_test.hpp"] |
| 39 | + |
| 40 | + |
| 41 | +def get_toplevel(): |
| 42 | + return check_output(["git", "rev-parse", "--show-toplevel"], universal_newlines=True, encoding="utf8").rstrip("\n") |
| 43 | + |
| 44 | + |
| 45 | +def list_files_by_suffix(suffixes): |
| 46 | + exclude_args = [":(exclude)" + dir for dir in EXCLUDED_DIRS] |
| 47 | + |
| 48 | + files_list = check_output(["git", "ls-files", "src"] + exclude_args, universal_newlines=True, encoding="utf8").splitlines() |
| 49 | + |
| 50 | + return [file for file in files_list if file.endswith(suffixes)] |
| 51 | + |
| 52 | + |
| 53 | +def find_duplicate_includes(include_list): |
| 54 | + tempset = set() |
| 55 | + duplicates = set() |
| 56 | + |
| 57 | + for inclusion in include_list: |
| 58 | + if inclusion in tempset: |
| 59 | + duplicates.add(inclusion) |
| 60 | + else: |
| 61 | + tempset.add(inclusion) |
| 62 | + |
| 63 | + return duplicates |
| 64 | + |
| 65 | + |
| 66 | +def find_included_cpps(): |
| 67 | + included_cpps = list() |
| 68 | + |
| 69 | + try: |
| 70 | + included_cpps = check_output(["git", "grep", "-E", r"^#include [<\"][^>\"]+\.cpp[>\"]", "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8").splitlines() |
| 71 | + except CalledProcessError as e: |
| 72 | + if e.returncode > 1: |
| 73 | + raise e |
| 74 | + |
| 75 | + return included_cpps |
| 76 | + |
| 77 | + |
| 78 | +def find_extra_boosts(): |
| 79 | + included_boosts = list() |
| 80 | + filtered_included_boost_set = set() |
| 81 | + exclusion_set = set() |
| 82 | + |
| 83 | + try: |
| 84 | + included_boosts = check_output(["git", "grep", "-E", r"^#include <boost/", "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8").splitlines() |
| 85 | + except CalledProcessError as e: |
| 86 | + if e.returncode > 1: |
| 87 | + raise e |
| 88 | + |
| 89 | + for boost in included_boosts: |
| 90 | + filtered_included_boost_set.add(re.findall(r'(?<=\<).+?(?=\>)', boost)[0]) |
| 91 | + |
| 92 | + for expected_boost in EXPECTED_BOOST_INCLUDES: |
| 93 | + for boost in filtered_included_boost_set: |
| 94 | + if expected_boost in boost: |
| 95 | + exclusion_set.add(boost) |
| 96 | + |
| 97 | + extra_boosts = set(filtered_included_boost_set.difference(exclusion_set)) |
| 98 | + |
| 99 | + return extra_boosts |
| 100 | + |
| 101 | + |
| 102 | +def find_quote_syntax_inclusions(): |
| 103 | + exclude_args = [":(exclude)" + dir for dir in EXCLUDED_DIRS] |
| 104 | + quote_syntax_inclusions = list() |
| 105 | + |
| 106 | + try: |
| 107 | + quote_syntax_inclusions = check_output(["git", "grep", r"^#include \"", "--", "*.cpp", "*.h"] + exclude_args, universal_newlines=True, encoding="utf8").splitlines() |
| 108 | + except CalledProcessError as e: |
| 109 | + if e.returncode > 1: |
| 110 | + raise e |
| 111 | + |
| 112 | + return quote_syntax_inclusions |
| 113 | + |
| 114 | + |
| 115 | +def main(): |
| 116 | + exit_code = 0 |
| 117 | + |
| 118 | + os.chdir(get_toplevel()) |
| 119 | + |
| 120 | + # Check for duplicate includes |
| 121 | + for filename in list_files_by_suffix((".cpp", ".h")): |
| 122 | + with open(filename, "r", encoding="utf8") as file: |
| 123 | + include_list = [line.rstrip("\n") for line in file if re.match(r"^#include", line)] |
| 124 | + |
| 125 | + duplicates = find_duplicate_includes(include_list) |
| 126 | + |
| 127 | + if duplicates: |
| 128 | + print(f"Duplicate include(s) in {filename}:") |
| 129 | + for duplicate in duplicates: |
| 130 | + print(duplicate) |
| 131 | + print("") |
| 132 | + exit_code = 1 |
| 133 | + |
| 134 | + # Check if code includes .cpp-files |
| 135 | + included_cpps = find_included_cpps() |
| 136 | + |
| 137 | + if included_cpps: |
| 138 | + print("The following files #include .cpp files:") |
| 139 | + for included_cpp in included_cpps: |
| 140 | + print(included_cpp) |
| 141 | + print("") |
| 142 | + exit_code = 1 |
| 143 | + |
| 144 | + # Guard against accidental introduction of new Boost dependencies |
| 145 | + extra_boosts = find_extra_boosts() |
| 146 | + |
| 147 | + if extra_boosts: |
| 148 | + for boost in extra_boosts: |
| 149 | + print(f"A new Boost dependency in the form of \"{boost}\" appears to have been introduced:") |
| 150 | + print(check_output(["git", "grep", boost, "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8")) |
| 151 | + exit_code = 1 |
| 152 | + |
| 153 | + # Check if Boost dependencies are no longer used |
| 154 | + for expected_boost in EXPECTED_BOOST_INCLUDES: |
| 155 | + try: |
| 156 | + check_output(["git", "grep", "-q", r"^#include <%s>" % expected_boost, "--", "*.cpp", "*.h"], universal_newlines=True, encoding="utf8") |
| 157 | + except CalledProcessError as e: |
| 158 | + if e.returncode > 1: |
| 159 | + raise e |
| 160 | + else: |
| 161 | + print(f"Good job! The Boost dependency \"{expected_boost}\" is no longer used. " |
| 162 | + "Please remove it from EXPECTED_BOOST_INCLUDES in test/lint/lint-includes.py " |
| 163 | + "to make sure this dependency is not accidentally reintroduced.\n") |
| 164 | + exit_code = 1 |
| 165 | + |
| 166 | + # Enforce bracket syntax includes |
| 167 | + quote_syntax_inclusions = find_quote_syntax_inclusions() |
| 168 | + |
| 169 | + if quote_syntax_inclusions: |
| 170 | + print("Please use bracket syntax includes (\"#include <foo.h>\") instead of quote syntax includes:") |
| 171 | + for quote_syntax_inclusion in quote_syntax_inclusions: |
| 172 | + print(quote_syntax_inclusion) |
| 173 | + exit_code = 1 |
| 174 | + |
| 175 | + sys.exit(exit_code) |
| 176 | + |
| 177 | + |
| 178 | +if __name__ == "__main__": |
| 179 | + main() |
0 commit comments