diff --git a/tools/buildman/kconfiglib.py b/tools/buildman/kconfiglib.py index 352ad43..68b470a 100644 --- a/tools/buildman/kconfiglib.py +++ b/tools/buildman/kconfiglib.py @@ -73,6 +73,7 @@ email service. Don't wrestle with internal APIs. Tell me what you need and I might add it in a safe way as a client API instead.""" import os +import platform import re import sys @@ -137,10 +138,8 @@ class Config(object): # The set of all symbols, indexed by name (a string) self.syms = {} # Python 2/3 compatibility hack. This is the only one needed. - if sys.version_info[0] >= 3: - self.syms_iter = self.syms.values - else: - self.syms_iter = self.syms.itervalues + self.syms_iter = self.syms.values if sys.version_info[0] >= 3 else \ + self.syms.itervalues # The set of all defined symbols in the configuration in the order they # appear in the Kconfig files. This excludes the special symbols n, m, @@ -173,7 +172,7 @@ class Config(object): self.m = register_special_symbol(TRISTATE, "m", "m") self.y = register_special_symbol(TRISTATE, "y", "y") # DEFCONFIG_LIST uses this - register_special_symbol(STRING, "UNAME_RELEASE", os.uname()[2]) + register_special_symbol(STRING, "UNAME_RELEASE", platform.uname()[2]) # The symbol with "option defconfig_list" set, containing a list of # default .config files @@ -183,16 +182,20 @@ class Config(object): self.arch = os.environ.get("ARCH") self.srcarch = os.environ.get("SRCARCH") + # If you set CONFIG_ in the environment, Kconfig will prefix all symbols + # with its value when saving the configuration, instead of using the default, "CONFIG_". + self.config_prefix = os.environ.get("CONFIG_") + if self.config_prefix is None: + self.config_prefix = "CONFIG_" + # See Config.__init__(). We need this for get_defconfig_filename(). self.srctree = os.environ.get("srctree") if self.srctree is None: self.srctree = "." self.filename = filename - if base_dir is None: - self.base_dir = self.srctree - else: - self.base_dir = os.path.expandvars(base_dir) + self.base_dir = self.srctree if base_dir is None else \ + os.path.expandvars(base_dir) # The 'mainmenu' text self.mainmenu_text = None @@ -222,7 +225,8 @@ class Config(object): self._transform_m = None # Parse the Kconfig files - self.top_block = self._parse_file(filename, None, None, None) + self.top_block = [] + self._parse_file(filename, None, None, None, self.top_block) # Build Symbol.dep for all symbols self._build_dep() @@ -405,6 +409,10 @@ class Config(object): """ self._warnings = [] + # Regular expressions for parsing .config files + _set_re_match = re.compile(r"{}(\w+)=(.*)".format(self.config_prefix)).match + _unset_re_match = re.compile(r"# {}(\w+) is not set".format(self.config_prefix)).match + # Put this first so that a missing file doesn't screw up our state filename = os.path.expandvars(filename) line_feeder = _FileFeed(filename) @@ -524,14 +532,12 @@ class Config(object): with open(filename, "w") as f: # Write header if header is not None: - f.write(_comment(header)) - f.write("\n") + f.write(_comment(header) + "\n") # Build and write configuration conf_strings = [] _make_block_conf(self.top_block, conf_strings.append) - f.write("\n".join(conf_strings)) - f.write("\n") + f.write("\n".join(conf_strings) + "\n") def eval(self, s): """Returns the value of the expression 's' -- where 's' is represented @@ -609,16 +615,18 @@ class Config(object): # Kconfig parsing # - def _parse_file(self, filename, parent, deps, visible_if_deps, res=None): - """Parses the Kconfig file 'filename'. Returns a list with the Items in - the file. See _parse_block() for the meaning of the parameters.""" - return self._parse_block(_FileFeed(filename), None, parent, deps, - visible_if_deps, res) + def _parse_file(self, filename, parent, deps, visible_if_deps, block): + """Parses the Kconfig file 'filename'. Appends the Items in the file + (and any file it sources) to the list passed in the 'block' parameter. + See _parse_block() for the meaning of the parameters.""" + self._parse_block(_FileFeed(filename), None, parent, deps, + visible_if_deps, block) def _parse_block(self, line_feeder, end_marker, parent, deps, - visible_if_deps, res=None): + visible_if_deps, block): """Parses a block, which is the contents of either a file or an if, - menu, or choice statement. Returns a list with the Items in the block. + menu, or choice statement. Appends the Items to the list passed in the + 'block' parameter. line_feeder: A _FileFeed instance feeding lines from a file. The Kconfig language is line-based in practice. @@ -634,10 +642,7 @@ class Config(object): visible_if_deps (default: None): 'visible if' dependencies from enclosing menus. - res (default: None): The list to add items to. If None, a new list is - created to hold the items.""" - - block = [] if res is None else res + block: The list to add items to.""" while 1: # Do we already have a tokenized line that we determined wasn't @@ -656,7 +661,7 @@ class Config(object): if end_marker is not None: raise Kconfig_Syntax_Error("Unexpected end of file {0}" .format(line_feeder.filename)) - return block + return tokens = self._tokenize(line, False, line_feeder.filename, line_feeder.linenr) @@ -679,14 +684,13 @@ class Config(object): # choice statements, the choice statement takes precedence. if not sym.is_defined_ or isinstance(parent, Choice): sym.parent = parent - sym.is_defined_ = True + self._parse_properties(line_feeder, sym, deps, visible_if_deps) + self.kconfig_syms.append(sym) block.append(sym) - self._parse_properties(line_feeder, sym, deps, visible_if_deps) - elif t0 == T_SOURCE: kconfig_file = tokens.get_next() exp_kconfig_file = self._expand_sym_refs(kconfig_file) @@ -705,7 +709,7 @@ class Config(object): elif t0 == end_marker: # We have reached the end of the block - return block + return elif t0 == T_IF: # If statements are treated as syntactic sugar for adding @@ -722,38 +726,39 @@ class Config(object): elif t0 == T_COMMENT: comment = Comment() - comment.config = self comment.parent = parent comment.filename = line_feeder.filename comment.linenr = line_feeder.linenr comment.text = tokens.get_next() - self.comments.append(comment) - block.append(comment) - self._parse_properties(line_feeder, comment, deps, visible_if_deps) + self.comments.append(comment) + block.append(comment) + elif t0 == T_MENU: menu = Menu() - menu.config = self menu.parent = parent menu.filename = line_feeder.filename menu.linenr = line_feeder.linenr menu.title = tokens.get_next() - self.menus.append(menu) - block.append(menu) - - # Parse properties and contents self._parse_properties(line_feeder, menu, deps, visible_if_deps) - menu.block = self._parse_block(line_feeder, T_ENDMENU, menu, - menu.dep_expr, - _make_and(visible_if_deps, - menu.visible_if_expr)) + + # This needs to go before _parse_block() so that we get the + # proper menu ordering in the case of nested functions + self.menus.append(menu) + # Parse contents and put Items in menu.block + self._parse_block(line_feeder, T_ENDMENU, menu, menu.dep_expr, + _make_and(visible_if_deps, + menu.visible_if_expr), + menu.block) + + block.append(menu) elif t0 == T_CHOICE: name = tokens.get_next() @@ -775,11 +780,12 @@ class Config(object): choice.def_locations.append((line_feeder.filename, line_feeder.linenr)) - # Parse properties and contents self._parse_properties(line_feeder, choice, deps, visible_if_deps) - choice.block = self._parse_block(line_feeder, T_ENDCHOICE, - choice, deps, visible_if_deps) + + # Parse contents and put Items in choice.block + self._parse_block(line_feeder, T_ENDCHOICE, choice, deps, + visible_if_deps, choice.block) choice._determine_actual_symbols() @@ -819,19 +825,19 @@ class Config(object): """Parses ' if ' constructs, where the 'if' part is optional. Returns a tuple containing the parsed expressions, with None as the second element if the 'if' part is missing.""" - val = self._parse_expr(tokens, stmt, line, filename, linenr, False) - if tokens.check(T_IF): - return (val, self._parse_expr(tokens, stmt, line, filename, - linenr)) - return (val, None) + return (self._parse_expr(tokens, stmt, line, filename, linenr, + False), + self._parse_expr(tokens, stmt, line, filename, linenr) + if tokens.check(T_IF) else None) # In case the symbol is defined in multiple locations, we need to - # remember what prompts, defaults, and selects are new for this - # definition, as "depends on" should only apply to the local + # remember what prompts, defaults, selects, and implies are new for + # this definition, as "depends on" should only apply to the local # definition. new_prompt = None new_def_exprs = [] new_selects = [] + new_implies = [] # Dependencies from 'depends on' statements depends_on_expr = None @@ -897,18 +903,27 @@ class Config(object): line_feeder.unget() - elif t0 == T_SELECT or t0 == T_IMPLY: + elif t0 == T_SELECT: target = tokens.get_next() stmt.referenced_syms.add(target) stmt.selected_syms.add(target) - if tokens.check(T_IF): - new_selects.append((target, - self._parse_expr(tokens, stmt, line, - filename, linenr))) - else: - new_selects.append((target, None)) + new_selects.append( + (target, + self._parse_expr(tokens, stmt, line, filename, linenr) + if tokens.check(T_IF) else None)) + + elif t0 == T_IMPLY: + target = tokens.get_next() + + stmt.referenced_syms.add(target) + stmt.implied_syms.add(target) + + new_implies.append( + (target, + self._parse_expr(tokens, stmt, line, filename, linenr) + if tokens.check(T_IF) else None)) elif t0 in (T_BOOL, T_TRISTATE, T_INT, T_HEX, T_STRING): stmt.type = TOKEN_TO_TYPE[t0] @@ -939,12 +954,10 @@ class Config(object): stmt.referenced_syms.add(low) stmt.referenced_syms.add(high) - if tokens.check(T_IF): - stmt.ranges.append((low, high, - self._parse_expr(tokens, stmt, line, - filename, linenr))) - else: - stmt.ranges.append((low, high, None)) + stmt.ranges.append( + (low, high, + self._parse_expr(tokens, stmt, line, filename, linenr) + if tokens.check(T_IF) else None)) elif t0 == T_DEF_TRISTATE: stmt.type = TRISTATE @@ -1051,21 +1064,20 @@ class Config(object): # Symbol or Choice # See comment for 'menu_dep' - stmt.menu_dep = depends_on_expr + stmt.menu_dep = _make_and(deps, depends_on_expr) # Propagate dependencies to prompts if new_prompt is not None: - # Propagate 'visible if' dependencies from enclosing menus prompt, cond_expr = new_prompt - cond_expr = _make_and(cond_expr, visible_if_deps) - # Propagate 'depends on' dependencies - new_prompt = (prompt, _make_and(cond_expr, depends_on_expr)) + # Propagate 'visible if' dependencies from menus and local + # 'depends on' dependencies + cond_expr = _make_and(_make_and(cond_expr, visible_if_deps), + depends_on_expr) # Save original - stmt.orig_prompts.append(new_prompt) + stmt.orig_prompts.append((prompt, cond_expr)) # Finalize with dependencies from enclosing menus and ifs - stmt.prompts.append((new_prompt[0], - _make_and(new_prompt[1], deps))) + stmt.prompts.append((prompt, _make_and(cond_expr, deps))) # Propagate dependencies to defaults @@ -1078,20 +1090,27 @@ class Config(object): stmt.def_exprs.extend([(val_expr, _make_and(cond_expr, deps)) for val_expr, cond_expr in new_def_exprs]) - # Propagate dependencies to selects + # Propagate dependencies to selects and implies - # Only symbols can select + # Only symbols can select and imply if isinstance(stmt, Symbol): # Propagate 'depends on' dependencies new_selects = [(target, _make_and(cond_expr, depends_on_expr)) for target, cond_expr in new_selects] + new_implies = [(target, _make_and(cond_expr, depends_on_expr)) + for target, cond_expr in new_implies] # Save original stmt.orig_selects.extend(new_selects) + stmt.orig_implies.extend(new_implies) # Finalize with dependencies from enclosing menus and ifs for target, cond in new_selects: - target.rev_dep = _make_or(target.rev_dep, - _make_and(stmt, - _make_and(cond, deps))) + target.rev_dep = \ + _make_or(target.rev_dep, + _make_and(stmt, _make_and(cond, deps))) + for target, cond in new_implies: + target.weak_rev_dep = \ + _make_or(target.weak_rev_dep, + _make_and(stmt, _make_and(cond, deps))) def _parse_expr(self, feed, cur_item, line, filename=None, linenr=None, transform_m=True): @@ -1483,7 +1502,8 @@ class Config(object): # The directly dependent symbols of a symbol are: # - Any symbols whose prompts, default values, rev_dep (select - # condition), or ranges depend on the symbol + # condition), weak_rev_dep (imply condition) or ranges depend on the + # symbol # - Any symbols that belong to the same choice statement as the symbol # (these won't be included in 'dep' as that makes the dependency # graph unwieldy, but Symbol._get_dependent() will include them) @@ -1497,6 +1517,7 @@ class Config(object): add_expr_deps(e, sym) add_expr_deps(sym.rev_dep, sym) + add_expr_deps(sym.weak_rev_dep, sym) for l, u, e in sym.ranges: add_expr_deps(l, sym) @@ -1625,20 +1646,16 @@ class Config(object): else: prompts_str_rows = [] for prompt, cond_expr in sc.orig_prompts: - if cond_expr is None: - prompts_str_rows.append(' "{0}"'.format(prompt)) - else: - prompts_str_rows.append( - ' "{0}" if {1}'.format(prompt, - self._expr_val_str(cond_expr))) + prompts_str_rows.append( + ' "{0}"'.format(prompt) if cond_expr is None else + ' "{0}" if {1}'.format(prompt, + self._expr_val_str(cond_expr))) prompts_str = "\n".join(prompts_str_rows) # Build locations string - if not sc.def_locations: - locations_str = "(no locations)" - else: - locations_str = " ".join(["{0}:{1}".format(filename, linenr) for - (filename, linenr) in sc.def_locations]) + locations_str = "(no locations)" if not sc.def_locations else \ + " ".join(["{0}:{1}".format(filename, linenr) for + filename, linenr in sc.def_locations]) # Build additional-dependencies-from-menus-and-ifs string additional_deps_str = " " + \ @@ -1657,13 +1674,11 @@ class Config(object): else: ranges_str_rows = [] for l, u, cond_expr in sc.ranges: - if cond_expr is None: - ranges_str_rows.append(" [{0}, {1}]".format(s(l), - s(u))) - else: - ranges_str_rows.append(" [{0}, {1}] if {2}" - .format(s(l), s(u), - self._expr_val_str(cond_expr))) + ranges_str_rows.append( + " [{0}, {1}]".format(s(l), s(u)) + if cond_expr is None else + " [{0}, {1}] if {2}" + .format(s(l), s(u), self._expr_val_str(cond_expr))) ranges_str = "\n".join(ranges_str_rows) # Build default values string @@ -1685,14 +1700,24 @@ class Config(object): else: selects_str_rows = [] for target, cond_expr in sc.orig_selects: - if cond_expr is None: - selects_str_rows.append(" {0}".format(target.name)) - else: - selects_str_rows.append( - " {0} if {1}".format(target.name, - self._expr_val_str(cond_expr))) + selects_str_rows.append( + " {0}".format(target.name) if cond_expr is None else + " {0} if {1}".format(target.name, + self._expr_val_str(cond_expr))) selects_str = "\n".join(selects_str_rows) + # Build implies string + if not sc.orig_implies: + implies_str = " (no implies)" + else: + implies_str_rows = [] + for target, cond_expr in sc.orig_implies: + implies_str_rows.append( + " {0}".format(target.name) if cond_expr is None else + " {0} if {1}".format(target.name, + self._expr_val_str(cond_expr))) + implies_str = "\n".join(implies_str_rows) + res = _lines("Symbol " + ("(no name)" if sc.name is None else sc.name), "Type : " + TYPENAME[sc.type], @@ -1711,9 +1736,16 @@ class Config(object): defaults_str, "Selects:", selects_str, + "Implies:", + implies_str, "Reverse (select-related) dependencies:", - " (no reverse dependencies)" if sc.rev_dep == "n" - else " " + self._expr_val_str(sc.rev_dep), + " (no reverse dependencies)" + if sc.rev_dep == "n" + else " " + self._expr_val_str(sc.rev_dep), + "Weak reverse (imply-related) dependencies:", + " (no weak reverse dependencies)" + if sc.weak_rev_dep == "n" + else " " + self._expr_val_str(sc.weak_rev_dep), "Additional dependencies from enclosing menus " "and ifs:", additional_deps_str, @@ -1735,11 +1767,10 @@ class Config(object): else: defaults_str_rows = [] for sym, cond_expr in sc.orig_def_exprs: - if cond_expr is None: - defaults_str_rows.append(" {0}".format(sym.name)) - else: - defaults_str_rows.append(" {0} if {1}".format(sym.name, - self._expr_val_str(cond_expr))) + defaults_str_rows.append( + " {0}".format(sym.name) if cond_expr is None else + " {0} if {1}".format(sym.name, + self._expr_val_str(cond_expr))) defaults_str = "\n".join(defaults_str_rows) # Build contained symbols string @@ -1919,26 +1950,25 @@ class Symbol(Item): self.write_to_conf = (mode != "n") if mode == "y": - if choice.get_selection() is self: - new_val = "y" - else: - new_val = "n" + new_val = "y" if choice.get_selection() is self \ + else "n" elif mode == "m": if self.user_val == "m" or self.user_val == "y": new_val = "m" else: # If the symbol is visible and has a user value, use that. - # Otherwise, look at defaults. - use_defaults = True + # Otherwise, look at defaults and weak reverse dependencies + # (implies). + use_defaults_and_weak_rev_deps = True if vis != "n": self.write_to_conf = True if self.user_val is not None: new_val = self.config._eval_min(self.user_val, vis) - use_defaults = False + use_defaults_and_weak_rev_deps = False - if use_defaults: + if use_defaults_and_weak_rev_deps: for val_expr, cond_expr in self.def_exprs: cond_eval = self.config._eval_expr(cond_expr) if cond_eval != "n": @@ -1947,14 +1977,25 @@ class Symbol(Item): cond_eval) break + weak_rev_dep_val = \ + self.config._eval_expr(self.weak_rev_dep) + if weak_rev_dep_val != "n": + self.write_to_conf = True + new_val = self.config._eval_max(new_val, + weak_rev_dep_val) + # Reverse (select-related) dependencies take precedence rev_dep_val = self.config._eval_expr(self.rev_dep) if rev_dep_val != "n": self.write_to_conf = True new_val = self.config._eval_max(new_val, rev_dep_val) - # Promote "m" to "y" for booleans - if new_val == "m" and self.type == BOOL: + # We need to promote "m" to "y" in two circumstances: + # 1) If our type is boolean + # 2) If our weak_rev_dep (from IMPLY) is "y" + if new_val == "m" and \ + (self.type == BOOL or + self.config._eval_expr(self.weak_rev_dep) == "y"): new_val = "y" elif self.type == INT or self.type == HEX: @@ -2189,6 +2230,13 @@ class Symbol(Item): get_referenced_symbols().""" return self.selected_syms + def get_implied_symbols(self): + """Returns the set() of all symbols X for which this symbol has an + 'imply X' or 'imply X if Y' (regardless of whether Y is satisfied or + not). This is a subset of the symbols returned by + get_referenced_symbols().""" + return self.implied_syms + def set_user_value(self, v): """Sets the user value of the symbol. @@ -2304,16 +2352,18 @@ class Symbol(Item): self.ranges = [] # 'range' properties (for int and hex) self.help = None # Help text self.rev_dep = "n" # Reverse (select-related) dependencies + self.weak_rev_dep = "n" # Weak reverse (imply-related) dependencies self.config = None self.parent = None self.user_val = None # Value set by user - # The prompt, default value and select conditions without any + # The prompt, default value, select, and imply conditions without any # dependencies from menus and ifs propagated to them self.orig_prompts = [] self.orig_def_exprs = [] self.orig_selects = [] + self.orig_implies = [] # Dependencies inherited from containing menus and ifs self.deps_from_containing = None @@ -2323,13 +2373,15 @@ class Symbol(Item): # The set of symbols selected by this symbol (see # get_selected_symbols()) self.selected_syms = set() + # The set of symbols implied by this symbol (see get_implied_symbols()) + self.implied_syms = set() # Like 'referenced_syms', but includes symbols from # dependencies inherited from enclosing menus and ifs self.all_referenced_syms = set() - # This records only dependencies specified with 'depends on'. Needed - # when determining actual choice items (hrrrr...). See also - # Choice._determine_actual_symbols(). + # This records only dependencies from enclosing ifs and menus together + # with local 'depends on' dependencies. Needed when determining actual + # choice items (hrrrr...). See Choice._determine_actual_symbols(). self.menu_dep = None # See Symbol.get_ref/def_locations(). @@ -2470,18 +2522,17 @@ class Symbol(Item): return if self.type == BOOL or self.type == TRISTATE: - if val == "y" or val == "m": - append_fn("CONFIG_{0}={1}".format(self.name, val)) - else: - append_fn("# CONFIG_{0} is not set".format(self.name)) + append_fn("{0}{1}={2}".format(self.config.config_prefix, self.name, val) + if val == "y" or val == "m" else + "# {0}{1} is not set".format(self.config.config_prefix, self.name)) elif self.type == INT or self.type == HEX: - append_fn("CONFIG_{0}={1}".format(self.name, val)) + append_fn("{0}{1}={2}".format(self.config.config_prefix, self.name, val)) elif self.type == STRING: # Escape \ and " - append_fn('CONFIG_{0}="{1}"' - .format(self.name, + append_fn('{0}{1}="{2}"' + .format(self.config.config_prefix, self.name, val.replace("\\", "\\\\").replace('"', '\\"'))) else: @@ -2635,7 +2686,7 @@ class Menu(Item): self.title = None self.dep_expr = None self.visible_if_expr = None - self.block = None + self.block = [] # List of contained items self.config = None self.parent = None @@ -2852,7 +2903,7 @@ class Choice(Item): self.prompts = [] self.def_exprs = [] # 'default' properties self.help = None # Help text - self.block = None # List of contained items + self.block = [] # List of contained items self.config = None self.parent = None @@ -3177,7 +3228,13 @@ def _get_visibility(sc): vis = sc.config._eval_max(vis, cond_expr) if isinstance(sc, Symbol) and sc.is_choice_sym: - vis = sc.config._eval_min(vis, _get_visibility(sc.parent)) + if sc.type == TRISTATE and vis == "m" and \ + sc.parent.get_mode() == "y": + # Choice symbols with visibility "m" are not visible if the + # choice has mode "y" + vis = "n" + else: + vis = sc.config._eval_min(vis, _get_visibility(sc.parent)) # Promote "m" to "y" if we're dealing with a non-tristate if vis == "m" and sc.type != TRISTATE: @@ -3434,7 +3491,7 @@ _get_keyword = \ "prompt": T_PROMPT, "default": T_DEFAULT, "bool": T_BOOL, "boolean": T_BOOL, "tristate": T_TRISTATE, "int": T_INT, "hex": T_HEX, "def_bool": T_DEF_BOOL, "def_tristate": T_DEF_TRISTATE, "string": T_STRING, "select": T_SELECT, - "imply": T_IMPLY, "range": T_RANGE, "option": T_OPTION, + "imply" : T_IMPLY, "range": T_RANGE, "option": T_OPTION, "allnoconfig_y": T_ALLNOCONFIG_Y, "env": T_ENV, "defconfig_list": T_DEFCONFIG_LIST, "modules": T_MODULES, "visible": T_VISIBLE}.get @@ -3455,10 +3512,6 @@ _initial_token_re_match = re.compile(r"[^\w]*(\w+)\s*").match # trailing whitespace as an optimization. _id_keyword_re_match = re.compile(r"\s*([\w./-]+)\s*").match -# Regular expressions for parsing .config files -_set_re_match = re.compile(r"CONFIG_(\w+)=(.*)").match -_unset_re_match = re.compile(r"# CONFIG_(\w+) is not set").match - # Regular expression for finding $-references to symbols in strings _sym_ref_re_search = re.compile(r"\$[A-Za-z0-9_]+").search diff --git a/tools/moveconfig.py b/tools/moveconfig.py index 6f549a5..e311646 100755 --- a/tools/moveconfig.py +++ b/tools/moveconfig.py @@ -1472,7 +1472,7 @@ def find_kconfig_rules(kconf, config, imply_config): """ sym = kconf.get_symbol(imply_config) if sym: - for sel in sym.get_selected_symbols(): + for sel in sym.get_selected_symbols() | sym.get_implied_symbols(): if sel.get_name() == config: return sym return None