diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index a6de3afcb2d..02c9b331138 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -199,6 +199,8 @@ class EDT: # All instance attributes should be initialized here. # This makes it easy to keep track of them, which makes # implementing __deepcopy__() easier. + # If you change this, make sure to update __deepcopy__() too, + # and update the tests for that method. # Public attributes (the rest are properties) self.nodes = [] @@ -209,7 +211,7 @@ class EDT: self.label2node = {} self.dep_ord2node = {} self.dts_path = dts - self.bindings_dirs = bindings_dirs + self.bindings_dirs = list(bindings_dirs) # Saved kwarg values for internal use self._warn_reg_unit_address_mismatch = warn_reg_unit_address_mismatch @@ -227,10 +229,16 @@ class EDT: for path in self._binding_paths} self._node2enode = {} # Maps dtlib.Node to edtlib.Node - try: - self._dt = DT(dts) - except DTError as e: - raise EDTError(e) from e + if dts is not None: + try: + self._dt = DT(dts) + except DTError as e: + raise EDTError(e) from e + self._finish_init() + + def _finish_init(self): + # This helper exists to make the __deepcopy__() implementation + # easier to keep in sync with __init__(). _check_dt(self._dt) self._init_compat2binding() @@ -285,6 +293,27 @@ class EDT: return f"" + def __deepcopy__(self, memo): + """ + Implements support for the standard library copy.deepcopy() + function on EDT instances. + """ + + ret = EDT( + None, + self.bindings_dirs, + warn_reg_unit_address_mismatch=self._warn_reg_unit_address_mismatch, + default_prop_types=self._default_prop_types, + support_fixed_partitions_on_any_bus=self._fixed_partitions_no_bus, + infer_binding_for_paths=set(self._infer_binding_for_paths), + vendor_prefixes=dict(self._vendor_prefixes), + werror=self._werror + ) + ret.dts_path = self.dts_path + ret._dt = deepcopy(self._dt, memo) + ret._finish_init() + return ret + @property def scc_order(self): try: diff --git a/scripts/dts/python-devicetree/tests/test_edtlib.py b/scripts/dts/python-devicetree/tests/test_edtlib.py index 0653eef78ba..b1defe0c327 100644 --- a/scripts/dts/python-devicetree/tests/test_edtlib.py +++ b/scripts/dts/python-devicetree/tests/test_edtlib.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause import contextlib +from copy import deepcopy import io from logging import WARNING import os @@ -627,6 +628,80 @@ def test_wrong_props(): assert value_str.endswith("but no 'specifier-space' was provided.") +def test_deepcopy(): + with from_here(): + # We intentionally use different kwarg values than the + # defaults to make sure they're getting copied. This implies + # we have to set werror=True, so we can't use test.dts, since + # that generates warnings on purpose. + edt = edtlib.EDT("test-multidir.dts", + ["test-bindings", "test-bindings-2"], + warn_reg_unit_address_mismatch=False, + default_prop_types=False, + support_fixed_partitions_on_any_bus=False, + infer_binding_for_paths=['/test-node'], + vendor_prefixes={'test-vnd': 'A test vendor'}, + werror=True) + edt_copy = deepcopy(edt) + + def equal_paths(list1, list2): + assert len(list1) == len(list2) + return all(elt1.path == elt2.path for elt1, elt2 in zip(list1, list2)) + + def equal_key2path(key2node1, key2node2): + assert len(key2node1) == len(key2node2) + return (all(key1 == key2 for (key1, key2) in + zip(key2node1, key2node2)) and + all(node1.path == node2.path for (node1, node2) in + zip(key2node1.values(), key2node2.values()))) + + def equal_key2paths(key2nodes1, key2nodes2): + assert len(key2nodes1) == len(key2nodes2) + return (all(key1 == key2 for (key1, key2) in + zip(key2nodes1, key2nodes2)) and + all(equal_paths(nodes1, nodes2) for (nodes1, nodes2) in + zip(key2nodes1.values(), key2nodes2.values()))) + + def test_equal_but_not_same(attribute, equal=None): + if equal is None: + equal = lambda a, b: a == b + copy = getattr(edt_copy, attribute) + original = getattr(edt, attribute) + assert equal(copy, original) + assert copy is not original + + test_equal_but_not_same("nodes", equal_paths) + test_equal_but_not_same("compat2nodes", equal_key2paths) + test_equal_but_not_same("compat2okay", equal_key2paths) + test_equal_but_not_same("compat2vendor") + test_equal_but_not_same("compat2model") + test_equal_but_not_same("label2node", equal_key2path) + test_equal_but_not_same("dep_ord2node", equal_key2path) + assert edt_copy.dts_path == "test-multidir.dts" + assert edt_copy.bindings_dirs == ["test-bindings", "test-bindings-2"] + assert edt_copy.bindings_dirs is not edt.bindings_dirs + assert not edt_copy._warn_reg_unit_address_mismatch + assert not edt_copy._default_prop_types + assert not edt_copy._fixed_partitions_no_bus + assert edt_copy._infer_binding_for_paths == set(["/test-node"]) + assert edt_copy._infer_binding_for_paths is not edt._infer_binding_for_paths + assert edt_copy._vendor_prefixes == {"test-vnd": "A test vendor"} + assert edt_copy._vendor_prefixes is not edt._vendor_prefixes + assert edt_copy._werror + test_equal_but_not_same("_compat2binding", equal_key2path) + test_equal_but_not_same("_binding_paths") + test_equal_but_not_same("_binding_fname2path") + assert len(edt_copy._node2enode) == len(edt._node2enode) + for node1, node2 in zip(edt_copy._node2enode, edt._node2enode): + enode1 = edt_copy._node2enode[node1] + enode2 = edt._node2enode[node2] + assert node1.path == node2.path + assert enode1.path == enode2.path + assert node1 is not node2 + assert enode1 is not enode2 + assert edt_copy._dt is not edt._dt + + def verify_error(dts, dts_file, expected_err): # Verifies that parsing a file 'dts_file' with the contents 'dts' # (a string) raises an EDTError with the message 'expected_err'.