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