devicetree: Add filename and line number tracking for nodes & properties

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é <benjamin@zephyrproject.org>
This commit is contained in:
Benjamin Cabé 2025-02-24 10:17:58 +01:00 committed by Fabio Baltieri
parent 8081008a08
commit 529656e2be
3 changed files with 112 additions and 5 deletions

View File

@ -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"<Property '{self.name}' at '{self.node.path}' in "
f"'{self.node.dt.filename}'>")
@ -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)

View File

@ -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"

View File

@ -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