qemu/scripts/minikconf.py
Stefan Hajnoczi ddd633e525 minikconf: explicitly set encoding to UTF-8
QEMU currently only has ASCII Kconfig files but Linux actually uses
UTF-8. Explicitly specify the encoding and that we're doing text file
I/O.

It's unclear whether or not QEMU will ever need Unicode in its Kconfig
files. If we start using the help text then it will become an issue
sooner or later. Make this change now for consistency with Linux
Kconfig.

Reported-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20200521153616.307100-1-stefanha@redhat.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
2020-06-23 15:46:05 +01:00

712 lines
23 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Mini-Kconfig parser
#
# Copyright (c) 2015 Red Hat Inc.
#
# Authors:
# Paolo Bonzini <pbonzini@redhat.com>
#
# This work is licensed under the terms of the GNU GPL, version 2
# or, at your option, any later version. See the COPYING file in
# the top-level directory.
import os
import sys
import re
import random
__all__ = [ 'KconfigDataError', 'KconfigParserError',
'KconfigData', 'KconfigParser' ,
'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
def debug_print(*args):
#print('# ' + (' '.join(str(x) for x in args)))
pass
# -------------------------------------------
# KconfigData implements the Kconfig semantics. For now it can only
# detect undefined symbols, i.e. symbols that were referenced in
# assignments or dependencies but were not declared with "config FOO".
#
# Semantic actions are represented by methods called do_*. The do_var
# method return the semantic value of a variable (which right now is
# 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:
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 = dict()
self.clauses = list()
# semantic analysis -------------
def check_undefined(self):
undef = False
for i in self.referenced_vars:
if not (i in self.defined_vars):
print("undefined symbol %s" % (i), file=sys.stderr)
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 KconfigDataError('variable "' + var + '" defined twice')
self.defined_vars.add(var.name)
# var is a string with the variable's name.
def do_var(self, 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):
self.clauses.append(KconfigData.AssignmentClause(var, val))
def do_default(self, var, val, cond=None):
val = self.value_mangler(val)
self.clauses.append(KconfigData.DefaultClause(var, val, cond))
def do_depends_on(self, var, expr):
self.clauses.append(KconfigData.DependsOnClause(var, expr))
def do_select(self, var, symbol, cond=None):
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):
# "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)
# Kconfig syntax.
# -------------------------------------------
# tokens table
TOKENS = {}
TOK_NONE = -1
TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("';
TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"';
TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="';
TOK_AND = 3; TOKENS[TOK_AND] = '"&&"';
TOK_OR = 4; TOKENS[TOK_OR] = '"||"';
TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"';
TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"';
TOK_ON = 7; TOKENS[TOK_ON] = '"on"';
TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"';
TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"';
TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"';
TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"';
TOK_Y = 12; TOKENS[TOK_Y] = '"y"';
TOK_N = 13; TOKENS[TOK_N] = '"n"';
TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"';
TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"';
TOK_IF = 16; TOKENS[TOK_IF] = '"if"';
TOK_ID = 17; TOKENS[TOK_ID] = 'identifier';
TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file';
class KconfigParserError(Exception):
def __init__(self, parser, msg, tok=None):
self.loc = parser.location()
tok = tok or parser.tok
if tok != TOK_NONE:
location = TOKENS.get(tok, None) or ('"%s"' % tok)
msg = '%s before %s' % (msg, location)
self.msg = msg
def __str__(self):
return "%s: %s" % (self.loc, self.msg)
class KconfigParser:
@classmethod
def parse(self, fp, mode=None):
data = KconfigData(mode or KconfigParser.defconfig)
parser = KconfigParser(data)
parser.parse_file(fp)
return data
def __init__(self, data):
self.data = data
def parse_file(self, fp):
self.abs_fname = os.path.abspath(fp.name)
self.fname = fp.name
self.data.previously_included.append(self.abs_fname)
self.src = fp.read()
if self.src == '' or self.src[-1] != '\n':
self.src += '\n'
self.cursor = 0
self.line = 1
self.line_pos = 0
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):
inf = self.data.incl_info
res = ""
while inf:
res = ("In file included from %s:%d:\n" % (inf['file'],
inf['line'])) + res
inf = inf['parent']
return res
def location(self):
col = 1
for ch in self.src[self.line_pos:self.pos]:
if ch == '\t':
col += 8 - ((col - 1) % 8)
else:
col += 1
return '%s%s:%d:%d' %(self.error_path(), self.fname, self.line, col)
def do_include(self, include):
incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
include)
# catch inclusion cycle
inf = self.data.incl_info
while inf:
if incl_abs_fname == os.path.abspath(inf['file']):
raise KconfigParserError(self, "Inclusion loop for %s"
% include)
inf = inf['parent']
# skip multiple include of the same file
if incl_abs_fname in self.data.previously_included:
return
try:
fp = open(incl_abs_fname, 'rt', encoding='utf-8')
except IOError as e:
raise KconfigParserError(self,
'%s: %s' % (e.strerror, include))
inf = self.data.incl_info
self.data.incl_info = { 'file': self.fname, 'line': self.line,
'parent': inf }
KconfigParser(self.data).parse_file(fp)
self.data.incl_info = inf
# recursive descent parser -----
# y_or_n: Y | N
def parse_y_or_n(self):
if self.tok == TOK_Y:
self.get_token()
return True
if self.tok == TOK_N:
self.get_token()
return False
raise KconfigParserError(self, 'Expected "y" or "n"')
# var: ID
def parse_var(self):
if self.tok == TOK_ID:
val = self.val
self.get_token()
return self.data.do_var(val)
else:
raise KconfigParserError(self, 'Expected identifier')
# assignment_var: ID (starting with "CONFIG_")
def parse_assignment_var(self):
if self.tok == TOK_ID:
val = self.val
if not val.startswith("CONFIG_"):
raise KconfigParserError(self,
'Expected identifier starting with "CONFIG_"', TOK_NONE)
self.get_token()
return self.data.do_var(val[7:])
else:
raise KconfigParserError(self, 'Expected identifier')
# assignment: var EQUAL y_or_n
def parse_assignment(self):
var = self.parse_assignment_var()
if self.tok != TOK_EQUAL:
raise KconfigParserError(self, 'Expected "="')
self.get_token()
self.data.do_assignment(var, self.parse_y_or_n())
# primary: NOT primary
# | LPAREN expr RPAREN
# | var
def parse_primary(self):
if self.tok == TOK_NOT:
self.get_token()
val = ~self.parse_primary()
elif self.tok == TOK_LPAREN:
self.get_token()
val = self.parse_expr()
if self.tok != TOK_RPAREN:
raise KconfigParserError(self, 'Expected ")"')
self.get_token()
elif self.tok == TOK_ID:
val = self.parse_var()
else:
raise KconfigParserError(self, 'Expected "!" or "(" or identifier')
return val
# disj: primary (OR primary)*
def parse_disj(self):
lhs = self.parse_primary()
while self.tok == TOK_OR:
self.get_token()
lhs = lhs | self.parse_primary()
return lhs
# expr: disj (AND disj)*
def parse_expr(self):
lhs = self.parse_disj()
while self.tok == TOK_AND:
self.get_token()
lhs = lhs & self.parse_disj()
return lhs
# condition: IF expr
# | empty
def parse_condition(self):
if self.tok == TOK_IF:
self.get_token()
return self.parse_expr()
else:
return None
# property: DEFAULT y_or_n condition
# | DEPENDS ON expr
# | SELECT var condition
# | BOOL
def parse_property(self, var):
if self.tok == TOK_DEFAULT:
self.get_token()
val = self.parse_y_or_n()
cond = self.parse_condition()
self.data.do_default(var, val, cond)
elif self.tok == TOK_DEPENDS:
self.get_token()
if self.tok != TOK_ON:
raise KconfigParserError(self, 'Expected "on"')
self.get_token()
self.data.do_depends_on(var, self.parse_expr())
elif self.tok == TOK_SELECT:
self.get_token()
symbol = self.parse_var()
cond = self.parse_condition()
self.data.do_select(var, symbol, cond)
elif self.tok == TOK_IMPLY:
self.get_token()
symbol = self.parse_var()
cond = self.parse_condition()
self.data.do_imply(var, symbol, cond)
elif self.tok == TOK_BOOL:
self.get_token()
else:
raise KconfigParserError(self, 'Error in recursive descent?')
# properties: properties property
# | /* empty */
def parse_properties(self, var):
had_default = False
while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
self.tok == TOK_IMPLY:
self.parse_property(var)
# for nicer error message
if self.tok != TOK_SOURCE and self.tok != TOK_CONFIG and \
self.tok != TOK_ID and self.tok != TOK_EOF:
raise KconfigParserError(self, 'expected "source", "config", identifier, '
+ '"default", "depends on", "imply" or "select"')
# declaration: config var properties
def parse_declaration(self):
if self.tok == TOK_CONFIG:
self.get_token()
var = self.parse_var()
self.data.do_declaration(var)
self.parse_properties(var)
else:
raise KconfigParserError(self, 'Error in recursive descent?')
# clause: SOURCE
# | declaration
# | assignment
def parse_clause(self):
if self.tok == TOK_SOURCE:
val = self.val
self.get_token()
self.do_include(val)
elif self.tok == TOK_CONFIG:
self.parse_declaration()
elif self.tok == TOK_ID:
self.parse_assignment()
else:
raise KconfigParserError(self, 'expected "source", "config" or identifier')
# config: clause+ EOF
def parse_config(self):
while self.tok != TOK_EOF:
self.parse_clause()
return self.data
# scanner -----
def get_token(self):
while True:
self.tok = self.src[self.cursor]
self.pos = self.cursor
self.cursor += 1
self.val = None
self.tok = self.scan_token()
if self.tok is not None:
return
def check_keyword(self, rest):
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] == '_':
return False
self.cursor += length
return True
def scan_token(self):
if self.tok == '#':
self.cursor = self.src.find('\n', self.cursor)
return None
elif self.tok == '=':
return TOK_EQUAL
elif self.tok == '(':
return TOK_LPAREN
elif self.tok == ')':
return TOK_RPAREN
elif self.tok == '&' and self.src[self.pos+1] == '&':
self.cursor += 1
return TOK_AND
elif self.tok == '|' and self.src[self.pos+1] == '|':
self.cursor += 1
return TOK_OR
elif self.tok == '!':
return TOK_NOT
elif self.tok == 'd' and self.check_keyword("epends"):
return TOK_DEPENDS
elif self.tok == 'o' and self.check_keyword("n"):
return TOK_ON
elif self.tok == 's' and self.check_keyword("elect"):
return TOK_SELECT
elif self.tok == 'i' and self.check_keyword("mply"):
return TOK_IMPLY
elif self.tok == 'c' and self.check_keyword("onfig"):
return TOK_CONFIG
elif self.tok == 'd' and self.check_keyword("efault"):
return TOK_DEFAULT
elif self.tok == 'b' and self.check_keyword("ool"):
return TOK_BOOL
elif self.tok == 'i' and self.check_keyword("f"):
return TOK_IF
elif self.tok == 'y' and self.check_keyword(""):
return TOK_Y
elif self.tok == 'n' and self.check_keyword(""):
return TOK_N
elif (self.tok == 's' and self.check_keyword("ource")) or \
self.tok == 'i' and self.check_keyword("nclude"):
# source FILENAME
# include FILENAME
while self.src[self.cursor].isspace():
self.cursor += 1
start = self.cursor
self.cursor = self.src.find('\n', self.cursor)
self.val = self.src[start:self.cursor]
return TOK_SOURCE
elif self.tok.isalnum():
# identifier
while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_':
self.cursor += 1
self.val = self.src[self.pos:self.cursor]
return TOK_ID
elif self.tok == '\n':
if self.cursor == len(self.src):
return TOK_EOF
self.line += 1
self.line_pos = self.cursor
elif not self.tok.isspace():
raise KconfigParserError(self, 'invalid input')
return None
if __name__ == '__main__':
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)
external_vars = set()
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')
external_vars.add(name[7:])
else:
fp = open(arg, 'rt', encoding='utf-8')
parser.parse_file(fp)
fp.close()
config = data.compute_config()
for key in sorted(config.keys()):
if key not in external_vars and config[key]:
print ('CONFIG_%s=y' % key)
deps = open(argv[2], 'wt', encoding='utf-8')
for fname in data.previously_included:
print ('%s: %s' % (argv[1], fname), file=deps)
deps.close()