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 <sjg@chromium.org>
diff --git a/tools/binman/binman.py b/tools/binman/binman.py
index 52e02ed..1536e95 100755
--- a/tools/binman/binman.py
+++ b/tools/binman/binman.py
@@ -77,9 +77,20 @@
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 @@
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 54e4fb1..f0de4de 100644
--- a/tools/binman/cmdline.py
+++ b/tools/binman/cmdline.py
@@ -28,6 +28,8 @@
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 3c931d9..2de1c86 100644
--- a/tools/binman/control.py
+++ b/tools/binman/control.py
@@ -92,6 +92,10 @@
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 de07f27..dc09b81 100644
--- a/tools/binman/entry.py
+++ b/tools/binman/entry.py
@@ -78,20 +78,18 @@
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 @@
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 @@
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 752d0ac..adcb898 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 @@
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 6968896..9c01805 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -21,6 +21,7 @@
import elf
import fdt
import fdt_util
+import test_util
import tools
import tout
@@ -1177,6 +1178,20 @@
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()