summaryrefslogtreecommitdiff
path: root/scripts/configure.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/configure.py')
-rwxr-xr-xscripts/configure.py290
1 files changed, 290 insertions, 0 deletions
diff --git a/scripts/configure.py b/scripts/configure.py
new file mode 100755
index 0000000..053aac9
--- /dev/null
+++ b/scripts/configure.py
@@ -0,0 +1,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()