]>
git.ipfire.org Git - thirdparty/qemu.git/blob - scripts/minikconf.py
5 # Copyright (c) 2015 Red Hat Inc.
8 # Paolo Bonzini <pbonzini@redhat.com>
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.
14 from __future__
import print_function
20 __all__
= [ 'KconfigDataError', 'KconfigParserError',
21 'KconfigData', 'KconfigParser' ,
22 'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
24 def debug_print(*args
):
25 #print('# ' + (' '.join(str(x) for x in args)))
28 # -------------------------------------------
29 # KconfigData implements the Kconfig semantics. For now it can only
30 # detect undefined symbols, i.e. symbols that were referenced in
31 # assignments or dependencies but were not declared with "config FOO".
33 # Semantic actions are represented by methods called do_*. The do_var
34 # method return the semantic value of a variable (which right now is
36 # -------------------------------------------
38 class KconfigDataError(Exception):
39 def __init__(self
, msg
):
45 allyesconfig
= lambda x
: True
46 allnoconfig
= lambda x
: False
47 defconfig
= lambda x
: x
48 randconfig
= lambda x
: random
.randint(0, 1) == 1
52 def __and__(self
, rhs
):
53 return KconfigData
.AND(self
, rhs
)
54 def __or__(self
, rhs
):
55 return KconfigData
.OR(self
, rhs
)
57 return KconfigData
.NOT(self
)
60 def add_edges_to(self
, var
):
66 def __init__(self
, lhs
, rhs
):
70 return "(%s && %s)" % (self
.lhs
, self
.rhs
)
72 def add_edges_to(self
, var
):
73 self
.lhs
.add_edges_to(var
)
74 self
.rhs
.add_edges_to(var
)
76 return self
.lhs
.evaluate() and self
.rhs
.evaluate()
79 def __init__(self
, lhs
, rhs
):
83 return "(%s || %s)" % (self
.lhs
, self
.rhs
)
85 def add_edges_to(self
, var
):
86 self
.lhs
.add_edges_to(var
)
87 self
.rhs
.add_edges_to(var
)
89 return self
.lhs
.evaluate() or self
.rhs
.evaluate()
92 def __init__(self
, lhs
):
95 return "!%s" % (self
.lhs
)
97 def add_edges_to(self
, var
):
98 self
.lhs
.add_edges_to(var
)
100 return not self
.lhs
.evaluate()
103 def __init__(self
, name
):
106 self
.outgoing
= set()
107 self
.clauses_for_var
= list()
112 return not (self
.value
is None)
113 def set_value(self
, val
, clause
):
114 self
.clauses_for_var
.append(clause
)
115 if self
.has_value() and self
.value
!= val
:
116 print("The following clauses were found for " + self
.name
)
117 for i
in self
.clauses_for_var
:
118 print(" " + str(i
), file=sys
.stderr
)
119 raise KconfigDataError('contradiction between clauses when setting %s' % self
)
120 debug_print("=> %s is now %s" % (self
.name
, val
))
123 # depth first search of the dependency graph
124 def dfs(self
, visited
, f
):
128 for v
in self
.outgoing
:
132 def add_edges_to(self
, var
):
133 self
.outgoing
.add(var
)
135 if not self
.has_value():
136 raise KconfigDataError('cycle found including %s' % self
)
140 def __init__(self
, dest
):
147 class AssignmentClause(Clause
):
148 def __init__(self
, dest
, value
):
149 KconfigData
.Clause
.__init
__(self
, dest
)
152 return "CONFIG_%s=%s" % (self
.dest
, 'y' if self
.value
else 'n')
155 self
.dest
.set_value(self
.value
, self
)
157 class DefaultClause(Clause
):
158 def __init__(self
, dest
, value
, cond
=None):
159 KconfigData
.Clause
.__init
__(self
, dest
)
162 if not (self
.cond
is None):
163 self
.cond
.add_edges_to(self
.dest
)
165 value
= 'y' if self
.value
else 'n'
166 if self
.cond
is None:
167 return "config %s default %s" % (self
.dest
, value
)
169 return "config %s default %s if %s" % (self
.dest
, value
, self
.cond
)
172 # Defaults are processed just before leaving the variable
175 if not self
.dest
.has_value() and \
176 (self
.cond
is None or self
.cond
.evaluate()):
177 self
.dest
.set_value(self
.value
, self
)
179 class DependsOnClause(Clause
):
180 def __init__(self
, dest
, expr
):
181 KconfigData
.Clause
.__init
__(self
, dest
)
183 self
.expr
.add_edges_to(self
.dest
)
185 return "config %s depends on %s" % (self
.dest
, self
.expr
)
188 if not self
.expr
.evaluate():
189 self
.dest
.set_value(False, self
)
191 class SelectClause(Clause
):
192 def __init__(self
, dest
, cond
):
193 KconfigData
.Clause
.__init
__(self
, dest
)
195 self
.cond
.add_edges_to(self
.dest
)
197 return "select %s if %s" % (self
.dest
, self
.cond
)
200 if self
.cond
.evaluate():
201 self
.dest
.set_value(True, self
)
203 def __init__(self
, value_mangler
=defconfig
):
204 self
.value_mangler
= value_mangler
205 self
.previously_included
= []
206 self
.incl_info
= None
207 self
.defined_vars
= set()
208 self
.referenced_vars
= dict()
209 self
.clauses
= list()
211 # semantic analysis -------------
213 def check_undefined(self
):
215 for i
in self
.referenced_vars
:
216 if not (i
in self
.defined_vars
):
217 print("undefined symbol %s" % (i
), file=sys
.stderr
)
221 def compute_config(self
):
222 if self
.check_undefined():
223 raise KconfigDataError("there were undefined symbols")
226 debug_print("Input:")
227 for clause
in self
.clauses
:
230 debug_print("\nDependency graph:")
231 for i
in self
.referenced_vars
:
232 debug_print(i
, "->", [str(x
) for x
in self
.referenced_vars
[i
].outgoing
])
234 # The reverse of the depth-first order is the topological sort
239 debug_print(var
, "has DFS number", len(dfo
))
242 for name
, v
in self
.referenced_vars
.items():
243 self
.do_default(v
, False)
244 v
.dfs(visited
, visit_fn
)
246 # Put higher DFS numbers and higher priorities first. This
247 # places the clauses in topological order and places defaults
248 # after assignments and dependencies.
249 self
.clauses
.sort(key
=lambda x
: (-dfo
[x
.dest
], -x
.priority()))
251 debug_print("\nSorted clauses:")
252 for clause
in self
.clauses
:
258 for name
, v
in self
.referenced_vars
.items():
259 debug_print("Evaluating", name
)
260 values
[name
] = v
.evaluate()
264 # semantic actions -------------
266 def do_declaration(self
, var
):
267 if (var
in self
.defined_vars
):
268 raise KconfigDataError('variable "' + var
+ '" defined twice')
270 self
.defined_vars
.add(var
.name
)
272 # var is a string with the variable's name.
273 def do_var(self
, var
):
274 if (var
in self
.referenced_vars
):
275 return self
.referenced_vars
[var
]
277 var_obj
= self
.referenced_vars
[var
] = KconfigData
.Var(var
)
280 def do_assignment(self
, var
, val
):
281 self
.clauses
.append(KconfigData
.AssignmentClause(var
, val
))
283 def do_default(self
, var
, val
, cond
=None):
284 val
= self
.value_mangler(val
)
285 self
.clauses
.append(KconfigData
.DefaultClause(var
, val
, cond
))
287 def do_depends_on(self
, var
, expr
):
288 self
.clauses
.append(KconfigData
.DependsOnClause(var
, expr
))
290 def do_select(self
, var
, symbol
, cond
=None):
291 cond
= (cond
& var
) if cond
is not None else var
292 self
.clauses
.append(KconfigData
.SelectClause(symbol
, cond
))
294 def do_imply(self
, var
, symbol
, cond
=None):
295 # "config X imply Y [if COND]" is the same as
296 # "config Y default y if X [&& COND]"
297 cond
= (cond
& var
) if cond
is not None else var
298 self
.do_default(symbol
, True, cond
)
300 # -------------------------------------------
301 # KconfigParser implements a recursive descent parser for (simplified)
303 # -------------------------------------------
308 TOK_LPAREN
= 0; TOKENS
[TOK_LPAREN
] = '"("';
309 TOK_RPAREN
= 1; TOKENS
[TOK_RPAREN
] = '")"';
310 TOK_EQUAL
= 2; TOKENS
[TOK_EQUAL
] = '"="';
311 TOK_AND
= 3; TOKENS
[TOK_AND
] = '"&&"';
312 TOK_OR
= 4; TOKENS
[TOK_OR
] = '"||"';
313 TOK_NOT
= 5; TOKENS
[TOK_NOT
] = '"!"';
314 TOK_DEPENDS
= 6; TOKENS
[TOK_DEPENDS
] = '"depends"';
315 TOK_ON
= 7; TOKENS
[TOK_ON
] = '"on"';
316 TOK_SELECT
= 8; TOKENS
[TOK_SELECT
] = '"select"';
317 TOK_IMPLY
= 9; TOKENS
[TOK_IMPLY
] = '"imply"';
318 TOK_CONFIG
= 10; TOKENS
[TOK_CONFIG
] = '"config"';
319 TOK_DEFAULT
= 11; TOKENS
[TOK_DEFAULT
] = '"default"';
320 TOK_Y
= 12; TOKENS
[TOK_Y
] = '"y"';
321 TOK_N
= 13; TOKENS
[TOK_N
] = '"n"';
322 TOK_SOURCE
= 14; TOKENS
[TOK_SOURCE
] = '"source"';
323 TOK_BOOL
= 15; TOKENS
[TOK_BOOL
] = '"bool"';
324 TOK_IF
= 16; TOKENS
[TOK_IF
] = '"if"';
325 TOK_ID
= 17; TOKENS
[TOK_ID
] = 'identifier';
326 TOK_EOF
= 18; TOKENS
[TOK_EOF
] = 'end of file';
328 class KconfigParserError(Exception):
329 def __init__(self
, parser
, msg
, tok
=None):
330 self
.loc
= parser
.location()
331 tok
= tok
or parser
.tok
333 location
= TOKENS
.get(tok
, None) or ('"%s"' % tok
)
334 msg
= '%s before %s' % (msg
, location
)
338 return "%s: %s" % (self
.loc
, self
.msg
)
343 def parse(self
, fp
, mode
=None):
344 data
= KconfigData(mode
or KconfigParser
.defconfig
)
345 parser
= KconfigParser(data
)
346 parser
.parse_file(fp
)
349 def __init__(self
, data
):
352 def parse_file(self
, fp
):
353 self
.abs_fname
= os
.path
.abspath(fp
.name
)
355 self
.data
.previously_included
.append(self
.abs_fname
)
357 if self
.src
== '' or self
.src
[-1] != '\n':
365 def do_assignment(self
, var
, val
):
366 if not var
.startswith("CONFIG_"):
367 raise Error('assigned variable should start with CONFIG_')
368 var
= self
.data
.do_var(var
[7:])
369 self
.data
.do_assignment(var
, val
)
371 # file management -----
373 def error_path(self
):
374 inf
= self
.data
.incl_info
377 res
= ("In file included from %s:%d:\n" % (inf
['file'],
384 for ch
in self
.src
[self
.line_pos
:self
.pos
]:
386 col
+= 8 - ((col
- 1) % 8)
389 return '%s%s:%d:%d' %(self
.error_path(), self
.fname
, self
.line
, col
)
391 def do_include(self
, include
):
392 incl_abs_fname
= os
.path
.join(os
.path
.dirname(self
.abs_fname
),
394 # catch inclusion cycle
395 inf
= self
.data
.incl_info
397 if incl_abs_fname
== os
.path
.abspath(inf
['file']):
398 raise KconfigParserError(self
, "Inclusion loop for %s"
402 # skip multiple include of the same file
403 if incl_abs_fname
in self
.data
.previously_included
:
406 fp
= open(incl_abs_fname
, 'r')
408 raise KconfigParserError(self
,
409 '%s: %s' % (e
.strerror
, include
))
411 inf
= self
.data
.incl_info
412 self
.data
.incl_info
= { 'file': self
.fname
, 'line': self
.line
,
414 KconfigParser(self
.data
).parse_file(fp
)
415 self
.data
.incl_info
= inf
417 # recursive descent parser -----
420 def parse_y_or_n(self
):
421 if self
.tok
== TOK_Y
:
424 if self
.tok
== TOK_N
:
427 raise KconfigParserError(self
, 'Expected "y" or "n"')
431 if self
.tok
== TOK_ID
:
434 return self
.data
.do_var(val
)
436 raise KconfigParserError(self
, 'Expected identifier')
438 # assignment_var: ID (starting with "CONFIG_")
439 def parse_assignment_var(self
):
440 if self
.tok
== TOK_ID
:
442 if not val
.startswith("CONFIG_"):
443 raise KconfigParserError(self
,
444 'Expected identifier starting with "CONFIG_"', TOK_NONE
)
446 return self
.data
.do_var(val
[7:])
448 raise KconfigParserError(self
, 'Expected identifier')
450 # assignment: var EQUAL y_or_n
451 def parse_assignment(self
):
452 var
= self
.parse_assignment_var()
453 if self
.tok
!= TOK_EQUAL
:
454 raise KconfigParserError(self
, 'Expected "="')
456 self
.data
.do_assignment(var
, self
.parse_y_or_n())
458 # primary: NOT primary
459 # | LPAREN expr RPAREN
461 def parse_primary(self
):
462 if self
.tok
== TOK_NOT
:
464 val
= ~self
.parse_primary()
465 elif self
.tok
== TOK_LPAREN
:
467 val
= self
.parse_expr()
468 if self
.tok
!= TOK_RPAREN
:
469 raise KconfigParserError(self
, 'Expected ")"')
471 elif self
.tok
== TOK_ID
:
472 val
= self
.parse_var()
474 raise KconfigParserError(self
, 'Expected "!" or "(" or identifier')
477 # disj: primary (OR primary)*
478 def parse_disj(self
):
479 lhs
= self
.parse_primary()
480 while self
.tok
== TOK_OR
:
482 lhs
= lhs | self
.parse_primary()
485 # expr: disj (AND disj)*
486 def parse_expr(self
):
487 lhs
= self
.parse_disj()
488 while self
.tok
== TOK_AND
:
490 lhs
= lhs
& self
.parse_disj()
495 def parse_condition(self
):
496 if self
.tok
== TOK_IF
:
498 return self
.parse_expr()
502 # property: DEFAULT y_or_n condition
504 # | SELECT var condition
506 def parse_property(self
, var
):
507 if self
.tok
== TOK_DEFAULT
:
509 val
= self
.parse_y_or_n()
510 cond
= self
.parse_condition()
511 self
.data
.do_default(var
, val
, cond
)
512 elif self
.tok
== TOK_DEPENDS
:
514 if self
.tok
!= TOK_ON
:
515 raise KconfigParserError(self
, 'Expected "on"')
517 self
.data
.do_depends_on(var
, self
.parse_expr())
518 elif self
.tok
== TOK_SELECT
:
520 symbol
= self
.parse_var()
521 cond
= self
.parse_condition()
522 self
.data
.do_select(var
, symbol
, cond
)
523 elif self
.tok
== TOK_IMPLY
:
525 symbol
= self
.parse_var()
526 cond
= self
.parse_condition()
527 self
.data
.do_imply(var
, symbol
, cond
)
528 elif self
.tok
== TOK_BOOL
:
531 raise KconfigParserError(self
, 'Error in recursive descent?')
533 # properties: properties property
535 def parse_properties(self
, var
):
537 while self
.tok
== TOK_DEFAULT
or self
.tok
== TOK_DEPENDS
or \
538 self
.tok
== TOK_SELECT
or self
.tok
== TOK_BOOL
or \
539 self
.tok
== TOK_IMPLY
:
540 self
.parse_property(var
)
542 # for nicer error message
543 if self
.tok
!= TOK_SOURCE
and self
.tok
!= TOK_CONFIG
and \
544 self
.tok
!= TOK_ID
and self
.tok
!= TOK_EOF
:
545 raise KconfigParserError(self
, 'expected "source", "config", identifier, '
546 + '"default", "depends on", "imply" or "select"')
548 # declaration: config var properties
549 def parse_declaration(self
):
550 if self
.tok
== TOK_CONFIG
:
552 var
= self
.parse_var()
553 self
.data
.do_declaration(var
)
554 self
.parse_properties(var
)
556 raise KconfigParserError(self
, 'Error in recursive descent?')
561 def parse_clause(self
):
562 if self
.tok
== TOK_SOURCE
:
566 elif self
.tok
== TOK_CONFIG
:
567 self
.parse_declaration()
568 elif self
.tok
== TOK_ID
:
569 self
.parse_assignment()
571 raise KconfigParserError(self
, 'expected "source", "config" or identifier')
573 # config: clause+ EOF
574 def parse_config(self
):
575 while self
.tok
!= TOK_EOF
:
583 self
.tok
= self
.src
[self
.cursor
]
584 self
.pos
= self
.cursor
588 self
.tok
= self
.scan_token()
589 if self
.tok
is not None:
592 def check_keyword(self
, rest
):
593 if not self
.src
.startswith(rest
, self
.cursor
):
596 if self
.src
[self
.cursor
+ length
].isalnum() or self
.src
[self
.cursor
+ length
] == '_':
598 self
.cursor
+= length
601 def scan_token(self
):
603 self
.cursor
= self
.src
.find('\n', self
.cursor
)
605 elif self
.tok
== '=':
607 elif self
.tok
== '(':
609 elif self
.tok
== ')':
611 elif self
.tok
== '&' and self
.src
[self
.pos
+1] == '&':
614 elif self
.tok
== '|' and self
.src
[self
.pos
+1] == '|':
617 elif self
.tok
== '!':
619 elif self
.tok
== 'd' and self
.check_keyword("epends"):
621 elif self
.tok
== 'o' and self
.check_keyword("n"):
623 elif self
.tok
== 's' and self
.check_keyword("elect"):
625 elif self
.tok
== 'i' and self
.check_keyword("mply"):
627 elif self
.tok
== 'c' and self
.check_keyword("onfig"):
629 elif self
.tok
== 'd' and self
.check_keyword("efault"):
631 elif self
.tok
== 'b' and self
.check_keyword("ool"):
633 elif self
.tok
== 'i' and self
.check_keyword("f"):
635 elif self
.tok
== 'y' and self
.check_keyword(""):
637 elif self
.tok
== 'n' and self
.check_keyword(""):
639 elif (self
.tok
== 's' and self
.check_keyword("ource")) or \
640 self
.tok
== 'i' and self
.check_keyword("nclude"):
643 while self
.src
[self
.cursor
].isspace():
646 self
.cursor
= self
.src
.find('\n', self
.cursor
)
647 self
.val
= self
.src
[start
:self
.cursor
]
649 elif self
.tok
.isalpha():
651 while self
.src
[self
.cursor
].isalnum() or self
.src
[self
.cursor
] == '_':
653 self
.val
= self
.src
[self
.pos
:self
.cursor
]
655 elif self
.tok
== '\n':
656 if self
.cursor
== len(self
.src
):
659 self
.line_pos
= self
.cursor
660 elif not self
.tok
.isspace():
661 raise KconfigParserError(self
, 'invalid input')
665 if __name__
== '__main__':
668 if len(sys
.argv
) > 1:
669 if argv
[1] == '--defconfig':
671 elif argv
[1] == '--randconfig':
675 elif argv
[1] == '--allyesconfig':
678 elif argv
[1] == '--allnoconfig':
683 print ("%s: at least one argument is required" % argv
[0], file=sys
.stderr
)
686 if argv
[1].startswith('-'):
687 print ("%s: invalid option %s" % (argv
[0], argv
[1]), file=sys
.stderr
)
690 data
= KconfigData(mode
)
691 parser
= KconfigParser(data
)
692 external_vars
= set()
694 m
= re
.match(r
'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg
)
696 name
, value
= m
.groups()
697 parser
.do_assignment(name
, value
== 'y')
698 external_vars
.add(name
[7:])
701 parser
.parse_file(fp
)
704 config
= data
.compute_config()
705 for key
in sorted(config
.keys()):
706 if key
not in external_vars
and config
[key
]:
707 print ('CONFIG_%s=y' % key
)
709 deps
= open(argv
[2], 'w')
710 for fname
in data
.previously_included
:
711 print ('%s: %s' % (argv
[1], fname
), file=deps
)