From 529656e2bea2767de3773d4fa53e4dbc330b91c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Cab=C3=A9?= Date: Mon, 24 Feb 2025 10:17:58 +0100 Subject: [PATCH] devicetree: Add filename and line number tracking for nodes & properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change enhances the devicetree library by adding support for tracking the source filename and line number for nodes and properties. Signed-off-by: Benjamin Cabé --- .../python-devicetree/src/devicetree/dtlib.py | 51 +++++++++++++++-- .../src/devicetree/edtlib.py | 10 ++++ .../dts/python-devicetree/tests/test_dtlib.py | 56 +++++++++++++++++++ 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/scripts/dts/python-devicetree/src/devicetree/dtlib.py b/scripts/dts/python-devicetree/src/devicetree/dtlib.py index c4cd1cdbb2b..98203768cc5 100644 --- a/scripts/dts/python-devicetree/src/devicetree/dtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/dtlib.py @@ -38,6 +38,12 @@ class Node: name: The name of the node (a string). + filename: + The name of the .dts file where the node is defined + + lineno: + The line number in the .dts file where the node starts. + unit_addr: The portion after the '@' in the node's name, or the empty string if the name has no '@' in it. @@ -85,13 +91,15 @@ class Node: # Public interface # - def __init__(self, name: str, parent: Optional['Node'], dt: 'DT'): + def __init__(self, name: str, parent: Optional["Node"], dt: "DT", filename: str, lineno: int): """ Node constructor. Not meant to be called directly by clients. """ # Remember to update DT.__deepcopy__() if you change this. self._name = name + self._filename = filename + self._lineno = lineno self.props: dict[str, Property] = {} self.nodes: dict[str, Node] = {} self.labels: list[str] = [] @@ -118,6 +126,20 @@ class Node: # via DT.move_node. return self._name + @property + def lineno(self) -> int: + """ + See the class documentation. + """ + return self._lineno + + @property + def filename(self) -> str: + """ + See the class documentation. + """ + return self._filename + @property def unit_addr(self) -> str: """ @@ -240,6 +262,12 @@ class Property: name: The name of the property (a string). + filename: + The name of the .dts file where the property is defined + + lineno: + The line number in the .dts file where the property starts. + value: The value of the property, as a 'bytes' string. Numbers are stored in big-endian format, and strings are null-terminated. Putting multiple @@ -308,6 +336,8 @@ class Property: node.dt._parse_error("'@' is only allowed in node names") self.name = name + self.filename = "" + self.lineno = -1 self.value = b"" self.labels: list[str] = [] # We have to wait to set this until later, when we've got @@ -613,7 +643,6 @@ class Property: return s + ";" - def __repr__(self): return (f"") @@ -927,7 +956,7 @@ class DT: # them without any properties. We will recursively initialize # copies of parents before copies of children next. path2node_copy = { - node.path: Node(node.name, None, ret) + node.path: Node(node.name, None, ret, node.filename, node.lineno) for node in self.node_iter() } @@ -956,6 +985,8 @@ class DT: prop_copy.offset_labels = prop.offset_labels.copy() prop_copy._label_offset_lst = prop._label_offset_lst[:] prop_copy._markers = [marker[:] for marker in prop._markers] + prop_copy.filename = prop.filename + prop_copy.lineno = prop.lineno node_copy.props = prop_name2prop_copy node_copy.nodes = { @@ -1083,7 +1114,9 @@ class DT: if tok.val == "/": # '/ { ... };', the root node if not self._root: - self._root = Node(name="/", parent=None, dt=self) + self._root = Node( + name="/", parent=None, dt=self, filename=self.filename, lineno=self._lineno + ) self._parse_node(self.root) elif tok.id in (_T.LABEL, _T.REF): @@ -1146,7 +1179,13 @@ class DT: if child.name in current_child_names: self._parse_error(f'{child.path}: duplicate node name') else: - child = Node(name=tok.val, parent=node, dt=self) + child = Node( + name=tok.val, + parent=node, + dt=self, + filename=self.filename, + lineno=self._lineno, + ) current_child_names.add(tok.val) for label in labels: @@ -1166,6 +1205,8 @@ class DT: "/omit-if-no-ref/ can only be used on nodes") prop = node._get_prop(tok.val) + prop.filename = self.filename + prop.lineno = self._lineno if self._check_token("="): self._parse_assignment(prop) diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index 0238c239065..06535d79ac0 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -1054,6 +1054,16 @@ class Node: "See the class docstring" return self._node.name + @property + def filename(self) -> str: + "See the class docstring" + return self._node.filename + + @property + def lineno(self) -> int: + "See the class docstring" + return self._node.lineno + @property def unit_addr(self) -> Optional[int]: "See the class docstring" diff --git a/scripts/dts/python-devicetree/tests/test_dtlib.py b/scripts/dts/python-devicetree/tests/test_dtlib.py index bf80e85638f..c16e1eae688 100644 --- a/scripts/dts/python-devicetree/tests/test_dtlib.py +++ b/scripts/dts/python-devicetree/tests/test_dtlib.py @@ -2499,3 +2499,59 @@ def test_move_node(): with dtlib_raises("can't move '/newpath' to '/foo/bar': " "parent node '/foo' doesn't exist"): dt.move_node(parent, '/foo/bar') + +def test_filename_and_lineno(): + """Test that filename and lineno are correctly tracked for nodes and properties.""" + + with tempfile.TemporaryDirectory() as tmpdir: + included_file = os.path.join(tmpdir, 'included.dtsi') + with open(included_file, 'w') as f: + f.write('''/* a new node */ +/ { + node1: test-node1 { + prop1A = "value1A"; + }; +}; +''') + + main_file = os.path.join(tmpdir, 'test_with_include.dts') + with open(main_file, 'w') as f: + f.write('''/dts-v1/; + +/include/ "included.dtsi" + +/ { + node2: test-node2 { + prop2A = "value2A"; + prop2B = "value2B"; + }; +}; + +&node1 { + prop1B = "value1B"; +}; +''') + + dt = dtlib.DT(main_file, include_path=[tmpdir]) + + test_node2 = dt.get_node('/test-node2') + prop2A = test_node2.props['prop2A'] + prop2B = test_node2.props['prop2B'] + + assert os.path.samefile(test_node2.filename, main_file) + assert test_node2.lineno == 6 + assert os.path.samefile(prop2A.filename, main_file) + assert prop2A.lineno == 7 + assert os.path.samefile(prop2B.filename, main_file) + assert prop2B.lineno == 8 + + test_node1 = dt.get_node('/test-node1') + prop1A = test_node1.props['prop1A'] + prop1B = test_node1.props['prop1B'] + + assert os.path.samefile(test_node1.filename, included_file) + assert test_node1.lineno == 3 + assert os.path.samefile(prop1A.filename, included_file) + assert prop1A.lineno == 4 + assert os.path.samefile(prop1B.filename, main_file) + assert prop1B.lineno == 13