source: chapter10/kernel/kernel-config.py@ 0b4c2e2

xry111/multilib
Last change on this file since 0b4c2e2 was f54ecb3, checked in by Xi Ruoyao <xry111@…>, 12 days ago

kernel-config: Add support to set arch

To be used for multilib.

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