xref: /linux-6.15/scripts/lib/abi/abi_parser.py (revision de61d651)
1484e9aa6SMauro Carvalho Chehab#!/usr/bin/env python3
2484e9aa6SMauro Carvalho Chehab# pylint: disable=R0902,R0903,R0911,R0912,R0913,R0914,R0915,R0917,C0302
3484e9aa6SMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <[email protected]>.
4484e9aa6SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
5484e9aa6SMauro Carvalho Chehab
6484e9aa6SMauro Carvalho Chehab"""
7484e9aa6SMauro Carvalho ChehabParse ABI documentation and produce results from it.
8484e9aa6SMauro Carvalho Chehab"""
9484e9aa6SMauro Carvalho Chehab
10484e9aa6SMauro Carvalho Chehabfrom argparse import Namespace
11484e9aa6SMauro Carvalho Chehabimport logging
12484e9aa6SMauro Carvalho Chehabimport os
13484e9aa6SMauro Carvalho Chehabimport re
14484e9aa6SMauro Carvalho Chehab
15484e9aa6SMauro Carvalho Chehabfrom pprint import pformat
16484e9aa6SMauro Carvalho Chehabfrom random import randrange, seed
17484e9aa6SMauro Carvalho Chehab
18484e9aa6SMauro Carvalho Chehab# Import Python modules
19484e9aa6SMauro Carvalho Chehab
20484e9aa6SMauro Carvalho Chehabfrom helpers import AbiDebug, ABI_DIR
21484e9aa6SMauro Carvalho Chehab
22484e9aa6SMauro Carvalho Chehab
23484e9aa6SMauro Carvalho Chehabclass AbiParser:
24484e9aa6SMauro Carvalho Chehab    """Main class to parse ABI files"""
25484e9aa6SMauro Carvalho Chehab
26484e9aa6SMauro Carvalho Chehab    TAGS = r"(what|where|date|kernelversion|contact|description|users)"
27484e9aa6SMauro Carvalho Chehab    XREF = r"(?:^|\s|\()(\/(?:sys|config|proc|dev|kvd)\/[^,.:;\)\s]+)(?:[,.:;\)\s]|\Z)"
28484e9aa6SMauro Carvalho Chehab
29484e9aa6SMauro Carvalho Chehab    def __init__(self, directory, logger=None,
30484e9aa6SMauro Carvalho Chehab                 enable_lineno=False, show_warnings=True, debug=0):
31484e9aa6SMauro Carvalho Chehab        """Stores arguments for the class and initialize class vars"""
32484e9aa6SMauro Carvalho Chehab
33484e9aa6SMauro Carvalho Chehab        self.directory = directory
34484e9aa6SMauro Carvalho Chehab        self.enable_lineno = enable_lineno
35484e9aa6SMauro Carvalho Chehab        self.show_warnings = show_warnings
36484e9aa6SMauro Carvalho Chehab        self.debug = debug
37484e9aa6SMauro Carvalho Chehab
38484e9aa6SMauro Carvalho Chehab        if not logger:
39484e9aa6SMauro Carvalho Chehab            self.log = logging.getLogger("get_abi")
40484e9aa6SMauro Carvalho Chehab        else:
41484e9aa6SMauro Carvalho Chehab            self.log = logger
42484e9aa6SMauro Carvalho Chehab
43484e9aa6SMauro Carvalho Chehab        self.data = {}
44484e9aa6SMauro Carvalho Chehab        self.what_symbols = {}
45484e9aa6SMauro Carvalho Chehab        self.file_refs = {}
46484e9aa6SMauro Carvalho Chehab        self.what_refs = {}
47484e9aa6SMauro Carvalho Chehab
48c67c3fbdSMauro Carvalho Chehab        # Ignore files that contain such suffixes
49c67c3fbdSMauro Carvalho Chehab        self.ignore_suffixes = (".rej", ".org", ".orig", ".bak", "~")
50c67c3fbdSMauro Carvalho Chehab
51484e9aa6SMauro Carvalho Chehab        # Regular expressions used on parser
52c67c3fbdSMauro Carvalho Chehab        self.re_abi_dir = re.compile(r"(.*)" + ABI_DIR)
53484e9aa6SMauro Carvalho Chehab        self.re_tag = re.compile(r"(\S+)(:\s*)(.*)", re.I)
54484e9aa6SMauro Carvalho Chehab        self.re_valid = re.compile(self.TAGS)
55484e9aa6SMauro Carvalho Chehab        self.re_start_spc = re.compile(r"(\s*)(\S.*)")
56484e9aa6SMauro Carvalho Chehab        self.re_whitespace = re.compile(r"^\s+")
57484e9aa6SMauro Carvalho Chehab
58484e9aa6SMauro Carvalho Chehab        # Regular used on print
59484e9aa6SMauro Carvalho Chehab        self.re_what = re.compile(r"(\/?(?:[\w\-]+\/?){1,2})")
60484e9aa6SMauro Carvalho Chehab        self.re_escape = re.compile(r"([\.\x01-\x08\x0e-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])")
61484e9aa6SMauro Carvalho Chehab        self.re_unprintable = re.compile(r"([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff]+)")
62484e9aa6SMauro Carvalho Chehab        self.re_title_mark = re.compile(r"\n[\-\*\=\^\~]+\n")
63484e9aa6SMauro Carvalho Chehab        self.re_doc = re.compile(r"Documentation/(?!devicetree)(\S+)\.rst")
64484e9aa6SMauro Carvalho Chehab        self.re_abi = re.compile(r"(Documentation/ABI/)([\w\/\-]+)")
65484e9aa6SMauro Carvalho Chehab        self.re_xref_node = re.compile(self.XREF)
66484e9aa6SMauro Carvalho Chehab
67484e9aa6SMauro Carvalho Chehab    def warn(self, fdata, msg, extra=None):
68484e9aa6SMauro Carvalho Chehab        """Displays a parse error if warning is enabled"""
69484e9aa6SMauro Carvalho Chehab
70484e9aa6SMauro Carvalho Chehab        if not self.show_warnings:
71484e9aa6SMauro Carvalho Chehab            return
72484e9aa6SMauro Carvalho Chehab
73484e9aa6SMauro Carvalho Chehab        msg = f"{fdata.fname}:{fdata.ln}: {msg}"
74484e9aa6SMauro Carvalho Chehab        if extra:
75484e9aa6SMauro Carvalho Chehab            msg += "\n\t\t" + extra
76484e9aa6SMauro Carvalho Chehab
77484e9aa6SMauro Carvalho Chehab        self.log.warning(msg)
78484e9aa6SMauro Carvalho Chehab
79484e9aa6SMauro Carvalho Chehab    def add_symbol(self, what, fname, ln=None, xref=None):
80484e9aa6SMauro Carvalho Chehab        """Create a reference table describing where each 'what' is located"""
81484e9aa6SMauro Carvalho Chehab
82484e9aa6SMauro Carvalho Chehab        if what not in self.what_symbols:
83484e9aa6SMauro Carvalho Chehab            self.what_symbols[what] = {"file": {}}
84484e9aa6SMauro Carvalho Chehab
85484e9aa6SMauro Carvalho Chehab        if fname not in self.what_symbols[what]["file"]:
86484e9aa6SMauro Carvalho Chehab            self.what_symbols[what]["file"][fname] = []
87484e9aa6SMauro Carvalho Chehab
88484e9aa6SMauro Carvalho Chehab        if ln and ln not in self.what_symbols[what]["file"][fname]:
89484e9aa6SMauro Carvalho Chehab            self.what_symbols[what]["file"][fname].append(ln)
90484e9aa6SMauro Carvalho Chehab
91484e9aa6SMauro Carvalho Chehab        if xref:
92484e9aa6SMauro Carvalho Chehab            self.what_symbols[what]["xref"] = xref
93484e9aa6SMauro Carvalho Chehab
94484e9aa6SMauro Carvalho Chehab    def _parse_line(self, fdata, line):
95484e9aa6SMauro Carvalho Chehab        """Parse a single line of an ABI file"""
96484e9aa6SMauro Carvalho Chehab
97484e9aa6SMauro Carvalho Chehab        new_what = False
98484e9aa6SMauro Carvalho Chehab        new_tag = False
99484e9aa6SMauro Carvalho Chehab        content = None
100484e9aa6SMauro Carvalho Chehab
101484e9aa6SMauro Carvalho Chehab        match = self.re_tag.match(line)
102484e9aa6SMauro Carvalho Chehab        if match:
103484e9aa6SMauro Carvalho Chehab            new = match.group(1).lower()
104484e9aa6SMauro Carvalho Chehab            sep = match.group(2)
105484e9aa6SMauro Carvalho Chehab            content = match.group(3)
106484e9aa6SMauro Carvalho Chehab
107484e9aa6SMauro Carvalho Chehab            match = self.re_valid.search(new)
108484e9aa6SMauro Carvalho Chehab            if match:
109484e9aa6SMauro Carvalho Chehab                new_tag = match.group(1)
110484e9aa6SMauro Carvalho Chehab            else:
111484e9aa6SMauro Carvalho Chehab                if fdata.tag == "description":
112484e9aa6SMauro Carvalho Chehab                    # New "tag" is actually part of description.
113484e9aa6SMauro Carvalho Chehab                    # Don't consider it a tag
114484e9aa6SMauro Carvalho Chehab                    new_tag = False
115484e9aa6SMauro Carvalho Chehab                elif fdata.tag != "":
116484e9aa6SMauro Carvalho Chehab                    self.warn(fdata, f"tag '{fdata.tag}' is invalid", line)
117484e9aa6SMauro Carvalho Chehab
118484e9aa6SMauro Carvalho Chehab        if new_tag:
119484e9aa6SMauro Carvalho Chehab            # "where" is Invalid, but was a common mistake. Warn if found
120484e9aa6SMauro Carvalho Chehab            if new_tag == "where":
121484e9aa6SMauro Carvalho Chehab                self.warn(fdata, "tag 'Where' is invalid. Should be 'What:' instead")
122484e9aa6SMauro Carvalho Chehab                new_tag = "what"
123484e9aa6SMauro Carvalho Chehab
124484e9aa6SMauro Carvalho Chehab            if new_tag == "what":
125484e9aa6SMauro Carvalho Chehab                fdata.space = None
126484e9aa6SMauro Carvalho Chehab
127484e9aa6SMauro Carvalho Chehab                if content not in self.what_symbols:
128484e9aa6SMauro Carvalho Chehab                    self.add_symbol(what=content, fname=fdata.fname, ln=fdata.ln)
129484e9aa6SMauro Carvalho Chehab
130484e9aa6SMauro Carvalho Chehab                if fdata.tag == "what":
131484e9aa6SMauro Carvalho Chehab                    fdata.what.append(content.strip("\n"))
132484e9aa6SMauro Carvalho Chehab                else:
133484e9aa6SMauro Carvalho Chehab                    if fdata.key:
134484e9aa6SMauro Carvalho Chehab                        if "description" not in self.data.get(fdata.key, {}):
135484e9aa6SMauro Carvalho Chehab                            self.warn(fdata, f"{fdata.key} doesn't have a description")
136484e9aa6SMauro Carvalho Chehab
137484e9aa6SMauro Carvalho Chehab                        for w in fdata.what:
138484e9aa6SMauro Carvalho Chehab                            self.add_symbol(what=w, fname=fdata.fname,
139484e9aa6SMauro Carvalho Chehab                                            ln=fdata.what_ln, xref=fdata.key)
140484e9aa6SMauro Carvalho Chehab
141484e9aa6SMauro Carvalho Chehab                    fdata.label = content
142484e9aa6SMauro Carvalho Chehab                    new_what = True
143484e9aa6SMauro Carvalho Chehab
144484e9aa6SMauro Carvalho Chehab                    key = "abi_" + content.lower()
145484e9aa6SMauro Carvalho Chehab                    fdata.key = self.re_unprintable.sub("_", key).strip("_")
146484e9aa6SMauro Carvalho Chehab
147484e9aa6SMauro Carvalho Chehab                    # Avoid duplicated keys but using a defined seed, to make
148484e9aa6SMauro Carvalho Chehab                    # the namespace identical if there aren't changes at the
149484e9aa6SMauro Carvalho Chehab                    # ABI symbols
150484e9aa6SMauro Carvalho Chehab                    seed(42)
151484e9aa6SMauro Carvalho Chehab
152484e9aa6SMauro Carvalho Chehab                    while fdata.key in self.data:
153484e9aa6SMauro Carvalho Chehab                        char = randrange(0, 51) + ord("A")
154484e9aa6SMauro Carvalho Chehab                        if char > ord("Z"):
155484e9aa6SMauro Carvalho Chehab                            char += ord("a") - ord("Z") - 1
156484e9aa6SMauro Carvalho Chehab
157484e9aa6SMauro Carvalho Chehab                        fdata.key += chr(char)
158484e9aa6SMauro Carvalho Chehab
159484e9aa6SMauro Carvalho Chehab                    if fdata.key and fdata.key not in self.data:
160484e9aa6SMauro Carvalho Chehab                        self.data[fdata.key] = {
161484e9aa6SMauro Carvalho Chehab                            "what": [content],
162484e9aa6SMauro Carvalho Chehab                            "file": [fdata.file_ref],
1632a21d80dSMauro Carvalho Chehab                            "path": fdata.ftype,
164484e9aa6SMauro Carvalho Chehab                            "line_no": fdata.ln,
165484e9aa6SMauro Carvalho Chehab                        }
166484e9aa6SMauro Carvalho Chehab
167484e9aa6SMauro Carvalho Chehab                    fdata.what = self.data[fdata.key]["what"]
168484e9aa6SMauro Carvalho Chehab
169484e9aa6SMauro Carvalho Chehab                self.what_refs[content] = fdata.key
170484e9aa6SMauro Carvalho Chehab                fdata.tag = new_tag
171484e9aa6SMauro Carvalho Chehab                fdata.what_ln = fdata.ln
172484e9aa6SMauro Carvalho Chehab
173484e9aa6SMauro Carvalho Chehab                if fdata.nametag["what"]:
174484e9aa6SMauro Carvalho Chehab                    t = (content, fdata.key)
175484e9aa6SMauro Carvalho Chehab                    if t not in fdata.nametag["symbols"]:
176484e9aa6SMauro Carvalho Chehab                        fdata.nametag["symbols"].append(t)
177484e9aa6SMauro Carvalho Chehab
178484e9aa6SMauro Carvalho Chehab                return
179484e9aa6SMauro Carvalho Chehab
180484e9aa6SMauro Carvalho Chehab            if fdata.tag and new_tag:
181484e9aa6SMauro Carvalho Chehab                fdata.tag = new_tag
182484e9aa6SMauro Carvalho Chehab
183484e9aa6SMauro Carvalho Chehab                if new_what:
184484e9aa6SMauro Carvalho Chehab                    fdata.label = ""
185484e9aa6SMauro Carvalho Chehab
186484e9aa6SMauro Carvalho Chehab                    if "description" in self.data[fdata.key]:
187484e9aa6SMauro Carvalho Chehab                        self.data[fdata.key]["description"] += "\n\n"
188484e9aa6SMauro Carvalho Chehab
189484e9aa6SMauro Carvalho Chehab                    if fdata.file_ref not in self.data[fdata.key]["file"]:
190484e9aa6SMauro Carvalho Chehab                        self.data[fdata.key]["file"].append(fdata.file_ref)
191484e9aa6SMauro Carvalho Chehab
192484e9aa6SMauro Carvalho Chehab                    if self.debug == AbiDebug.WHAT_PARSING:
193484e9aa6SMauro Carvalho Chehab                        self.log.debug("what: %s", fdata.what)
194484e9aa6SMauro Carvalho Chehab
195484e9aa6SMauro Carvalho Chehab                if not fdata.what:
196484e9aa6SMauro Carvalho Chehab                    self.warn(fdata, "'What:' should come first:", line)
197484e9aa6SMauro Carvalho Chehab                    return
198484e9aa6SMauro Carvalho Chehab
199484e9aa6SMauro Carvalho Chehab                if new_tag == "description":
200484e9aa6SMauro Carvalho Chehab                    fdata.space = None
201484e9aa6SMauro Carvalho Chehab
202484e9aa6SMauro Carvalho Chehab                    if content:
203484e9aa6SMauro Carvalho Chehab                        sep = sep.replace(":", " ")
204484e9aa6SMauro Carvalho Chehab
205484e9aa6SMauro Carvalho Chehab                        c = " " * len(new_tag) + sep + content
206484e9aa6SMauro Carvalho Chehab                        c = c.expandtabs()
207484e9aa6SMauro Carvalho Chehab
208484e9aa6SMauro Carvalho Chehab                        match = self.re_start_spc.match(c)
209484e9aa6SMauro Carvalho Chehab                        if match:
210484e9aa6SMauro Carvalho Chehab                            # Preserve initial spaces for the first line
211484e9aa6SMauro Carvalho Chehab                            fdata.space = match.group(1)
212484e9aa6SMauro Carvalho Chehab                            content = match.group(2) + "\n"
213484e9aa6SMauro Carvalho Chehab
214484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] = content
215484e9aa6SMauro Carvalho Chehab
216484e9aa6SMauro Carvalho Chehab            return
217484e9aa6SMauro Carvalho Chehab
218484e9aa6SMauro Carvalho Chehab        # Store any contents before tags at the database
219484e9aa6SMauro Carvalho Chehab        if not fdata.tag and "what" in fdata.nametag:
220484e9aa6SMauro Carvalho Chehab            fdata.nametag["description"] += line
221484e9aa6SMauro Carvalho Chehab            return
222484e9aa6SMauro Carvalho Chehab
223484e9aa6SMauro Carvalho Chehab        if fdata.tag == "description":
224484e9aa6SMauro Carvalho Chehab            content = line.expandtabs()
225484e9aa6SMauro Carvalho Chehab
226484e9aa6SMauro Carvalho Chehab            if self.re_whitespace.sub("", content) == "":
227484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] += "\n"
228484e9aa6SMauro Carvalho Chehab                return
229484e9aa6SMauro Carvalho Chehab
230484e9aa6SMauro Carvalho Chehab            if fdata.space is None:
231484e9aa6SMauro Carvalho Chehab                match = self.re_start_spc.match(content)
232484e9aa6SMauro Carvalho Chehab                if match:
233484e9aa6SMauro Carvalho Chehab                    # Preserve initial spaces for the first line
234484e9aa6SMauro Carvalho Chehab                    fdata.space = match.group(1)
235484e9aa6SMauro Carvalho Chehab
236484e9aa6SMauro Carvalho Chehab                    content = match.group(2) + "\n"
237484e9aa6SMauro Carvalho Chehab            else:
238484e9aa6SMauro Carvalho Chehab                if content.startswith(fdata.space):
239484e9aa6SMauro Carvalho Chehab                    content = content[len(fdata.space):]
240484e9aa6SMauro Carvalho Chehab
241484e9aa6SMauro Carvalho Chehab                else:
242484e9aa6SMauro Carvalho Chehab                    fdata.space = ""
243484e9aa6SMauro Carvalho Chehab
244484e9aa6SMauro Carvalho Chehab            if fdata.tag == "what":
245484e9aa6SMauro Carvalho Chehab                w = content.strip("\n")
246484e9aa6SMauro Carvalho Chehab                if w:
247484e9aa6SMauro Carvalho Chehab                    self.data[fdata.key][fdata.tag].append(w)
248484e9aa6SMauro Carvalho Chehab            else:
249484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] += content
250484e9aa6SMauro Carvalho Chehab            return
251484e9aa6SMauro Carvalho Chehab
252484e9aa6SMauro Carvalho Chehab        content = line.strip()
253484e9aa6SMauro Carvalho Chehab        if fdata.tag:
254484e9aa6SMauro Carvalho Chehab            if fdata.tag == "what":
255484e9aa6SMauro Carvalho Chehab                w = content.strip("\n")
256484e9aa6SMauro Carvalho Chehab                if w:
257484e9aa6SMauro Carvalho Chehab                    self.data[fdata.key][fdata.tag].append(w)
258484e9aa6SMauro Carvalho Chehab            else:
259484e9aa6SMauro Carvalho Chehab                self.data[fdata.key][fdata.tag] += "\n" + content.rstrip("\n")
260484e9aa6SMauro Carvalho Chehab            return
261484e9aa6SMauro Carvalho Chehab
262484e9aa6SMauro Carvalho Chehab        # Everything else is error
263484e9aa6SMauro Carvalho Chehab        if content:
264484e9aa6SMauro Carvalho Chehab            self.warn(fdata, "Unexpected content", line)
265484e9aa6SMauro Carvalho Chehab
26698a4324aSMauro Carvalho Chehab    def parse_readme(self, nametag, fname):
26798a4324aSMauro Carvalho Chehab        """Parse ABI README file"""
26898a4324aSMauro Carvalho Chehab
269*de61d651SMauro Carvalho Chehab        nametag["what"] = ["Introduction"]
2705d7871d7SMauro Carvalho Chehab        nametag["path"] = "README"
27198a4324aSMauro Carvalho Chehab        with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp:
27298a4324aSMauro Carvalho Chehab            for line in fp:
2735d7871d7SMauro Carvalho Chehab                match = self.re_tag.match(line)
2745d7871d7SMauro Carvalho Chehab                if match:
2755d7871d7SMauro Carvalho Chehab                    new = match.group(1).lower()
27698a4324aSMauro Carvalho Chehab
2775d7871d7SMauro Carvalho Chehab                    match = self.re_valid.search(new)
2785d7871d7SMauro Carvalho Chehab                    if match:
2795d7871d7SMauro Carvalho Chehab                        nametag["description"] += "\n:" + line
2805d7871d7SMauro Carvalho Chehab                        continue
2815d7871d7SMauro Carvalho Chehab
2825d7871d7SMauro Carvalho Chehab                nametag["description"] += line
28398a4324aSMauro Carvalho Chehab
284484e9aa6SMauro Carvalho Chehab    def parse_file(self, fname, path, basename):
285484e9aa6SMauro Carvalho Chehab        """Parse a single file"""
286484e9aa6SMauro Carvalho Chehab
287484e9aa6SMauro Carvalho Chehab        ref = f"abi_file_{path}_{basename}"
288484e9aa6SMauro Carvalho Chehab        ref = self.re_unprintable.sub("_", ref).strip("_")
289484e9aa6SMauro Carvalho Chehab
290484e9aa6SMauro Carvalho Chehab        # Store per-file state into a namespace variable. This will be used
291484e9aa6SMauro Carvalho Chehab        # by the per-line parser state machine and by the warning function.
292484e9aa6SMauro Carvalho Chehab        fdata = Namespace
293484e9aa6SMauro Carvalho Chehab
294484e9aa6SMauro Carvalho Chehab        fdata.fname = fname
295484e9aa6SMauro Carvalho Chehab        fdata.name = basename
296484e9aa6SMauro Carvalho Chehab
297484e9aa6SMauro Carvalho Chehab        pos = fname.find(ABI_DIR)
298484e9aa6SMauro Carvalho Chehab        if pos > 0:
299484e9aa6SMauro Carvalho Chehab            f = fname[pos:]
300484e9aa6SMauro Carvalho Chehab        else:
301484e9aa6SMauro Carvalho Chehab            f = fname
302484e9aa6SMauro Carvalho Chehab
303484e9aa6SMauro Carvalho Chehab        fdata.file_ref = (f, ref)
304484e9aa6SMauro Carvalho Chehab        self.file_refs[f] = ref
305484e9aa6SMauro Carvalho Chehab
306484e9aa6SMauro Carvalho Chehab        fdata.ln = 0
307484e9aa6SMauro Carvalho Chehab        fdata.what_ln = 0
308484e9aa6SMauro Carvalho Chehab        fdata.tag = ""
309484e9aa6SMauro Carvalho Chehab        fdata.label = ""
310484e9aa6SMauro Carvalho Chehab        fdata.what = []
311484e9aa6SMauro Carvalho Chehab        fdata.key = None
312484e9aa6SMauro Carvalho Chehab        fdata.xrefs = None
313484e9aa6SMauro Carvalho Chehab        fdata.space = None
314484e9aa6SMauro Carvalho Chehab        fdata.ftype = path.split("/")[0]
315484e9aa6SMauro Carvalho Chehab
316484e9aa6SMauro Carvalho Chehab        fdata.nametag = {}
317dc525a76SMauro Carvalho Chehab        fdata.nametag["what"] = [f"ABI file {path}/{basename}"]
318484e9aa6SMauro Carvalho Chehab        fdata.nametag["type"] = "File"
3192a21d80dSMauro Carvalho Chehab        fdata.nametag["path"] = fdata.ftype
320484e9aa6SMauro Carvalho Chehab        fdata.nametag["file"] = [fdata.file_ref]
321484e9aa6SMauro Carvalho Chehab        fdata.nametag["line_no"] = 1
322484e9aa6SMauro Carvalho Chehab        fdata.nametag["description"] = ""
323484e9aa6SMauro Carvalho Chehab        fdata.nametag["symbols"] = []
324484e9aa6SMauro Carvalho Chehab
325484e9aa6SMauro Carvalho Chehab        self.data[ref] = fdata.nametag
326484e9aa6SMauro Carvalho Chehab
327484e9aa6SMauro Carvalho Chehab        if self.debug & AbiDebug.WHAT_OPEN:
328484e9aa6SMauro Carvalho Chehab            self.log.debug("Opening file %s", fname)
329484e9aa6SMauro Carvalho Chehab
33098a4324aSMauro Carvalho Chehab        if basename == "README":
33198a4324aSMauro Carvalho Chehab            self.parse_readme(fdata.nametag, fname)
33298a4324aSMauro Carvalho Chehab            return
33398a4324aSMauro Carvalho Chehab
334484e9aa6SMauro Carvalho Chehab        with open(fname, "r", encoding="utf8", errors="backslashreplace") as fp:
335484e9aa6SMauro Carvalho Chehab            for line in fp:
336484e9aa6SMauro Carvalho Chehab                fdata.ln += 1
337484e9aa6SMauro Carvalho Chehab
338484e9aa6SMauro Carvalho Chehab                self._parse_line(fdata, line)
339484e9aa6SMauro Carvalho Chehab
340484e9aa6SMauro Carvalho Chehab            if "description" in fdata.nametag:
341484e9aa6SMauro Carvalho Chehab                fdata.nametag["description"] = fdata.nametag["description"].lstrip("\n")
342484e9aa6SMauro Carvalho Chehab
343484e9aa6SMauro Carvalho Chehab            if fdata.key:
344484e9aa6SMauro Carvalho Chehab                if "description" not in self.data.get(fdata.key, {}):
345484e9aa6SMauro Carvalho Chehab                    self.warn(fdata, f"{fdata.key} doesn't have a description")
346484e9aa6SMauro Carvalho Chehab
347484e9aa6SMauro Carvalho Chehab                for w in fdata.what:
348484e9aa6SMauro Carvalho Chehab                    self.add_symbol(what=w, fname=fname, xref=fdata.key)
349484e9aa6SMauro Carvalho Chehab
350c67c3fbdSMauro Carvalho Chehab    def _parse_abi(self, root=None):
351c67c3fbdSMauro Carvalho Chehab        """Internal function to parse documentation ABI recursively"""
352484e9aa6SMauro Carvalho Chehab
353c67c3fbdSMauro Carvalho Chehab        if not root:
354c67c3fbdSMauro Carvalho Chehab            root = self.directory
355484e9aa6SMauro Carvalho Chehab
356c67c3fbdSMauro Carvalho Chehab        with os.scandir(root) as obj:
357c67c3fbdSMauro Carvalho Chehab            for entry in obj:
358c67c3fbdSMauro Carvalho Chehab                name = os.path.join(root, entry.name)
359c67c3fbdSMauro Carvalho Chehab
360c67c3fbdSMauro Carvalho Chehab                if entry.is_dir():
3619bec7870SMauro Carvalho Chehab                    self._parse_abi(name)
362484e9aa6SMauro Carvalho Chehab                    continue
363484e9aa6SMauro Carvalho Chehab
364c67c3fbdSMauro Carvalho Chehab                if not entry.is_file():
365c67c3fbdSMauro Carvalho Chehab                    continue
366c67c3fbdSMauro Carvalho Chehab
367c67c3fbdSMauro Carvalho Chehab                basename = os.path.basename(name)
368484e9aa6SMauro Carvalho Chehab
369c67c3fbdSMauro Carvalho Chehab                if basename.startswith("."):
370484e9aa6SMauro Carvalho Chehab                    continue
371484e9aa6SMauro Carvalho Chehab
372c67c3fbdSMauro Carvalho Chehab                if basename.endswith(self.ignore_suffixes):
373c67c3fbdSMauro Carvalho Chehab                    continue
374484e9aa6SMauro Carvalho Chehab
375c67c3fbdSMauro Carvalho Chehab                path = self.re_abi_dir.sub("", os.path.dirname(name))
376c67c3fbdSMauro Carvalho Chehab
377c67c3fbdSMauro Carvalho Chehab                self.parse_file(name, path, basename)
378c67c3fbdSMauro Carvalho Chehab
379c67c3fbdSMauro Carvalho Chehab    def parse_abi(self, root=None):
380c67c3fbdSMauro Carvalho Chehab        """Parse documentation ABI"""
381c67c3fbdSMauro Carvalho Chehab
382c67c3fbdSMauro Carvalho Chehab        self._parse_abi(root)
383484e9aa6SMauro Carvalho Chehab
384484e9aa6SMauro Carvalho Chehab        if self.debug & AbiDebug.DUMP_ABI_STRUCTS:
385484e9aa6SMauro Carvalho Chehab            self.log.debug(pformat(self.data))
386484e9aa6SMauro Carvalho Chehab
3879bec7870SMauro Carvalho Chehab    def desc_txt(self, desc):
388484e9aa6SMauro Carvalho Chehab        """Print description as found inside ABI files"""
389484e9aa6SMauro Carvalho Chehab
390484e9aa6SMauro Carvalho Chehab        desc = desc.strip(" \t\n")
391484e9aa6SMauro Carvalho Chehab
3929bec7870SMauro Carvalho Chehab        return desc + "\n\n"
393484e9aa6SMauro Carvalho Chehab
394c9408169SMauro Carvalho Chehab    def xref(self, fname):
395c9408169SMauro Carvalho Chehab        """
396c9408169SMauro Carvalho Chehab        Converts a Documentation/ABI + basename into a ReST cross-reference
397c9408169SMauro Carvalho Chehab        """
398c9408169SMauro Carvalho Chehab
399c9408169SMauro Carvalho Chehab        xref = self.file_refs.get(fname)
400c9408169SMauro Carvalho Chehab        if not xref:
401c9408169SMauro Carvalho Chehab            return None
402c9408169SMauro Carvalho Chehab        else:
403c9408169SMauro Carvalho Chehab            return xref
404c9408169SMauro Carvalho Chehab
4059bec7870SMauro Carvalho Chehab    def desc_rst(self, desc):
406484e9aa6SMauro Carvalho Chehab        """Enrich ReST output by creating cross-references"""
407484e9aa6SMauro Carvalho Chehab
408484e9aa6SMauro Carvalho Chehab        # Remove title markups from the description
409484e9aa6SMauro Carvalho Chehab        # Having titles inside ABI files will only work if extra
410484e9aa6SMauro Carvalho Chehab        # care would be taken in order to strictly follow the same
411484e9aa6SMauro Carvalho Chehab        # level order for each markup.
412484e9aa6SMauro Carvalho Chehab        desc = self.re_title_mark.sub("\n\n", "\n" + desc)
413484e9aa6SMauro Carvalho Chehab        desc = desc.rstrip(" \t\n").lstrip("\n")
414484e9aa6SMauro Carvalho Chehab
415484e9aa6SMauro Carvalho Chehab        # Python's regex performance for non-compiled expressions is a lot
416484e9aa6SMauro Carvalho Chehab        # than Perl, as Perl automatically caches them at their
417484e9aa6SMauro Carvalho Chehab        # first usage. Here, we'll need to do the same, as otherwise the
418484e9aa6SMauro Carvalho Chehab        # performance penalty is be high
419484e9aa6SMauro Carvalho Chehab
420484e9aa6SMauro Carvalho Chehab        new_desc = ""
421484e9aa6SMauro Carvalho Chehab        for d in desc.split("\n"):
422484e9aa6SMauro Carvalho Chehab            if d == "":
423484e9aa6SMauro Carvalho Chehab                new_desc += "\n"
424484e9aa6SMauro Carvalho Chehab                continue
425484e9aa6SMauro Carvalho Chehab
426484e9aa6SMauro Carvalho Chehab            # Use cross-references for doc files where needed
427484e9aa6SMauro Carvalho Chehab            d = self.re_doc.sub(r":doc:`/\1`", d)
428484e9aa6SMauro Carvalho Chehab
429484e9aa6SMauro Carvalho Chehab            # Use cross-references for ABI generated docs where needed
430484e9aa6SMauro Carvalho Chehab            matches = self.re_abi.findall(d)
431484e9aa6SMauro Carvalho Chehab            for m in matches:
432484e9aa6SMauro Carvalho Chehab                abi = m[0] + m[1]
433484e9aa6SMauro Carvalho Chehab
434484e9aa6SMauro Carvalho Chehab                xref = self.file_refs.get(abi)
435484e9aa6SMauro Carvalho Chehab                if not xref:
436484e9aa6SMauro Carvalho Chehab                    # This may happen if ABI is on a separate directory,
437484e9aa6SMauro Carvalho Chehab                    # like parsing ABI testing and symbol is at stable.
438484e9aa6SMauro Carvalho Chehab                    # The proper solution is to move this part of the code
439484e9aa6SMauro Carvalho Chehab                    # for it to be inside sphinx/kernel_abi.py
440484e9aa6SMauro Carvalho Chehab                    self.log.info("Didn't find ABI reference for '%s'", abi)
441484e9aa6SMauro Carvalho Chehab                else:
442484e9aa6SMauro Carvalho Chehab                    new = self.re_escape.sub(r"\\\1", m[1])
443484e9aa6SMauro Carvalho Chehab                    d = re.sub(fr"\b{abi}\b", f":ref:`{new} <{xref}>`", d)
444484e9aa6SMauro Carvalho Chehab
445484e9aa6SMauro Carvalho Chehab            # Seek for cross reference symbols like /sys/...
446484e9aa6SMauro Carvalho Chehab            # Need to be careful to avoid doing it on a code block
447484e9aa6SMauro Carvalho Chehab            if d[0] not in [" ", "\t"]:
448484e9aa6SMauro Carvalho Chehab                matches = self.re_xref_node.findall(d)
449484e9aa6SMauro Carvalho Chehab                for m in matches:
450484e9aa6SMauro Carvalho Chehab                    # Finding ABI here is more complex due to wildcards
451484e9aa6SMauro Carvalho Chehab                    xref = self.what_refs.get(m)
452484e9aa6SMauro Carvalho Chehab                    if xref:
453484e9aa6SMauro Carvalho Chehab                        new = self.re_escape.sub(r"\\\1", m)
454484e9aa6SMauro Carvalho Chehab                        d = re.sub(fr"\b{m}\b", f":ref:`{new} <{xref}>`", d)
455484e9aa6SMauro Carvalho Chehab
456484e9aa6SMauro Carvalho Chehab            new_desc += d + "\n"
457484e9aa6SMauro Carvalho Chehab
4589bec7870SMauro Carvalho Chehab        return new_desc + "\n\n"
459484e9aa6SMauro Carvalho Chehab
4602a21d80dSMauro Carvalho Chehab    def doc(self, output_in_txt=False, show_symbols=True, show_file=True,
4612a21d80dSMauro Carvalho Chehab            filter_path=None):
462484e9aa6SMauro Carvalho Chehab        """Print ABI at stdout"""
463484e9aa6SMauro Carvalho Chehab
464484e9aa6SMauro Carvalho Chehab        part = None
465484e9aa6SMauro Carvalho Chehab        for key, v in sorted(self.data.items(),
466484e9aa6SMauro Carvalho Chehab                             key=lambda x: (x[1].get("type", ""),
467484e9aa6SMauro Carvalho Chehab                                            x[1].get("what"))):
468484e9aa6SMauro Carvalho Chehab
4692a21d80dSMauro Carvalho Chehab            wtype = v.get("type", "Symbol")
470484e9aa6SMauro Carvalho Chehab            file_ref = v.get("file")
471484e9aa6SMauro Carvalho Chehab            names = v.get("what", [""])
472484e9aa6SMauro Carvalho Chehab
4732a21d80dSMauro Carvalho Chehab            if wtype == "File":
4742a21d80dSMauro Carvalho Chehab                if not show_file:
4752a21d80dSMauro Carvalho Chehab                    continue
4762a21d80dSMauro Carvalho Chehab            else:
4772a21d80dSMauro Carvalho Chehab                if not show_symbols:
4782a21d80dSMauro Carvalho Chehab                    continue
4792a21d80dSMauro Carvalho Chehab
4802a21d80dSMauro Carvalho Chehab            if filter_path:
4812a21d80dSMauro Carvalho Chehab                if v.get("path") != filter_path:
482484e9aa6SMauro Carvalho Chehab                    continue
483484e9aa6SMauro Carvalho Chehab
4849bec7870SMauro Carvalho Chehab            msg = ""
4859bec7870SMauro Carvalho Chehab
486484e9aa6SMauro Carvalho Chehab            if wtype != "File":
487484e9aa6SMauro Carvalho Chehab                cur_part = names[0]
488484e9aa6SMauro Carvalho Chehab                if cur_part.find("/") >= 0:
489484e9aa6SMauro Carvalho Chehab                    match = self.re_what.match(cur_part)
490484e9aa6SMauro Carvalho Chehab                    if match:
491484e9aa6SMauro Carvalho Chehab                        symbol = match.group(1).rstrip("/")
492484e9aa6SMauro Carvalho Chehab                        cur_part = "Symbols under " + symbol
493484e9aa6SMauro Carvalho Chehab
494484e9aa6SMauro Carvalho Chehab                if cur_part and cur_part != part:
495484e9aa6SMauro Carvalho Chehab                    part = cur_part
4966649b421SMauro Carvalho Chehab                    msg += part + "\n"+ "-" * len(part) +"\n\n"
497484e9aa6SMauro Carvalho Chehab
4989bec7870SMauro Carvalho Chehab                msg += f".. _{key}:\n\n"
499484e9aa6SMauro Carvalho Chehab
500484e9aa6SMauro Carvalho Chehab                max_len = 0
501484e9aa6SMauro Carvalho Chehab                for i in range(0, len(names)):           # pylint: disable=C0200
502484e9aa6SMauro Carvalho Chehab                    names[i] = "**" + self.re_escape.sub(r"\\\1", names[i]) + "**"
503484e9aa6SMauro Carvalho Chehab
504484e9aa6SMauro Carvalho Chehab                    max_len = max(max_len, len(names[i]))
505484e9aa6SMauro Carvalho Chehab
5069bec7870SMauro Carvalho Chehab                msg += "+-" + "-" * max_len + "-+\n"
507484e9aa6SMauro Carvalho Chehab                for name in names:
5089bec7870SMauro Carvalho Chehab                    msg += f"| {name}" + " " * (max_len - len(name)) + " |\n"
5099bec7870SMauro Carvalho Chehab                    msg += "+-" + "-" * max_len + "-+\n"
5109bec7870SMauro Carvalho Chehab                msg += "\n"
511484e9aa6SMauro Carvalho Chehab
512484e9aa6SMauro Carvalho Chehab            for ref in file_ref:
513484e9aa6SMauro Carvalho Chehab                if wtype == "File":
5149bec7870SMauro Carvalho Chehab                    msg += f".. _{ref[1]}:\n\n"
515484e9aa6SMauro Carvalho Chehab                else:
516484e9aa6SMauro Carvalho Chehab                    base = os.path.basename(ref[0])
5179bec7870SMauro Carvalho Chehab                    msg += f"Defined on file :ref:`{base} <{ref[1]}>`\n\n"
518484e9aa6SMauro Carvalho Chehab
519484e9aa6SMauro Carvalho Chehab            if wtype == "File":
5206649b421SMauro Carvalho Chehab                msg += names[0] +"\n" + "-" * len(names[0]) +"\n\n"
521484e9aa6SMauro Carvalho Chehab
522484e9aa6SMauro Carvalho Chehab            desc = v.get("description")
523484e9aa6SMauro Carvalho Chehab            if not desc and wtype != "File":
5249bec7870SMauro Carvalho Chehab                msg += f"DESCRIPTION MISSING for {names[0]}\n\n"
525484e9aa6SMauro Carvalho Chehab
526484e9aa6SMauro Carvalho Chehab            if desc:
527484e9aa6SMauro Carvalho Chehab                if output_in_txt:
5289bec7870SMauro Carvalho Chehab                    msg += self.desc_txt(desc)
529484e9aa6SMauro Carvalho Chehab                else:
5309bec7870SMauro Carvalho Chehab                    msg += self.desc_rst(desc)
531484e9aa6SMauro Carvalho Chehab
532484e9aa6SMauro Carvalho Chehab            symbols = v.get("symbols")
533484e9aa6SMauro Carvalho Chehab            if symbols:
5349bec7870SMauro Carvalho Chehab                msg += "Has the following ABI:\n\n"
535484e9aa6SMauro Carvalho Chehab
536484e9aa6SMauro Carvalho Chehab                for w, label in symbols:
537484e9aa6SMauro Carvalho Chehab                    # Escape special chars from content
538484e9aa6SMauro Carvalho Chehab                    content = self.re_escape.sub(r"\\\1", w)
539484e9aa6SMauro Carvalho Chehab
5409bec7870SMauro Carvalho Chehab                    msg += f"- :ref:`{content} <{label}>`\n\n"
541484e9aa6SMauro Carvalho Chehab
542484e9aa6SMauro Carvalho Chehab            users = v.get("users")
543484e9aa6SMauro Carvalho Chehab            if users and users.strip(" \t\n"):
5446649b421SMauro Carvalho Chehab                users = users.strip("\n").replace('\n', '\n\t')
5456649b421SMauro Carvalho Chehab                msg += f"Users:\n\t{users}\n\n"
5469bec7870SMauro Carvalho Chehab
547aea5e52dSMauro Carvalho Chehab            ln = v.get("line_no", 1)
548aea5e52dSMauro Carvalho Chehab
549aea5e52dSMauro Carvalho Chehab            yield (msg, file_ref[0][0], ln)
550484e9aa6SMauro Carvalho Chehab
551484e9aa6SMauro Carvalho Chehab    def check_issues(self):
552484e9aa6SMauro Carvalho Chehab        """Warn about duplicated ABI entries"""
553484e9aa6SMauro Carvalho Chehab
554484e9aa6SMauro Carvalho Chehab        for what, v in self.what_symbols.items():
555484e9aa6SMauro Carvalho Chehab            files = v.get("file")
556484e9aa6SMauro Carvalho Chehab            if not files:
557484e9aa6SMauro Carvalho Chehab                # Should never happen if the parser works properly
558484e9aa6SMauro Carvalho Chehab                self.log.warning("%s doesn't have a file associated", what)
559484e9aa6SMauro Carvalho Chehab                continue
560484e9aa6SMauro Carvalho Chehab
561484e9aa6SMauro Carvalho Chehab            if len(files) == 1:
562484e9aa6SMauro Carvalho Chehab                continue
563484e9aa6SMauro Carvalho Chehab
564484e9aa6SMauro Carvalho Chehab            f = []
565484e9aa6SMauro Carvalho Chehab            for fname, lines in sorted(files.items()):
566484e9aa6SMauro Carvalho Chehab                if not lines:
567484e9aa6SMauro Carvalho Chehab                    f.append(f"{fname}")
568484e9aa6SMauro Carvalho Chehab                elif len(lines) == 1:
569484e9aa6SMauro Carvalho Chehab                    f.append(f"{fname}:{lines[0]}")
570484e9aa6SMauro Carvalho Chehab                else:
5716649b421SMauro Carvalho Chehab                    m = fname + "lines "
5726649b421SMauro Carvalho Chehab                    m += ", ".join(str(x) for x in lines)
5736649b421SMauro Carvalho Chehab                    f.append(m)
574484e9aa6SMauro Carvalho Chehab
575484e9aa6SMauro Carvalho Chehab            self.log.warning("%s is defined %d times: %s", what, len(f), "; ".join(f))
5766b48bea1SMauro Carvalho Chehab
5776b48bea1SMauro Carvalho Chehab    def search_symbols(self, expr):
5786b48bea1SMauro Carvalho Chehab        """ Searches for ABI symbols """
5796b48bea1SMauro Carvalho Chehab
5806b48bea1SMauro Carvalho Chehab        regex = re.compile(expr, re.I)
5816b48bea1SMauro Carvalho Chehab
5826b48bea1SMauro Carvalho Chehab        found_keys = 0
5836b48bea1SMauro Carvalho Chehab        for t in sorted(self.data.items(), key=lambda x: [0]):
5846b48bea1SMauro Carvalho Chehab            v = t[1]
5856b48bea1SMauro Carvalho Chehab
5866b48bea1SMauro Carvalho Chehab            wtype = v.get("type", "")
5876b48bea1SMauro Carvalho Chehab            if wtype == "File":
5886b48bea1SMauro Carvalho Chehab                continue
5896b48bea1SMauro Carvalho Chehab
5906b48bea1SMauro Carvalho Chehab            for what in v.get("what", [""]):
5916b48bea1SMauro Carvalho Chehab                if regex.search(what):
5926b48bea1SMauro Carvalho Chehab                    found_keys += 1
5936b48bea1SMauro Carvalho Chehab
5946b48bea1SMauro Carvalho Chehab                    kernelversion = v.get("kernelversion", "").strip(" \t\n")
5956b48bea1SMauro Carvalho Chehab                    date = v.get("date", "").strip(" \t\n")
5966b48bea1SMauro Carvalho Chehab                    contact = v.get("contact", "").strip(" \t\n")
5976b48bea1SMauro Carvalho Chehab                    users = v.get("users", "").strip(" \t\n")
5986b48bea1SMauro Carvalho Chehab                    desc = v.get("description", "").strip(" \t\n")
5996b48bea1SMauro Carvalho Chehab
6006b48bea1SMauro Carvalho Chehab                    files = []
6016b48bea1SMauro Carvalho Chehab                    for f in v.get("file", ()):
6026b48bea1SMauro Carvalho Chehab                        files.append(f[0])
6036b48bea1SMauro Carvalho Chehab
6046b48bea1SMauro Carvalho Chehab                    what = str(found_keys) + ". " + what
6056b48bea1SMauro Carvalho Chehab                    title_tag = "-" * len(what)
6066b48bea1SMauro Carvalho Chehab
6076b48bea1SMauro Carvalho Chehab                    print(f"\n{what}\n{title_tag}\n")
6086b48bea1SMauro Carvalho Chehab
6096b48bea1SMauro Carvalho Chehab                    if kernelversion:
6106b48bea1SMauro Carvalho Chehab                        print(f"Kernel version:\t\t{kernelversion}")
6116b48bea1SMauro Carvalho Chehab
6126b48bea1SMauro Carvalho Chehab                    if date:
6136b48bea1SMauro Carvalho Chehab                        print(f"Date:\t\t\t{date}")
6146b48bea1SMauro Carvalho Chehab
6156b48bea1SMauro Carvalho Chehab                    if contact:
6166b48bea1SMauro Carvalho Chehab                        print(f"Contact:\t\t{contact}")
6176b48bea1SMauro Carvalho Chehab
6186b48bea1SMauro Carvalho Chehab                    if users:
6196b48bea1SMauro Carvalho Chehab                        print(f"Users:\t\t\t{users}")
6206b48bea1SMauro Carvalho Chehab
6216649b421SMauro Carvalho Chehab                    print("Defined on file(s):\t" + ", ".join(files))
6226b48bea1SMauro Carvalho Chehab
6236b48bea1SMauro Carvalho Chehab                    if desc:
6246649b421SMauro Carvalho Chehab                        desc = desc.strip("\n")
6256649b421SMauro Carvalho Chehab                        print(f"\n{desc}\n")
6266b48bea1SMauro Carvalho Chehab
6276b48bea1SMauro Carvalho Chehab        if not found_keys:
6286b48bea1SMauro Carvalho Chehab            print(f"Regular expression /{expr}/ not found.")
629