source: kernel-config/kernel-config.py@ d9e1464

12.0 12.1 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 d9e1464 was 831ba20a, checked in by Xi Ruoyao <xry111@…>, 13 months ago

kernel-config: Handle if/endif pairs in Kconfig, and allow to set "forced" attribute for options

For example, CONFIG_INPUT is forced w/o CONFIG_EXPERT.

libevdev kernel section needs these features to be rendered
successfully.

  • Property mode set to 100755
File size: 7.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' }
27
28def expand_var(s):
29 for k in expand_var_mp:
30 s = s.replace('$(' + k + ')', expand_var_mp[k])
31 return s
32
33def pop_stack(cond):
34 global ind0, ind1, stack
35 assert(cond(stack[-1][0]))
36 s, i0, i1, _ = stack[-1]
37 stack = stack[:-1]
38 ind0 -= i0
39 ind1 -= i1
40
41def pop_stack_while(cond):
42 while len(stack) and cond(stack[-1][0]):
43 pop_stack(cond)
44
45def cur_menu():
46 global stack
47 return stack[-1][3] if len(stack) else 0
48
49def cur_if():
50 global if_stack
51 return if_stack[-1] if len(if_stack) else []
52
53def parse_config(buf):
54 global ind0, ind1, stack, menu_id
55 is_menu = buf[0].startswith('menu')
56 key = buf[0].split()[1].strip()
57
58 deps = ['menu'] + cur_if()
59 title = None
60 klass = None
61 for line in buf[1:]:
62 line = line.strip()
63 if line.startswith('depends on '):
64 new_deps = line[len('depends on '):].split('&&')
65 deps += [x.strip() for x in new_deps]
66 else:
67 for prefix in ['tristate', 'bool', 'string']:
68 if line.startswith(prefix + ' '):
69 title = line[len(prefix) + 1:]
70 klass = prefix
71 elif line.startswith('def_' + prefix + ' '):
72 klass = prefix
73 elif line.startswith('prompt '):
74 title = line[len('prompt '):]
75
76 pop_stack_while(lambda x: x not in deps)
77
78 if key not in known_config:
79 return []
80 val = known_config[key]
81 comment = None
82 forced = None
83
84 if type(val) == dict:
85 comment = val.get('comment')
86 forced = val.get('forced')
87 val = val['value']
88
89 assert(title and klass)
90 title = title.strip().lstrip('"')
91 title = title[:title.find('"')]
92
93 if klass == 'string':
94 val = '(' + val + ')'
95 else:
96 assert((val == 'X') == bool(cur_menu() & choice_bit))
97 if (val == 'X'):
98 val = '(X)'
99 else:
100 val = list(val)
101 val.sort()
102 for c in val:
103 if c not in 'M* ' or (c == 'M' and klass != 'tristate'):
104 raise Exception('unknown setting %s for %s' % (c, key))
105 bracket = None
106 if klass == 'tristate':
107 if forced and 'M' not in val:
108 # render this "as-is" a forced bool
109 klass = 'bool'
110 else:
111 bracket = '{}' if forced else '<>'
112
113 if klass == 'bool':
114 bracket = '--' if forced else '[]'
115
116 if not bracket:
117 raise Exception('should not reach here')
118 val = bracket[0] + '/'.join(val) + bracket[1]
119
120 arrow = ' --->' if is_menu else ''
121 r = [(ind0, val, ind1, title, arrow, key, cur_menu(), comment)]
122 menu_id += is_menu
123 stack_ent = (key, 2, 0, menu_id) if is_menu else (key, 0, 2, cur_menu())
124 ind0 += stack_ent[1]
125 ind1 += stack_ent[2]
126 stack += [stack_ent]
127 return r
128
129def parse_choice(buf):
130 global ind0, ind1, stack, menu_id
131 assert(buf[0] == 'choice\n')
132 title = ''
133 for line in buf:
134 line = line.strip()
135 if line.startswith('prompt '):
136 title = line[len('prompt '):].strip().strip('"')
137 r = [(ind0, "", ind1, title, ' --->', '', cur_menu(), None)]
138 menu_id += 1
139 stack += [('menu', 2, 0, menu_id | choice_bit)]
140 ind0 += 2
141 return r
142
143def load_kconfig(file):
144 global ind0, ind1, stack, path, menu_id, if_stack
145 r = []
146 config_buf = []
147 with open(path + file) as f:
148 for line in f:
149 if len(config_buf):
150 if not line.startswith('\t'):
151 if config_buf[0] == 'choice\n':
152 r += parse_choice(config_buf)
153 else:
154 r += parse_config(config_buf)
155 config_buf = []
156 else:
157 config_buf += [line]
158 continue
159 if line.startswith('source'):
160 sub = expand_var(line.split()[1].strip('"'))
161 r += load_kconfig(sub)
162 elif line.startswith('config') or line.startswith('menuconfig'):
163 config_buf = [line]
164 elif line.startswith('choice'):
165 config_buf = [line]
166 elif line.startswith("menu"):
167 title = expand_var(line[4:].strip().strip('"'))
168 r += [(ind0, "", ind1, title, ' --->', '', cur_menu(), None)]
169 menu_id += 1
170 stack += [('menu', 2, 0, menu_id)]
171 ind0 += 2
172 elif line.startswith('endmenu') or line.startswith('endchoice'):
173 pop_stack_while(lambda x: x != 'menu')
174 pop_stack(lambda x: x == 'menu')
175 if r[-1][1] == "":
176 # prune empty menu
177 r = r[:-1]
178 elif line.startswith('if '):
179 line = line[3:]
180 top = cur_if()
181 top += [x.strip() for x in line.split("&&")]
182 if_stack += [top]
183 elif line.startswith('endif'):
184 if_stack = if_stack[:-1]
185 return r
186
187known_config = {}
188
189from sys import argv
190import tomllib
191
192path = argv[1]
193if path[-1] != '/':
194 path += '/'
195with open(argv[2], 'rb') as f:
196 known_config = tomllib.load(f)
197
198r = load_kconfig("Kconfig")
199
200# Now we are going to pretty-print r
201
202## Calculate the maximum value length for each menu
203max_val_len = {}
204for _, val, _, _, _, _, menu, _ in r:
205 x = max_val_len[menu] if menu in max_val_len else 0
206 max_val_len[menu] = max(x, len(val))
207
208## Output
209
210max_line = 80
211buf = []
212
213done = [x[5] for x in r]
214for i in known_config:
215 if i not in done:
216 raise Exception("%s seems not exist" % i)
217
218for i0, val, i1, title, arrow, key, menu, comment in r:
219 if val:
220 val += (max_val_len[menu] - len(val)) * ' '
221 line = i0 * ' ' + val + (i1 + bool(val)) * ' '
222
223 rem = max_line - len(line) - len(arrow)
224
225 if len(title) > rem:
226 title = title[:rem - 3] + '...'
227
228 line += title + arrow
229 rem = max_line - len(line)
230
231 if key:
232 key = ' [' + key + ']'
233
234 if len(key) <= rem:
235 line += (rem - len(key)) * ' ' + key
236 else:
237 key = '... ' + key
238 line += '\n' + ' ' * (max_line - len(key)) + key
239 if comment:
240 line = ' ' * i0 + '# ' + comment + ':\n' + line
241 buf += [line.replace('<', '&lt;').replace('>', '&gt;').rstrip()]
242
243import kernel_version
244kver = kernel_version.kernel_version(path)
245
246print('''<?xml version="1.0" encoding="ISO-8859-1"?>
247<!DOCTYPE note PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
248 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">''')
249print('<!-- Automatically generated by kernel-config.py for Linux', kver)
250print(' DO NOT EDIT! -->')
251print('<screen><literal>' + '\n'.join(buf) + '</literal></screen>')
Note: See TracBrowser for help on using the repository browser.