minikconf.py (23656B)
1#!/usr/bin/env python3 2# 3# Mini-Kconfig parser 4# 5# Copyright (c) 2015 Red Hat Inc. 6# 7# Authors: 8# Paolo Bonzini <pbonzini@redhat.com> 9# 10# This work is licensed under the terms of the GNU GPL, version 2 11# or, at your option, any later version. See the COPYING file in 12# the top-level directory. 13 14import os 15import sys 16import re 17import random 18 19__all__ = [ 'KconfigDataError', 'KconfigParserError', 20 'KconfigData', 'KconfigParser' , 21 'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ] 22 23def debug_print(*args): 24 #print('# ' + (' '.join(str(x) for x in args))) 25 pass 26 27# ------------------------------------------- 28# KconfigData implements the Kconfig semantics. For now it can only 29# detect undefined symbols, i.e. symbols that were referenced in 30# assignments or dependencies but were not declared with "config FOO". 31# 32# Semantic actions are represented by methods called do_*. The do_var 33# method return the semantic value of a variable (which right now is 34# just its name). 35# ------------------------------------------- 36 37class KconfigDataError(Exception): 38 def __init__(self, msg): 39 self.msg = msg 40 41 def __str__(self): 42 return self.msg 43 44allyesconfig = lambda x: True 45allnoconfig = lambda x: False 46defconfig = lambda x: x 47randconfig = lambda x: random.randint(0, 1) == 1 48 49class KconfigData: 50 class Expr: 51 def __and__(self, rhs): 52 return KconfigData.AND(self, rhs) 53 def __or__(self, rhs): 54 return KconfigData.OR(self, rhs) 55 def __invert__(self): 56 return KconfigData.NOT(self) 57 58 # Abstract methods 59 def add_edges_to(self, var): 60 pass 61 def evaluate(self): 62 assert False 63 64 class AND(Expr): 65 def __init__(self, lhs, rhs): 66 self.lhs = lhs 67 self.rhs = rhs 68 def __str__(self): 69 return "(%s && %s)" % (self.lhs, self.rhs) 70 71 def add_edges_to(self, var): 72 self.lhs.add_edges_to(var) 73 self.rhs.add_edges_to(var) 74 def evaluate(self): 75 return self.lhs.evaluate() and self.rhs.evaluate() 76 77 class OR(Expr): 78 def __init__(self, lhs, rhs): 79 self.lhs = lhs 80 self.rhs = rhs 81 def __str__(self): 82 return "(%s || %s)" % (self.lhs, self.rhs) 83 84 def add_edges_to(self, var): 85 self.lhs.add_edges_to(var) 86 self.rhs.add_edges_to(var) 87 def evaluate(self): 88 return self.lhs.evaluate() or self.rhs.evaluate() 89 90 class NOT(Expr): 91 def __init__(self, lhs): 92 self.lhs = lhs 93 def __str__(self): 94 return "!%s" % (self.lhs) 95 96 def add_edges_to(self, var): 97 self.lhs.add_edges_to(var) 98 def evaluate(self): 99 return not self.lhs.evaluate() 100 101 class Var(Expr): 102 def __init__(self, name): 103 self.name = name 104 self.value = None 105 self.outgoing = set() 106 self.clauses_for_var = list() 107 def __str__(self): 108 return self.name 109 110 def has_value(self): 111 return not (self.value is None) 112 def set_value(self, val, clause): 113 self.clauses_for_var.append(clause) 114 if self.has_value() and self.value != val: 115 print("The following clauses were found for " + self.name) 116 for i in self.clauses_for_var: 117 print(" " + str(i), file=sys.stderr) 118 raise KconfigDataError('contradiction between clauses when setting %s' % self) 119 debug_print("=> %s is now %s" % (self.name, val)) 120 self.value = val 121 122 # depth first search of the dependency graph 123 def dfs(self, visited, f): 124 if self in visited: 125 return 126 visited.add(self) 127 for v in self.outgoing: 128 v.dfs(visited, f) 129 f(self) 130 131 def add_edges_to(self, var): 132 self.outgoing.add(var) 133 def evaluate(self): 134 if not self.has_value(): 135 raise KconfigDataError('cycle found including %s' % self) 136 return self.value 137 138 class Clause: 139 def __init__(self, dest): 140 self.dest = dest 141 def priority(self): 142 return 0 143 def process(self): 144 pass 145 146 class AssignmentClause(Clause): 147 def __init__(self, dest, value): 148 KconfigData.Clause.__init__(self, dest) 149 self.value = value 150 def __str__(self): 151 return "CONFIG_%s=%s" % (self.dest, 'y' if self.value else 'n') 152 153 def process(self): 154 self.dest.set_value(self.value, self) 155 156 class DefaultClause(Clause): 157 def __init__(self, dest, value, cond=None): 158 KconfigData.Clause.__init__(self, dest) 159 self.value = value 160 self.cond = cond 161 if not (self.cond is None): 162 self.cond.add_edges_to(self.dest) 163 def __str__(self): 164 value = 'y' if self.value else 'n' 165 if self.cond is None: 166 return "config %s default %s" % (self.dest, value) 167 else: 168 return "config %s default %s if %s" % (self.dest, value, self.cond) 169 170 def priority(self): 171 # Defaults are processed just before leaving the variable 172 return -1 173 def process(self): 174 if not self.dest.has_value() and \ 175 (self.cond is None or self.cond.evaluate()): 176 self.dest.set_value(self.value, self) 177 178 class DependsOnClause(Clause): 179 def __init__(self, dest, expr): 180 KconfigData.Clause.__init__(self, dest) 181 self.expr = expr 182 self.expr.add_edges_to(self.dest) 183 def __str__(self): 184 return "config %s depends on %s" % (self.dest, self.expr) 185 186 def process(self): 187 if not self.expr.evaluate(): 188 self.dest.set_value(False, self) 189 190 class SelectClause(Clause): 191 def __init__(self, dest, cond): 192 KconfigData.Clause.__init__(self, dest) 193 self.cond = cond 194 self.cond.add_edges_to(self.dest) 195 def __str__(self): 196 return "select %s if %s" % (self.dest, self.cond) 197 198 def process(self): 199 if self.cond.evaluate(): 200 self.dest.set_value(True, self) 201 202 def __init__(self, value_mangler=defconfig): 203 self.value_mangler = value_mangler 204 self.previously_included = [] 205 self.incl_info = None 206 self.defined_vars = set() 207 self.referenced_vars = dict() 208 self.clauses = list() 209 210 # semantic analysis ------------- 211 212 def check_undefined(self): 213 undef = False 214 for i in self.referenced_vars: 215 if not (i in self.defined_vars): 216 print("undefined symbol %s" % (i), file=sys.stderr) 217 undef = True 218 return undef 219 220 def compute_config(self): 221 if self.check_undefined(): 222 raise KconfigDataError("there were undefined symbols") 223 return None 224 225 debug_print("Input:") 226 for clause in self.clauses: 227 debug_print(clause) 228 229 debug_print("\nDependency graph:") 230 for i in self.referenced_vars: 231 debug_print(i, "->", [str(x) for x in self.referenced_vars[i].outgoing]) 232 233 # The reverse of the depth-first order is the topological sort 234 dfo = dict() 235 visited = set() 236 debug_print("\n") 237 def visit_fn(var): 238 debug_print(var, "has DFS number", len(dfo)) 239 dfo[var] = len(dfo) 240 241 for name, v in self.referenced_vars.items(): 242 self.do_default(v, False) 243 v.dfs(visited, visit_fn) 244 245 # Put higher DFS numbers and higher priorities first. This 246 # places the clauses in topological order and places defaults 247 # after assignments and dependencies. 248 self.clauses.sort(key=lambda x: (-dfo[x.dest], -x.priority())) 249 250 debug_print("\nSorted clauses:") 251 for clause in self.clauses: 252 debug_print(clause) 253 clause.process() 254 255 debug_print("") 256 values = dict() 257 for name, v in self.referenced_vars.items(): 258 debug_print("Evaluating", name) 259 values[name] = v.evaluate() 260 261 return values 262 263 # semantic actions ------------- 264 265 def do_declaration(self, var): 266 if (var in self.defined_vars): 267 raise KconfigDataError('variable "' + var + '" defined twice') 268 269 self.defined_vars.add(var.name) 270 271 # var is a string with the variable's name. 272 def do_var(self, var): 273 if (var in self.referenced_vars): 274 return self.referenced_vars[var] 275 276 var_obj = self.referenced_vars[var] = KconfigData.Var(var) 277 return var_obj 278 279 def do_assignment(self, var, val): 280 self.clauses.append(KconfigData.AssignmentClause(var, val)) 281 282 def do_default(self, var, val, cond=None): 283 val = self.value_mangler(val) 284 self.clauses.append(KconfigData.DefaultClause(var, val, cond)) 285 286 def do_depends_on(self, var, expr): 287 self.clauses.append(KconfigData.DependsOnClause(var, expr)) 288 289 def do_select(self, var, symbol, cond=None): 290 cond = (cond & var) if cond is not None else var 291 self.clauses.append(KconfigData.SelectClause(symbol, cond)) 292 293 def do_imply(self, var, symbol, cond=None): 294 # "config X imply Y [if COND]" is the same as 295 # "config Y default y if X [&& COND]" 296 cond = (cond & var) if cond is not None else var 297 self.do_default(symbol, True, cond) 298 299# ------------------------------------------- 300# KconfigParser implements a recursive descent parser for (simplified) 301# Kconfig syntax. 302# ------------------------------------------- 303 304# tokens table 305TOKENS = {} 306TOK_NONE = -1 307TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("'; 308TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"'; 309TOK_EQUAL = 2; TOKENS[TOK_EQUAL] = '"="'; 310TOK_AND = 3; TOKENS[TOK_AND] = '"&&"'; 311TOK_OR = 4; TOKENS[TOK_OR] = '"||"'; 312TOK_NOT = 5; TOKENS[TOK_NOT] = '"!"'; 313TOK_DEPENDS = 6; TOKENS[TOK_DEPENDS] = '"depends"'; 314TOK_ON = 7; TOKENS[TOK_ON] = '"on"'; 315TOK_SELECT = 8; TOKENS[TOK_SELECT] = '"select"'; 316TOK_IMPLY = 9; TOKENS[TOK_IMPLY] = '"imply"'; 317TOK_CONFIG = 10; TOKENS[TOK_CONFIG] = '"config"'; 318TOK_DEFAULT = 11; TOKENS[TOK_DEFAULT] = '"default"'; 319TOK_Y = 12; TOKENS[TOK_Y] = '"y"'; 320TOK_N = 13; TOKENS[TOK_N] = '"n"'; 321TOK_SOURCE = 14; TOKENS[TOK_SOURCE] = '"source"'; 322TOK_BOOL = 15; TOKENS[TOK_BOOL] = '"bool"'; 323TOK_IF = 16; TOKENS[TOK_IF] = '"if"'; 324TOK_ID = 17; TOKENS[TOK_ID] = 'identifier'; 325TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file'; 326 327class KconfigParserError(Exception): 328 def __init__(self, parser, msg, tok=None): 329 self.loc = parser.location() 330 tok = tok or parser.tok 331 if tok != TOK_NONE: 332 location = TOKENS.get(tok, None) or ('"%s"' % tok) 333 msg = '%s before %s' % (msg, location) 334 self.msg = msg 335 336 def __str__(self): 337 return "%s: %s" % (self.loc, self.msg) 338 339class KconfigParser: 340 341 @classmethod 342 def parse(self, fp, mode=None): 343 data = KconfigData(mode or KconfigParser.defconfig) 344 parser = KconfigParser(data) 345 parser.parse_file(fp) 346 return data 347 348 def __init__(self, data): 349 self.data = data 350 351 def parse_file(self, fp): 352 self.abs_fname = os.path.abspath(fp.name) 353 self.fname = fp.name 354 self.data.previously_included.append(self.abs_fname) 355 self.src = fp.read() 356 if self.src == '' or self.src[-1] != '\n': 357 self.src += '\n' 358 self.cursor = 0 359 self.line = 1 360 self.line_pos = 0 361 self.get_token() 362 self.parse_config() 363 364 def do_assignment(self, var, val): 365 if not var.startswith("CONFIG_"): 366 raise Error('assigned variable should start with CONFIG_') 367 var = self.data.do_var(var[7:]) 368 self.data.do_assignment(var, val) 369 370 # file management ----- 371 372 def error_path(self): 373 inf = self.data.incl_info 374 res = "" 375 while inf: 376 res = ("In file included from %s:%d:\n" % (inf['file'], 377 inf['line'])) + res 378 inf = inf['parent'] 379 return res 380 381 def location(self): 382 col = 1 383 for ch in self.src[self.line_pos:self.pos]: 384 if ch == '\t': 385 col += 8 - ((col - 1) % 8) 386 else: 387 col += 1 388 return '%s%s:%d:%d' %(self.error_path(), self.fname, self.line, col) 389 390 def do_include(self, include): 391 incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname), 392 include) 393 # catch inclusion cycle 394 inf = self.data.incl_info 395 while inf: 396 if incl_abs_fname == os.path.abspath(inf['file']): 397 raise KconfigParserError(self, "Inclusion loop for %s" 398 % include) 399 inf = inf['parent'] 400 401 # skip multiple include of the same file 402 if incl_abs_fname in self.data.previously_included: 403 return 404 try: 405 fp = open(incl_abs_fname, 'rt', encoding='utf-8') 406 except IOError as e: 407 raise KconfigParserError(self, 408 '%s: %s' % (e.strerror, include)) 409 410 inf = self.data.incl_info 411 self.data.incl_info = { 'file': self.fname, 'line': self.line, 412 'parent': inf } 413 KconfigParser(self.data).parse_file(fp) 414 self.data.incl_info = inf 415 416 # recursive descent parser ----- 417 418 # y_or_n: Y | N 419 def parse_y_or_n(self): 420 if self.tok == TOK_Y: 421 self.get_token() 422 return True 423 if self.tok == TOK_N: 424 self.get_token() 425 return False 426 raise KconfigParserError(self, 'Expected "y" or "n"') 427 428 # var: ID 429 def parse_var(self): 430 if self.tok == TOK_ID: 431 val = self.val 432 self.get_token() 433 return self.data.do_var(val) 434 else: 435 raise KconfigParserError(self, 'Expected identifier') 436 437 # assignment_var: ID (starting with "CONFIG_") 438 def parse_assignment_var(self): 439 if self.tok == TOK_ID: 440 val = self.val 441 if not val.startswith("CONFIG_"): 442 raise KconfigParserError(self, 443 'Expected identifier starting with "CONFIG_"', TOK_NONE) 444 self.get_token() 445 return self.data.do_var(val[7:]) 446 else: 447 raise KconfigParserError(self, 'Expected identifier') 448 449 # assignment: var EQUAL y_or_n 450 def parse_assignment(self): 451 var = self.parse_assignment_var() 452 if self.tok != TOK_EQUAL: 453 raise KconfigParserError(self, 'Expected "="') 454 self.get_token() 455 self.data.do_assignment(var, self.parse_y_or_n()) 456 457 # primary: NOT primary 458 # | LPAREN expr RPAREN 459 # | var 460 def parse_primary(self): 461 if self.tok == TOK_NOT: 462 self.get_token() 463 val = ~self.parse_primary() 464 elif self.tok == TOK_LPAREN: 465 self.get_token() 466 val = self.parse_expr() 467 if self.tok != TOK_RPAREN: 468 raise KconfigParserError(self, 'Expected ")"') 469 self.get_token() 470 elif self.tok == TOK_ID: 471 val = self.parse_var() 472 else: 473 raise KconfigParserError(self, 'Expected "!" or "(" or identifier') 474 return val 475 476 # disj: primary (OR primary)* 477 def parse_disj(self): 478 lhs = self.parse_primary() 479 while self.tok == TOK_OR: 480 self.get_token() 481 lhs = lhs | self.parse_primary() 482 return lhs 483 484 # expr: disj (AND disj)* 485 def parse_expr(self): 486 lhs = self.parse_disj() 487 while self.tok == TOK_AND: 488 self.get_token() 489 lhs = lhs & self.parse_disj() 490 return lhs 491 492 # condition: IF expr 493 # | empty 494 def parse_condition(self): 495 if self.tok == TOK_IF: 496 self.get_token() 497 return self.parse_expr() 498 else: 499 return None 500 501 # property: DEFAULT y_or_n condition 502 # | DEPENDS ON expr 503 # | SELECT var condition 504 # | BOOL 505 def parse_property(self, var): 506 if self.tok == TOK_DEFAULT: 507 self.get_token() 508 val = self.parse_y_or_n() 509 cond = self.parse_condition() 510 self.data.do_default(var, val, cond) 511 elif self.tok == TOK_DEPENDS: 512 self.get_token() 513 if self.tok != TOK_ON: 514 raise KconfigParserError(self, 'Expected "on"') 515 self.get_token() 516 self.data.do_depends_on(var, self.parse_expr()) 517 elif self.tok == TOK_SELECT: 518 self.get_token() 519 symbol = self.parse_var() 520 cond = self.parse_condition() 521 self.data.do_select(var, symbol, cond) 522 elif self.tok == TOK_IMPLY: 523 self.get_token() 524 symbol = self.parse_var() 525 cond = self.parse_condition() 526 self.data.do_imply(var, symbol, cond) 527 elif self.tok == TOK_BOOL: 528 self.get_token() 529 else: 530 raise KconfigParserError(self, 'Error in recursive descent?') 531 532 # properties: properties property 533 # | /* empty */ 534 def parse_properties(self, var): 535 had_default = False 536 while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \ 537 self.tok == TOK_SELECT or self.tok == TOK_BOOL or \ 538 self.tok == TOK_IMPLY: 539 self.parse_property(var) 540 541 # for nicer error message 542 if self.tok != TOK_SOURCE and self.tok != TOK_CONFIG and \ 543 self.tok != TOK_ID and self.tok != TOK_EOF: 544 raise KconfigParserError(self, 'expected "source", "config", identifier, ' 545 + '"default", "depends on", "imply" or "select"') 546 547 # declaration: config var properties 548 def parse_declaration(self): 549 if self.tok == TOK_CONFIG: 550 self.get_token() 551 var = self.parse_var() 552 self.data.do_declaration(var) 553 self.parse_properties(var) 554 else: 555 raise KconfigParserError(self, 'Error in recursive descent?') 556 557 # clause: SOURCE 558 # | declaration 559 # | assignment 560 def parse_clause(self): 561 if self.tok == TOK_SOURCE: 562 val = self.val 563 self.get_token() 564 self.do_include(val) 565 elif self.tok == TOK_CONFIG: 566 self.parse_declaration() 567 elif self.tok == TOK_ID: 568 self.parse_assignment() 569 else: 570 raise KconfigParserError(self, 'expected "source", "config" or identifier') 571 572 # config: clause+ EOF 573 def parse_config(self): 574 while self.tok != TOK_EOF: 575 self.parse_clause() 576 return self.data 577 578 # scanner ----- 579 580 def get_token(self): 581 while True: 582 self.tok = self.src[self.cursor] 583 self.pos = self.cursor 584 self.cursor += 1 585 586 self.val = None 587 self.tok = self.scan_token() 588 if self.tok is not None: 589 return 590 591 def check_keyword(self, rest): 592 if not self.src.startswith(rest, self.cursor): 593 return False 594 length = len(rest) 595 if self.src[self.cursor + length].isalnum() or self.src[self.cursor + length] == '_': 596 return False 597 self.cursor += length 598 return True 599 600 def scan_token(self): 601 if self.tok == '#': 602 self.cursor = self.src.find('\n', self.cursor) 603 return None 604 elif self.tok == '=': 605 return TOK_EQUAL 606 elif self.tok == '(': 607 return TOK_LPAREN 608 elif self.tok == ')': 609 return TOK_RPAREN 610 elif self.tok == '&' and self.src[self.pos+1] == '&': 611 self.cursor += 1 612 return TOK_AND 613 elif self.tok == '|' and self.src[self.pos+1] == '|': 614 self.cursor += 1 615 return TOK_OR 616 elif self.tok == '!': 617 return TOK_NOT 618 elif self.tok == 'd' and self.check_keyword("epends"): 619 return TOK_DEPENDS 620 elif self.tok == 'o' and self.check_keyword("n"): 621 return TOK_ON 622 elif self.tok == 's' and self.check_keyword("elect"): 623 return TOK_SELECT 624 elif self.tok == 'i' and self.check_keyword("mply"): 625 return TOK_IMPLY 626 elif self.tok == 'c' and self.check_keyword("onfig"): 627 return TOK_CONFIG 628 elif self.tok == 'd' and self.check_keyword("efault"): 629 return TOK_DEFAULT 630 elif self.tok == 'b' and self.check_keyword("ool"): 631 return TOK_BOOL 632 elif self.tok == 'i' and self.check_keyword("f"): 633 return TOK_IF 634 elif self.tok == 'y' and self.check_keyword(""): 635 return TOK_Y 636 elif self.tok == 'n' and self.check_keyword(""): 637 return TOK_N 638 elif (self.tok == 's' and self.check_keyword("ource")) or \ 639 self.tok == 'i' and self.check_keyword("nclude"): 640 # source FILENAME 641 # include FILENAME 642 while self.src[self.cursor].isspace(): 643 self.cursor += 1 644 start = self.cursor 645 self.cursor = self.src.find('\n', self.cursor) 646 self.val = self.src[start:self.cursor] 647 return TOK_SOURCE 648 elif self.tok.isalnum(): 649 # identifier 650 while self.src[self.cursor].isalnum() or self.src[self.cursor] == '_': 651 self.cursor += 1 652 self.val = self.src[self.pos:self.cursor] 653 return TOK_ID 654 elif self.tok == '\n': 655 if self.cursor == len(self.src): 656 return TOK_EOF 657 self.line += 1 658 self.line_pos = self.cursor 659 elif not self.tok.isspace(): 660 raise KconfigParserError(self, 'invalid input') 661 662 return None 663 664if __name__ == '__main__': 665 argv = sys.argv 666 mode = defconfig 667 if len(sys.argv) > 1: 668 if argv[1] == '--defconfig': 669 del argv[1] 670 elif argv[1] == '--randconfig': 671 random.seed() 672 mode = randconfig 673 del argv[1] 674 elif argv[1] == '--allyesconfig': 675 mode = allyesconfig 676 del argv[1] 677 elif argv[1] == '--allnoconfig': 678 mode = allnoconfig 679 del argv[1] 680 681 if len(argv) == 1: 682 print ("%s: at least one argument is required" % argv[0], file=sys.stderr) 683 sys.exit(1) 684 685 if argv[1].startswith('-'): 686 print ("%s: invalid option %s" % (argv[0], argv[1]), file=sys.stderr) 687 sys.exit(1) 688 689 data = KconfigData(mode) 690 parser = KconfigParser(data) 691 external_vars = set() 692 for arg in argv[3:]: 693 m = re.match(r'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg) 694 if m is not None: 695 name, value = m.groups() 696 parser.do_assignment(name, value == 'y') 697 external_vars.add(name[7:]) 698 else: 699 fp = open(arg, 'rt', encoding='utf-8') 700 parser.parse_file(fp) 701 fp.close() 702 703 config = data.compute_config() 704 for key in sorted(config.keys()): 705 if key not in external_vars and config[key]: 706 print ('CONFIG_%s=y' % key) 707 708 deps = open(argv[2], 'wt', encoding='utf-8') 709 for fname in data.previously_included: 710 print ('%s: %s' % (argv[1], fname), file=deps) 711 deps.close()