Created
February 29, 2024 10:19
-
-
Save MrKevinWeiss/671f28b06eb0a478d5d2ce1640dc5bdf to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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