| #!/usr/bin/env python3 | |
| # pylint: disable=R0903 | |
| # Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. | |
| # SPDX-License-Identifier: GPL-2.0 | |
| """ | |
| Parse ABI documentation and produce results from it. | |
| """ | |
| import argparse | |
| import logging | |
| import os | |
| import sys | |
| # Import Python modules | |
| LIB_DIR = "lib/abi" | |
| SRC_DIR = os.path.dirname(os.path.realpath(__file__)) | |
| sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) | |
| from abi_parser import AbiParser # pylint: disable=C0413 | |
| from abi_regex import AbiRegex # pylint: disable=C0413 | |
| from helpers import ABI_DIR, DEBUG_HELP # pylint: disable=C0413 | |
| from system_symbols import SystemSymbols # pylint: disable=C0413 | |
| # Command line classes | |
| REST_DESC = """ | |
| Produce output in ReST format. | |
| The output is done on two sections: | |
| - Symbols: show all parsed symbols in alphabetic order; | |
| - Files: cross reference the content of each file with the symbols on it. | |
| """ | |
| class AbiRest: | |
| """Initialize an argparse subparser for rest output""" | |
| def __init__(self, subparsers): | |
| """Initialize argparse subparsers""" | |
| parser = subparsers.add_parser("rest", | |
| formatter_class=argparse.RawTextHelpFormatter, | |
| description=REST_DESC) | |
| parser.add_argument("--enable-lineno", action="store_true", | |
| help="enable lineno") | |
| parser.add_argument("--raw", action="store_true", | |
| help="output text as contained in the ABI files. " | |
| "It not used, output will contain dynamically" | |
| " generated cross references when possible.") | |
| parser.add_argument("--no-file", action="store_true", | |
| help="Don't the files section") | |
| parser.add_argument("--show-hints", help="Show-hints") | |
| parser.set_defaults(func=self.run) | |
| def run(self, args): | |
| """Run subparser""" | |
| parser = AbiParser(args.dir, debug=args.debug) | |
| parser.parse_abi() | |
| parser.check_issues() | |
| for t in parser.doc(args.raw, not args.no_file): | |
| if args.enable_lineno: | |
| print (f".. LINENO {t[1]}#{t[2]}\n\n") | |
| print(t[0]) | |
| class AbiValidate: | |
| """Initialize an argparse subparser for ABI validation""" | |
| def __init__(self, subparsers): | |
| """Initialize argparse subparsers""" | |
| parser = subparsers.add_parser("validate", | |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter, | |
| description="list events") | |
| parser.set_defaults(func=self.run) | |
| def run(self, args): | |
| """Run subparser""" | |
| parser = AbiParser(args.dir, debug=args.debug) | |
| parser.parse_abi() | |
| parser.check_issues() | |
| class AbiSearch: | |
| """Initialize an argparse subparser for ABI search""" | |
| def __init__(self, subparsers): | |
| """Initialize argparse subparsers""" | |
| parser = subparsers.add_parser("search", | |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter, | |
| description="Search ABI using a regular expression") | |
| parser.add_argument("expression", | |
| help="Case-insensitive search pattern for the ABI symbol") | |
| parser.set_defaults(func=self.run) | |
| def run(self, args): | |
| """Run subparser""" | |
| parser = AbiParser(args.dir, debug=args.debug) | |
| parser.parse_abi() | |
| parser.search_symbols(args.expression) | |
| UNDEFINED_DESC=""" | |
| Check undefined ABIs on local machine. | |
| Read sysfs devnodes and check if the devnodes there are defined inside | |
| ABI documentation. | |
| The search logic tries to minimize the number of regular expressions to | |
| search per each symbol. | |
| By default, it runs on a single CPU, as Python support for CPU threads | |
| is still experimental, and multi-process runs on Python is very slow. | |
| On experimental tests, if the number of ABI symbols to search per devnode | |
| is contained on a limit of ~150 regular expressions, using a single CPU | |
| is a lot faster than using multiple processes. However, if the number of | |
| regular expressions to check is at the order of ~30000, using multiple | |
| CPUs speeds up the check. | |
| """ | |
| class AbiUndefined: | |
| """ | |
| Initialize an argparse subparser for logic to check undefined ABI at | |
| the current machine's sysfs | |
| """ | |
| def __init__(self, subparsers): | |
| """Initialize argparse subparsers""" | |
| parser = subparsers.add_parser("undefined", | |
| formatter_class=argparse.RawTextHelpFormatter, | |
| description=UNDEFINED_DESC) | |
| parser.add_argument("-S", "--sysfs-dir", default="/sys", | |
| help="directory where sysfs is mounted") | |
| parser.add_argument("-s", "--search-string", | |
| help="search string regular expression to limit symbol search") | |
| parser.add_argument("-H", "--show-hints", action="store_true", | |
| help="Hints about definitions for missing ABI symbols.") | |
| parser.add_argument("-j", "--jobs", "--max-workers", type=int, default=1, | |
| help="If bigger than one, enables multiprocessing.") | |
| parser.add_argument("-c", "--max-chunk-size", type=int, default=50, | |
| help="Maximum number of chunk size") | |
| parser.add_argument("-f", "--found", action="store_true", | |
| help="Also show found items. " | |
| "Helpful to debug the parser."), | |
| parser.add_argument("-d", "--dry-run", action="store_true", | |
| help="Don't actually search for undefined. " | |
| "Helpful to debug the parser."), | |
| parser.set_defaults(func=self.run) | |
| def run(self, args): | |
| """Run subparser""" | |
| abi = AbiRegex(args.dir, debug=args.debug, | |
| search_string=args.search_string) | |
| abi_symbols = SystemSymbols(abi=abi, hints=args.show_hints, | |
| sysfs=args.sysfs_dir) | |
| abi_symbols.check_undefined_symbols(dry_run=args.dry_run, | |
| found=args.found, | |
| max_workers=args.jobs, | |
| chunk_size=args.max_chunk_size) | |
| def main(): | |
| """Main program""" | |
| parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) | |
| parser.add_argument("-d", "--debug", type=int, default=0, help="debug level") | |
| parser.add_argument("-D", "--dir", default=ABI_DIR, help=DEBUG_HELP) | |
| subparsers = parser.add_subparsers() | |
| AbiRest(subparsers) | |
| AbiValidate(subparsers) | |
| AbiSearch(subparsers) | |
| AbiUndefined(subparsers) | |
| args = parser.parse_args() | |
| if args.debug: | |
| level = logging.DEBUG | |
| else: | |
| level = logging.INFO | |
| logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") | |
| if "func" in args: | |
| args.func(args) | |
| else: | |
| sys.exit(f"Please specify a valid command for {sys.argv[0]}") | |
| # Call main method | |
| if __name__ == "__main__": | |
| main() |