binman: Allow creation of entry documentation
authorSimon Glass <sjg@chromium.org>
Tue, 17 Jul 2018 19:25:36 +0000 (13:25 -0600)
committerSimon Glass <sjg@chromium.org>
Wed, 1 Aug 2018 22:30:48 +0000 (16:30 -0600)
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 <sjg@chromium.org>
tools/binman/binman.py
tools/binman/cmdline.py
tools/binman/control.py
tools/binman/entry.py
tools/binman/etype/u_boot_dtb_with_ucode.py
tools/binman/ftest.py

index 52e02ed91b4f7de47b995adbbcc055ffb49b67b5..1536e95651755c506bbea85380c4e2efb30648ac 100755 (executable)
@@ -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:
index 54e4fb13dc04537feb1fc7d672630195909e6c51..f0de4ded4433e944f3cd3dbf563d38596fb72257 100644 (file)
@@ -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',
index 3c931d9aeb63290222f645561fa1214b8f8abc81..2de1c86ecfeb90c8383464481721cbbe7f8a2a6b 100644 (file)
@@ -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
 
index de07f2721599d502856e4d83ebdea367473b0c24..dc09b81677de05813e0dbee7857d4070f0f4650f 100644 (file)
@@ -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))
index 752d0ac0ba27bb1a62fbb7b0b4d7ea7b9cffe588..adcb898a7b20187a61d3123da94cff2bf75c4fbc 100644 (file)
@@ -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')
index 696889601ef66f18f3e063c4e8b799e2a3c44e8f..9c01805c72e548b55b7f5f0975b6dfff6da3f1f5 100644 (file)
@@ -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()