|  | #!/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() |