1#!/usr/bin/env python3 2# pylint: disable=R0903 3# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>. 4# SPDX-License-Identifier: GPL-2.0 5 6""" 7Parse ABI documentation and produce results from it. 8""" 9 10import argparse 11import logging 12import os 13import sys 14 15# Import Python modules 16 17LIB_DIR = "lib/abi" 18SRC_DIR = os.path.dirname(os.path.realpath(__file__)) 19 20sys.path.insert(0, os.path.join(SRC_DIR, LIB_DIR)) 21 22from abi_parser import AbiParser # pylint: disable=C0413 23from abi_regex import AbiRegex # pylint: disable=C0413 24from helpers import ABI_DIR, DEBUG_HELP # pylint: disable=C0413 25from system_symbols import SystemSymbols # pylint: disable=C0413 26 27# Command line classes 28 29 30REST_DESC = """ 31Produce output in ReST format. 32 33The output is done on two sections: 34 35- Symbols: show all parsed symbols in alphabetic order; 36- Files: cross reference the content of each file with the symbols on it. 37""" 38 39class AbiRest: 40 """Initialize an argparse subparser for rest output""" 41 42 def __init__(self, subparsers): 43 """Initialize argparse subparsers""" 44 45 parser = subparsers.add_parser("rest", 46 formatter_class=argparse.RawTextHelpFormatter, 47 description=REST_DESC) 48 49 parser.add_argument("--enable-lineno", action="store_true", 50 help="enable lineno") 51 parser.add_argument("--raw", action="store_true", 52 help="output text as contained in the ABI files. " 53 "It not used, output will contain dynamically" 54 " generated cross references when possible.") 55 parser.add_argument("--no-file", action="store_true", 56 help="Don't the files section") 57 parser.add_argument("--show-hints", help="Show-hints") 58 59 parser.set_defaults(func=self.run) 60 61 def run(self, args): 62 """Run subparser""" 63 64 parser = AbiParser(args.dir, debug=args.debug) 65 parser.parse_abi() 66 parser.check_issues() 67 68 for t in parser.doc(args.raw, not args.no_file): 69 if args.enable_lineno: 70 print (f".. LINENO {t[1]}#{t[2]}\n\n") 71 72 print(t[0]) 73 74class AbiValidate: 75 """Initialize an argparse subparser for ABI validation""" 76 77 def __init__(self, subparsers): 78 """Initialize argparse subparsers""" 79 80 parser = subparsers.add_parser("validate", 81 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 82 description="list events") 83 84 parser.set_defaults(func=self.run) 85 86 def run(self, args): 87 """Run subparser""" 88 89 parser = AbiParser(args.dir, debug=args.debug) 90 parser.parse_abi() 91 parser.check_issues() 92 93 94class AbiSearch: 95 """Initialize an argparse subparser for ABI search""" 96 97 def __init__(self, subparsers): 98 """Initialize argparse subparsers""" 99 100 parser = subparsers.add_parser("search", 101 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 102 description="Search ABI using a regular expression") 103 104 parser.add_argument("expression", 105 help="Case-insensitive search pattern for the ABI symbol") 106 107 parser.set_defaults(func=self.run) 108 109 def run(self, args): 110 """Run subparser""" 111 112 parser = AbiParser(args.dir, debug=args.debug) 113 parser.parse_abi() 114 parser.search_symbols(args.expression) 115 116UNDEFINED_DESC=""" 117Check undefined ABIs on local machine. 118 119Read sysfs devnodes and check if the devnodes there are defined inside 120ABI documentation. 121 122The search logic tries to minimize the number of regular expressions to 123search per each symbol. 124 125By default, it runs on a single CPU, as Python support for CPU threads 126is still experimental, and multi-process runs on Python is very slow. 127 128On experimental tests, if the number of ABI symbols to search per devnode 129is contained on a limit of ~150 regular expressions, using a single CPU 130is a lot faster than using multiple processes. However, if the number of 131regular expressions to check is at the order of ~30000, using multiple 132CPUs speeds up the check. 133""" 134 135class AbiUndefined: 136 """ 137 Initialize an argparse subparser for logic to check undefined ABI at 138 the current machine's sysfs 139 """ 140 141 def __init__(self, subparsers): 142 """Initialize argparse subparsers""" 143 144 parser = subparsers.add_parser("undefined", 145 formatter_class=argparse.RawTextHelpFormatter, 146 description=UNDEFINED_DESC) 147 148 parser.add_argument("-S", "--sysfs-dir", default="/sys", 149 help="directory where sysfs is mounted") 150 parser.add_argument("-s", "--search-string", 151 help="search string regular expression to limit symbol search") 152 parser.add_argument("-H", "--show-hints", action="store_true", 153 help="Hints about definitions for missing ABI symbols.") 154 parser.add_argument("-j", "--jobs", "--max-workers", type=int, default=1, 155 help="If bigger than one, enables multiprocessing.") 156 parser.add_argument("-c", "--max-chunk-size", type=int, default=50, 157 help="Maximum number of chunk size") 158 parser.add_argument("-f", "--found", action="store_true", 159 help="Also show found items. " 160 "Helpful to debug the parser."), 161 parser.add_argument("-d", "--dry-run", action="store_true", 162 help="Don't actually search for undefined. " 163 "Helpful to debug the parser."), 164 165 parser.set_defaults(func=self.run) 166 167 def run(self, args): 168 """Run subparser""" 169 170 abi = AbiRegex(args.dir, debug=args.debug, 171 search_string=args.search_string) 172 173 abi_symbols = SystemSymbols(abi=abi, hints=args.show_hints, 174 sysfs=args.sysfs_dir) 175 176 abi_symbols.check_undefined_symbols(dry_run=args.dry_run, 177 found=args.found, 178 max_workers=args.jobs, 179 chunk_size=args.max_chunk_size) 180 181 182def main(): 183 """Main program""" 184 185 parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) 186 187 parser.add_argument("-d", "--debug", type=int, default=0, help="debug level") 188 parser.add_argument("-D", "--dir", default=ABI_DIR, help=DEBUG_HELP) 189 190 subparsers = parser.add_subparsers() 191 192 AbiRest(subparsers) 193 AbiValidate(subparsers) 194 AbiSearch(subparsers) 195 AbiUndefined(subparsers) 196 197 args = parser.parse_args() 198 199 if args.debug: 200 level = logging.DEBUG 201 else: 202 level = logging.INFO 203 204 logging.basicConfig(level=level, format="[%(levelname)s] %(message)s") 205 206 if "func" in args: 207 args.func(args) 208 else: 209 sys.exit(f"Please specify a valid command for {sys.argv[0]}") 210 211 212# Call main method 213if __name__ == "__main__": 214 main() 215