diff options
| author | Ulf Magnusson <ulfalizer@gmail.com> | 2018-08-23 02:43:00 +0200 |
|---|---|---|
| committer | Ulf Magnusson <ulfalizer@gmail.com> | 2018-08-24 01:01:36 +0200 |
| commit | 13884e934ec8eda928234c6506ae27f0334ec31b (patch) | |
| tree | 0f42365dad68240c5ebe0d81f7a7327affd1bf20 | |
| parent | 4b8805df373abebdf8e940b9511b2602640ff518 (diff) | |
Show include paths in menuconfig symbol information
Add a MenuNode.include_path attribute that holds a tuple of
(filename, linenr) tuples, giving the locations of the 'source'
statements via which the node's Kconfig file was included, starting from
the top-level Kconfig file.
Use MenuNode.include_path to give the include path for symbols and other
items in the help display in the menuconfig interface. This is useful
for figuring out how Kconfig files are organized, and when reorganizing
things.
| -rw-r--r-- | kconfiglib.py | 60 | ||||
| -rwxr-xr-x | menuconfig.py | 25 | ||||
| -rw-r--r-- | tests/Kinclude_path | 12 | ||||
| -rw-r--r-- | tests/Kinclude_path_sourced_1 | 12 | ||||
| -rw-r--r-- | tests/Kinclude_path_sourced_2 | 11 | ||||
| -rw-r--r-- | testsuite.py | 46 |
6 files changed, 145 insertions, 21 deletions
diff --git a/kconfiglib.py b/kconfiglib.py index a16a215..3d46630 100644 --- a/kconfiglib.py +++ b/kconfiglib.py @@ -590,6 +590,7 @@ class Kconfig(object): "_file", "_filename", "_linenr", + "_include_path", "_filestack", "_line", "_saved_line", @@ -752,6 +753,7 @@ class Kconfig(object): self.top_node.dep = self.y self.top_node.filename = filename self.top_node.linenr = 1 + self.top_node.include_path = () # Parse the Kconfig files @@ -760,8 +762,9 @@ class Kconfig(object): self._has_tokens = False # Keeps track of the location in the parent Kconfig files. Kconfig - # files usually source other Kconfig files. + # files usually source other Kconfig files. See _enter_file(). self._filestack = [] + self._include_path = () # The current parsing location self._filename = filename @@ -1542,19 +1545,35 @@ class Kconfig(object): # self._filename (which makes it indirectly show up in # MenuNode.filename). Equals full_filename for absolute paths. - self._filestack.append((self._filename, self._linenr, self._file)) + # The parent Kconfig files are represented as a list of + # (<include path>, <Python 'file' object for Kconfig file>) tuples. + # + # <include path> is immutable and holds a *tuple* of + # (<filename>, <linenr>) tuples, giving the locations of the 'source' + # statements in the parent Kconfig files. The current include path is + # also available in Kconfig._include_path. + # + # The point of this redundant setup is to allow Kconfig._include_path + # to be assigned directly to MenuNode.include_path without having to + # copy it, sharing it wherever possible. + + # Save include path and 'file' object before entering the file + self._filestack.append((self._include_path, self._file)) + + # _include_path is a tuple, so this rebinds the variable instead of + # doing in-place modification + self._include_path += ((self._filename, self._linenr),) # Check for recursive 'source' - for name, _, _ in self._filestack: + for name, _ in self._include_path: if name == rel_filename: raise KconfigError( "\n{}:{}: Recursive 'source' of '{}' detected. Check that " "environment variables are set correctly.\n" - "Backtrace:\n{}" + "Include path:\n{}" .format(self._filename, self._linenr, rel_filename, "\n".join("{}:{}".format(name, linenr) - for name, linenr, _ - in reversed(self._filestack)))) + for name, linenr in self._include_path))) # Note: We already know that the file exists @@ -1569,10 +1588,14 @@ class Kconfig(object): self._linenr = 0 def _leave_file(self): - # Returns from a Kconfig file to the file that sourced it + # Returns from a Kconfig file to the file that sourced it. See + # _enter_file(). self._file.close() - self._filename, self._linenr, self._file = self._filestack.pop() + # Restore location from parent Kconfig file + self._filename, self._linenr = self._include_path[-1] + # Restore include path and 'file' object + self._include_path, self._file = self._filestack.pop() def _next_line(self): # Fetches and tokenizes the next line from the current Kconfig file. @@ -2221,6 +2244,7 @@ class Kconfig(object): node.parent = parent node.filename = self._filename node.linenr = self._linenr + node.include_path = self._include_path sym.nodes.append(node) @@ -2304,6 +2328,7 @@ class Kconfig(object): node.parent = parent node.filename = self._filename node.linenr = self._linenr + node.include_path = self._include_path self.menus.append(node) @@ -2323,6 +2348,7 @@ class Kconfig(object): node.parent = parent node.filename = self._filename node.linenr = self._linenr + node.include_path = self._include_path self.comments.append(node) @@ -2358,6 +2384,7 @@ class Kconfig(object): node.parent = parent node.filename = self._filename node.linenr = self._linenr + node.include_path = self._include_path choice.nodes.append(node) @@ -3112,11 +3139,12 @@ class Kconfig(object): # Hint printed when Kconfig files can't be found or .config files can't # be opened - return ". Perhaps the $srctree environment variable (set to '{}') " \ + return ". Perhaps the $srctree environment variable ({}) " \ "is set incorrectly. Note that the current value of $srctree " \ "is saved when the Kconfig instance is created (for " \ "consistency and to cleanly separate instances)." \ - .format(self.srctree if self.srctree else "unset or blank") + .format("set to '{}'".format(self.srctree) if self.srctree + else "unset or blank") class Symbol(object): """ @@ -3868,7 +3896,7 @@ class Symbol(object): self._dependents = set() # Used during dependency loop detection and (independently) in - # write_config() + # node_iter() self._visited = 0 def _assignable(self): @@ -4615,6 +4643,15 @@ class MenuNode(object): $srctree (or to the current directory if $srctree isn't set), except absolute paths passed to 'source' and Kconfig.__init__() are preserved. + include_path: + A tuple of (filename, linenr) tuples, giving the locations of the + 'source' statements via which the Kconfig file containing this menu node + was included. The first element is the location of the 'source' statement + in the top-level Kconfig file passed to Kconfig.__init__(), etc. + + Note that the Kconfig file of the menu node itself isn't included. Check + 'filename' and 'linenr' for that. + kconfig: The Kconfig instance the menu node is from. """ @@ -4622,6 +4659,7 @@ class MenuNode(object): "dep", "filename", "help", + "include_path", "is_menuconfig", "item", "kconfig", diff --git a/menuconfig.py b/menuconfig.py index 8f5dbe0..8c575b5 100755 --- a/menuconfig.py +++ b/menuconfig.py @@ -2075,22 +2075,31 @@ def _select_imply_info(sym): def _kconfig_def_info(item): # Returns a string with the definition of 'item' in Kconfig syntax, - # together with the definition location(s) + # together with the definition location(s) and their include and menu paths nodes = [item] if isinstance(item, MenuNode) else item.nodes s = "Kconfig definition{}, with propagated dependencies\n" \ .format("s" if len(nodes) > 1 else "") - s += (len(s) - 1)*"=" + "\n\n" - - s += "\n\n".join("At {}:{}, in menu {}:\n\n{}".format( - node.filename, node.linenr, _menu_path_info(node), - textwrap.indent(node.custom_str(_name_and_val_str), - " ")) - for node in nodes) + s += (len(s) - 1)*"=" + + for node in nodes: + s += "\n\n" \ + "At {}:{}\n" \ + "Included via {}\n" \ + "Menu path: {}\n\n" \ + "{}" \ + .format(node.filename, node.linenr, + _include_path_info(node), + _menu_path_info(node), + textwrap.indent(node.custom_str(_name_and_val_str), " ")) return s +def _include_path_info(node): + return " -> ".join("{}:{}".format(filename, linenr) + for filename, linenr in node.include_path) + def _menu_path_info(node): # Returns a string describing the menu path leading up to 'node' diff --git a/tests/Kinclude_path b/tests/Kinclude_path new file mode 100644 index 0000000..7a3badb --- /dev/null +++ b/tests/Kinclude_path @@ -0,0 +1,12 @@ +config TOP + bool + +source "Kinclude_path_sourced_1" + +config TOP + bool + +source "Kinclude_path_sourced_1" + +config TOP + bool diff --git a/tests/Kinclude_path_sourced_1 b/tests/Kinclude_path_sourced_1 new file mode 100644 index 0000000..f4dee98 --- /dev/null +++ b/tests/Kinclude_path_sourced_1 @@ -0,0 +1,12 @@ +config ONE_DOWN + bool + +source "Kinclude_path_sourced_2" + +config ONE_DOWN + bool + +source "Kinclude_path_sourced_2" + +config ONE_DOWN + bool diff --git a/tests/Kinclude_path_sourced_2 b/tests/Kinclude_path_sourced_2 new file mode 100644 index 0000000..068f18d --- /dev/null +++ b/tests/Kinclude_path_sourced_2 @@ -0,0 +1,11 @@ +config TWO_DOWN + bool + +menu "menu" +endmenu + +comment "comment" + +choice + bool "choice" +endchoice diff --git a/testsuite.py b/testsuite.py index a610c4b..1c49209 100644 --- a/testsuite.py +++ b/testsuite.py @@ -1041,9 +1041,9 @@ g except KconfigError as e: verify_equal(str(e), """ tests/Krecursive2:1: Recursive 'source' of 'tests/Krecursive1' detected. Check that environment variables are set correctly. -Backtrace: -tests/Krecursive2:1 +Include path: tests/Krecursive1:1 +tests/Krecursive2:1 """[:-1]) except: fail("recursive 'source' raised wrong exception") @@ -1108,6 +1108,48 @@ tests/Krecursive1:1 os.environ.pop("srctree", None) + print("Testing MenuNode.include_path") + + os.environ["srctree"] = "Kconfiglib/tests" + + c = Kconfig("Kinclude_path") + + def verify_node_path(node, *expected): + if node.include_path != expected: + fail("Wrong include path for node {!r}. Got {}, expected {}." + .format(node, node.include_path, expected)) + + def verify_sym_path(sym_name, node_i, *expected): + verify_node_path(c.syms[sym_name].nodes[node_i], *expected) + + verify_sym_path("TOP", 0) + verify_sym_path("TOP", 1) + verify_sym_path("TOP", 2) + + verify_sym_path("ONE_DOWN", 0, ("Kinclude_path", 4)) + verify_sym_path("ONE_DOWN", 1, ("Kinclude_path", 4)) + verify_sym_path("ONE_DOWN", 2, ("Kinclude_path", 4)) + verify_sym_path("ONE_DOWN", 3, ("Kinclude_path", 9)) + verify_sym_path("ONE_DOWN", 4, ("Kinclude_path", 9)) + verify_sym_path("ONE_DOWN", 5, ("Kinclude_path", 9)) + + verify_sym_path("TWO_DOWN", 0, + ('Kinclude_path', 4), ('Kinclude_path_sourced_1', 4)) + verify_sym_path("TWO_DOWN", 1, + ('Kinclude_path', 4), ('Kinclude_path_sourced_1', 9)) + verify_sym_path("TWO_DOWN", 2, + ('Kinclude_path', 9), ('Kinclude_path_sourced_1', 4)) + verify_sym_path("TWO_DOWN", 3, + ('Kinclude_path', 9), ('Kinclude_path_sourced_1', 9)) + + verify_node_path(c.top_node) + verify_node_path(c.menus[0], ('Kinclude_path', 4), ('Kinclude_path_sourced_1', 4)) + verify_node_path(c.comments[0], ('Kinclude_path', 4), ('Kinclude_path_sourced_1', 4)) + verify_node_path(c.choices[0].nodes[0], ('Kinclude_path', 4), ('Kinclude_path_sourced_1', 4)) + + os.environ.pop("srctree", None) + + print("Testing Kconfig.choices/menus/comments") c = Kconfig("Kconfiglib/tests/Kitemlists") |
