source: kernel-config/kernel-config.py@ 2fbed80

12.0 12.1 12.2 gimp3 ken/TL2024 ken/tuningfonts lazarus plabs/newcss python3.11 rahul/power-profiles-daemon renodr/vulkan-addition trunk xry111/for-12.3 xry111/llvm18 xry111/spidermonkey128
Last change on this file since 2fbed80 was 2fbed80, checked in by Xi Ruoyao <xry111@…>, 14 months ago

kernel-config: Fix a bug causing some menu not rendered

For example:

menu "Power management and ACPI options"

config ARCH_SUPPORTS_ACPI

bool

config ACPI

bool
depends on ARCH_SUPPORTS_ACPI
prompt "ACPI (Advanced Configuration and Power Interface) Support"

endmenu

If ACPI=y, we need to "backward propagate" it to ARCH_SUPPORTS_ACPI, in
order to further mark the menu used. Otherwise the ACPI menu won't show
up.

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