Skip to content

Instantly share code, notes, and snippets.

@MrKevinWeiss
Created February 29, 2024 10:19
Show Gist options
  • Save MrKevinWeiss/671f28b06eb0a478d5d2ce1640dc5bdf to your computer and use it in GitHub Desktop.
Save MrKevinWeiss/671f28b06eb0a478d5d2ce1640dc5bdf to your computer and use it in GitHub Desktop.
"""Script to try to identify missing configs in the kconfig files.
It is important to run this on a clean repo, I usually just clone fresh in /tmp
This prevents build artifacts and downloaded vendor/packages from getting scanned.
"""
import os
import pathlib
import argparse
import json
ignore_contained = {"DOXYGEN", "_PARAM", "_SAUL_INFO", "_DEV"}
ignore_starts_with = {
"BIG_ENDIAN",
"BIT" "CPU_",
"CLASS_IS_" "TEST_",
"HAS_",
"HAVE_",
"MAX",
}
def valid_config(line: str, warn: bool = False):
if not line.startswith("#ifndef "):
return False
if len(line.split()) < 2:
return False
config = line.split()[1]
if config.startswith("CONFIG_"):
return config
if config.endswith("_H"):
return False
if config.endswith("_HPP"):
return False
if config.startswith("_"):
return False
if config.startswith("MODULE_"):
return False
if any(ignore_configs in config for ignore_configs in ignore_contained):
return False
if any(config.startswith(ignore_configs) for ignore_configs in ignore_starts_with):
return False
if any(c.islower() for c in config):
if warn:
print(f"Warning: {config}.")
return False
return config
def forward_search(lines, config):
ind_count = 1
for idx, line in enumerate(lines):
if line.startswith("#ifndef"):
ind_count += 1
if line.startswith("#endif"):
ind_count -= 1
if ind_count == 0:
return False
if config in line:
return True
def main():
parser = argparse.ArgumentParser(
description="Scan through repo to find which configs are missing from kconfig."
)
parser.add_argument(
"dir", type=pathlib.Path, help="Path to the base repo directory."
)
parser.add_argument(
"--print-matching",
"-m",
action="store_true",
help="print all configs that are both identified in header files and in Kconfig.",
)
parser.add_argument(
"--print-unidentified",
"-i",
action="store_true",
help="print all unidentified configs.",
)
parser.add_argument(
"--print-missing",
"-s",
action="store_true",
help="print all configs that must be implemented in Kconfig.",
)
parser.add_argument(
"--show-warnings", "-w", action="store_true", help="print all warnings."
)
args = parser.parse_args()
board_defs = set()
for root, dirs, files in os.walk(args.dir):
for file in files:
if file.endswith("board.h") or file.endswith("periph_conf.h"):
with open(os.path.join(root, file), "r") as f:
lines = f.readlines()
for idx, line in enumerate(lines):
if not line.startswith("#define "):
continue
if len(line.split()) < 2:
continue
board_defs.add(line.split()[1])
# scan through all .h files in the repo for any line that starts with "#define CONFIG_"
# and store the config names in a set
good_configs = set()
bad_configs = set()
for root, dirs, files in os.walk(args.dir):
for file in files:
if file.endswith(".h") or file.endswith(".hpp"):
with open(os.path.join(root, file), "r") as f:
lines = f.readlines()
for idx, line in enumerate(lines):
config = valid_config(line, args.show_warnings)
if config == False:
continue
if config in board_defs:
continue
if config.startswith("CONFIG_"):
good_configs.add(config)
continue
if not forward_search(lines[idx:], config):
continue
bad_configs.add(config)
# # Search for up to 20 lines forward for an "#endif" and if there is a the config is within the block add it
# for i in range(5):
# line = f.readline()
# # if this line starts with "#ifdef" then we need to go back one line
# if line.startswith("#ifdef"):
# print(f"ERROR: {config} in {file} skipped one.")
# break
# if line.startswith("#endif"):
# break
# if line.startswith("#define CONFIG_"):
# config = line.split()[1]
# if len(config) < 2:
# continue
# if config.endswith("_H"):
# continue
# if "_PARAM" in config:
# continue
# if config.startswith("_"):
# continue
# if config.startswith("MODULE_"):
# continue
# if config.startswith("CONFIG_"):
# good_configs.add(config)
# else:
# bad_configs.add(config)
# Not scan through all Kconfig* files in the repo for any line that starts
# with "config " or "menuconfig " or "choice " and store the config names in a set
kconfig_configs = set()
for root, dirs, files in os.walk(args.dir):
for file in files:
if file.startswith("Kconfig"):
with open(os.path.join(root, file), "r") as f:
for line in f:
if (
line.startswith("config ")
or line.startswith("menuconfig ")
or line.startswith("choice ")
):
config = line.split()[1]
if len(config) < 2:
continue
kconfig_configs.add(f"CONFIG_{config.upper()}")
if args.print_matching:
print(json.dumps(sorted(list((good_configs & kconfig_configs))), indent=4))
if args.print_unidentified:
print(json.dumps(sorted(list((bad_configs))), indent=4))
if args.print_missing:
print(json.dumps(sorted(list((good_configs - kconfig_configs))), indent=4))
print(
f"Found {len(good_configs - kconfig_configs)} of {len(good_configs)} "
f"({len(good_configs - kconfig_configs) / len(good_configs) * 100 :.2f}%) "
"identified configs not yet in kconfig."
)
print(f"Found {len(bad_configs)} unidentified configs in the repo.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment