xref: /linux/tools/lib/python/kdoc/kdoc_files.py (revision 72c395024dac5e215136cbff793455f065603b06)
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"""
8*8d08c7c6SMauro Carvalho ChehabClasses for navigating through the files that kernel-doc needs to handle
9*8d08c7c6SMauro Carvalho Chehabto generate documentation.
10ee13b3f3SMauro Carvalho Chehab"""
11ee13b3f3SMauro Carvalho Chehab
12ee13b3f3SMauro Carvalho Chehabimport argparse
13ee13b3f3SMauro Carvalho Chehabimport logging
14ee13b3f3SMauro Carvalho Chehabimport os
15ee13b3f3SMauro Carvalho Chehabimport re
16ee13b3f3SMauro Carvalho Chehab
17992a9df4SJonathan Corbetfrom kdoc.kdoc_parser import KernelDoc
18992a9df4SJonathan Corbetfrom kdoc.kdoc_output import OutputFormat
19ee13b3f3SMauro Carvalho Chehab
20ee13b3f3SMauro Carvalho Chehab
21ee13b3f3SMauro Carvalho Chehabclass GlobSourceFiles:
22ee13b3f3SMauro Carvalho Chehab    """
23ee13b3f3SMauro Carvalho Chehab    Parse C source code file names and directories via an Interactor.
24ee13b3f3SMauro Carvalho Chehab    """
25ee13b3f3SMauro Carvalho Chehab
26ee13b3f3SMauro Carvalho Chehab    def __init__(self, srctree=None, valid_extensions=None):
27ee13b3f3SMauro Carvalho Chehab        """
28ee13b3f3SMauro Carvalho Chehab        Initialize valid extensions with a tuple.
29ee13b3f3SMauro Carvalho Chehab
30ee13b3f3SMauro Carvalho Chehab        If not defined, assume default C extensions (.c and .h)
31ee13b3f3SMauro Carvalho Chehab
32ee13b3f3SMauro Carvalho Chehab        It would be possible to use python's glob function, but it is
33ee13b3f3SMauro Carvalho Chehab        very slow, and it is not interactive. So, it would wait to read all
34ee13b3f3SMauro Carvalho Chehab        directories before actually do something.
35ee13b3f3SMauro Carvalho Chehab
36ee13b3f3SMauro Carvalho Chehab        So, let's use our own implementation.
37ee13b3f3SMauro Carvalho Chehab        """
38ee13b3f3SMauro Carvalho Chehab
39ee13b3f3SMauro Carvalho Chehab        if not valid_extensions:
40ee13b3f3SMauro Carvalho Chehab            self.extensions = (".c", ".h")
41ee13b3f3SMauro Carvalho Chehab        else:
42ee13b3f3SMauro Carvalho Chehab            self.extensions = valid_extensions
43ee13b3f3SMauro Carvalho Chehab
44ee13b3f3SMauro Carvalho Chehab        self.srctree = srctree
45ee13b3f3SMauro Carvalho Chehab
46ee13b3f3SMauro Carvalho Chehab    def _parse_dir(self, dirname):
47*8d08c7c6SMauro Carvalho Chehab        """Internal function to parse files recursively."""
48ee13b3f3SMauro Carvalho Chehab
49ee13b3f3SMauro Carvalho Chehab        with os.scandir(dirname) as obj:
50ee13b3f3SMauro Carvalho Chehab            for entry in obj:
51ee13b3f3SMauro Carvalho Chehab                name = os.path.join(dirname, entry.name)
52ee13b3f3SMauro Carvalho Chehab
53f64c7e11SMauro Carvalho Chehab                if entry.is_dir(follow_symlinks=False):
54ee13b3f3SMauro Carvalho Chehab                    yield from self._parse_dir(name)
55ee13b3f3SMauro Carvalho Chehab
56ee13b3f3SMauro Carvalho Chehab                if not entry.is_file():
57ee13b3f3SMauro Carvalho Chehab                    continue
58ee13b3f3SMauro Carvalho Chehab
59ee13b3f3SMauro Carvalho Chehab                basename = os.path.basename(name)
60ee13b3f3SMauro Carvalho Chehab
61ee13b3f3SMauro Carvalho Chehab                if not basename.endswith(self.extensions):
62ee13b3f3SMauro Carvalho Chehab                    continue
63ee13b3f3SMauro Carvalho Chehab
64ee13b3f3SMauro Carvalho Chehab                yield name
65ee13b3f3SMauro Carvalho Chehab
66ee13b3f3SMauro Carvalho Chehab    def parse_files(self, file_list, file_not_found_cb):
67ee13b3f3SMauro Carvalho Chehab        """
685f88f44dSRandy Dunlap        Define an iterator to parse all source files from file_list,
69*8d08c7c6SMauro Carvalho Chehab        handling directories if any.
70ee13b3f3SMauro Carvalho Chehab        """
71ee13b3f3SMauro Carvalho Chehab
7216740c29SMauro Carvalho Chehab        if not file_list:
7316740c29SMauro Carvalho Chehab            return
7416740c29SMauro Carvalho Chehab
75ee13b3f3SMauro Carvalho Chehab        for fname in file_list:
76ee13b3f3SMauro Carvalho Chehab            if self.srctree:
77ee13b3f3SMauro Carvalho Chehab                f = os.path.join(self.srctree, fname)
78ee13b3f3SMauro Carvalho Chehab            else:
79ee13b3f3SMauro Carvalho Chehab                f = fname
80ee13b3f3SMauro Carvalho Chehab
81ee13b3f3SMauro Carvalho Chehab            if os.path.isdir(f):
82ee13b3f3SMauro Carvalho Chehab                yield from self._parse_dir(f)
83ee13b3f3SMauro Carvalho Chehab            elif os.path.isfile(f):
84ee13b3f3SMauro Carvalho Chehab                yield f
85ee13b3f3SMauro Carvalho Chehab            elif file_not_found_cb:
86ee13b3f3SMauro Carvalho Chehab                file_not_found_cb(fname)
87ee13b3f3SMauro Carvalho Chehab
88ee13b3f3SMauro Carvalho Chehab
89ee13b3f3SMauro Carvalho Chehabclass KernelFiles():
90ee13b3f3SMauro Carvalho Chehab    """
9116740c29SMauro Carvalho Chehab    Parse kernel-doc tags on multiple kernel source files.
9216740c29SMauro Carvalho Chehab
9316740c29SMauro Carvalho Chehab    There are two type of parsers defined here:
9416740c29SMauro Carvalho Chehab        - self.parse_file(): parses both kernel-doc markups and
95*8d08c7c6SMauro Carvalho Chehab          ``EXPORT_SYMBOL*`` macros;
96*8d08c7c6SMauro Carvalho Chehab        - self.process_export_file(): parses only ``EXPORT_SYMBOL*`` macros.
97ee13b3f3SMauro Carvalho Chehab    """
98ee13b3f3SMauro Carvalho Chehab
9916740c29SMauro Carvalho Chehab    def warning(self, msg):
100*8d08c7c6SMauro Carvalho Chehab        """Ancillary routine to output a warning and increment error count."""
10116740c29SMauro Carvalho Chehab
10216740c29SMauro Carvalho Chehab        self.config.log.warning(msg)
10316740c29SMauro Carvalho Chehab        self.errors += 1
10416740c29SMauro Carvalho Chehab
10516740c29SMauro Carvalho Chehab    def error(self, msg):
106*8d08c7c6SMauro Carvalho Chehab        """Ancillary routine to output an error and increment error count."""
10716740c29SMauro Carvalho Chehab
10816740c29SMauro Carvalho Chehab        self.config.log.error(msg)
10916740c29SMauro Carvalho Chehab        self.errors += 1
11016740c29SMauro Carvalho Chehab
111ee13b3f3SMauro Carvalho Chehab    def parse_file(self, fname):
112ee13b3f3SMauro Carvalho Chehab        """
113ee13b3f3SMauro Carvalho Chehab        Parse a single Kernel source.
114ee13b3f3SMauro Carvalho Chehab        """
115ee13b3f3SMauro Carvalho Chehab
11616740c29SMauro Carvalho Chehab        # Prevent parsing the same file twice if results are cached
11716740c29SMauro Carvalho Chehab        if fname in self.files:
11816740c29SMauro Carvalho Chehab            return
119ee13b3f3SMauro Carvalho Chehab
12016740c29SMauro Carvalho Chehab        doc = KernelDoc(self.config, fname)
12116740c29SMauro Carvalho Chehab        export_table, entries = doc.parse_kdoc()
12216740c29SMauro Carvalho Chehab
12316740c29SMauro Carvalho Chehab        self.export_table[fname] = export_table
12416740c29SMauro Carvalho Chehab
12516740c29SMauro Carvalho Chehab        self.files.add(fname)
12616740c29SMauro Carvalho Chehab        self.export_files.add(fname)      # parse_kdoc() already check exports
12716740c29SMauro Carvalho Chehab
12816740c29SMauro Carvalho Chehab        self.results[fname] = entries
129ee13b3f3SMauro Carvalho Chehab
130ee13b3f3SMauro Carvalho Chehab    def process_export_file(self, fname):
131ee13b3f3SMauro Carvalho Chehab        """
132*8d08c7c6SMauro Carvalho Chehab        Parses ``EXPORT_SYMBOL*`` macros from a single Kernel source file.
133ee13b3f3SMauro Carvalho Chehab        """
134ee13b3f3SMauro Carvalho Chehab
13516740c29SMauro Carvalho Chehab        # Prevent parsing the same file twice if results are cached
13616740c29SMauro Carvalho Chehab        if fname in self.export_files:
13716740c29SMauro Carvalho Chehab            return
13816740c29SMauro Carvalho Chehab
13916740c29SMauro Carvalho Chehab        doc = KernelDoc(self.config, fname)
14016740c29SMauro Carvalho Chehab        export_table = doc.parse_export()
14116740c29SMauro Carvalho Chehab
14216740c29SMauro Carvalho Chehab        if not export_table:
14316740c29SMauro Carvalho Chehab            self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}")
14416740c29SMauro Carvalho Chehab            export_table = set()
14516740c29SMauro Carvalho Chehab
14616740c29SMauro Carvalho Chehab        self.export_table[fname] = export_table
14716740c29SMauro Carvalho Chehab        self.export_files.add(fname)
148ee13b3f3SMauro Carvalho Chehab
149ee13b3f3SMauro Carvalho Chehab    def file_not_found_cb(self, fname):
150ee13b3f3SMauro Carvalho Chehab        """
151ee13b3f3SMauro Carvalho Chehab        Callback to warn if a file was not found.
152ee13b3f3SMauro Carvalho Chehab        """
153ee13b3f3SMauro Carvalho Chehab
15416740c29SMauro Carvalho Chehab        self.error(f"Cannot find file {fname}")
155ee13b3f3SMauro Carvalho Chehab
156799b0d2aSMauro Carvalho Chehab    def __init__(self, verbose=False, out_style=None,
157ee13b3f3SMauro Carvalho Chehab                 werror=False, wreturn=False, wshort_desc=False,
158ee13b3f3SMauro Carvalho Chehab                 wcontents_before_sections=False,
1592ab867a4SMauro Carvalho Chehab                 logger=None):
160ee13b3f3SMauro Carvalho Chehab        """
161*8d08c7c6SMauro Carvalho Chehab        Initialize startup variables and parse all files.
162ee13b3f3SMauro Carvalho Chehab        """
163ee13b3f3SMauro Carvalho Chehab
164ee13b3f3SMauro Carvalho Chehab        if not verbose:
165ee13b3f3SMauro Carvalho Chehab            verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
166ee13b3f3SMauro Carvalho Chehab
16743ecfe6bSMauro Carvalho Chehab        if out_style is None:
16843ecfe6bSMauro Carvalho Chehab            out_style = OutputFormat()
16943ecfe6bSMauro Carvalho Chehab
170ee13b3f3SMauro Carvalho Chehab        if not werror:
171ee13b3f3SMauro Carvalho Chehab            kcflags = os.environ.get("KCFLAGS", None)
172ee13b3f3SMauro Carvalho Chehab            if kcflags:
173ee13b3f3SMauro Carvalho Chehab                match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags)
174ee13b3f3SMauro Carvalho Chehab                if match:
175ee13b3f3SMauro Carvalho Chehab                    werror = True
176ee13b3f3SMauro Carvalho Chehab
177ee13b3f3SMauro Carvalho Chehab            # reading this variable is for backwards compat just in case
178ee13b3f3SMauro Carvalho Chehab            # someone was calling it with the variable from outside the
179ee13b3f3SMauro Carvalho Chehab            # kernel's build system
180ee13b3f3SMauro Carvalho Chehab            kdoc_werror = os.environ.get("KDOC_WERROR", None)
181ee13b3f3SMauro Carvalho Chehab            if kdoc_werror:
182ee13b3f3SMauro Carvalho Chehab                werror = kdoc_werror
183ee13b3f3SMauro Carvalho Chehab
18416740c29SMauro Carvalho Chehab        # Some variables are global to the parser logic as a whole as they are
18516740c29SMauro Carvalho Chehab        # used to send control configuration to KernelDoc class. As such,
18616740c29SMauro Carvalho Chehab        # those variables are read-only inside the KernelDoc.
187ee13b3f3SMauro Carvalho Chehab        self.config = argparse.Namespace
188ee13b3f3SMauro Carvalho Chehab
189ee13b3f3SMauro Carvalho Chehab        self.config.verbose = verbose
190ee13b3f3SMauro Carvalho Chehab        self.config.werror = werror
191ee13b3f3SMauro Carvalho Chehab        self.config.wreturn = wreturn
192ee13b3f3SMauro Carvalho Chehab        self.config.wshort_desc = wshort_desc
193ee13b3f3SMauro Carvalho Chehab        self.config.wcontents_before_sections = wcontents_before_sections
194ee13b3f3SMauro Carvalho Chehab
195ee13b3f3SMauro Carvalho Chehab        if not logger:
196ee13b3f3SMauro Carvalho Chehab            self.config.log = logging.getLogger("kernel-doc")
197ee13b3f3SMauro Carvalho Chehab        else:
198ee13b3f3SMauro Carvalho Chehab            self.config.log = logger
199ee13b3f3SMauro Carvalho Chehab
20016740c29SMauro Carvalho Chehab        self.config.warning = self.warning
20116740c29SMauro Carvalho Chehab
202ee13b3f3SMauro Carvalho Chehab        self.config.src_tree = os.environ.get("SRCTREE", None)
203ee13b3f3SMauro Carvalho Chehab
20416740c29SMauro Carvalho Chehab        # Initialize variables that are internal to KernelFiles
20516740c29SMauro Carvalho Chehab
206ee13b3f3SMauro Carvalho Chehab        self.out_style = out_style
207ee13b3f3SMauro Carvalho Chehab
20816740c29SMauro Carvalho Chehab        self.errors = 0
209a566ba5aSMauro Carvalho Chehab        self.results = {}
210ee13b3f3SMauro Carvalho Chehab
211ee13b3f3SMauro Carvalho Chehab        self.files = set()
212799b0d2aSMauro Carvalho Chehab        self.export_files = set()
21316740c29SMauro Carvalho Chehab        self.export_table = {}
214ee13b3f3SMauro Carvalho Chehab
215799b0d2aSMauro Carvalho Chehab    def parse(self, file_list, export_file=None):
216ee13b3f3SMauro Carvalho Chehab        """
217*8d08c7c6SMauro Carvalho Chehab        Parse all files.
218ee13b3f3SMauro Carvalho Chehab        """
219ee13b3f3SMauro Carvalho Chehab
220ee13b3f3SMauro Carvalho Chehab        glob = GlobSourceFiles(srctree=self.config.src_tree)
221ee13b3f3SMauro Carvalho Chehab
222799b0d2aSMauro Carvalho Chehab        for fname in glob.parse_files(file_list, self.file_not_found_cb):
22316740c29SMauro Carvalho Chehab            self.parse_file(fname)
224ee13b3f3SMauro Carvalho Chehab
225799b0d2aSMauro Carvalho Chehab        for fname in glob.parse_files(export_file, self.file_not_found_cb):
226ee13b3f3SMauro Carvalho Chehab            self.process_export_file(fname)
227ee13b3f3SMauro Carvalho Chehab
228ee13b3f3SMauro Carvalho Chehab    def out_msg(self, fname, name, arg):
229ee13b3f3SMauro Carvalho Chehab        """
2304fa5e411SMauro Carvalho Chehab        Return output messages from a file name using the output style
2314fa5e411SMauro Carvalho Chehab        filtering.
232ee13b3f3SMauro Carvalho Chehab
2335f88f44dSRandy Dunlap        If output type was not handled by the styler, return None.
234ee13b3f3SMauro Carvalho Chehab        """
235ee13b3f3SMauro Carvalho Chehab
236ee13b3f3SMauro Carvalho Chehab        # NOTE: we can add rules here to filter out unwanted parts,
237ee13b3f3SMauro Carvalho Chehab        # although OutputFormat.msg already does that.
238ee13b3f3SMauro Carvalho Chehab
239ee13b3f3SMauro Carvalho Chehab        return self.out_style.msg(fname, name, arg)
240ee13b3f3SMauro Carvalho Chehab
241ee13b3f3SMauro Carvalho Chehab    def msg(self, enable_lineno=False, export=False, internal=False,
242a566ba5aSMauro Carvalho Chehab            symbol=None, nosymbol=None, no_doc_sections=False,
24316740c29SMauro Carvalho Chehab            filenames=None, export_file=None):
244ee13b3f3SMauro Carvalho Chehab        """
2454fa5e411SMauro Carvalho Chehab        Interacts over the kernel-doc results and output messages,
246*8d08c7c6SMauro Carvalho Chehab        returning kernel-doc markups on each interaction.
247ee13b3f3SMauro Carvalho Chehab        """
248ee13b3f3SMauro Carvalho Chehab
249ee13b3f3SMauro Carvalho Chehab        self.out_style.set_config(self.config)
250ee13b3f3SMauro Carvalho Chehab
251a566ba5aSMauro Carvalho Chehab        if not filenames:
252a566ba5aSMauro Carvalho Chehab            filenames = sorted(self.results.keys())
253a566ba5aSMauro Carvalho Chehab
25447c2d416SMauro Carvalho Chehab        glob = GlobSourceFiles(srctree=self.config.src_tree)
25547c2d416SMauro Carvalho Chehab
256a566ba5aSMauro Carvalho Chehab        for fname in filenames:
25716740c29SMauro Carvalho Chehab            function_table = set()
25816740c29SMauro Carvalho Chehab
25916740c29SMauro Carvalho Chehab            if internal or export:
26016740c29SMauro Carvalho Chehab                if not export_file:
26116740c29SMauro Carvalho Chehab                    export_file = [fname]
26216740c29SMauro Carvalho Chehab
26347c2d416SMauro Carvalho Chehab                for f in glob.parse_files(export_file, self.file_not_found_cb):
26416740c29SMauro Carvalho Chehab                    function_table |= self.export_table[f]
26516740c29SMauro Carvalho Chehab
26616740c29SMauro Carvalho Chehab            if symbol:
26716740c29SMauro Carvalho Chehab                for s in symbol:
26816740c29SMauro Carvalho Chehab                    function_table.add(s)
26916740c29SMauro Carvalho Chehab
27016740c29SMauro Carvalho Chehab            self.out_style.set_filter(export, internal, symbol, nosymbol,
27116740c29SMauro Carvalho Chehab                                      function_table, enable_lineno,
27216740c29SMauro Carvalho Chehab                                      no_doc_sections)
27316740c29SMauro Carvalho Chehab
2744fa5e411SMauro Carvalho Chehab            msg = ""
27527565cfcSMauro Carvalho Chehab            if fname not in self.results:
27627565cfcSMauro Carvalho Chehab                self.config.log.warning("No kernel-doc for file %s", fname)
27727565cfcSMauro Carvalho Chehab                continue
27827565cfcSMauro Carvalho Chehab
279104e0a68SMauro Carvalho Chehab            symbols = self.results[fname]
280104e0a68SMauro Carvalho Chehab            self.out_style.set_symbols(symbols)
281104e0a68SMauro Carvalho Chehab
282104e0a68SMauro Carvalho Chehab            for arg in symbols:
283703f9074SJonathan Corbet                m = self.out_msg(fname, arg.name, arg)
2844fa5e411SMauro Carvalho Chehab
285439111eeSMauro Carvalho Chehab                if m is None:
286ee13b3f3SMauro Carvalho Chehab                    ln = arg.get("ln", 0)
287ee13b3f3SMauro Carvalho Chehab                    dtype = arg.get('type', "")
288ee13b3f3SMauro Carvalho Chehab
289ee13b3f3SMauro Carvalho Chehab                    self.config.log.warning("%s:%d Can't handle %s",
290ee13b3f3SMauro Carvalho Chehab                                            fname, ln, dtype)
291439111eeSMauro Carvalho Chehab                else:
292439111eeSMauro Carvalho Chehab                    msg += m
293439111eeSMauro Carvalho Chehab
2944fa5e411SMauro Carvalho Chehab            if msg:
2954fa5e411SMauro Carvalho Chehab                yield fname, msg
296