#!/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 = { "KERNEL_VERSION_MAJOR": "0", "KERNEL_VERSION_MINOR": "0", "KERNEL_VERSION_PATCH": "1", "KERNEL_VERSION_NAME": "Husky", "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", "MAX_MEMORY_REGIONS": "256", "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()