From dd0e227216e247d2040cdd40bf7397702880cdc4 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Mon, 9 Oct 2017 23:05:00 +0200 Subject: Kconfiglib 2 backup WIP --- tests/Kmisc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'tests/Kmisc') diff --git a/tests/Kmisc b/tests/Kmisc index d2e55b2..c3a21f8 100644 --- a/tests/Kmisc +++ b/tests/Kmisc @@ -1,6 +1,6 @@ # For testing various minor APIs -# is_optional() +# optional choices choice NOT_OPTIONAL bool "not optional" -- cgit v1.2.3 From 6bce225d38433c9b14ff94ff0fb66d1ac87ff4e3 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Wed, 1 Nov 2017 23:53:38 +0100 Subject: Do not invalidate everything when loading .configs In replace mode, only unset symbols that didn't get set. Get rid of _set_value_no_invalidate() and just do a normal set_value() with invalidation. It's speedy with the new invalidation algorithm, and simpler. Not a big performance boost (but a small one), but means that invalidation must be even more rock solid for the test suite to pass (no global invalidations to hide behind), which is nice. Also make y assignments to choice symbol update just the choice user value. Gives nicer behavior when the choice mode is changed. --- kconfiglib.py | 187 +++++++++++++++++++++++++++++++++++----------------------- tests/Kmisc | 4 +- 2 files changed, 115 insertions(+), 76 deletions(-) (limited to 'tests/Kmisc') diff --git a/kconfiglib.py b/kconfiglib.py index 6fe9f88..d5143cf 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -420,6 +420,7 @@ class Kconfig(object): """ __slots__ = ( "_choices", + "_loading_config", "_print_undef_assign", "_print_warnings", "_set_re_match", @@ -583,6 +584,8 @@ class Kconfig(object): # Build Symbol._dependents for all symbols self._build_dep() + self._loading_config = False + @property def mainmenu_text(self): """ @@ -624,12 +627,30 @@ class Kconfig(object): True if all existing user values should be cleared before loading the .config. """ + # Are we currently loading a .config file? This disables a warning. + self._loading_config = True + + # This stub only exists to make sure _loading_config gets unset + try: + self._load_config(filename, replace) + finally: + self._loading_config = False + + def _load_config(self, filename, replace): with self._open(filename) as f: if replace: - # Invalidates all symbols as a side effect - self.unset_values() - else: - self._invalidate_all() + # If we're replacing the configuration, keep track of which + # symbols and choices got set so that we can unset the rest + # later. This avoids invalidating everything and is a tiny bit + # faster in the test suite. The main benefit though is that + # invalidation must be rock solid for it to work, making it a + # good test. + + for sym in self.defined_syms: + sym._was_set = False + + for choice in self._choices: + choice._was_set = False # Small optimizations set_re_match = self._set_re_match @@ -715,7 +736,7 @@ class Kconfig(object): # Done parsing the assignment. Set the value. - if sym.user_value is not None: + if sym._was_set: # Use strings for tristate values in the warning if sym.orig_type in (BOOL, TRISTATE): display_val = TRI_TO_STR[val] @@ -729,7 +750,19 @@ class Kconfig(object): .format(name, display_user_val, display_val), filename, linenr) - sym._set_value_no_invalidate(val, True) + sym.set_value(val) + + if replace: + # If we're replacing the configuration, unset the symbols that + # didn't get set + + for sym in self.defined_syms: + if not sym._was_set: + sym.unset_value() + + for choice in self._choices: + if not choice._was_set: + choice.unset_value() def write_config(self, filename, header="# Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib)\n"): @@ -791,16 +824,11 @@ class Kconfig(object): # set_value() already rejects undefined symbols, and they don't need to # be invalidated (because their value never changes), so we can just # iterate over defined symbols - for sym in self.defined_syms: - # We're iterating over all (defined) symbols, so no need for - # symbols to invalidate their dependent symbols - sym.user_value = None - sym._invalidate() + sym.unset_value() for choice in self._choices: - choice.user_value = choice.user_selection = None - choice._invalidate() + choice.unset_value() def enable_warnings(self): """ @@ -2187,6 +2215,7 @@ class Symbol(object): "_cached_tri_val", "_cached_vis", "_dependents", + "_was_set", "_write_to_conf", "choice", "defaults", @@ -2482,21 +2511,69 @@ class Symbol(object): BOOL) are ignored and won't be stored in Symbol.user_str/tri_value. Kconfiglib will print a warning by default for invalid assignments. """ - self._set_value_no_invalidate(value, False) + if value == self.user_value: + # We know the value must be valid if it was successfully set + # previously + self._was_set = True + return - if self is self.kconfig.modules: - # Changing MODULES has wide-ranging effects - self.kconfig._invalidate_all() + # Check if the value is valid for our type + if not ((self.orig_type == BOOL and value in (0, 2) ) or + (self.orig_type == TRISTATE and value in (0, 1, 2) ) or + (self.orig_type == STRING and isinstance(value, str)) or + (self.orig_type == INT and isinstance(value, str) + and _is_base_n(value, 10) ) or + (self.orig_type == HEX and isinstance(value, str) + and _is_base_n(value, 16))): + + warning = "the value '{}' is invalid for {}, which has type {}" \ + .format(value, self.name, TYPE_TO_STR[self.orig_type]) + + if self.orig_type in (BOOL, TRISTATE) and \ + value in ("n", "m", "y"): + warning += ' (pass 0, 1, 2 for n, m, y, respectively)' + + self.kconfig._warn(warning) + + return + + if not self.nodes: + self.kconfig._warn_undef_assign( + "{} is constant or undefined. '{}' assignment ignored." + .format(self.name, value)) + return + + # Assignments to promptless symbols are expected when loading a .config + if not self.kconfig._loading_config: + for node in self.nodes: + if node.prompt is not None: + break + else: + self.kconfig._warn("{} has no prompt. '{}' assignment ignored." + .format(self.name, value)) + return + + if self.choice is not None and value == 2: + # Remember this as a choice selection only. Makes switching back + # and forth between choice modes work as expected, and makes the + # check for whether the user value is the same as before above + # safe. + self.choice.user_selection = self + self.choice._rec_invalidate() + self.choice._was_set = True else: + self.user_value = value self._rec_invalidate() + self._was_set = True def unset_value(self): """ Resets the user value of the symbol, as if the symbol had never gotten a user value via Kconfig.load_config() or Symbol.set_value(). """ - self.user_value = None - self._rec_invalidate() + if self.user_value is not None: + self.user_value = None + self._rec_invalidate() def __repr__(self): """ @@ -2618,6 +2695,8 @@ class Symbol(object): self.env_var = None self.is_allnoconfig_y = False + self._was_set = False + # Should the symbol get an entry in .config? Calculated along with the # value. self._write_to_conf = False @@ -2665,55 +2744,6 @@ class Symbol(object): return (1,) - def _set_value_no_invalidate(self, value, suppress_prompt_warning): - """ - Like set_value(), but does not invalidate any symbols. - - suppress_prompt_warning: - The warning about assigning a value to a promptless symbol gets - spammy for Linux defconfigs, so turn it off when loading .configs. - It's still helpful when manually invoking set_value(). - """ - # Check if the value is valid for our type - if not ((self.orig_type == BOOL and value in (0, 2) ) or - (self.orig_type == TRISTATE and value in (0, 1, 2) ) or - (self.orig_type == STRING and isinstance(value, str)) or - (self.orig_type == INT and isinstance(value, str) - and _is_base_n(value, 10) ) or - (self.orig_type == HEX and isinstance(value, str) - and _is_base_n(value, 16))): - - warning = "the value '{}' is invalid for {}, which has type {}" \ - .format(value, self.name, TYPE_TO_STR[self.orig_type]) - - if self.orig_type in (BOOL, TRISTATE) and \ - value in ("n", "m", "y"): - warning += ' (pass 0, 1, 2 for n, m, y, respectively)' - - self.kconfig._warn(warning) - - return - - if not self.nodes: - self.kconfig._warn_undef_assign( - 'assigning the value "{}" to the undefined symbol {} will ' - "have no effect".format(value, self.name)) - - if not suppress_prompt_warning: - for node in self.nodes: - if node.prompt is not None: - break - else: - self.kconfig._warn('assigning the value "{}" to the ' - "promptless symbol {} will have no effect" - .format(value, self.name)) - - self.user_value = value - - if self.choice is not None and value == 2: - self.choice.user_selection = self - self.choice._rec_invalidate() - def _invalidate(self): """ Marks the symbol as needing to be recalculated. @@ -2725,10 +2755,10 @@ class Symbol(object): """ Invalidates the symbol and all items that (possibly) depend on it. """ - # Constant symbols must never be invalidated, because they lose their - # value. They never appear as dependencies, but can still be manually - # assigned a user value (and that's OK, though pointless). - if not self.is_constant: + if self is self.kconfig.modules: + # Invalidating MODULES has wide-ranging effects + self.kconfig._invalidate_all() + else: self._invalidate() for item in self._dependents: @@ -2892,6 +2922,7 @@ class Choice(object): "_cached_selection", "_cached_vis", "_dependents", + "_was_set", "defaults", "is_constant", "is_optional", @@ -3015,6 +3046,12 @@ class Choice(object): attribute (is_optional) can never be in n mode, but 0 is still accepted (and ignored) since it's not a malformed value. """ + if value == self.user_value: + # We know the value must be valid if it was successfully set + # previously + self._was_set = True + return + if not ((self.orig_type == BOOL and value in (0, 2) ) or (self.orig_type == TRISTATE and value in (0, 1, 2))): self.kconfig._warn("the value '{}' is invalid for the choice, " @@ -3024,14 +3061,16 @@ class Choice(object): self.user_value = value self._rec_invalidate() + self._was_set = True def unset_value(self): """ Resets the user value (mode) and user selection of the Choice, as if the user had never touched the mode or any of the choice symbols. """ - self.user_value = self.user_selection = None - self._rec_invalidate() + if self.user_value is not None or self.user_selection is not None: + self.user_value = self.user_selection = None + self._rec_invalidate() def __repr__(self): """ diff --git a/tests/Kmisc b/tests/Kmisc index c3a21f8..cea3ca9 100644 --- a/tests/Kmisc +++ b/tests/Kmisc @@ -39,13 +39,13 @@ config BOOL bool "bool" if NOT_DEFINED_1 config TRISTATE - tristate # Visibility should not affect user value + tristate "A" config STRING string "string" config INT - int # Visibility should not affect user value + int "INT" config HEX hex "hex" -- cgit v1.2.3 From 57e5a8eb6d6ff0a393116d5471f720cc1af33f88 Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Thu, 2 Nov 2017 09:41:36 +0100 Subject: Always save user values if they're valid Can just skip the invalidation for promptless symbols. Makes things less magic and more intuitive while still being fast. Means no docs need to be rewritten too. Now the warning gets printed for unset_value() as well. --- kconfiglib.py | 76 +++++++++++++++++++++++++++++++++-------------------------- tests/Kmisc | 4 ++-- testsuite.py | 4 ++-- 3 files changed, 47 insertions(+), 37 deletions(-) (limited to 'tests/Kmisc') diff --git a/kconfiglib.py b/kconfiglib.py index 32a4041..8bb8957 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -420,11 +420,11 @@ class Kconfig(object): """ __slots__ = ( "_choices", - "_loading_config", "_print_undef_assign", "_print_warnings", "_set_re_match", "_unset_re_match", + "_warn_no_prompt", "config_prefix", "const_syms", "defconfig_list", @@ -584,7 +584,7 @@ class Kconfig(object): # Build Symbol._dependents for all symbols self._build_dep() - self._loading_config = False + self._warn_no_prompt = False @property def mainmenu_text(self): @@ -627,14 +627,15 @@ class Kconfig(object): True if all existing user values should be cleared before loading the .config. """ - # Are we currently loading a .config file? This disables a warning. - self._loading_config = True + # Disable the warning about assigning to symbols without prompts. This + # is normal and expected within a .config file. + self._warn_no_prompt = False - # This stub only exists to make sure _loading_config gets unset + # This stub only exists to make sure _warn_no_prompt gets reenabled try: self._load_config(filename, replace) finally: - self._loading_config = False + self._warn_no_prompt = True def _load_config(self, filename, replace): with self._open(filename) as f: @@ -821,14 +822,18 @@ class Kconfig(object): Resets the user values of all symbols, as if Kconfig.load_config() or Symbol.set_value() had never been called. """ - # set_value() already rejects undefined symbols, and they don't need to - # be invalidated (because their value never changes), so we can just - # iterate over defined symbols - for sym in self.defined_syms: - sym.unset_value() + self._warn_no_prompt = False + try: + # set_value() already rejects undefined symbols, and they don't + # need to be invalidated (because their value never changes), so we + # can just iterate over defined symbols + for sym in self.defined_syms: + sym.unset_value() - for choice in self._choices: - choice.unset_value() + for choice in self._choices: + choice.unset_value() + finally: + self._warn_no_prompt = True def enable_warnings(self): """ @@ -2537,35 +2542,20 @@ class Symbol(object): return - if not self.nodes: - self.kconfig._warn_undef_assign( - "{} is constant or undefined. '{}' assignment ignored." - .format(self.name, value)) - return - - for node in self.nodes: - if node.prompt is not None: - break - else: - # Assignments to promptless symbols are expected when loading a - # .config - if not self.kconfig._loading_config: - self.kconfig._warn("{} has no prompt. '{}' assignment ignored." - .format(self.name, value)) - return - if self.choice is not None and value == 2: # Remember this as a choice selection only. Makes switching back # and forth between choice modes work as expected, and makes the # check for whether the user value is the same as before above # safe. self.choice.user_selection = self - self.choice._rec_invalidate() self.choice._was_set = True + if self._is_user_assignable(): + self.choice._rec_invalidate() else: self.user_value = value - self._rec_invalidate() self._was_set = True + if self._is_user_assignable(): + self._rec_invalidate() def unset_value(self): """ @@ -2574,7 +2564,8 @@ class Symbol(object): """ if self.user_value is not None: self.user_value = None - self._rec_invalidate() + if self._is_user_assignable(): + self._rec_invalidate() def __repr__(self): """ @@ -2745,6 +2736,25 @@ class Symbol(object): return (1,) + def _is_user_assignable(self): + """ + Returns True if the symbol has a prompt, meaning a user value might + have an effect on it. Used as an optimization to skip invalidation when + promptless symbols are assigned to (given a user value). + + Prints a warning if the symbol has no prompt. In some contexts (e.g. + when loading a .config files) assignments to promptless symbols are + normal and expected, so the warning can be disabled. + """ + for node in self.nodes: + if node.prompt is not None: + return True + + if self.kconfig._warn_no_prompt: + self.kconfig._warn(self.name + " has no prompt, meaning user " + "values have no effect on it") + return False + def _invalidate(self): """ Marks the symbol as needing to be recalculated. diff --git a/tests/Kmisc b/tests/Kmisc index cea3ca9..c3a21f8 100644 --- a/tests/Kmisc +++ b/tests/Kmisc @@ -39,13 +39,13 @@ config BOOL bool "bool" if NOT_DEFINED_1 config TRISTATE - tristate "A" + tristate # Visibility should not affect user value config STRING string "string" config INT - int "INT" + int # Visibility should not affect user value config HEX hex "hex" diff --git a/testsuite.py b/testsuite.py index 4cfc245..586899a 100644 --- a/testsuite.py +++ b/testsuite.py @@ -1933,11 +1933,11 @@ def test_sanity(conf, arch): conf.disable_warnings() sym.set_value(2) sym.set_value("foo") + sym.unset_value() conf.enable_warnings() sym.str_value sym.tri_value sym.type - sym.unset_value() sym.user_value sym.visibility @@ -1971,11 +1971,11 @@ def test_sanity(conf, arch): conf.disable_warnings() sym.set_value(2) sym.set_value("foo") + sym.unset_value() conf.enable_warnings() sym.str_value sym.tri_value sym.type - sym.unset_value() sym.visibility # Cheat with internals -- cgit v1.2.3 From 852eae7d41abed9051f72d0ba72bfb854cfb6fcb Mon Sep 17 00:00:00 2001 From: Ulf Magnusson Date: Thu, 2 Nov 2017 20:36:41 +0100 Subject: Clean up .kconfig tests Kinda silly to test if they're separate since a long time, but can do that easily too with some reorganization. --- tests/Kmisc | 3 +++ testsuite.py | 33 ++++++++++++--------------------- 2 files changed, 15 insertions(+), 21 deletions(-) (limited to 'tests/Kmisc') diff --git a/tests/Kmisc b/tests/Kmisc index c3a21f8..44fdfdc 100644 --- a/tests/Kmisc +++ b/tests/Kmisc @@ -51,7 +51,10 @@ config HEX hex "hex" depends on NOT_DEFINED_2 +config COMMENT_HOOK comment "comment" + +config MENU_HOOK menu "menu" depends on NOT_DEFINED_3 || NOT_DEFINED_2 depends on !NOT_DEFINED_4 diff --git a/testsuite.py b/testsuite.py index 156a640..3afa66a 100644 --- a/testsuite.py +++ b/testsuite.py @@ -1261,27 +1261,18 @@ g verify_value("IGNOREME", "y") - print("Testing Kconfig separation") - - c1 = Kconfig("Kconfiglib/tests/Kmisc", warn=False) - c2 = Kconfig("Kconfiglib/tests/Kmisc", warn=False) - - c1_undef, c1_bool, c1_choice, c1_menu, c1_comment = c1.syms["BOOL"], \ - c1.syms["NOT_DEFINED_1"], get_choices(c1)[0], get_menus(c1)[0], \ - get_comments(c1)[0] - c2_undef, c2_bool, c2_choice, c2_menu, c2_comment = c2.syms["BOOL"], \ - c2.syms["NOT_DEFINED_1"], get_choices(c2)[0], get_menus(c2)[0], \ - get_comments(c2)[0] - - verify((c1_undef is not c2_undef) and (c1_bool is not c2_bool) and - (c1_choice is not c2_choice) and (c1_menu is not c2_menu) and - (c1_comment is not c2_comment) and - (c1_undef.kconfig is c1) and (c2_undef.kconfig is c2) and - (c1_bool.kconfig is c1) and (c2_bool.kconfig is c2) and - (c1_choice.kconfig is c1) and (c2_choice.kconfig is c2) and - (c1_menu.kconfig is c1) and (c2_menu.kconfig is c2) and - (c1_comment.kconfig is c1) and (c2_comment.kconfig is c2), - "Config instance state separation or .config is broken") + print("Testing Kconfig fetching and separation") + + for c in Kconfig("Kconfiglib/tests/Kmisc", warn=False), \ + Kconfig("Kconfiglib/tests/Kmisc", warn=False): + for item in c.syms["BOOL"], \ + c.syms["BOOL"].nodes[0], \ + c.named_choices["OPTIONAL"], \ + c.named_choices["OPTIONAL"].nodes[0], \ + c.syms["MENU_HOOK"].nodes[0].next, \ + c.syms["COMMENT_HOOK"].nodes[0].next: + verify(item.kconfig is c, + ".kconfig not properly set for " + repr(item)) print("Testing imply semantics") -- cgit v1.2.3