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