qemu/scripts/minikconf.py
<<
>>
Prefs
   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()
 712