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

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 6043559 was 6043559, checked in by Xi Ruoyao <xry111@…>, 14 months ago

Add kernel-config infrastructure

The kernel-config.py script takes a toml file containing a set of
kernel configuration key-value pairs. Then it parses the Kconfig files
in a kernel source tree and render the given configuration as a
LFS-style <screen> in a separate XML file. The XML file can be used in
the book with xinclude.

Some "features":

  1. The lines are limited to 80 columns. If the text of the configuration option is too long, it will be trimmed; if the symbolic name of the option cannot fit in this line, a separate line will be used for it.
  2. If a configuration option is given but it does not exist in Kconfig files, the script will abort immediately. This helps catching removed options.
  3. The script also aborts immediately if a configuration option is illegal, for example setting an option to 'M' while it cannot be a module.
  4. The infrastructure is not wired into the main Makefile. It's because not all editors have the latest kernel tree, and even if they do the locations of the kernel tree are still different. To update the generated XML files, use "make -C kernel-config KERNEL_TREE=/sources/linux-x.y.z".

Backword incompatible change:

The script no longer outputs "CONFIG_" prefix for the symbolic name. It
really does not make too much sense to waste 7 characters here because
it's a common prefix for all options!

A limitation:

The script does not really validate the configuration. Generally
validating the configuration requires to solve the 3-CNF-SAT problem,
which is NP-complete.

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