From 2bfb909dde1241111ab5568f30c45d2644bdaf25 Mon Sep 17 00:00:00 2001 From: Lunaixsky Date: Sat, 10 May 2025 01:47:51 +0100 Subject: [PATCH] add validator to restrict the flexibility of LConfig * allow a bool config option change value based on other option's value similar to "select" in kconfig, but it is distributed to the actual affecting flags rather than centered around the master option --- lunaix-os/LConfig | 16 ++- lunaix-os/arch/LConfig | 7 +- lunaix-os/arch/x86/LConfig | 6 +- lunaix-os/hal/LConfig | 3 +- lunaix-os/hal/bus/LConfig | 2 +- lunaix-os/hal/char/uart/LConfig | 5 +- lunaix-os/kernel/mm/LConfig | 8 +- .../build-tools/lcfg2/ast_validator.py | 71 ++++++++++++ lunaix-os/scripts/build-tools/lcfg2/common.py | 1 + lunaix-os/scripts/build-tools/lcfg2/lazy.py | 8 +- lunaix-os/scripts/build-tools/lcfg2/nodes.py | 9 +- .../scripts/build-tools/lcfg2/rewriter.py | 104 ++++++++++++++++-- lunaix-os/scripts/build-tools/lcfg2/rules.py | 96 ++++++++++++++++ .../scripts/build-tools/lcfg2/sanitiser.py | 1 - lunaix-os/scripts/build-tools/lib/utils.py | 33 +++++- lunaix-os/scripts/build-tools/luna_build.py | 9 +- .../build-tools/shared/shconfig/commands.py | 12 +- 17 files changed, 350 insertions(+), 41 deletions(-) create mode 100644 lunaix-os/scripts/build-tools/lcfg2/ast_validator.py create mode 100644 lunaix-os/scripts/build-tools/lcfg2/rules.py diff --git a/lunaix-os/LConfig b/lunaix-os/LConfig index d66375d..56cc436 100644 --- a/lunaix-os/LConfig +++ b/lunaix-os/LConfig @@ -2,6 +2,15 @@ from datetime import datetime, date from . import kernel, arch, hal +@native +def get_patch_seq(): + today = date.today() + year = today.year + start_of_year = datetime(year, 1, 1).date() + seq_num = (today - start_of_year).days + + return "%d%d"%(year - 2000, seq_num) + @"Kernel Version" @readonly def lunaix_ver() -> str: @@ -9,12 +18,7 @@ def lunaix_ver() -> str: Lunaix kernel version """ - today = date.today() - year = today.year - start_of_year = datetime(year, 1, 1).date() - seq_num = (today - start_of_year).days - - return "%s v0.%d%d"%(arch.val, year - 2000, seq_num) + return f"{arch.val} v0.0.{get_patch_seq()}" @"Kernel Debug and Testing" def debug_and_testing(): diff --git a/lunaix-os/arch/LConfig b/lunaix-os/arch/LConfig index 78d557a..bb22a10 100644 --- a/lunaix-os/arch/LConfig +++ b/lunaix-os/arch/LConfig @@ -8,15 +8,16 @@ def architecture_support(): @flag def arch_x86_32() -> bool: - return arch.val == "i386" + when(arch is "i386") @flag def arch_x86_64() -> bool: - return arch.val == "x86_64" + when(arch is "x86_64") @flag def arch_x86() -> bool: - return arch.val in ["x86_64", "i386"] + when(arch is "i386") + when(arch is "x86_64") @"Architecture" def arch() -> "i386" | "x86_64": diff --git a/lunaix-os/arch/x86/LConfig b/lunaix-os/arch/x86/LConfig index e5f0cd4..0e4d463 100644 --- a/lunaix-os/arch/x86/LConfig +++ b/lunaix-os/arch/x86/LConfig @@ -6,11 +6,11 @@ def x86_configurations(): @flag def x86_bl_mb() -> bool: - return x86_bl.val == "mb" + when (x86_bl is "mb") @flag def x86_bl_mb2() -> bool: - return x86_bl.val == "mb2" + when (x86_bl is "mb2") @"Use SSE2/3/4 extension" def x86_enable_sse_feature() -> bool: @@ -18,7 +18,7 @@ def x86_configurations(): Config whether to allow using SSE feature for certain optimization """ - + return False @"Bootloader Model" diff --git a/lunaix-os/hal/LConfig b/lunaix-os/hal/LConfig index 26f53b8..b2e00fa 100644 --- a/lunaix-os/hal/LConfig +++ b/lunaix-os/hal/LConfig @@ -16,8 +16,9 @@ def hal(): devicetree might be mandatory and perhaps the only way. """ + require(not arch_x86) - return arch.val not in ["x86_64", "i386"] + return False @"Maximum size of device tree blob (in KiB)" @readonly diff --git a/lunaix-os/hal/bus/LConfig b/lunaix-os/hal/bus/LConfig index edbe2ef..769072a 100644 --- a/lunaix-os/hal/bus/LConfig +++ b/lunaix-os/hal/bus/LConfig @@ -22,4 +22,4 @@ def bus_if(): require(not pcie_ext and pci_enable) require(arch_x86) - return arch.val in [ "i386", "x86_64" ] + return True diff --git a/lunaix-os/hal/char/uart/LConfig b/lunaix-os/hal/char/uart/LConfig index 831169c..10a74f5 100644 --- a/lunaix-os/hal/char/uart/LConfig +++ b/lunaix-os/hal/char/uart/LConfig @@ -7,8 +7,9 @@ def uart_16x50(): @"16x50 XT-Compat" def xt_16x50() -> bool: """ Enable the 16x50 for PC-compatible platform """ - - return arch.val in ["i386", "x86_64"] + require(arch_x86) + + return True @"16x50 PCI" def pci_16x50() -> bool: diff --git a/lunaix-os/kernel/mm/LConfig b/lunaix-os/kernel/mm/LConfig index 8f8d4b9..fa7c431 100644 --- a/lunaix-os/kernel/mm/LConfig +++ b/lunaix-os/kernel/mm/LConfig @@ -10,15 +10,15 @@ def memory_subsystem(): @flag def pmalloc_method_simple() -> bool: - return pmalloc_method.val == "simple" + when (pmalloc_method is "simple") @flag def pmalloc_method_buddy() -> bool: - return pmalloc_method.val == "buddy" + when (pmalloc_method is "buddy") @flag def pmalloc_method_ncontig() -> bool: - return pmalloc_method.val == "ncontig" + when (pmalloc_method is "ncontig") @"Allocation policy" def pmalloc_method() -> "simple" | "buddy" | "ncontig": @@ -29,7 +29,7 @@ def memory_subsystem(): @"PMalloc Thresholds" def pmalloc_simple_po_thresholds(): - require(pmalloc_method_simple) + require (pmalloc_method_simple) @"Maximum cached order-0 free pages" def pmalloc_simple_max_po0() -> int: diff --git a/lunaix-os/scripts/build-tools/lcfg2/ast_validator.py b/lunaix-os/scripts/build-tools/lcfg2/ast_validator.py new file mode 100644 index 0000000..2b63e31 --- /dev/null +++ b/lunaix-os/scripts/build-tools/lcfg2/ast_validator.py @@ -0,0 +1,71 @@ +import ast +import inspect +import textwrap + +from typing import Callable +from lib.utils import Schema, ConfigASTVisitor, SourceLogger +from .common import Schema, ConfigNodeError + +class Rule: + def __init__(self, t, v, name, fn): + self.type = t + self.__name = name + self.__var = v + self.__fn = fn + self.__help_msg = inspect.getdoc(fn) + self.__help_msg = textwrap.dedent(self.__help_msg.strip()) + + def match_variant(self, astn): + if not self.__var: + return True + return self.__var.match(astn) + + def invoke(self, reducer, node): + if self.__fn(reducer._rules, reducer, node): + return + + SourceLogger.warn(reducer._cfgn, node, + f"rule violation: {self.__name}: {self.__help_msg}") + # raise ConfigNodeError(reducer._cfgn, + # f"rule failed: {self.__name}: {self.__help_msg}") + + +def rule(ast_type: type, variant: Schema, name: str): + def __rule(fn: Callable): + return Rule(ast_type, variant, name, fn) + return __rule + +class RuleCollection: + def __init__(self): + self.__rules = {} + + members = inspect.getmembers(self, lambda p: isinstance(p, Rule)) + for _, rule in members: + t = rule.type + if rule.type not in self.__rules: + self.__rules[t] = [rule] + else: + self.__rules[t].append(rule) + + def execute(self, reducer, node): + rules = self.__rules.get(type(node)) + if not rules: + return + + for rule in rules: + if not rule.match_variant(node): + continue + rule.invoke(reducer, node) + +class NodeValidator(ast.NodeTransformer): + def __init__(self, all_rules): + super().__init__() + self._rules = all_rules + + def validate(self, cfgn, astn): + self._cfgn = cfgn + self.visit(astn) + + def visit(self, node): + self._rules.execute(self, node) + return super().visit(node) diff --git a/lunaix-os/scripts/build-tools/lcfg2/common.py b/lunaix-os/scripts/build-tools/lcfg2/common.py index 94cdcea..21e75b1 100644 --- a/lunaix-os/scripts/build-tools/lcfg2/common.py +++ b/lunaix-os/scripts/build-tools/lcfg2/common.py @@ -19,6 +19,7 @@ class NodeProperty: Enabled = PropertyAccessor("$enabled") Status = PropertyAccessor("$status") Dependency = PropertyAccessor("$depends") + WhenToggle = PropertyAccessor("$when") Hidden = PropertyAccessor("hidden") Parent = PropertyAccessor("parent") Label = PropertyAccessor("label") diff --git a/lunaix-os/scripts/build-tools/lcfg2/lazy.py b/lunaix-os/scripts/build-tools/lcfg2/lazy.py index ddc02ed..1c634fd 100644 --- a/lunaix-os/scripts/build-tools/lcfg2/lazy.py +++ b/lunaix-os/scripts/build-tools/lcfg2/lazy.py @@ -84,9 +84,14 @@ class Lazy: type_ = astn.attr target = astn.value.id + + return Lazy.from_type(cfgnode, type_, target) + + @staticmethod + def from_type(cfgnode, type_, target): key = Lazy.get_key_from(type_, target) - lz = cfgnode._lazy_table.get(key) + if lz: return key @@ -94,3 +99,4 @@ class Lazy: cfgnode._lazy_table.put(lz) return key + diff --git a/lunaix-os/scripts/build-tools/lcfg2/nodes.py b/lunaix-os/scripts/build-tools/lcfg2/nodes.py index 703b8f9..19ead50 100644 --- a/lunaix-os/scripts/build-tools/lcfg2/nodes.py +++ b/lunaix-os/scripts/build-tools/lcfg2/nodes.py @@ -5,6 +5,10 @@ from .common import NodeProperty, ConfigNodeError, NodeDependency from .lazy import LazyLookup from .rewriter import ConfigNodeASTRewriter +from .ast_validator import NodeValidator +from .rules import SyntaxRule + +validator = NodeValidator(SyntaxRule()) class ConfigDecorator: Label = Schema(ast.Constant) @@ -37,7 +41,10 @@ class ConfigNode: NodeProperty.Status[self] = "Empty" def set_node_body(self, ast_nodes, rewriter = ConfigNodeASTRewriter): - new_ast = rewriter(self).visit(ast.Module(ast_nodes)) + new_ast = ast.Module(ast_nodes, []) + validator.validate(self, new_ast) + + new_ast = rewriter(self).rewrite(new_ast) NodeDependency.try_create(self) fn_name = f"__fn_{self._name}" diff --git a/lunaix-os/scripts/build-tools/lcfg2/rewriter.py b/lunaix-os/scripts/build-tools/lcfg2/rewriter.py index 9f47ad3..d2003eb 100644 --- a/lunaix-os/scripts/build-tools/lcfg2/rewriter.py +++ b/lunaix-os/scripts/build-tools/lcfg2/rewriter.py @@ -4,29 +4,46 @@ from lib.utils import Schema from .lazy import Lazy from .common import NodeProperty +class RewriteRule: + MaybeBuiltin = Schema( + ast.Call, + func=Schema(ast.Name), + args=[ast.expr]) + + WhenTogglerItem = Schema( + ast.Compare, + left=ast.Name, + ops=[Schema.Union(ast.Is, ast.IsNot)], + comparators=[ast.Constant]) + + WhenToggler = Schema( + Schema.Union( + WhenTogglerItem, + Schema(ast.BoolOp, + op=ast.And, + values=Schema.List(WhenTogglerItem)))) + class ConfigNodeASTRewriter(ast.NodeTransformer): - Depend = Schema( - ast.Call, - func=Schema(ast.Name, id='require'), - args=[ast.expr]) + def __init__(self, cfg_node): super().__init__() self.__cfg_node = cfg_node + def __subscript_accessor(self, name, ctx, token): + return ast.Subscript( + value=ast.Name("__lzLut__", ctx=ast.Load()), + slice=ast.Constant(name), + ctx=ctx, + ) + def __gen_accessor(self, orig): key = Lazy.from_astn(self.__cfg_node, orig) if not key: return self.generic_visit(orig) - return ast.Subscript( - value=ast.Name("__lzLut__", ctx=ast.Load()), - slice=ast.Constant(key), - ctx=orig.ctx, - lineno=orig.lineno, - col_offset=orig.col_offset - ) + return self.__subscript_accessor(key, orig.ctx, orig) def __gen_dependency(self, node): cfgn = self.__cfg_node @@ -42,20 +59,83 @@ class ConfigNodeASTRewriter(ast.NodeTransformer): dep_expr = ast.BoolOp(ast.And(), [dep_expr, node]) NodeProperty.Dependency[cfgn] = dep_expr + def __gen_when_expr(self, node): + and_list = [] + cfgn = self.__cfg_node + + if RewriteRule.WhenToggler != node: + raise cfgn.config_error( + f"invalid when(...) expression: {ast.unparse(node)}") + + if RewriteRule.WhenTogglerItem == node: + and_list.append(node) + else: + and_list += node.values + + for i in range(len(and_list)): + item = and_list[i] + operator = item.ops[0] + + name = Lazy.from_type(cfgn, Lazy.NodeValue, item.left.id) + acc = self.__subscript_accessor(name, ast.Load(), node) + + if isinstance(operator, ast.Is): + operator = ast.Eq() + else: + operator = ast.NotEq() + + item.left = acc + item.ops = [operator] + + + current = ast.BoolOp( + op=ast.And(), + values=[ast.Constant(True), *and_list]) + + expr = NodeProperty.WhenToggle[cfgn] + if expr: + assert isinstance(expr, ast.expr) + current = ast.BoolOp(op=ast.Or(), values=[expr, current]) + + NodeProperty.WhenToggle[cfgn] = current + def visit_Attribute(self, node): return self.__gen_accessor(node) def visit_Expr(self, node): val = node.value - if ConfigNodeASTRewriter.Depend != val: + if RewriteRule.MaybeBuiltin != val: return self.generic_visit(node) # Process marker functions name = val.func.id if name == "require": self.__gen_dependency(val.args[0]) + elif name == "when": + self.__gen_when_expr(val.args[0]) else: return self.generic_visit(node) return None + + def visit_Return(self, node): + if NodeProperty.WhenToggle[self.__cfg_node]: + return None + return self.generic_visit(node) + + def visit_Is(self, node): + return ast.Eq() + + def rewrite(self, node): + assert isinstance(node, ast.Module) + node = self.visit(node) + + expr = NodeProperty.WhenToggle[self.__cfg_node] + if not expr: + return node + + del NodeProperty.WhenToggle[self.__cfg_node] + + node.body.append(ast.Return(expr, lineno=0, col_offset=0)) + return node diff --git a/lunaix-os/scripts/build-tools/lcfg2/rules.py b/lunaix-os/scripts/build-tools/lcfg2/rules.py new file mode 100644 index 0000000..35f770f --- /dev/null +++ b/lunaix-os/scripts/build-tools/lcfg2/rules.py @@ -0,0 +1,96 @@ +import ast + +from .ast_validator import RuleCollection, rule +from lib.utils import Schema + +class SyntaxRule(RuleCollection): + NodeAssigment = Schema(ast.Subscript, + value=Schema(ast.Name, id='__lzLut__'), + ctx=ast.Store) + TrivialValue = Schema(Schema.Union( + ast.Constant, + ast.Name, + Schema(ast.Subscript, + value=Schema(ast.Name, id='__lzLut__'), + slice=ast.Constant) + )) + + BoolOperators = Schema(Schema.Union(ast.Or, ast.And)) + + TrivialTest = Schema(ast.Compare, + left=TrivialValue, + ops=[Schema.Union(ast.Eq, ast.In)], + comparators=[Schema.Union( + ast.Constant, + Schema(ast.List, elts=Schema.List(ast.Constant)) + )]) + + InlineIf = Schema(ast.IfExp, + test=Schema.Union(TrivialTest, TrivialValue), + body=TrivialValue, + orelse=TrivialValue) + + TrivialLogic = Schema(ast.BoolOp, + op=BoolOperators, + values=Schema.List( + Schema.Union(TrivialTest, ast.Name) + )) + + TrivialReturn = Schema(Schema.Union( + TrivialValue, + InlineIf, + TrivialLogic, + ast.JoinedStr + )) + + def __init__(self): + super().__init__() + + @rule(ast.If, None, "dynamic-logic") + def __dynamic_logic(self, reducer, node): + """ + Conditional branching could interfering dependency resolving + """ + return False + + @rule(ast.While, None, "while-loop") + def __while_loop(self, reducer, node): + """ + loop construct may impact with readability. + """ + return False + + @rule(ast.For, None, "for-loop") + def __for_loop(self, reducer, node): + """ + loop construct may impact with readability. + """ + return False + + @rule(ast.ClassDef, None, "class-def") + def __class_definition(self, reducer, node): + """ + use of custom class is not recommended + """ + return False + + @rule(ast.Dict, None, "complex-struct") + def __complex_datastruct(self, reducer, node): + """ + use of complex data structure is not recommended + """ + return False + + @rule(ast.Subscript, NodeAssigment, "side-effect-option") + def __side_effect(self, reducer, node): + """ + Option modifying other options dynamically unpredictable behaviour + """ + return False + + @rule(ast.Return, None, "non-trivial-value") + def __nontrivial_return(self, reducer, node): + """ + Option default should be kept as constant or simple membership check + """ + return SyntaxRule.TrivialReturn == node.value \ No newline at end of file diff --git a/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py b/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py index 9a32971..1c84a77 100644 --- a/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py +++ b/lunaix-os/scripts/build-tools/lcfg2/sanitiser.py @@ -1,7 +1,6 @@ import ast from lib.utils import Schema, ConfigASTVisitor, SourceLogger -from .common import NodeProperty class TreeSanitiser(ConfigASTVisitor): DecoNative = Schema(ast.Name, id="native") diff --git a/lunaix-os/scripts/build-tools/lib/utils.py b/lunaix-os/scripts/build-tools/lib/utils.py index 9d85da5..6dd1ace 100644 --- a/lunaix-os/scripts/build-tools/lib/utils.py +++ b/lunaix-os/scripts/build-tools/lib/utils.py @@ -1,5 +1,6 @@ import os, ast from pathlib import Path +from typing import Any, List def join_path(stem, path): if os.path.isabs(path): @@ -14,6 +15,9 @@ class Schema: def __str__(self): return "Any" + + def __repr__(self): + return self.__str__() class Union: def __init__(self, *args): @@ -22,6 +26,20 @@ class Schema: def __str__(self): strs = [Schema.get_type_str(t) for t in self.union] return f"{' | '.join(strs)}" + + def __repr__(self): + return self.__str__() + + class List: + def __init__(self, el_type): + self.el_type = el_type + + def __str__(self): + strs = Schema.get_type_str(self.el_type) + return f"*{strs}" + + def __repr__(self): + return self.__str__() def __init__(self, type, **kwargs): self.__type = type @@ -37,6 +55,16 @@ class Schema: return True + def __match_list_member(self, actual, expect): + if not isinstance(actual, List): + return False + + for a in actual: + if not self.__match(a, expect.el_type): + return False + + return True + def __match_union(self, actual, union): for s in union.union: if self.__match(actual, s): @@ -44,12 +72,15 @@ class Schema: return False def __match(self, val, scheme): - if isinstance(scheme, Schema.Any): + if scheme is Any: return True if isinstance(scheme, Schema): return scheme.match(val) + if isinstance(scheme, Schema.List): + return self.__match_list_member(val, scheme) + if isinstance(scheme, list) and isinstance(val, list): return self.__match_list(val, scheme) diff --git a/lunaix-os/scripts/build-tools/luna_build.py b/lunaix-os/scripts/build-tools/luna_build.py index efe527e..aafdb0a 100755 --- a/lunaix-os/scripts/build-tools/luna_build.py +++ b/lunaix-os/scripts/build-tools/luna_build.py @@ -6,6 +6,7 @@ from lbuild.build import BuildEnvironment from lbuild.scope import ScopeProvider from lcfg2.builder import NodeBuilder from lcfg2.config import ConfigEnvironment +from lcfg2.common import ConfigNodeError from shared.export import ExportJsonFile from shared.export import ExportHeaderFile @@ -91,8 +92,12 @@ def main(): opts = parser.parse_args() builder = LunaBuild(opts) - builder.load() - builder.restore() + try: + builder.load() + builder.restore() + except ConfigNodeError as e: + print(e) + exit(1) builder.visual_config() diff --git a/lunaix-os/scripts/build-tools/shared/shconfig/commands.py b/lunaix-os/scripts/build-tools/shared/shconfig/commands.py index 911fadd..8c4e670 100644 --- a/lunaix-os/scripts/build-tools/shared/shconfig/commands.py +++ b/lunaix-os/scripts/build-tools/shared/shconfig/commands.py @@ -5,7 +5,7 @@ from .common import CmdTable, ShconfigException from .common import select, cmd from lcfg2.config import ConfigEnvironment -from lcfg2.common import NodeProperty, NodeDependency +from lcfg2.common import NodeProperty, NodeDependency, ConfigNodeError class Commands(CmdTable): def __init__(self, env: ConfigEnvironment): @@ -84,8 +84,14 @@ class Commands(CmdTable): if node is None: raise ShconfigException(f"no such config: {name}") - NodeProperty.Value[node] = value - self.__env.refresh() + if NodeProperty.Readonly[node]: + raise ShconfigException(f"node is read only") + + try: + NodeProperty.Value[node] = value + self.__env.refresh() + except ConfigNodeError as e: + print(e) @cmd("dep") def __fn_dep(self, name: str): -- 2.27.0