1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
|
#!/usr/bin/env python3
"""
configure.py — MyOS Kernel Configuration Generator
Reads .config (or a specified config file), validates dependencies,
and generates:
- include/kernel/config.h (C++ preprocessor defines)
- config_generated.mk (Makefile variables)
Usage:
python3 scripts/configure.py # reads .config
python3 scripts/configure.py myconfig # reads myconfig
python3 scripts/configure.py --arch x86_64 # set arch, use default config
python3 scripts/configure.py --check # validate only, don't generate
The ARCH variable is NOT set in .config — it comes from the build system.
This script receives it via --arch and injects it into the generated output.
"""
import sys
import os
import argparse
from pathlib import Path
from datetime import datetime
# ============================================================================
# Dependency rules
#
# Format: (option, requires, description)
# If 'option' is 'y', then 'requires' must also be 'y'
# ============================================================================
DEPENDENCIES = [
("FS_EXT2", "DRIVER_VIRTIO_BLK", "ext2 requires a block device driver"),
("FS_FAT32", "DRIVER_VIRTIO_BLK", "FAT32 requires a block device driver"),
("FEATURE_NETWORK", "DRIVER_VIRTIO_NET", "networking requires a network driver"),
("FEATURE_MODULES", "FS_INITRAMFS", "loadable modules require a filesystem"),
("FEATURE_SMP", None, None), # no deps, just a marker
]
# ============================================================================
# Conflict rules
#
# Format: (option_a, option_b, description)
# Both cannot be 'y' at the same time
# ============================================================================
CONFLICTS = [
# None yet — add as needed
]
# ============================================================================
# Valid values for non-boolean options
# ============================================================================
VALID_VALUES = {
"PMM_TYPE": ["buddy", "bitmap"],
"HEAP_TYPE": ["slab", "simple"],
}
# ============================================================================
# Options that must be present (with defaults if missing)
# ============================================================================
DEFAULTS = {
"DEBUG_SERIAL": "n",
"DEBUG_VERBOSE_BOOT": "n",
"DEBUG_PAGE_ALLOC": "n",
"DEBUG_SCHEDULER": "n",
"DEBUG_SYSCALL_TRACE": "n",
"MAX_CPUS": "4",
"KERNEL_STACK_SIZE": "16384",
"PMM_TYPE": "buddy",
"HEAP_TYPE": "slab",
"DRIVER_FBCON": "y",
"DRIVER_UART_16550": "n",
"DRIVER_PL011_UART": "n",
"DRIVER_PS2KBD": "n",
"DRIVER_VIRTIO_BLK": "n",
"DRIVER_AHCI": "n",
"DRIVER_USB": "n",
"DRIVER_VIRTIO_NET": "n",
"FS_INITRAMFS": "y",
"FS_TMPFS": "y",
"FS_EXT2": "n",
"FS_FAT32": "n",
"FBCON_FG_COLOR": "0x00CCCCCC",
"FBCON_BG_COLOR": "0x001A1A2E",
"FEATURE_SMP": "n",
"FEATURE_MODULES": "n",
"FEATURE_NETWORK": "n",
"FEATURE_POSIX_SIGNALS": "n",
"KERNEL_FILE_NAME": "kernel.elf",
}
def parse_config(path: str) -> dict:
"""Parse a .config file into a dict."""
config = {}
with open(path) as f:
for lineno, line in enumerate(f, 1):
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
print(f"WARNING: {path}:{lineno}: malformed line: {line}",
file=sys.stderr)
continue
key, val = line.split("=", 1)
key = key.strip()
val = val.strip()
# Strip surrounding quotes from string values
if len(val) >= 2 and val[0] == '"' and val[-1] == '"':
val = val[1:-1]
config[key] = val
return config
def validate(config: dict) -> list:
"""Validate config. Returns list of error strings (empty = ok)."""
errors = []
# Check dependencies
for opt, requires, desc in DEPENDENCIES:
if requires is None:
continue
if config.get(opt) == "y" and config.get(requires) != "y":
errors.append(f"{opt}=y requires {requires}=y ({desc})")
# Check conflicts
for opt_a, opt_b, desc in CONFLICTS:
if config.get(opt_a) == "y" and config.get(opt_b) == "y":
errors.append(f"{opt_a} conflicts with {opt_b} ({desc})")
# Check valid values
for key, valid in VALID_VALUES.items():
val = config.get(key)
if val is not None and val not in valid:
errors.append(f"{key}={val} is invalid. Must be one of: {valid}")
# Check numeric values
for key in ("MAX_CPUS", "KERNEL_STACK_SIZE"):
val = config.get(key)
if val is not None:
try:
int(val)
except ValueError:
errors.append(f"{key}={val} must be a number")
return errors
def generate_config_h(config: dict, arch: str, output: str):
"""Generate include/kernel/config.h from config dict."""
lines = [
"#pragma once",
"",
"// ============================================================================",
"// config.h — Auto-generated by scripts/configure.py",
f"// Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
f"// Architecture: {arch}",
"//",
"// DO NOT EDIT — edit .config and re-run make instead.",
"// ============================================================================",
"",
]
# Group by prefix for readability
groups = {}
for key, val in sorted(config.items()):
prefix = key.split("_")[0]
if prefix not in groups:
groups[prefix] = []
groups[prefix].append((key, val))
for prefix, items in groups.items():
lines.append(f"// ── {prefix} " + "─" * (60 - len(prefix)))
for key, val in items:
if val == "y":
lines.append(f"#define CONFIG_{key} 1")
elif val == "n":
lines.append(f"/* #undef CONFIG_{key} */")
elif val.startswith("0x"):
# Hex constant
lines.append(f"#define CONFIG_{key} {val}")
else:
# Try numeric
try:
int(val)
lines.append(f"#define CONFIG_{key} {val}")
except ValueError:
# String value — quote it for some, raw for others
lines.append(f'#define CONFIG_{key} "{val}"')
# For enum-like options, also define a variant for preprocessor comparisons
if key in VALID_VALUES:
lines.append(f'#define CONFIG_{key}_{val.upper()} 1')
lines.append("")
os.makedirs(os.path.dirname(output), exist_ok=True)
with open(output, "w") as f:
f.write("\n".join(lines) + "\n")
def generate_config_mk(config: dict, arch: str, output: str):
"""Generate config_generated.mk from config dict."""
lines = [
"# ============================================================================",
"# config_generated.mk — Auto-generated by scripts/configure.py",
f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"# DO NOT EDIT — edit .config and re-run make instead.",
"# ============================================================================",
"",
]
for key, val in sorted(config.items()):
lines.append(f"CONFIG_{key} := {val}")
with open(output, "w") as f:
f.write("\n".join(lines) + "\n")
def main():
parser = argparse.ArgumentParser(description="MyOS kernel configuration generator")
parser.add_argument("config_file", nargs="?", default=".config",
help="Path to config file (default: .config)")
parser.add_argument("--arch", default=None,
help="Target architecture (x86_64 or aarch64)")
parser.add_argument("--check", action="store_true",
help="Validate only, don't generate files")
parser.add_argument("--config-h", default="include/kernel/config.h",
help="Output path for config.h")
parser.add_argument("--config-mk", default="config_generated.mk",
help="Output path for config_generated.mk")
args = parser.parse_args()
# ── Read config ─────────────────────────────────────────────────────
if not os.path.exists(args.config_file):
print(f"ERROR: Config file '{args.config_file}' not found.",
file=sys.stderr)
print(f" Create one with: cp configs/default_x86_64 .config",
file=sys.stderr)
sys.exit(1)
config = parse_config(args.config_file)
# ── Apply defaults for missing keys ─────────────────────────────────
for key, default_val in DEFAULTS.items():
if key not in config:
config[key] = default_val
# ── Determine architecture ──────────────────────────────────────────
arch = args.arch or os.environ.get("ARCH", "x86_64")
# ── Validate ────────────────────────────────────────────────────────
errors = validate(config)
if errors:
print("Configuration errors:", file=sys.stderr)
for e in errors:
print(f" ERROR: {e}", file=sys.stderr)
sys.exit(1)
if args.check:
print("Configuration is valid.")
sys.exit(0)
# ── Generate ────────────────────────────────────────────────────────
generate_config_h(config, arch, args.config_h)
generate_config_mk(config, arch, args.config_mk)
# ── Summary ─────────────────────────────────────────────────────────
enabled = [k for k, v in config.items() if v == "y"]
disabled = [k for k, v in config.items() if v == "n"]
other = [k for k, v in config.items() if v not in ("y", "n")]
print(f"Configuration generated for {arch}:")
print(f" {len(enabled)} options enabled, {len(disabled)} disabled, "
f"{len(other)} custom values")
print(f" → {args.config_h}")
print(f" → {args.config_mk}")
if __name__ == "__main__":
main()
|