dtoc: Allow outputing to multiple files

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@chromium.org>
This commit is contained in:
Simon Glass 2020-12-28 20:34:51 -07:00
parent 192c111cfc
commit be44f27156
2 changed files with 94 additions and 10 deletions

View File

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

View File

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