source: kernel-config/kernel-config.py@ 5a809f3

12.1 ken/TL2024 lazarus plabs/newcss python3.11 rahul/power-profiles-daemon trunk xry111/llvm18
Last change on this file since 5a809f3 was 5a809f3, checked in by Xi Ruoyao <xry111@…>, 6 months ago

kernel-config: Fix "the last config entry in a Kconfig file thrown away"

Triggered when I'm trying to add libnl test suite kernel configuration
parsing net/l3mdev/Kconfig :(.

  • Property mode set to 100755
File size: 8.3 KB
Line 
1#!/usr/bin/env python3
2
3# SPDX-License-Identifier: MIT
4# Copyright 2023 The LFS Editors
5
6# Stupid script to render "mconf"-style kernel configuration
7# Usage: kernel-config.py [path to kernel tree] [needed config].toml
8# The toml file should be like:
9# for bool and tristate:
10# EXT4="*"
11# DRM="*M"
12# EXPERT=" "
13# DRM_I915="*M"
14# for choice:
15# HIGHMEM64G="X"
16# an entry with comment:
17# DRM_I915 = { value = " *M", comment = "for i915, crocus, or iris" }
18
19choice_bit = 1 << 30
20ind0 = 0
21ind1 = 0
22menu_id = 1
23stack = []
24if_stack = []
25
26expand_var_mp = { 'SRCARCH': 'x86' }
27main_dep = {}
28
29def expand_var(s):
30 for k in expand_var_mp:
31 s = s.replace('$(' + k + ')', expand_var_mp[k])
32 return s
33
34def pop_stack(cond):
35 global ind0, ind1, stack
36 assert(cond(stack[-1][0]))
37 s, i0, i1, _ = stack[-1]
38 stack = stack[:-1]
39 ind0 -= i0
40 ind1 -= i1
41
42def pop_stack_while(cond):
43 while stack and cond(stack[-1][0]):
44 pop_stack(cond)
45
46def cur_menu():
47 global stack
48 return stack[-1][3] if stack else 0
49
50def cur_if():
51 global if_stack
52 return if_stack[-1][:] if if_stack else []
53
54def clean_dep(d):
55 d = d.strip()
56 if d.endswith('=y') or d.endswith('=M'):
57 d = d[:-2]
58 elif d.endswith(' != ""'):
59 d = d[:-6]
60 return d
61
62def parse_config(buf):
63 global ind0, ind1, stack, menu_id
64 is_choice = buf[0].strip() == 'choice'
65 is_menu = buf[0].startswith('menu') or is_choice
66 is_nonconfig_menu = buf[0].startswith('menu ') or is_choice
67 key = None if is_nonconfig_menu else buf[0].split()[1].strip()
68 title = buf[0][len('menu '):] if is_nonconfig_menu else None
69 deps = ['menu'] + cur_if()
70 klass = None
71
72 for line in buf[1:]:
73 line = line.strip()
74 if line.startswith('depends on '):
75 new_deps = line[len('depends on '):].split('&&')
76 deps += [clean_dep(x) for x in new_deps]
77 elif line.startswith('prompt'):
78 title = line[len('prompt '):]
79 else:
80 for prefix in ['tristate', 'bool', 'string']:
81 if line.startswith(prefix + ' '):
82 title = line[len(prefix) + 1:]
83 klass = prefix
84 elif line == prefix:
85 klass = prefix
86 elif line.startswith('def_' + prefix + ' '):
87 klass = prefix
88 else:
89 continue
90
91 if '"' in line:
92 tail = line[line.rfind('"') + 1:].strip()
93 if tail[:3] == 'if ':
94 deps += [clean_dep(x) for x in tail[3:].split('&&')]
95
96 pop_stack_while(lambda x: x not in deps)
97
98 menu_id += is_menu
99 internal_key = key or menu_id
100 if stack:
101 fa = stack[-1][0]
102 if fa == 'menu':
103 fa = cur_menu() & ~choice_bit
104 main_dep[internal_key] = fa
105
106 val = known_config.get(key)
107 comment = None
108 forced = None
109
110 if type(val) == dict:
111 comment = val.get('comment')
112 forced = val.get('forced')
113 val = val['value']
114
115 klass = klass or 'string'
116 if title:
117 title = title.strip().lstrip('"')
118 title = title[:title.find('"')]
119
120 if not val:
121 pass
122 elif klass == 'string':
123 val = '(' + val + ')'
124 else:
125 assert((val == 'X') == bool(cur_menu() & choice_bit))
126 if (val == 'X'):
127 val = '(X)'
128 else:
129 val = list(val)
130 val.sort()
131 for c in val:
132 if c not in 'M* ' or (c == 'M' and klass != 'tristate'):
133 raise Exception('unknown setting %s for %s' % (c, key))
134 bracket = None
135 if klass == 'tristate' and forced != '*' :
136 bracket = '{}' if forced else '<>'
137 else:
138 bracket = '--' if forced else '[]'
139
140 val = bracket[0] + '/'.join(val) + bracket[1]
141
142 arrow = ' --->' if is_menu else ''
143 r = [ind0, val, ind1, title, arrow, internal_key, cur_menu(), comment]
144
145 # Don't indent for untitled (internal) entries
146 x = 2 if title else 0
147
148 key = key or 'menu'
149 menu = (menu_id if is_menu else cur_menu())
150 menu |= choice_bit if is_choice else 0
151 stack_ent = (key, 2, 0, menu) if is_menu else (key, 0, x, menu)
152 ind0 += stack_ent[1]
153 ind1 += stack_ent[2]
154 stack += [stack_ent]
155
156 return r
157
158def load_kconfig(file):
159 global ind0, ind1, stack, path, menu_id, if_stack
160 r = []
161 config_buf = []
162 with open(path + file) as f:
163 for line in f:
164 if config_buf:
165 if not (line.startswith('\t') or line.startswith(' ')):
166 r += [parse_config(config_buf)]
167 config_buf = []
168 else:
169 config_buf += [line]
170 continue
171 if line.startswith('source') or line.startswith('\tsource'):
172 sub = expand_var(line.strip().split()[1].strip('"'))
173 r += load_kconfig(sub)
174 elif line.startswith('config') or line.startswith('menu'):
175 config_buf = [line]
176 elif line.startswith('choice'):
177 config_buf = [line]
178 elif line.startswith('endmenu') or line.startswith('endchoice'):
179 pop_stack_while(lambda x: x != 'menu')
180 pop_stack(lambda x: x == 'menu')
181 elif line.startswith('if '):
182 line = line[3:]
183 top = cur_if()
184 top += [x.strip() for x in line.split("&&")]
185 if_stack += [top]
186 elif line.startswith('endif'):
187 if_stack = if_stack[:-1]
188
189 if config_buf:
190 r += [parse_config(config_buf)]
191
192 return r
193
194known_config = {}
195
196def escape(x):
197 return x.replace('<', '&lt;').replace('>', '&gt;')
198
199from sys import argv
200import tomllib
201
202path = argv[1]
203if path[-1] != '/':
204 path += '/'
205with open(argv[2], 'rb') as f:
206 known_config = tomllib.load(f)
207
208r = load_kconfig('Kconfig')
209
210# Refcount all menus
211
212index_ikey = {}
213for i in reversed(range(len(r))):
214 index_ikey[r[i][5]] = i
215
216for i in reversed(range(len(r))):
217 if r[i][1] != None:
218 key = r[i][5]
219 fa = main_dep.get(key)
220 if not fa:
221 continue
222 j = index_ikey[fa]
223 if type(fa) == int or not r[j][3]:
224 # The main dependency is a menu or untitled magic entry,
225 # just mark it used
226 r[j][1] = ''
227 if r[j][1] is None:
228 raise Exception('[%s] needs unselected [%s]' % (key, fa))
229
230r = [i for i in r if i[1] != None and i[3]]
231
232# Now we are going to pretty-print r
233
234## Calculate the maximum value length for each menu
235max_val_len = {}
236for _, val, _, _, _, _, menu, _ in r:
237 x = max_val_len.get(menu) or 0
238 max_val_len[menu] = max(x, len(val))
239
240## Output
241
242max_line = 80
243buf = []
244
245done = [x[5] for x in r] + ['revision']
246for i in known_config:
247 if i not in done:
248 raise Exception("%s seems not exist" % i)
249
250sep = known_config.get('separate_toplevel_menu')
251
252for i0, val, i1, title, arrow, key, menu, comment in r:
253 rem = max_line
254
255 if val:
256 val += (max_val_len[menu] - len(val)) * ' '
257
258 rem -= i0 + i1 + bool(val) + len(val)
259 line = i0 * ' ' + escape(val) + (i1 + bool(val)) * ' '
260
261 rem -= len(arrow)
262
263 if len(title) > rem:
264 title = title[:rem - 3] + '...'
265
266 b = title.lstrip('YyMmNnHh')
267 a = title[:len(title) - len(b)]
268 b0 = "<emphasis role='blue'>" + escape(b[0]) + "</emphasis>"
269 line += escape(a) + b0 + escape(b[1:]) + escape(arrow)
270
271 rem -= len(title)
272
273 key = ' [' + key + ']' if type(key) == str else ''
274
275 if len(key) <= rem:
276 line += (rem - len(key)) * ' ' + key
277 else:
278 key = '... ' + key
279 line += '\n' + ' ' * (max_line - len(key)) + key
280 if type(comment) == str:
281 comment = [comment]
282 if comment:
283 comment = '\n'.join([' ' * i0 + '# ' + line for line in comment])
284 buf += [escape(comment) + ':']
285
286 if not menu and buf:
287 buf += ['']
288
289 buf += [line.rstrip()]
290
291from jinja2 import Template
292
293t = Template('''<?xml version="1.0" encoding="ISO-8859-1"?>
294<!DOCTYPE note PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
295 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
296<!-- Automatically generated by kernel-config.py
297 DO NOT EDIT! -->
298<screen{{ rev }}>{{ '\n'.join(buf) }}</screen>''')
299
300rev = known_config.get('revision')
301rev = ' revision="%s"' % rev if rev else ''
302print(t.render(rev = rev, buf = buf))
Note: See TracBrowser for help on using the repository browser.