Skip to content

Instantly share code, notes, and snippets.

@dgelessus
Created February 16, 2026 21:23
Show Gist options
  • Select an option

  • Save dgelessus/d20e744dca85b35c798c9131b18ce2e2 to your computer and use it in GitHub Desktop.

Select an option

Save dgelessus/d20e744dca85b35c798c9131b18ce2e2 to your computer and use it in GitHub Desktop.
Simple script to find potentially interesting conditional macro names in a C/C++/etc. codebase
import collections
import os
import pathlib
import re
import sys
c_header_suffixes = {
".h",
".hpp",
}
c_suffixes = {
*c_header_suffixes,
".c",
".cpp",
".inl",
".m",
".mm",
".rc",
}
cmake_suffix = ".cmake"
c_header_suffixes_combined = {(suffix,) for suffix in c_header_suffixes}
c_header_suffixes_combined |= {(suffix, cmake_suffix) for suffix in c_header_suffixes}
c_suffixes_combined = {(suffix,) for suffix in c_suffixes}
c_suffixes_combined |= {(suffix, cmake_suffix) for suffix in c_suffixes}
ignored_dirs = {
"cmake-build",
"gtest",
"Scripts",
"vcpkg",
}
def find_ifdefs_in_file(file_path, stream, is_header):
include_guard_name = None
directive_count = 0
for line_number, line in enumerate(stream, 1):
line = line.lstrip()
# Intentionally also find commented out directives.
is_comment = False
if line.startswith("//"):
is_comment = True
line = line.lstrip("/").lstrip()
if not line.startswith("#"):
continue
line = line.removeprefix("#").lstrip()
split = line.split(maxsplit=1)
if not split:
continue
directive_name = split[0]
arg = split[1] if len(split) > 1 else ""
arg = re.sub(r"//.*$|/\*.*?\*/", " ", arg)
arg = arg.strip()
if not is_comment:
if is_header and directive_count == 0 and directive_name == "ifndef":
include_guard_name = arg
directive_count += 1
continue
elif include_guard_name is not None and directive_count == 1:
if directive_name != "define" or arg != include_guard_name:
include_guard_name = None
directive_count += 1
continue
if directive_name in {"ifdef", "ifndef", "elifdef", "elifndef"}:
yield line_number, arg
elif directive_name in {"if", "elif"}:
for match in re.finditer(r"(?:^|\W)([A-Za-z_]\w*)", arg):
macro = match[1]
if macro == "defined":
continue
yield line_number, macro
elif directive_name in {"cmakedefine", "define"}:
split = arg.split()
if split:
macro = split[0]
value = split[1] if len(split) > 1 else ""
if "(" not in macro and value in {"", "0", "1", "false", "true"}:
yield line_number, macro
if not is_comment:
directive_count += 1
if is_header and include_guard_name is None:
print(f"WARNING: Header {file_path} seems to have no include guard")
def find_ifdefs_in_dir(sources_root):
ifdefs = collections.defaultdict(lambda: collections.defaultdict(list))
for dir_path, dir_names, file_names in os.walk(sources_root):
for i in reversed(range(len(dir_names))):
dir_name = dir_names[i]
if dir_name.startswith(".") or dir_name in ignored_dirs:
del dir_names[i]
dir_path = pathlib.Path(dir_path)
for file_name in file_names:
file_path = dir_path / file_name
suffixes = tuple(file_path.suffixes)
if suffixes not in c_suffixes_combined:
continue
print(f".", end="", file=sys.stderr, flush=True)
with open(file_path, "r", encoding="utf-8", errors="replace") as stream:
for line_number, macro in find_ifdefs_in_file(file_path, stream, is_header=suffixes in c_header_suffixes_combined):
ifdefs[macro][file_path].append(line_number)
return ifdefs
def format_file_lines(file, line_numbers):
if len(line_numbers) == 1:
lines_desc = f"line {line_numbers[0]}"
else:
lines_desc = f"lines {line_numbers}"
return f"{file} at {lines_desc}"
def main():
_, sources_root = sys.argv
ifdefs = find_ifdefs_in_dir(sources_root)
print(file=sys.stderr, flush=True) # Newline to terminate the sequence of ...s
for macro, files in sorted(ifdefs.items()):
if len(files) == 1:
file, line_numbers = next(iter(files.items()))
print(f"macro {macro} in 1 file: {format_file_lines(file, line_numbers)}")
else:
print(f"macro {macro} in {len(files)} files:")
for file, line_numbers in sorted(files.items()):
print(f"\t{format_file_lines(file, line_numbers)}")
sys.exit(0)
if __name__ == "__main__":
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment