dts: marshal the EDT object for later use

We need to save and restore the devicetree data to generate optimized
dependency information later on in the build, in particular during the
final application link.

Make this happen by pickling the EDT object in BUILD_DIR/edt.pickle.

The existence of this file is an implementation detail, so do not add
it to the documentation.

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
diff --git a/cmake/dts.cmake b/cmake/dts.cmake
index a2203d5..81f7d4f 100644
--- a/cmake/dts.cmake
+++ b/cmake/dts.cmake
@@ -17,6 +17,13 @@
 # See the Devicetree user guide in the Zephyr documentation for details.
 set(GEN_DEFINES_SCRIPT          ${ZEPHYR_BASE}/scripts/dts/gen_defines.py)
 set(ZEPHYR_DTS                  ${PROJECT_BINARY_DIR}/zephyr.dts)
+# This contains the edtlib.EDT object created from zephyr.dts in Python's
+# pickle data marshalling format (https://docs.python.org/3/library/pickle.html)
+#
+# Its existence is an implementation detail used to speed up further
+# use of the devicetree by processes that run later on in the build,
+# and should not be made part of the documentation.
+set(EDT_PICKLE                  ${PROJECT_BINARY_DIR}/edt.pickle)
 set(DEVICETREE_UNFIXED_H        ${PROJECT_BINARY_DIR}/include/generated/devicetree_unfixed.h)
 set(DEVICETREE_UNFIXED_LEGACY_H ${PROJECT_BINARY_DIR}/include/generated/devicetree_legacy_unfixed.h)
 set(DTS_POST_CPP                ${PROJECT_BINARY_DIR}/${BOARD}.dts.pre.tmp)
@@ -206,6 +213,7 @@
   --bindings-dirs ${DTS_ROOT_BINDINGS}
   --header-out ${DEVICETREE_UNFIXED_H}
   --dts-out ${ZEPHYR_DTS} # As a debugging aid
+  --edt-pickle-out ${EDT_PICKLE}
   )
 
   #
diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py
index 94f385a..be725e4 100755
--- a/scripts/dts/gen_defines.py
+++ b/scripts/dts/gen_defines.py
@@ -22,6 +22,7 @@
 from collections import defaultdict
 import os
 import pathlib
+import pickle
 import re
 import sys
 
@@ -67,6 +68,7 @@
         edt.compat2nodes[compat] = sorted(
             nodes, key=lambda node: 0 if node.status == "okay" else 1)
 
+    # Create the generated header.
     with open(args.header_out, "w", encoding="utf-8") as header_file:
         write_top_comment(edt)
 
@@ -92,6 +94,9 @@
         write_chosen(edt)
         write_global_compat_info(edt)
 
+    if args.edt_pickle_out:
+        write_pickled_edt(edt, args.edt_pickle_out)
+
 
 def node_z_path_id(node):
     # Return the node specific bit of the node's path identifier:
@@ -127,6 +132,8 @@
     parser.add_argument("--dts-out", required=True,
                         help="path to write merged DTS source code to (e.g. "
                              "as a debugging aid)")
+    parser.add_argument("--edt-pickle-out",
+                        help="path to write pickled edtlib.EDT object to")
 
     return parser.parse_args()
 
@@ -713,6 +720,21 @@
     return f'"{escape(s)}"'
 
 
+def write_pickled_edt(edt, out_file):
+    # Writes the edt object in pickle format to out_file.
+
+    with open(out_file, 'wb') as f:
+        # Pickle protocol version 4 is the default as of Python 3.8
+        # and was introduced in 3.4, so it is both available and
+        # recommended on all versions of Python that Zephyr supports
+        # (at time of writing, Python 3.6 was Zephyr's minimum
+        # version, and 3.8 the most recent CPython release).
+        #
+        # Using a common protocol version here will hopefully avoid
+        # reproducibility issues in different Python installations.
+        pickle.dump(edt, f, protocol=4)
+
+
 def err(s):
     raise Exception(s)