Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/lib/python/kdoc/kdoc_files.py
122941 views
1
#!/usr/bin/env python3
2
# SPDX-License-Identifier: GPL-2.0
3
# Copyright(c) 2025: Mauro Carvalho Chehab <[email protected]>.
4
#
5
# pylint: disable=R0903,R0913,R0914,R0917
6
7
"""
8
Classes for navigating through the files that kernel-doc needs to handle
9
to generate documentation.
10
"""
11
12
import argparse
13
import logging
14
import os
15
import re
16
17
from kdoc.kdoc_parser import KernelDoc
18
from kdoc.kdoc_output import OutputFormat
19
20
21
class GlobSourceFiles:
22
"""
23
Parse C source code file names and directories via an Interactor.
24
"""
25
26
def __init__(self, srctree=None, valid_extensions=None):
27
"""
28
Initialize valid extensions with a tuple.
29
30
If not defined, assume default C extensions (.c and .h)
31
32
It would be possible to use python's glob function, but it is
33
very slow, and it is not interactive. So, it would wait to read all
34
directories before actually do something.
35
36
So, let's use our own implementation.
37
"""
38
39
if not valid_extensions:
40
self.extensions = (".c", ".h")
41
else:
42
self.extensions = valid_extensions
43
44
self.srctree = srctree
45
46
def _parse_dir(self, dirname):
47
"""Internal function to parse files recursively."""
48
49
with os.scandir(dirname) as obj:
50
for entry in obj:
51
name = os.path.join(dirname, entry.name)
52
53
if entry.is_dir(follow_symlinks=False):
54
yield from self._parse_dir(name)
55
56
if not entry.is_file():
57
continue
58
59
basename = os.path.basename(name)
60
61
if not basename.endswith(self.extensions):
62
continue
63
64
yield name
65
66
def parse_files(self, file_list, file_not_found_cb):
67
"""
68
Define an iterator to parse all source files from file_list,
69
handling directories if any.
70
"""
71
72
if not file_list:
73
return
74
75
for fname in file_list:
76
if self.srctree:
77
f = os.path.join(self.srctree, fname)
78
else:
79
f = fname
80
81
if os.path.isdir(f):
82
yield from self._parse_dir(f)
83
elif os.path.isfile(f):
84
yield f
85
elif file_not_found_cb:
86
file_not_found_cb(fname)
87
88
89
class KernelFiles():
90
"""
91
Parse kernel-doc tags on multiple kernel source files.
92
93
There are two type of parsers defined here:
94
- self.parse_file(): parses both kernel-doc markups and
95
``EXPORT_SYMBOL*`` macros;
96
- self.process_export_file(): parses only ``EXPORT_SYMBOL*`` macros.
97
"""
98
99
def warning(self, msg):
100
"""Ancillary routine to output a warning and increment error count."""
101
102
self.config.log.warning(msg)
103
self.errors += 1
104
105
def error(self, msg):
106
"""Ancillary routine to output an error and increment error count."""
107
108
self.config.log.error(msg)
109
self.errors += 1
110
111
def parse_file(self, fname):
112
"""
113
Parse a single Kernel source.
114
"""
115
116
# Prevent parsing the same file twice if results are cached
117
if fname in self.files:
118
return
119
120
doc = KernelDoc(self.config, fname)
121
export_table, entries = doc.parse_kdoc()
122
123
self.export_table[fname] = export_table
124
125
self.files.add(fname)
126
self.export_files.add(fname) # parse_kdoc() already check exports
127
128
self.results[fname] = entries
129
130
def process_export_file(self, fname):
131
"""
132
Parses ``EXPORT_SYMBOL*`` macros from a single Kernel source file.
133
"""
134
135
# Prevent parsing the same file twice if results are cached
136
if fname in self.export_files:
137
return
138
139
doc = KernelDoc(self.config, fname)
140
export_table = doc.parse_export()
141
142
if not export_table:
143
self.error(f"Error: Cannot check EXPORT_SYMBOL* on {fname}")
144
export_table = set()
145
146
self.export_table[fname] = export_table
147
self.export_files.add(fname)
148
149
def file_not_found_cb(self, fname):
150
"""
151
Callback to warn if a file was not found.
152
"""
153
154
self.error(f"Cannot find file {fname}")
155
156
def __init__(self, verbose=False, out_style=None,
157
werror=False, wreturn=False, wshort_desc=False,
158
wcontents_before_sections=False,
159
logger=None):
160
"""
161
Initialize startup variables and parse all files.
162
"""
163
164
if not verbose:
165
verbose = bool(os.environ.get("KBUILD_VERBOSE", 0))
166
167
if out_style is None:
168
out_style = OutputFormat()
169
170
if not werror:
171
kcflags = os.environ.get("KCFLAGS", None)
172
if kcflags:
173
match = re.search(r"(\s|^)-Werror(\s|$)/", kcflags)
174
if match:
175
werror = True
176
177
# reading this variable is for backwards compat just in case
178
# someone was calling it with the variable from outside the
179
# kernel's build system
180
kdoc_werror = os.environ.get("KDOC_WERROR", None)
181
if kdoc_werror:
182
werror = kdoc_werror
183
184
# Some variables are global to the parser logic as a whole as they are
185
# used to send control configuration to KernelDoc class. As such,
186
# those variables are read-only inside the KernelDoc.
187
self.config = argparse.Namespace
188
189
self.config.verbose = verbose
190
self.config.werror = werror
191
self.config.wreturn = wreturn
192
self.config.wshort_desc = wshort_desc
193
self.config.wcontents_before_sections = wcontents_before_sections
194
195
if not logger:
196
self.config.log = logging.getLogger("kernel-doc")
197
else:
198
self.config.log = logger
199
200
self.config.warning = self.warning
201
202
self.config.src_tree = os.environ.get("SRCTREE", None)
203
204
# Initialize variables that are internal to KernelFiles
205
206
self.out_style = out_style
207
208
self.errors = 0
209
self.results = {}
210
211
self.files = set()
212
self.export_files = set()
213
self.export_table = {}
214
215
def parse(self, file_list, export_file=None):
216
"""
217
Parse all files.
218
"""
219
220
glob = GlobSourceFiles(srctree=self.config.src_tree)
221
222
for fname in glob.parse_files(file_list, self.file_not_found_cb):
223
self.parse_file(fname)
224
225
for fname in glob.parse_files(export_file, self.file_not_found_cb):
226
self.process_export_file(fname)
227
228
def out_msg(self, fname, name, arg):
229
"""
230
Return output messages from a file name using the output style
231
filtering.
232
233
If output type was not handled by the styler, return None.
234
"""
235
236
# NOTE: we can add rules here to filter out unwanted parts,
237
# although OutputFormat.msg already does that.
238
239
return self.out_style.msg(fname, name, arg)
240
241
def msg(self, enable_lineno=False, export=False, internal=False,
242
symbol=None, nosymbol=None, no_doc_sections=False,
243
filenames=None, export_file=None):
244
"""
245
Interacts over the kernel-doc results and output messages,
246
returning kernel-doc markups on each interaction.
247
"""
248
249
self.out_style.set_config(self.config)
250
251
if not filenames:
252
filenames = sorted(self.results.keys())
253
254
glob = GlobSourceFiles(srctree=self.config.src_tree)
255
256
for fname in filenames:
257
function_table = set()
258
259
if internal or export:
260
if not export_file:
261
export_file = [fname]
262
263
for f in glob.parse_files(export_file, self.file_not_found_cb):
264
function_table |= self.export_table[f]
265
266
if symbol:
267
for s in symbol:
268
function_table.add(s)
269
270
self.out_style.set_filter(export, internal, symbol, nosymbol,
271
function_table, enable_lineno,
272
no_doc_sections)
273
274
msg = ""
275
if fname not in self.results:
276
self.config.log.warning("No kernel-doc for file %s", fname)
277
continue
278
279
symbols = self.results[fname]
280
self.out_style.set_symbols(symbols)
281
282
for arg in symbols:
283
m = self.out_msg(fname, arg.name, arg)
284
285
if m is None:
286
ln = arg.get("ln", 0)
287
dtype = arg.get('type', "")
288
289
self.config.log.warning("%s:%d Can't handle %s",
290
fname, ln, dtype)
291
else:
292
msg += m
293
294
if msg:
295
yield fname, msg
296
297