]> git.proxmox.com Git - mirror_qemu.git/blobdiff - scripts/minikconf.py
Include qemu-common.h exactly where needed
[mirror_qemu.git] / scripts / minikconf.py
index fd75d96c6afb6aa5877dc4771b4a19809ebb270c..0ffc6c38da77d8935bf5d83ec4bd2271de1edc56 100644 (file)
 from __future__ import print_function
 import os
 import sys
+import re
+import random
 
-__all__ = [ 'KconfigParserError', 'KconfigData', 'KconfigParser' ]
+__all__ = [ 'KconfigDataError', 'KconfigParserError',
+            'KconfigData', 'KconfigParser' ,
+            'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
 
 def debug_print(*args):
     #print('# ' + (' '.join(str(x) for x in args)))
@@ -30,12 +34,178 @@ def debug_print(*args):
 # just its name).
 # -------------------------------------------
 
+class KconfigDataError(Exception):
+    def __init__(self, msg):
+        self.msg = msg
+
+    def __str__(self):
+        return self.msg
+
+allyesconfig = lambda x: True
+allnoconfig = lambda x: False
+defconfig = lambda x: x
+randconfig = lambda x: random.randint(0, 1) == 1
+
 class KconfigData:
-    def __init__(self):
+    class Expr:
+        def __and__(self, rhs):
+            return KconfigData.AND(self, rhs)
+        def __or__(self, rhs):
+            return KconfigData.OR(self, rhs)
+        def __invert__(self):
+            return KconfigData.NOT(self)
+
+        # Abstract methods
+        def add_edges_to(self, var):
+            pass
+        def evaluate(self):
+            assert False
+
+    class AND(Expr):
+        def __init__(self, lhs, rhs):
+            self.lhs = lhs
+            self.rhs = rhs
+        def __str__(self):
+            return "(%s && %s)" % (self.lhs, self.rhs)
+
+        def add_edges_to(self, var):
+            self.lhs.add_edges_to(var)
+            self.rhs.add_edges_to(var)
+        def evaluate(self):
+            return self.lhs.evaluate() and self.rhs.evaluate()
+
+    class OR(Expr):
+        def __init__(self, lhs, rhs):
+            self.lhs = lhs
+            self.rhs = rhs
+        def __str__(self):
+            return "(%s || %s)" % (self.lhs, self.rhs)
+
+        def add_edges_to(self, var):
+            self.lhs.add_edges_to(var)
+            self.rhs.add_edges_to(var)
+        def evaluate(self):
+            return self.lhs.evaluate() or self.rhs.evaluate()
+
+    class NOT(Expr):
+        def __init__(self, lhs):
+            self.lhs = lhs
+        def __str__(self):
+            return "!%s" % (self.lhs)
+
+        def add_edges_to(self, var):
+            self.lhs.add_edges_to(var)
+        def evaluate(self):
+            return not self.lhs.evaluate()
+
+    class Var(Expr):
+        def __init__(self, name):
+            self.name = name
+            self.value = None
+            self.outgoing = set()
+            self.clauses_for_var = list()
+        def __str__(self):
+            return self.name
+
+        def has_value(self):
+            return not (self.value is None)
+        def set_value(self, val, clause):
+            self.clauses_for_var.append(clause)
+            if self.has_value() and self.value != val:
+                print("The following clauses were found for " + self.name)
+                for i in self.clauses_for_var:
+                    print("    " + str(i), file=sys.stderr)
+                raise KconfigDataError('contradiction between clauses when setting %s' % self)
+            debug_print("=> %s is now %s" % (self.name, val))
+            self.value = val
+
+        # depth first search of the dependency graph
+        def dfs(self, visited, f):
+            if self in visited:
+                return
+            visited.add(self)
+            for v in self.outgoing:
+                v.dfs(visited, f)
+            f(self)
+
+        def add_edges_to(self, var):
+            self.outgoing.add(var)
+        def evaluate(self):
+            if not self.has_value():
+                raise KconfigDataError('cycle found including %s' % self)
+            return self.value
+
+    class Clause:
+        def __init__(self, dest):
+            self.dest = dest
+        def priority(self):
+            return 0
+        def process(self):
+            pass
+
+    class AssignmentClause(Clause):
+        def __init__(self, dest, value):
+            KconfigData.Clause.__init__(self, dest)
+            self.value = value
+        def __str__(self):
+            return "CONFIG_%s=%s" % (self.dest, 'y' if self.value else 'n')
+
+        def process(self):
+            self.dest.set_value(self.value, self)
+
+    class DefaultClause(Clause):
+        def __init__(self, dest, value, cond=None):
+            KconfigData.Clause.__init__(self, dest)
+            self.value = value
+            self.cond = cond
+            if not (self.cond is None):
+                self.cond.add_edges_to(self.dest)
+        def __str__(self):
+            value = 'y' if self.value else 'n'
+            if self.cond is None:
+                return "config %s default %s" % (self.dest, value)
+            else:
+                return "config %s default %s if %s" % (self.dest, value, self.cond)
+
+        def priority(self):
+            # Defaults are processed just before leaving the variable
+            return -1
+        def process(self):
+            if not self.dest.has_value() and \
+                    (self.cond is None or self.cond.evaluate()):
+                self.dest.set_value(self.value, self)
+
+    class DependsOnClause(Clause):
+        def __init__(self, dest, expr):
+            KconfigData.Clause.__init__(self, dest)
+            self.expr = expr
+            self.expr.add_edges_to(self.dest)
+        def __str__(self):
+            return "config %s depends on %s" % (self.dest, self.expr)
+
+        def process(self):
+            if not self.expr.evaluate():
+                self.dest.set_value(False, self)
+
+    class SelectClause(Clause):
+        def __init__(self, dest, cond):
+            KconfigData.Clause.__init__(self, dest)
+            self.cond = cond
+            self.cond.add_edges_to(self.dest)
+        def __str__(self):
+            return "select %s if %s" % (self.dest, self.cond)
+
+        def process(self):
+            if self.cond.evaluate():
+                self.dest.set_value(True, self)
+
+    def __init__(self, value_mangler=defconfig):
+        self.value_mangler = value_mangler
         self.previously_included = []
         self.incl_info = None
         self.defined_vars = set()
-        self.referenced_vars = set()
+        self.referenced_vars = dict()
+        self.clauses = list()
 
     # semantic analysis -------------
 
@@ -47,35 +217,84 @@ class KconfigData:
                 undef = True
         return undef
 
+    def compute_config(self):
+        if self.check_undefined():
+            raise KconfigDataError("there were undefined symbols")
+            return None
+
+        debug_print("Input:")
+        for clause in self.clauses:
+            debug_print(clause)
+
+        debug_print("\nDependency graph:")
+        for i in self.referenced_vars:
+            debug_print(i, "->", [str(x) for x in self.referenced_vars[i].outgoing])
+
+        # The reverse of the depth-first order is the topological sort
+        dfo = dict()
+        visited = set()
+        debug_print("\n")
+        def visit_fn(var):
+            debug_print(var, "has DFS number", len(dfo))
+            dfo[var] = len(dfo)
+
+        for name, v in self.referenced_vars.items():
+            self.do_default(v, False)
+            v.dfs(visited, visit_fn)
+
+        # Put higher DFS numbers and higher priorities first.  This
+        # places the clauses in topological order and places defaults
+        # after assignments and dependencies.
+        self.clauses.sort(key=lambda x: (-dfo[x.dest], -x.priority()))
+
+        debug_print("\nSorted clauses:")
+        for clause in self.clauses:
+            debug_print(clause)
+            clause.process()
+
+        debug_print("")
+        values = dict()
+        for name, v in self.referenced_vars.items():
+            debug_print("Evaluating", name)
+            values[name] = v.evaluate()
+
+        return values
+
     # semantic actions -------------
 
     def do_declaration(self, var):
         if (var in self.defined_vars):
-            raise Exception('variable "' + var + '" defined twice')
+            raise KconfigDataError('variable "' + var + '" defined twice')
 
-        self.defined_vars.add(var)
+        self.defined_vars.add(var.name)
 
     # var is a string with the variable's name.
-    #
-    # For now this just returns the variable's name itself.
     def do_var(self, var):
-        self.referenced_vars.add(var)
-        return var
+        if (var in self.referenced_vars):
+            return self.referenced_vars[var]
+
+        var_obj = self.referenced_vars[var] = KconfigData.Var(var)
+        return var_obj
 
     def do_assignment(self, var, val):
-        pass
+        self.clauses.append(KconfigData.AssignmentClause(var, val))
 
     def do_default(self, var, val, cond=None):
-        pass
+        val = self.value_mangler(val)
+        self.clauses.append(KconfigData.DefaultClause(var, val, cond))
 
     def do_depends_on(self, var, expr):
-        pass
+        self.clauses.append(KconfigData.DependsOnClause(var, expr))
 
     def do_select(self, var, symbol, cond=None):
-        pass
+        cond = (cond & var) if cond is not None else var
+        self.clauses.append(KconfigData.SelectClause(symbol, cond))
 
     def do_imply(self, var, symbol, cond=None):
-        pass
+        # "config X imply Y [if COND]" is the same as
+        # "config Y default y if X [&& COND]"
+        cond = (cond & var) if cond is not None else var
+        self.do_default(symbol, True, cond)
 
 # -------------------------------------------
 # KconfigParser implements a recursive descent parser for (simplified)
@@ -118,14 +337,12 @@ class KconfigParserError(Exception):
         return "%s: %s" % (self.loc, self.msg)
 
 class KconfigParser:
+
     @classmethod
-    def parse(self, fp):
-        data = KconfigData()
+    def parse(self, fp, mode=None):
+        data = KconfigData(mode or KconfigParser.defconfig)
         parser = KconfigParser(data)
         parser.parse_file(fp)
-        if data.check_undefined():
-            raise KconfigParserError(parser, "there were undefined symbols")
-
         return data
 
     def __init__(self, data):
@@ -144,6 +361,12 @@ class KconfigParser:
         self.get_token()
         self.parse_config()
 
+    def do_assignment(self, var, val):
+        if not var.startswith("CONFIG_"):
+            raise Error('assigned variable should start with CONFIG_')
+        var = self.data.do_var(var[7:])
+        self.data.do_assignment(var, val)
+
     # file management -----
 
     def error_path(self):
@@ -237,31 +460,34 @@ class KconfigParser:
     def parse_primary(self):
         if self.tok == TOK_NOT:
             self.get_token()
-            self.parse_primary()
+            val = ~self.parse_primary()
         elif self.tok == TOK_LPAREN:
             self.get_token()
-            self.parse_expr()
+            val = self.parse_expr()
             if self.tok != TOK_RPAREN:
                 raise KconfigParserError(self, 'Expected ")"')
             self.get_token()
         elif self.tok == TOK_ID:
-            self.parse_var()
+            val = self.parse_var()
         else:
             raise KconfigParserError(self, 'Expected "!" or "(" or identifier')
+        return val
 
     # disj: primary (OR primary)*
     def parse_disj(self):
-        self.parse_primary()
+        lhs = self.parse_primary()
         while self.tok == TOK_OR:
             self.get_token()
-            self.parse_primary()
+            lhs = lhs | self.parse_primary()
+        return lhs
 
     # expr: disj (AND disj)*
     def parse_expr(self):
-        self.parse_disj()
+        lhs = self.parse_disj()
         while self.tok == TOK_AND:
             self.get_token()
-            self.parse_disj()
+            lhs = lhs & self.parse_disj()
+        return lhs
 
     # condition: IF expr
     #       | empty
@@ -311,7 +537,6 @@ class KconfigParser:
               self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
               self.tok == TOK_IMPLY:
             self.parse_property(var)
-        self.data.do_default(var, False)
 
         # for nicer error message
         if self.tok != TOK_SOURCE and self.tok != TOK_CONFIG and \
@@ -367,7 +592,7 @@ class KconfigParser:
         if not self.src.startswith(rest, self.cursor):
             return False
         length = len(rest)
-        if self.src[self.cursor + length].isalnum() or self.src[self.cursor + length] == '|':
+        if self.src[self.cursor + length].isalnum() or self.src[self.cursor + length] == '_':
             return False
         self.cursor += length
         return True
@@ -437,5 +662,47 @@ class KconfigParser:
         return None
 
 if __name__ == '__main__':
-    fname = len(sys.argv) > 1 and sys.argv[1] or 'Kconfig.test'
-    KconfigParser.parse(open(fname, 'r'))
+    argv = sys.argv
+    mode = defconfig
+    if len(sys.argv) > 1:
+        if argv[1] == '--defconfig':
+            del argv[1]
+        elif argv[1] == '--randconfig':
+            random.seed()
+            mode = randconfig
+            del argv[1]
+        elif argv[1] == '--allyesconfig':
+            mode = allyesconfig
+            del argv[1]
+        elif argv[1] == '--allnoconfig':
+            mode = allnoconfig
+            del argv[1]
+
+    if len(argv) == 1:
+        print ("%s: at least one argument is required" % argv[0], file=sys.stderr)
+        sys.exit(1)
+
+    if argv[1].startswith('-'):
+        print ("%s: invalid option %s" % (argv[0], argv[1]), file=sys.stderr)
+        sys.exit(1)
+
+    data = KconfigData(mode)
+    parser = KconfigParser(data)
+    for arg in argv[3:]:
+        m = re.match(r'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg)
+        if m is not None:
+            name, value = m.groups()
+            parser.do_assignment(name, value == 'y')
+        else:
+            fp = open(arg, 'r')
+            parser.parse_file(fp)
+            fp.close()
+
+    config = data.compute_config()
+    for key in sorted(config.keys()):
+        print ('CONFIG_%s=%s' % (key, ('y' if config[key] else 'n')))
+
+    deps = open(argv[2], 'w')
+    for fname in data.previously_included:
+        print ('%s: %s' % (argv[1], fname), file=deps)
+    deps.close()