[PATCH 05/49] dtoc: Allow outputing to multiple files

Simon Glass sjg at chromium.org
Tue Dec 29 04:34:51 CET 2020


Implement the 'output directory' feature, allowing dtoc to write the
output files separately to the supplied directories. This allows us to
handle the struct and platdata output in one run of dtoc.

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 tools/dtoc/dtb_platdata.py | 96 ++++++++++++++++++++++++++++++++++----
 tools/dtoc/test_dtoc.py    |  8 ++++
 2 files changed, 94 insertions(+), 10 deletions(-)

diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py
index 62a65d6214a..e2fddfd3018 100644
--- a/tools/dtoc/dtb_platdata.py
+++ b/tools/dtoc/dtb_platdata.py
@@ -15,6 +15,7 @@ See doc/driver-model/of-plat.rst for more informaiton
 
 import collections
 import copy
+from enum import IntEnum
 import os
 import re
 import sys
@@ -49,6 +50,15 @@ TYPE_NAMES = {
 STRUCT_PREFIX = 'dtd_'
 VAL_PREFIX = 'dtv_'
 
+class Ftype(IntEnum):
+    SOURCE, HEADER = range(2)
+
+
+# This holds information about each type of output file dtoc can create
+# type: Type of file (Ftype)
+# fname: Filename excluding directory, e.g. 'dt-platdata.c'
+OutputFile = collections.namedtuple('OutputFile', ['ftype', 'fname'])
+
 # This holds information about a property which includes phandles.
 #
 # max_args: integer: Maximum number or arguments that any phandle uses (int).
@@ -180,6 +190,8 @@ class DtbPlatdata():
                 U_BOOT_DRIVER_ALIAS(driver_alias, driver_name)
             value: Driver name declared with U_BOOT_DRIVER(driver_name)
         _drivers_additional: List of additional drivers to use during scanning
+        _dirname: Directory to hold output files, or None for none (all files
+            go to stdout)
     """
     def __init__(self, dtb_fname, include_disabled, warning_disabled,
                  drivers_additional=None):
@@ -193,6 +205,7 @@ class DtbPlatdata():
         self._drivers = {}
         self._driver_aliases = {}
         self._drivers_additional = drivers_additional or []
+        self._dirnames = [None] * len(Ftype)
 
     def get_normalized_compat_name(self, node):
         """Get a node's normalized compat name
@@ -230,20 +243,68 @@ class DtbPlatdata():
 
         return compat_list_c[0], compat_list_c[1:]
 
-    def setup_output(self, fname):
+    def setup_output_dirs(self, output_dirs):
+        """Set up the output directories
+
+        This should be done before setup_output() is called
+
+        Args:
+            output_dirs (tuple of str):
+                Directory to use for C output files.
+                    Use None to write files relative current directory
+                Directory to use for H output files.
+                    Defaults to the C output dir
+        """
+        def process_dir(ftype, dirname):
+            if dirname:
+                os.makedirs(dirname, exist_ok=True)
+                self._dirnames[ftype] = dirname
+
+        if output_dirs:
+            c_dirname = output_dirs[0]
+            h_dirname = output_dirs[1] if len(output_dirs) > 1 else c_dirname
+            process_dir(Ftype.SOURCE, c_dirname)
+            process_dir(Ftype.HEADER, h_dirname)
+
+    def setup_output(self, ftype, fname):
         """Set up the output destination
 
         Once this is done, future calls to self.out() will output to this
-        file.
+        file. The file used is as follows:
+
+        self._dirnames[ftype] is None: output to fname, or stdout if None
+        self._dirnames[ftype] is not None: output to fname in that directory
+
+        Calling this function multiple times will close the old file and open
+        the new one. If they are the same file, nothing happens and output will
+        continue to the same file.
 
         Args:
-            fname (str): Filename to send output to, or None for stdout
+            ftype (str): Type of file to create ('c' or 'h')
+            fname (str): Filename to send output to. If there is a directory in
+                self._dirnames for this file type, it will be put in that
+                directory
         """
-        if fname:
-            self._outfile = open(fname, 'w')
+        dirname = self._dirnames[ftype]
+        if dirname:
+            pathname = os.path.join(dirname, fname)
+            if self._outfile:
+                self._outfile.close()
+            self._outfile = open(pathname, 'w')
+        elif fname:
+            if not self._outfile:
+                self._outfile = open(fname, 'w')
         else:
             self._outfile = sys.stdout
 
+    def finish_output(self):
+        """Finish outputing to a file
+
+        This closes the output file, if one is in use
+        """
+        if self._outfile != sys.stdout:
+            self._outfile.close()
+
     def out(self, line):
         """Output a string to the output file
 
@@ -758,6 +819,15 @@ class DtbPlatdata():
         self.out(''.join(self.get_buf()))
 
 
+# Types of output file we understand
+# key: Command used to generate this file
+# value: OutputFile for this command
+OUTPUT_FILES = {
+    'struct': OutputFile(Ftype.HEADER, 'dt-structs-gen.h'),
+    'platdata': OutputFile(Ftype.SOURCE, 'dt-platdata.c'),
+    }
+
+
 def run_steps(args, dtb_file, include_disabled, output, output_dirs,
               warning_disabled=False, drivers_additional=None):
     """Run all the steps of the dtoc tool
@@ -778,7 +848,9 @@ def run_steps(args, dtb_file, include_disabled, output, output_dirs,
         ValueError: if args has no command, or an unknown command
     """
     if not args:
-        raise ValueError('Please specify a command: struct, platdata')
+        raise ValueError('Please specify a command: struct, platdata, all')
+    if output and output_dirs and any(output_dirs):
+        raise ValueError('Must specify either output or output_dirs, not both')
 
     plat = DtbPlatdata(dtb_file, include_disabled, warning_disabled,
                        drivers_additional)
@@ -786,15 +858,19 @@ def run_steps(args, dtb_file, include_disabled, output, output_dirs,
     plat.scan_dtb()
     plat.scan_tree()
     plat.scan_reg_sizes()
-    plat.setup_output(output)
+    plat.setup_output_dirs(output_dirs)
     structs = plat.scan_structs()
     plat.scan_phandles()
 
     for cmd in args[0].split(','):
+        outfile = OUTPUT_FILES.get(cmd)
+        if not outfile:
+            raise ValueError("Unknown command '%s': (use: %s)" %
+                             (cmd, ', '.join(OUTPUT_FILES.keys())))
+        plat.setup_output(outfile.ftype,
+                          outfile.fname if output_dirs else output)
         if cmd == 'struct':
             plat.generate_structs(structs)
         elif cmd == 'platdata':
             plat.generate_tables()
-        else:
-            raise ValueError("Unknown command '%s': (use: struct, platdata)" %
-                             cmd)
+    plat.finish_output()
diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py
index b023a1e14a5..6f9af905e82 100755
--- a/tools/dtoc/test_dtoc.py
+++ b/tools/dtoc/test_dtoc.py
@@ -884,6 +884,14 @@ U_BOOT_DEVICE(spl_test2) = {
             self.run_test(['struct'], dtb_file, None)
         self._check_strings(self.struct_text, stdout.getvalue())
 
+    def test_multi_to_file(self):
+        """Test output of multiple pieces to a single file"""
+        dtb_file = get_dtb_file('dtoc_test_simple.dts')
+        output = tools.GetOutputFilename('output')
+        self.run_test(['struct,platdata'], dtb_file, output)
+        data = tools.ReadFile(output, binary=False)
+        self._check_strings(self.struct_text + self.platdata_text, data)
+
     def test_no_command(self):
         """Test running dtoc without a command"""
         with self.assertRaises(ValueError) as exc:
-- 
2.29.2.729.g45daf8777d-goog



More information about the U-Boot mailing list