source: kernel-config/kernel-config.py@ 185ffd9

12.0 12.1 ken/TL2024 ken/tuningfonts lazarus plabs/newcss python3.11 rahul/power-profiles-daemon renodr/vulkan-addition trunk xry111/llvm18
Last change on this file since 185ffd9 was 185ffd9, checked in by Xi Ruoyao <xry111@…>, 10 months ago

kernel-config: Allow specifying non-module style forcing for tristate

instead of relying on unsound heuristic.

  • Property mode set to 100755
File size: 7.1 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' and forced != '*' :
107 bracket = '{}' if forced else '<>'
108 else:
109 bracket = '--' if forced else '[]'
110
111 val = bracket[0] + '/'.join(val) + bracket[1]
112
113 arrow = ' --->' if is_menu else ''
114 r = [(ind0, val, ind1, title, arrow, key, cur_menu(), comment)]
115 menu_id += is_menu
116 stack_ent = (key, 2, 0, menu_id) if is_menu else (key, 0, 2, cur_menu())
117 ind0 += stack_ent[1]
118 ind1 += stack_ent[2]
119 stack += [stack_ent]
120 return r
121
122def parse_choice(buf):
123 global ind0, ind1, stack, menu_id
124 assert(buf[0] == 'choice\n')
125 title = ''
126 for line in buf:
127 line = line.strip()
128 if line.startswith('prompt '):
129 title = line[len('prompt '):].strip().strip('"')
130 r = [(ind0, "", ind1, title, ' --->', '', cur_menu(), None)]
131 menu_id += 1
132 stack += [('menu', 2, 0, menu_id | choice_bit)]
133 ind0 += 2
134 return r
135
136def load_kconfig(file):
137 global ind0, ind1, stack, path, menu_id, if_stack
138 r = []
139 config_buf = []
140 with open(path + file) as f:
141 for line in f:
142 if len(config_buf):
143 if not line.startswith('\t'):
144 if config_buf[0] == 'choice\n':
145 r += parse_choice(config_buf)
146 else:
147 r += parse_config(config_buf)
148 config_buf = []
149 else:
150 config_buf += [line]
151 continue
152 if line.startswith('source') or line.startswith('\tsource'):
153 sub = expand_var(line.strip().split()[1].strip('"'))
154 r += load_kconfig(sub)
155 elif line.startswith('config') or line.startswith('menuconfig'):
156 config_buf = [line]
157 elif line.startswith('choice'):
158 config_buf = [line]
159 elif line.startswith("menu"):
160 title = expand_var(line[4:].strip().strip('"'))
161 r += [(ind0, "", ind1, title, ' --->', '', cur_menu(), None)]
162 menu_id += 1
163 stack += [('menu', 2, 0, menu_id)]
164 ind0 += 2
165 elif line.startswith('endmenu') or line.startswith('endchoice'):
166 pop_stack_while(lambda x: x != 'menu')
167 pop_stack(lambda x: x == 'menu')
168 if r[-1][1] == "":
169 # prune empty menu
170 r = r[:-1]
171 elif line.startswith('if '):
172 line = line[3:]
173 top = cur_if()
174 top += [x.strip() for x in line.split("&&")]
175 if_stack += [top]
176 elif line.startswith('endif'):
177 if_stack = if_stack[:-1]
178 return r
179
180known_config = {}
181
182from sys import argv
183import tomllib
184
185path = argv[1]
186if path[-1] != '/':
187 path += '/'
188with open(argv[2], 'rb') as f:
189 known_config = tomllib.load(f)
190
191r = load_kconfig("Kconfig")
192
193# Now we are going to pretty-print r
194
195## Calculate the maximum value length for each menu
196max_val_len = {}
197for _, val, _, _, _, _, menu, _ in r:
198 x = max_val_len[menu] if menu in max_val_len else 0
199 max_val_len[menu] = max(x, len(val))
200
201## Output
202
203max_line = 80
204buf = []
205
206done = [x[5] for x in r]
207for i in known_config:
208 if i not in done:
209 raise Exception("%s seems not exist" % i)
210
211for i0, val, i1, title, arrow, key, menu, comment in r:
212 if val:
213 val += (max_val_len[menu] - len(val)) * ' '
214 line = i0 * ' ' + val + (i1 + bool(val)) * ' '
215
216 rem = max_line - len(line) - len(arrow)
217
218 if len(title) > rem:
219 title = title[:rem - 3] + '...'
220
221 line += title + arrow
222 rem = max_line - len(line)
223
224 if key:
225 key = ' [' + key + ']'
226
227 if len(key) <= rem:
228 line += (rem - len(key)) * ' ' + key
229 else:
230 key = '... ' + key
231 line += '\n' + ' ' * (max_line - len(key)) + key
232 if comment:
233 line = ' ' * i0 + '# ' + comment + ':\n' + line
234 buf += [line.replace('<', '&lt;').replace('>', '&gt;').rstrip()]
235
236import kernel_version
237kver = kernel_version.kernel_version(path)
238
239print('''<?xml version="1.0" encoding="ISO-8859-1"?>
240<!DOCTYPE note PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
241 "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">''')
242print('<!-- Automatically generated by kernel-config.py for Linux', kver)
243print(' DO NOT EDIT! -->')
244print('<screen><literal>' + '\n'.join(buf) + '</literal></screen>')
Note: See TracBrowser for help on using the repository browser.