xref: /linux/scripts/lib/kdoc/kdoc_files.py (revision 3e443d167327b10966166c1953631936547b03d0)
1ee13b3f3SMauro Carvalho Chehab#!/usr/bin/env python3
2ee13b3f3SMauro Carvalho Chehab# SPDX-License-Identifier: GPL-2.0
3ee13b3f3SMauro Carvalho Chehab# Copyright(c) 2025: Mauro Carvalho Chehab <mchehab@kernel.org>.
4ee13b3f3SMauro Carvalho Chehab#
5ee13b3f3SMauro Carvalho Chehab# pylint: disable=R0903,R0913,R0914,R0917
6ee13b3f3SMauro Carvalho Chehab
7ee13b3f3SMauro Carvalho Chehab"""
8ee13b3f3SMauro Carvalho ChehabParse lernel-doc tags on multiple kernel source files.
9ee13b3f3SMauro Carvalho Chehab"""
10ee13b3f3SMauro Carvalho Chehab
11ee13b3f3SMauro Carvalho Chehabimport argparse
12ee13b3f3SMauro Carvalho Chehabimport logging
13ee13b3f3SMauro Carvalho Chehabimport os
14ee13b3f3SMauro Carvalho Chehabimport re
15ee13b3f3SMauro Carvalho Chehab
16ee13b3f3SMauro Carvalho Chehabfrom kdoc_parser import KernelDoc
1743ecfe6bSMauro Carvalho Chehabfrom kdoc_output import OutputFormat
18ee13b3f3SMauro Carvalho Chehab
19ee13b3f3SMauro Carvalho Chehab
20ee13b3f3SMauro Carvalho Chehabclass GlobSourceFiles:
21ee13b3f3SMauro Carvalho Chehab    """
22ee13b3f3SMauro Carvalho Chehab    Parse C source code file names and directories via an Interactor.
23ee13b3f3SMauro Carvalho Chehab    """
24ee13b3f3SMauro Carvalho Chehab
25ee13b3f3SMauro Carvalho Chehab    def __init__(self, srctree=None, valid_extensions=None):
26ee13b3f3SMauro Carvalho Chehab        """
27ee13b3f3SMauro Carvalho Chehab        Initialize valid extensions with a tuple.
28ee13b3f3SMauro Carvalho Chehab
29ee13b3f3SMauro Carvalho Chehab        If not defined, assume default C extensions (.c and .h)
30ee13b3f3SMauro Carvalho Chehab
31ee13b3f3SMauro Carvalho Chehab        It would be possible to use python's glob function, but it is
32ee13b3f3SMauro Carvalho Chehab        very slow, and it is not interactive. So, it would wait to read all
33ee13b3f3SMauro Carvalho Chehab        directories before actually do something.
34ee13b3f3SMauro Carvalho Chehab
35ee13b3f3SMauro Carvalho Chehab        So, let's use our own implementation.
36ee13b3f3SMauro Carvalho Chehab        """
37ee13b3f3SMauro Carvalho Chehab
38ee13b3f3SMauro Carvalho Chehab        if not valid_extensions:
39ee13b3f3SMauro Carvalho Chehab            self.extensions = (".c", ".h")
40ee13b3f3SMauro Carvalho Chehab        else:
41ee13b3f3SMauro Carvalho Chehab            self.extensions = valid_extensions
42ee13b3f3SMauro Carvalho Chehab
43ee13b3f3SMauro Carvalho Chehab        self.srctree = srctree
44ee13b3f3SMauro Carvalho Chehab
45ee13b3f3SMauro Carvalho Chehab    def _parse_dir(self, dirname):
46ee13b3f3SMauro Carvalho Chehab        """Internal function to parse files recursively"""
47ee13b3f3SMauro Carvalho Chehab
48ee13b3f3SMauro Carvalho Chehab        with os.scandir(dirname) as obj:
49ee13b3f3SMauro Carvalho Chehab            for entry in obj:
50ee13b3f3SMauro Carvalho Chehab                name = os.path.join(dirname, entry.name)
51ee13b3f3SMauro Carvalho Chehab
52ee13b3f3SMauro Carvalho Chehab                if entry.is_dir():
53ee13b3f3SMauro Carvalho Chehab                    yield from self._parse_dir(name)
54ee13b3f3SMauro Carvalho Chehab
55ee13b3f3SMauro Carvalho Chehab                if not entry.is_file():
56ee13b3f3SMauro Carvalho Chehab                    continue
57ee13b3f3SMauro Carvalho Chehab
58ee13b3f3SMauro Carvalho Chehab                basename = os.path.basename(name)
59ee13b3f3SMauro Carvalho Chehab
60ee13b3f3SMauro Carvalho Chehab                if not basename.endswith(self.extensions):
61ee13b3f3SMauro Carvalho Chehab                    continue
62ee13b3f3SMauro Carvalho Chehab
63ee13b3f3SMauro Carvalho Chehab                yield name
64ee13b3f3SMauro Carvalho Chehab
65ee13b3f3SMauro Carvalho Chehab    def parse_files(self, file_list, file_not_found_cb):
66ee13b3f3SMauro Carvalho Chehab        """
67ee13b3f3SMauro Carvalho Chehab        Define an interator to parse all source files from file_list,
68ee13b3f3SMauro Carvalho Chehab        handling directories if any
69ee13b3f3SMauro Carvalho Chehab        """
70ee13b3f3SMauro Carvalho Chehab
7116740c29SMauro Carvalho Chehab        if not file_list:
7216740c29SMauro Carvalho Chehab            return
7316740c29SMauro Carvalho Chehab
74ee13b3f3SMauro Carvalho Chehab        for fname in file_list:
75ee13b3f3SMauro Carvalho Chehab            if self.srctree:
76ee13b3f3SMauro Carvalho Chehab                f = os.path.join(self.srctree, fname)
77ee13b3f3SMauro Carvalho Chehab            else:
78ee13b3f3SMauro Carvalho Chehab                f = fname
79ee13b3f3SMauro Carvalho Chehab
80ee13b3f3SMauro Carvalho Chehab            if os.path.isdir(f):
81ee13b3f3SMauro Carvalho Chehab                yield from self._parse_dir(f)
82ee13b3f3SMauro Carvalho Chehab            elif os.path.isfile(f):
83ee13b3f3SMauro Carvalho Chehab                yield f
84ee13b3f3SMauro Carvalho Chehab            elif file_not_found_cb:
85ee13b3f3SMauro Carvalho Chehab                file_not_found_cb(fname)
86ee13b3f3SMauro Carvalho Chehab
87ee13b3f3SMauro Carvalho Chehab
88ee13b3f3SMauro Carvalho Chehabclass KernelFiles():
89ee13b3f3SMauro Carvalho Chehab    """
9016740c29SMauro Carvalho Chehab    Parse kernel-doc tags on multiple kernel source files.
9116740c29SMauro Carvalho Chehab
9216740c29SMauro Carvalho Chehab    There are two type of parsers defined here:
9316740c29SMauro Carvalho Chehab        - self.parse_file(): parses both kernel-doc markups and
9416740c29SMauro Carvalho Chehab          EXPORT_SYMBOL* macros;
9516740c29SMauro Carvalho Chehab        - self.process_export_file(): parses only EXPORT_SYMBOL* macros.
96ee13b3f3SMauro Carvalho Chehab    """
97ee13b3f3SMauro Carvalho Chehab
9816740c29SMauro Carvalho Chehab    def warning(self, msg):
9916740c29SMauro Carvalho Chehab        """Ancillary routine to output a warning and increment error count"""
10016740c29SMauro Carvalho Chehab
10116740c29SMauro Carvalho Chehab        self.config.log.warning(msg)
10216740c29SMauro Carvalho Chehab        self.errors += 1
10316740c29SMauro Carvalho Chehab
10416740c29SMauro Carvalho Chehab    def error(self, msg):
10516740c29SMauro Carvalho Chehab        """Ancillary routine to output an error and increment error count"""
10616740c29SMauro Carvalho Chehab
10716740c29SMauro Carvalho Chehab        self.config.log.error(msg)
10816740c29SMauro Carvalho Chehab        self.errors += 1
10916740c29SMauro Carvalho Chehab
110ee13b3f3SMauro Carvalho Chehab    def parse_file(self, fname):
111ee13b3f3SMauro Carvalho Chehab        """
112ee13b3f3SMauro Carvalho Chehab        Parse a single Kernel source.
113ee13b3f3SMauro Carvalho Chehab        """
114ee13b3f3SMauro Carvalho Chehab
11516740c29SMauro Carvalho Chehab        # Prevent parsing the same file twice if results are cached
11616740c29SMauro Carvalho Chehab        if fname in self.files:
11716740c29SMauro Carvalho Chehab            return
118ee13b3f3SMauro Carvalho Chehab
11916740c29SMauro Carvalho Chehab        doc = KernelDoc(self.config, fname)
12016740c29SMauro Carvalho Chehab        export_table, entries = doc.parse_kdoc()
12116740c29SMauro Carvalho Chehab
12216740c29SMauro Carvalho Chehab        self.export_table[fname] = export_table
12316740c29SMauro Carvalho Chehab
12416740c29SMauro Carvalho Chehab        self.files.add(fname)
12516740c29SMauro Carvalho Chehab        self.export_files.add(fname)      # parse_kdoc() already check exports
12616740c29SMauro Carvalho Chehab
12716740c29SMauro Carvalho Chehab        self.results[fname] = entries
128ee13b3f3SMauro Carvalho Chehab
129ee13b3f3SMauro Carvalho Chehab    def process_export_file(self, fname):
130ee13b3f3SMauro Carvalho Chehab        """
131ee13b3f3SMauro Carvalho Chehab        Parses EXPORT_SYMBOL* macros from a single Kernel source file.
132ee13b3f3SMauro Carvalho Chehab        """
133ee13b3f3SMauro Carvalho Chehab
13416740c29SMauro Carvalho Chehab        # Prevent parsing the same file twice if results are cached
13516740c29SMauro Carvalho Chehab        if fname in self.export_files:
13616740c29SMauro Carvalho Chehab            return
13716740c29SMauro Carvalho Chehab
13816740c29SMauro Carvalho Chehab        doc = KernelDoc(self.config, fname)
13916740c29SMauro Carvalho Chehab        export_table = doc.parse_export()
14016740c29SMauro Carvalho Chehab
14116740c29SMauro Carvalho Chehab        if not export_table:
14216740c29SMauro Carvalho Chehab            self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}")
14316740c29SMauro Carvalho Chehab            export_table = set()
14416740c29SMauro Carvalho Chehab
14516740c29SMauro Carvalho Chehab        self.export_table[fname] = export_table
14616740c29SMauro Carvalho Chehab        self.export_files.add(fname)
147ee13b3f3SMauro Carvalho Chehab
148ee13b3f3SMauro Carvalho Chehab    def file_not_found_cb(self, fname):
149ee13b3f3SMauro Carvalho Chehab        """
150ee13b3f3SMauro Carvalho Chehab        Callback to warn if a file was not found.
151ee13b3f3SMauro Carvalho Chehab        """
152ee13b3f3SMauro Carvalho Chehab
15316740c29SMauro Carvalho Chehab        self.error(f"Cannot find file {fname}")
154ee13b3f3SMauro Carvalho Chehab
155799b0d2aSMauro Carvalho Chehab    def __init__(self, verbose=False, out_style=None,
156ee13b3f3SMauro Carvalho Chehab                 werror=False, wreturn=False, wshort_desc=False,
157ee13b3f3SMauro Carvalho Chehab                 wcontents_before_sections=False,
1582ab867a4SMauro Carvalho Chehab                 logger=None):
159ee13b3f3SMauro Carvalho Chehab        """
160ee13b3f3SMauro Carvalho Chehab        Initialize startup variables and parse all files
161ee13b3f3SMauro Carvalho Chehab        """
162ee13b3f3SMauro Carvalho Chehab
163ee13b3f3SMauro Carvalho Chehab        if not verbose:
164ee13b3f3SMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
165ee13b3f3SMauro Carvalho Chehab
16643ecfe6bSMauro Carvalho Chehab        if out_style is None:
16743ecfe6bSMauro Carvalho Chehab            out_style = OutputFormat()
16843ecfe6bSMauro Carvalho Chehab
169ee13b3f3SMauro Carvalho Chehab        if not werror:
170ee13b3f3SMauro Carvalho Chehab            kcflags = os.environ.get("KCFLAGS", None)
171ee13b3f3SMauro Carvalho Chehab            if kcflags:
172ee13b3f3SMauro Carvalho Chehab                match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags)
173ee13b3f3SMauro Carvalho Chehab                if match:
174ee13b3f3SMauro Carvalho Chehab                    werror = True
175ee13b3f3SMauro Carvalho Chehab
176ee13b3f3SMauro Carvalho Chehab            # reading this variable is for backwards compat just in case
177ee13b3f3SMauro Carvalho Chehab            # someone was calling it with the variable from outside the
178ee13b3f3SMauro Carvalho Chehab            # kernel's build system
179ee13b3f3SMauro Carvalho Chehab            kdoc_werror = os.environ.get("KDOC_WERROR", None)
180ee13b3f3SMauro Carvalho Chehab            if kdoc_werror:
181ee13b3f3SMauro Carvalho Chehab                werror = kdoc_werror
182ee13b3f3SMauro Carvalho Chehab
18316740c29SMauro Carvalho Chehab        # Some variables are global to the parser logic as a whole as they are
18416740c29SMauro Carvalho Chehab        # used to send control configuration to KernelDoc class. As such,
18516740c29SMauro Carvalho Chehab        # those variables are read-only inside the KernelDoc.
186ee13b3f3SMauro Carvalho Chehab        self.config = argparse.Namespace
187ee13b3f3SMauro Carvalho Chehab
188ee13b3f3SMauro Carvalho Chehab        self.config.verbose = verbose
189ee13b3f3SMauro Carvalho Chehab        self.config.werror = werror
190ee13b3f3SMauro Carvalho Chehab        self.config.wreturn = wreturn
191ee13b3f3SMauro Carvalho Chehab        self.config.wshort_desc = wshort_desc
192ee13b3f3SMauro Carvalho Chehab        self.config.wcontents_before_sections = wcontents_before_sections
193ee13b3f3SMauro Carvalho Chehab
194ee13b3f3SMauro Carvalho Chehab        if not logger:
195ee13b3f3SMauro Carvalho Chehab            self.config.log = logging.getLogger("kernel-doc")
196ee13b3f3SMauro Carvalho Chehab        else:
197ee13b3f3SMauro Carvalho Chehab            self.config.log = logger
198ee13b3f3SMauro Carvalho Chehab
19916740c29SMauro Carvalho Chehab        self.config.warning = self.warning
20016740c29SMauro Carvalho Chehab
201ee13b3f3SMauro Carvalho Chehab        self.config.src_tree = os.environ.get("SRCTREE", None)
202ee13b3f3SMauro Carvalho Chehab
20316740c29SMauro Carvalho Chehab        # Initialize variables that are internal to KernelFiles
20416740c29SMauro Carvalho Chehab
205ee13b3f3SMauro Carvalho Chehab        self.out_style = out_style
206ee13b3f3SMauro Carvalho Chehab
20716740c29SMauro Carvalho Chehab        self.errors = 0
208a566ba5aSMauro Carvalho Chehab        self.results = {}
209ee13b3f3SMauro Carvalho Chehab
210ee13b3f3SMauro Carvalho Chehab        self.files = set()
211799b0d2aSMauro Carvalho Chehab        self.export_files = set()
21216740c29SMauro Carvalho Chehab        self.export_table = {}
213ee13b3f3SMauro Carvalho Chehab
214799b0d2aSMauro Carvalho Chehab    def parse(self, file_list, export_file=None):
215ee13b3f3SMauro Carvalho Chehab        """
216ee13b3f3SMauro Carvalho Chehab        Parse all files
217ee13b3f3SMauro Carvalho Chehab        """
218ee13b3f3SMauro Carvalho Chehab
219ee13b3f3SMauro Carvalho Chehab        glob = GlobSourceFiles(srctree=self.config.src_tree)
220ee13b3f3SMauro Carvalho Chehab
221799b0d2aSMauro Carvalho Chehab        for fname in glob.parse_files(file_list, self.file_not_found_cb):
22216740c29SMauro Carvalho Chehab            self.parse_file(fname)
223ee13b3f3SMauro Carvalho Chehab
224799b0d2aSMauro Carvalho Chehab        for fname in glob.parse_files(export_file, self.file_not_found_cb):
225ee13b3f3SMauro Carvalho Chehab            self.process_export_file(fname)
226ee13b3f3SMauro Carvalho Chehab
227ee13b3f3SMauro Carvalho Chehab    def out_msg(self, fname, name, arg):
228ee13b3f3SMauro Carvalho Chehab        """
2294fa5e411SMauro Carvalho Chehab        Return output messages from a file name using the output style
2304fa5e411SMauro Carvalho Chehab        filtering.
231ee13b3f3SMauro Carvalho Chehab
2324fa5e411SMauro Carvalho Chehab        If output type was not handled by the syler, return None.
233ee13b3f3SMauro Carvalho Chehab        """
234ee13b3f3SMauro Carvalho Chehab
235ee13b3f3SMauro Carvalho Chehab        # NOTE: we can add rules here to filter out unwanted parts,
236ee13b3f3SMauro Carvalho Chehab        # although OutputFormat.msg already does that.
237ee13b3f3SMauro Carvalho Chehab
238ee13b3f3SMauro Carvalho Chehab        return self.out_style.msg(fname, name, arg)
239ee13b3f3SMauro Carvalho Chehab
240ee13b3f3SMauro Carvalho Chehab    def msg(self, enable_lineno=False, export=False, internal=False,
241a566ba5aSMauro Carvalho Chehab            symbol=None, nosymbol=None, no_doc_sections=False,
24216740c29SMauro Carvalho Chehab            filenames=None, export_file=None):
243ee13b3f3SMauro Carvalho Chehab        """
2444fa5e411SMauro Carvalho Chehab        Interacts over the kernel-doc results and output messages,
2454fa5e411SMauro Carvalho Chehab        returning kernel-doc markups on each interaction
246ee13b3f3SMauro Carvalho Chehab        """
247ee13b3f3SMauro Carvalho Chehab
248ee13b3f3SMauro Carvalho Chehab        self.out_style.set_config(self.config)
249ee13b3f3SMauro Carvalho Chehab
250a566ba5aSMauro Carvalho Chehab        if not filenames:
251a566ba5aSMauro Carvalho Chehab            filenames = sorted(self.results.keys())
252a566ba5aSMauro Carvalho Chehab
25347c2d416SMauro Carvalho Chehab        glob = GlobSourceFiles(srctree=self.config.src_tree)
25447c2d416SMauro Carvalho Chehab
255a566ba5aSMauro Carvalho Chehab        for fname in filenames:
25616740c29SMauro Carvalho Chehab            function_table = set()
25716740c29SMauro Carvalho Chehab
25816740c29SMauro Carvalho Chehab            if internal or export:
25916740c29SMauro Carvalho Chehab                if not export_file:
26016740c29SMauro Carvalho Chehab                    export_file = [fname]
26116740c29SMauro Carvalho Chehab
26247c2d416SMauro Carvalho Chehab                for f in glob.parse_files(export_file, self.file_not_found_cb):
26316740c29SMauro Carvalho Chehab                    function_table |= self.export_table[f]
26416740c29SMauro Carvalho Chehab
26516740c29SMauro Carvalho Chehab            if symbol:
26616740c29SMauro Carvalho Chehab                for s in symbol:
26716740c29SMauro Carvalho Chehab                    function_table.add(s)
26816740c29SMauro Carvalho Chehab
26916740c29SMauro Carvalho Chehab            self.out_style.set_filter(export, internal, symbol, nosymbol,
27016740c29SMauro Carvalho Chehab                                      function_table, enable_lineno,
27116740c29SMauro Carvalho Chehab                                      no_doc_sections)
27216740c29SMauro Carvalho Chehab
2734fa5e411SMauro Carvalho Chehab            msg = ""
274*27565cfcSMauro Carvalho Chehab            if fname not in self.results:
275*27565cfcSMauro Carvalho Chehab                self.config.log.warning("No kernel-doc for file %s", fname)
276*27565cfcSMauro Carvalho Chehab                continue
277*27565cfcSMauro Carvalho Chehab
278a566ba5aSMauro Carvalho Chehab            for name, arg in self.results[fname]:
279439111eeSMauro Carvalho Chehab                m = self.out_msg(fname, name, arg)
2804fa5e411SMauro Carvalho Chehab
281439111eeSMauro Carvalho Chehab                if m is None:
282ee13b3f3SMauro Carvalho Chehab                    ln = arg.get("ln", 0)
283ee13b3f3SMauro Carvalho Chehab                    dtype = arg.get('type', "")
284ee13b3f3SMauro Carvalho Chehab
285ee13b3f3SMauro Carvalho Chehab                    self.config.log.warning("%s:%d Can't handle %s",
286ee13b3f3SMauro Carvalho Chehab                                            fname, ln, dtype)
287439111eeSMauro Carvalho Chehab                else:
288439111eeSMauro Carvalho Chehab                    msg += m
289439111eeSMauro Carvalho Chehab
2904fa5e411SMauro Carvalho Chehab            if msg:
2914fa5e411SMauro Carvalho Chehab                yield fname, msg
292