From fd8d1f79623d2944d9ca8469a3681d53b8b277f9 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Tue, 17 Jul 2018 13:25:36 -0600 Subject: [PATCH] binman: Allow creation of entry documentation Binman supports quite a number of different entries now. The operation of these is not always obvious but at present the source code is the only reference for understanding how an entry works. Add a way to create documentation (from the source code) which can be put in a new 'README.entries' file. Signed-off-by: Simon Glass --- tools/binman/binman.py | 22 +++-- tools/binman/cmdline.py | 2 + tools/binman/control.py | 4 + tools/binman/entry.py | 93 ++++++++++++++++++--- tools/binman/etype/u_boot_dtb_with_ucode.py | 4 +- tools/binman/ftest.py | 15 ++++ 6 files changed, 118 insertions(+), 22 deletions(-) diff --git a/tools/binman/binman.py b/tools/binman/binman.py index 52e02ed91b..1536e95651 100755 --- a/tools/binman/binman.py +++ b/tools/binman/binman.py @@ -77,9 +77,20 @@ def RunTests(debug, args): return 1 return 0 +def GetEntryModules(include_testing=True): + """Get a set of entry class implementations + + Returns: + Set of paths to entry class filenames + """ + glob_list = glob.glob(os.path.join(our_path, 'etype/*.py')) + return set([os.path.splitext(os.path.basename(item))[0] + for item in glob_list + if include_testing or '_testing' not in item]) + def RunTestCoverage(): """Run the tests and check that we get 100% coverage""" - glob_list = glob.glob(os.path.join(our_path, 'etype/*.py')) + glob_list = GetEntryModules(False) all_set = set([os.path.splitext(os.path.basename(item))[0] for item in glob_list if '_testing' not in item]) test_util.RunTestCoverage('tools/binman/binman.py', None, @@ -107,13 +118,8 @@ def RunBinman(options, args): elif options.test_coverage: RunTestCoverage() - elif options.full_help: - pager = os.getenv('PAGER') - if not pager: - pager = 'more' - fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), - 'README') - command.Run(pager, fname) + elif options.entry_docs: + control.WriteEntryDocs(GetEntryModules()) else: try: diff --git a/tools/binman/cmdline.py b/tools/binman/cmdline.py index 54e4fb13dc..f0de4ded44 100644 --- a/tools/binman/cmdline.py +++ b/tools/binman/cmdline.py @@ -28,6 +28,8 @@ def ParseArgs(argv): help='Configuration file (.dtb) to use') parser.add_option('-D', '--debug', action='store_true', help='Enabling debugging (provides a full traceback on error)') + parser.add_option('-E', '--entry-docs', action='store_true', + help='Write out entry documentation (see README.entries)') parser.add_option('-I', '--indir', action='append', help='Add a path to a directory to use for input files') parser.add_option('-H', '--full-help', action='store_true', diff --git a/tools/binman/control.py b/tools/binman/control.py index 3c931d9aeb..2de1c86ecf 100644 --- a/tools/binman/control.py +++ b/tools/binman/control.py @@ -92,6 +92,10 @@ def SetEntryArgs(args): def GetEntryArg(name): return entry_args.get(name) +def WriteEntryDocs(modules, test_missing=None): + from entry import Entry + Entry.WriteDocs(modules, test_missing) + def Binman(options, args): """The main control code for binman diff --git a/tools/binman/entry.py b/tools/binman/entry.py index de07f27215..dc09b81677 100644 --- a/tools/binman/entry.py +++ b/tools/binman/entry.py @@ -78,20 +78,18 @@ class Entry(object): self.ReadNode() @staticmethod - def Create(section, node, etype=None): - """Create a new entry for a node. + def Lookup(section, node_path, etype): + """Look up the entry class for a node. Args: - section: Section object containing this node - node: Node object containing information about the entry to create - etype: Entry type to use, or None to work it out (used for tests) + section: Section object containing this node + node_node: Path name of Node object containing information about + the entry to create (used for errors) + etype: Entry type to use Returns: - A new Entry object of the correct type (a subclass of Entry) + The entry class object if found, else None """ - if not etype: - etype = fdt_util.GetString(node, 'type', node.name) - # Convert something like 'u-boot@0' to 'u_boot' since we are only # interested in the type. module_name = etype.replace('-', '_') @@ -110,15 +108,34 @@ class Entry(object): module = importlib.import_module(module_name) else: module = __import__(module_name) - except ImportError: - raise ValueError("Unknown entry type '%s' in node '%s'" % - (etype, node.path)) + except ImportError as e: + raise ValueError("Unknown entry type '%s' in node '%s' (expected etype/%s.py, error '%s'" % + (etype, node_path, module_name, e)) finally: sys.path = old_path modules[module_name] = module + # Look up the expected class name + return getattr(module, 'Entry_%s' % module_name) + + @staticmethod + def Create(section, node, etype=None): + """Create a new entry for a node. + + Args: + section: Section object containing this node + node: Node object containing information about the entry to + create + etype: Entry type to use, or None to work it out (used for tests) + + Returns: + A new Entry object of the correct type (a subclass of Entry) + """ + if not etype: + etype = fdt_util.GetString(node, 'type', node.name) + obj = Entry.Lookup(section, node.path, etype) + # Call its constructor to get the object we want. - obj = getattr(module, 'Entry_%s' % module_name) return obj(section, etype, node) def ReadNode(self): @@ -376,3 +393,53 @@ class Entry(object): else: value = fdt_util.GetDatatype(self._node, name, datatype) return value + + @staticmethod + def WriteDocs(modules, test_missing=None): + """Write out documentation about the various entry types to stdout + + Args: + modules: List of modules to include + test_missing: Used for testing. This is a module to report + as missing + """ + print('''Binman Entry Documentation +=========================== + +This file describes the entry types supported by binman. These entry types can +be placed in an image one by one to build up a final firmware image. It is +fairly easy to create new entry types. Just add a new file to the 'etype' +directory. You can use the existing entries as examples. + +Note that some entries are subclasses of others, using and extending their +features to produce new behaviours. + + +''') + modules = sorted(modules) + + # Don't show the test entry + if '_testing' in modules: + modules.remove('_testing') + missing = [] + for name in modules: + module = Entry.Lookup(name, name, name) + docs = getattr(module, '__doc__') + if test_missing == name: + docs = None + if docs: + lines = docs.splitlines() + first_line = lines[0] + rest = [line[4:] for line in lines[1:]] + hdr = 'Entry: %s: %s' % (name.replace('_', '-'), first_line) + print(hdr) + print('-' * len(hdr)) + print('\n'.join(rest)) + print() + print() + else: + missing.append(name) + + if missing: + raise ValueError('Documentation is missing for modules: %s' % + ', '.join(missing)) diff --git a/tools/binman/etype/u_boot_dtb_with_ucode.py b/tools/binman/etype/u_boot_dtb_with_ucode.py index 752d0ac0ba..adcb898a7b 100644 --- a/tools/binman/etype/u_boot_dtb_with_ucode.py +++ b/tools/binman/etype/u_boot_dtb_with_ucode.py @@ -6,7 +6,6 @@ # import control -import fdt from entry import Entry from blob import Entry_blob import tools @@ -38,6 +37,9 @@ class Entry_u_boot_dtb_with_ucode(Entry_blob): return 'u-boot.dtb' def ProcessFdt(self, fdt): + # So the module can be loaded without it + import fdt + # If the section does not need microcode, there is nothing to do ucode_dest_entry = self.section.FindEntryType( 'u-boot-spl-with-ucode-ptr') diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py index 696889601e..9c01805c72 100644 --- a/tools/binman/ftest.py +++ b/tools/binman/ftest.py @@ -21,6 +21,7 @@ import control import elf import fdt import fdt_util +import test_util import tools import tout @@ -1177,6 +1178,20 @@ class TestFunctional(unittest.TestCase): TEXT_DATA3 + 'some text') self.assertEqual(expected, data) + def testEntryDocs(self): + """Test for creation of entry documentation""" + with test_util.capture_sys_output() as (stdout, stderr): + control.WriteEntryDocs(binman.GetEntryModules()) + self.assertTrue(len(stdout.getvalue()) > 0) + + def testEntryDocsMissing(self): + """Test handling of missing entry documentation""" + with self.assertRaises(ValueError) as e: + with test_util.capture_sys_output() as (stdout, stderr): + control.WriteEntryDocs(binman.GetEntryModules(), 'u_boot') + self.assertIn('Documentation is missing for modules: u_boot', + str(e.exception)) + if __name__ == "__main__": unittest.main() -- 2.25.1