Skip to content

Commit

Permalink
binman: Allow creation of entry documentation
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
sjg20 committed Aug 1, 2018
1 parent 3fb397b commit fd8d1f7
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 22 deletions.
22 changes: 14 additions & 8 deletions tools/binman/binman.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions tools/binman/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 4 additions & 0 deletions tools/binman/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 80 additions & 13 deletions tools/binman/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,18 @@ def __init__(self, section, etype, node, read_node=True, name_prefix=''):
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('-', '_')
Expand All @@ -110,15 +108,34 @@ def Create(section, node, etype=None):
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):
Expand Down Expand Up @@ -376,3 +393,53 @@ def GetArg(self, name, datatype=str):
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))
4 changes: 3 additions & 1 deletion tools/binman/etype/u_boot_dtb_with_ucode.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#

import control
import fdt
from entry import Entry
from blob import Entry_blob
import tools
Expand Down Expand Up @@ -38,6 +37,9 @@ def GetDefaultFilename(self):
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')
Expand Down
15 changes: 15 additions & 0 deletions tools/binman/ftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import elf
import fdt
import fdt_util
import test_util
import tools
import tout

Expand Down Expand Up @@ -1177,6 +1178,20 @@ def testText(self):
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()

0 comments on commit fd8d1f7

Please sign in to comment.