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:
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():
@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":
@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:
Config whether to allow using SSE feature for certain
optimization
"""
-
+
return False
@"Bootloader Model"
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
require(not pcie_ext and pci_enable)
require(arch_x86)
- return arch.val in [ "i386", "x86_64" ]
+ return True
@"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:
@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":
@"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:
--- /dev/null
+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)
Enabled = PropertyAccessor("$enabled")
Status = PropertyAccessor("$status")
Dependency = PropertyAccessor("$depends")
+ WhenToggle = PropertyAccessor("$when")
Hidden = PropertyAccessor("hidden")
Parent = PropertyAccessor("parent")
Label = PropertyAccessor("label")
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
cfgnode._lazy_table.put(lz)
return key
+
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)
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}"
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
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
--- /dev/null
+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
import ast
from lib.utils import Schema, ConfigASTVisitor, SourceLogger
-from .common import NodeProperty
class TreeSanitiser(ConfigASTVisitor):
DecoNative = Schema(ast.Name, id="native")
import os, ast
from pathlib import Path
+from typing import Any, List
def join_path(stem, path):
if os.path.isabs(path):
def __str__(self):
return "Any"
+
+ def __repr__(self):
+ return self.__str__()
class Union:
def __init__(self, *args):
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
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):
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)
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
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()
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):
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):