binman: Add support for passing arguments to entries
authorSimon Glass <sjg@chromium.org>
Tue, 17 Jul 2018 19:25:32 +0000 (13:25 -0600)
committerSimon Glass <sjg@chromium.org>
Wed, 1 Aug 2018 22:30:47 +0000 (16:30 -0600)
Sometimes it is useful to pass binman the value of an entry property from
the command line. For example some entries need access to files and it is
not always convenient to put these filenames in the image definition
(device tree).

Add a -a option which can be used like this:

   -a<prop>=<value>

where

   <prop> is the property to set
   <value> is the value to set it to

Signed-off-by: Simon Glass <sjg@chromium.org>
12 files changed:
tools/binman/README
tools/binman/cmdline.py
tools/binman/control.py
tools/binman/entry.py
tools/binman/etype/_testing.py
tools/binman/ftest.py
tools/binman/test/62_entry_args.dts [new file with mode: 0644]
tools/binman/test/63_entry_args_missing.dts [new file with mode: 0644]
tools/binman/test/64_entry_args_required.dts [new file with mode: 0644]
tools/binman/test/65_entry_args_unknown_datatype.dts [new file with mode: 0644]
tools/dtoc/fdt_util.py
tools/dtoc/test_fdt.py

index df88819a1c398a0ff205b789d4912dcb7f770806..d60c7fd6a3ecd145ca66177b476af1b18ae3a7ae 100644 (file)
@@ -615,6 +615,26 @@ each entry is also shown, in bytes (hex). The indentation shows the entries
 nested inside their sections.
 
 
+Passing command-line arguments to entries
+-----------------------------------------
+
+Sometimes it is useful to pass binman the value of an entry property from the
+command line. For example some entries need access to files and it is not
+always convenient to put these filenames in the image definition (device tree).
+
+The-a option supports this:
+
+    -a<prop>=<value>
+
+where
+
+    <prop> is the property to set
+    <value> is the value to set it to
+
+Not all properties can be provided this way. Only some entries support it,
+typically for filenames.
+
+
 Code coverage
 -------------
 
index 5c9b4dfeadc25a5332217fd2888e60235792426a..54e4fb13dc04537feb1fc7d672630195909e6c51 100644 (file)
@@ -18,6 +18,8 @@ def ParseArgs(argv):
             args is a list of string arguments
     """
     parser = OptionParser()
+    parser.add_option('-a', '--entry-arg', type='string', action='append',
+            help='Set argument value arg=value')
     parser.add_option('-b', '--board', type='string',
             help='Board name to build')
     parser.add_option('-B', '--build-dir', type='string', default='b',
index 9ac392b7e53d079f14a0f09e6251944ef477c7a9..ab894a8aa8db5663e34571302e5ba0d7107494aa 100644 (file)
@@ -7,6 +7,7 @@
 
 from collections import OrderedDict
 import os
+import re
 import sys
 import tools
 
@@ -25,6 +26,9 @@ images = OrderedDict()
 # 'u-boot-spl.dtb')
 fdt_files = {}
 
+# Arguments passed to binman to provide arguments to entries
+entry_args = {}
+
 
 def _ReadImageDesc(binman_node):
     """Read the image descriptions from the /binman node
@@ -76,6 +80,20 @@ def GetFdt(fname):
 def GetFdtPath(fname):
     return fdt_files[fname]._fname
 
+def SetEntryArgs(args):
+    global entry_args
+
+    entry_args = {}
+    if args:
+        for arg in args:
+            m = re.match('([^=]*)=(.*)', arg)
+            if not m:
+                raise ValueError("Invalid entry arguemnt '%s'" % arg)
+            entry_args[m.group(1)] = m.group(2)
+
+def GetEntryArg(name):
+    return entry_args.get(name)
+
 def Binman(options, args):
     """The main control code for binman
 
@@ -116,6 +134,7 @@ def Binman(options, args):
         try:
             tools.SetInputDirs(options.indir)
             tools.PrepareOutputDir(options.outdir, options.preserve)
+            SetEntryArgs(options.entry_arg)
 
             # Get the device tree ready by compiling it and copying the compiled
             # output into a file in our output directly. Then scan it for use
index 8004918eb58f36f2c66bd6825ef9e0eb37e768fc..de07f2721599d502856e4d83ebdea367473b0c24 100644 (file)
@@ -6,6 +6,8 @@
 
 from __future__ import print_function
 
+from collections import namedtuple
+
 # importlib was introduced in Python 2.7 but there was a report of it not
 # working in 2.7.12, so we work around this:
 # http://lists.denx.de/pipermail/u-boot/2016-October/269729.html
@@ -16,6 +18,7 @@ except:
     have_importlib = False
 
 import fdt_util
+import control
 import os
 import sys
 import tools
@@ -24,6 +27,12 @@ modules = {}
 
 our_path = os.path.dirname(os.path.realpath(__file__))
 
+
+# An argument which can be passed to entries on the command line, in lieu of
+# device-tree properties.
+EntryArg = namedtuple('EntryArg', ['name', 'datatype'])
+
+
 class Entry(object):
     """An Entry in the section
 
@@ -249,6 +258,33 @@ class Entry(object):
         """Convenience function to raise an error referencing a node"""
         raise ValueError("Node '%s': %s" % (self._node.path, msg))
 
+    def GetEntryArgsOrProps(self, props, required=False):
+        """Return the values of a set of properties
+
+        Args:
+            props: List of EntryArg objects
+
+        Raises:
+            ValueError if a property is not found
+        """
+        values = []
+        missing = []
+        for prop in props:
+            python_prop = prop.name.replace('-', '_')
+            if hasattr(self, python_prop):
+                value = getattr(self, python_prop)
+            else:
+                value = None
+            if value is None:
+                value = self.GetArg(prop.name, prop.datatype)
+            if value is None and required:
+                missing.append(prop.name)
+            values.append(value)
+        if missing:
+            self.Raise('Missing required properties/entry args: %s' %
+                       (', '.join(missing)))
+        return values
+
     def GetPath(self):
         """Get the path of a node
 
@@ -307,3 +343,36 @@ class Entry(object):
             indent: Curent indent level of map (0=none, 1=one level, etc.)
         """
         self.WriteMapLine(fd, indent, self.name, self.offset, self.size)
+
+    def GetArg(self, name, datatype=str):
+        """Get the value of an entry argument or device-tree-node property
+
+        Some node properties can be provided as arguments to binman. First check
+        the entry arguments, and fall back to the device tree if not found
+
+        Args:
+            name: Argument name
+            datatype: Data type (str or int)
+
+        Returns:
+            Value of argument as a string or int, or None if no value
+
+        Raises:
+            ValueError if the argument cannot be converted to in
+        """
+        value = control.GetEntryArg(name)
+        if value is not None:
+            if datatype == int:
+                try:
+                    value = int(value)
+                except ValueError:
+                    self.Raise("Cannot convert entry arg '%s' (value '%s') to integer" %
+                               (name, value))
+            elif datatype == str:
+                pass
+            else:
+                raise ValueError("GetArg() internal error: Unknown data type '%s'" %
+                                 datatype)
+        else:
+            value = fdt_util.GetDatatype(self._node, name, datatype)
+        return value
index 31f625c026cf4cd41f85cab0caa9e47d6503f6b3..3eeec72b360cc7b3e467d87b85dbea25871bf6e4 100644 (file)
@@ -5,7 +5,9 @@
 # Entry-type module for testing purposes. Not used in real images.
 #
 
-from entry import Entry
+from collections import OrderedDict
+
+from entry import Entry, EntryArg
 import fdt_util
 import tools
 
@@ -27,6 +29,21 @@ class Entry__testing(Entry):
         self.process_fdt_ready = False
         self.never_complete_process_fdt = fdt_util.GetBool(self._node,
                                                 'never-complete-process-fdt')
+        self.require_args = fdt_util.GetBool(self._node, 'require-args')
+
+        # This should be picked up by GetEntryArgsOrProps()
+        self.test_existing_prop = 'existing'
+        self.force_bad_datatype = fdt_util.GetBool(self._node,
+                                                   'force-bad-datatype')
+        (self.test_str_fdt, self.test_str_arg, self.test_int_fdt,
+         self.test_int_arg, existing) = self.GetEntryArgsOrProps([
+            EntryArg('test-str-fdt', str),
+            EntryArg('test-str-arg', str),
+            EntryArg('test-int-fdt', int),
+            EntryArg('test-int-arg', int),
+            EntryArg('test-existing-prop', str)], self.require_args)
+        if self.force_bad_datatype:
+            self.GetEntryArgsOrProps([EntryArg('test-bad-datatype-arg', bool)])
 
     def ObtainContents(self):
         if self.return_unknown_contents:
index 94a50aac1665c6b253215d3b5f3fa2e091ff34f4..c54cd12e717bd91fc69aee3ff635411147ec095b 100644 (file)
@@ -146,7 +146,8 @@ class TestFunctional(unittest.TestCase):
         # options.verbosity = tout.DEBUG
         return control.Binman(options, args)
 
-    def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False):
+    def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
+                    entry_args=None):
         """Run binman with a given test file
 
         Args:
@@ -163,6 +164,9 @@ class TestFunctional(unittest.TestCase):
             args.append('-m')
         if update_dtb:
             args.append('-up')
+        if entry_args:
+            for arg, value in entry_args.iteritems():
+                args.append('-a%s=%s' % (arg, value))
         return self._DoBinman(*args)
 
     def _SetupDtb(self, fname, outfile='u-boot.dtb'):
@@ -188,7 +192,7 @@ class TestFunctional(unittest.TestCase):
             return data
 
     def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False,
-                       update_dtb=False):
+                       update_dtb=False, entry_args=None):
         """Run binman and return the resulting image
 
         This runs binman with a given test file and then reads the resulting
@@ -220,7 +224,8 @@ class TestFunctional(unittest.TestCase):
             dtb_data = self._SetupDtb(fname)
 
         try:
-            retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb)
+            retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb,
+                                       entry_args=entry_args)
             self.assertEqual(0, retcode)
             out_dtb_fname = control.GetFdtPath('u-boot.dtb')
 
@@ -1085,5 +1090,77 @@ class TestFunctional(unittest.TestCase):
         self.assertIn('Could not complete processing of Fdt: remaining '
                       '[<_testing.Entry__testing', str(e.exception))
 
+    def testEntryArgs(self):
+        """Test passing arguments to entries from the command line"""
+        entry_args = {
+            'test-str-arg': 'test1',
+            'test-int-arg': '456',
+        }
+        self._DoReadFileDtb('62_entry_args.dts', entry_args=entry_args)
+        self.assertIn('image', control.images)
+        entry = control.images['image'].GetEntries()['_testing']
+        self.assertEqual('test0', entry.test_str_fdt)
+        self.assertEqual('test1', entry.test_str_arg)
+        self.assertEqual(123, entry.test_int_fdt)
+        self.assertEqual(456, entry.test_int_arg)
+
+    def testEntryArgsMissing(self):
+        """Test missing arguments and properties"""
+        entry_args = {
+            'test-int-arg': '456',
+        }
+        self._DoReadFileDtb('63_entry_args_missing.dts', entry_args=entry_args)
+        entry = control.images['image'].GetEntries()['_testing']
+        self.assertEqual('test0', entry.test_str_fdt)
+        self.assertEqual(None, entry.test_str_arg)
+        self.assertEqual(None, entry.test_int_fdt)
+        self.assertEqual(456, entry.test_int_arg)
+
+    def testEntryArgsRequired(self):
+        """Test missing arguments and properties"""
+        entry_args = {
+            'test-int-arg': '456',
+        }
+        with self.assertRaises(ValueError) as e:
+            self._DoReadFileDtb('64_entry_args_required.dts')
+        self.assertIn("Node '/binman/_testing': Missing required "
+            'properties/entry args: test-str-arg, test-int-fdt, test-int-arg',
+            str(e.exception))
+
+    def testEntryArgsInvalidFormat(self):
+        """Test that an invalid entry-argument format is detected"""
+        args = ['-d', self.TestFile('64_entry_args_required.dts'), '-ano-value']
+        with self.assertRaises(ValueError) as e:
+            self._DoBinman(*args)
+        self.assertIn("Invalid entry arguemnt 'no-value'", str(e.exception))
+
+    def testEntryArgsInvalidInteger(self):
+        """Test that an invalid entry-argument integer is detected"""
+        entry_args = {
+            'test-int-arg': 'abc',
+        }
+        with self.assertRaises(ValueError) as e:
+            self._DoReadFileDtb('62_entry_args.dts', entry_args=entry_args)
+        self.assertIn("Node '/binman/_testing': Cannot convert entry arg "
+                      "'test-int-arg' (value 'abc') to integer",
+            str(e.exception))
+
+    def testEntryArgsInvalidDatatype(self):
+        """Test that an invalid entry-argument datatype is detected
+
+        This test could be written in entry_test.py except that it needs
+        access to control.entry_args, which seems more than that module should
+        be able to see.
+        """
+        entry_args = {
+            'test-bad-datatype-arg': '12',
+        }
+        with self.assertRaises(ValueError) as e:
+            self._DoReadFileDtb('65_entry_args_unknown_datatype.dts',
+                                entry_args=entry_args)
+        self.assertIn('GetArg() internal error: Unknown data type ',
+                      str(e.exception))
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/binman/test/62_entry_args.dts b/tools/binman/test/62_entry_args.dts
new file mode 100644 (file)
index 0000000..4d4f102
--- /dev/null
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+/ {
+       #address-cells = <1>;
+       #size-cells = <1>;
+
+       binman {
+               _testing {
+                       test-str-fdt = "test0";
+                       test-int-fdt = <123>;
+               };
+       };
+};
diff --git a/tools/binman/test/63_entry_args_missing.dts b/tools/binman/test/63_entry_args_missing.dts
new file mode 100644 (file)
index 0000000..1644e2f
--- /dev/null
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+/ {
+       #address-cells = <1>;
+       #size-cells = <1>;
+
+       binman {
+               _testing {
+                       test-str-fdt = "test0";
+               };
+       };
+};
diff --git a/tools/binman/test/64_entry_args_required.dts b/tools/binman/test/64_entry_args_required.dts
new file mode 100644 (file)
index 0000000..705be10
--- /dev/null
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+/ {
+       #address-cells = <1>;
+       #size-cells = <1>;
+
+       binman {
+               _testing {
+                       require-args;
+                       test-str-fdt = "test0";
+               };
+       };
+};
diff --git a/tools/binman/test/65_entry_args_unknown_datatype.dts b/tools/binman/test/65_entry_args_unknown_datatype.dts
new file mode 100644 (file)
index 0000000..3e4838f
--- /dev/null
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0+
+/dts-v1/;
+
+/ {
+       #address-cells = <1>;
+       #size-cells = <1>;
+
+       binman {
+               _testing {
+                       test-str-fdt = "test0";
+                       test-int-fdt = <123>;
+                       force-bad-datatype;
+               };
+       };
+};
index 05cb9c0775891322cefe996f5f244df829893d06..b2290385695d994f24b694ac2048265acf2d6c11 100644 (file)
@@ -147,3 +147,24 @@ def GetBool(node, propname, default=False):
     if propname in node.props:
         return True
     return default
+
+def GetDatatype(node, propname, datatype):
+    """Get a value of a given type from a property
+
+    Args:
+        node: Node object to read from
+        propname: property name to read
+        datatype: Type to read (str or int)
+
+    Returns:
+        value read, or None if none
+
+    Raises:
+        ValueError if datatype is not str or int
+    """
+    if datatype == str:
+        return GetString(node, propname)
+    elif datatype == int:
+        return GetInt(node, propname)
+    raise ValueError("fdt_util internal error: Unknown data type '%s'" %
+                     datatype)
index f085b1dd1a9f65d85a78ce1c1358295042f74908..03cf4b4f7cfbb30d7687e5f03af38ec7733f0d56 100755 (executable)
@@ -380,6 +380,14 @@ class TestFdtUtil(unittest.TestCase):
         self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
 
+    def testGetDataType(self):
+        self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
+        self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
+                                                         str))
+        with self.assertRaises(ValueError) as e:
+            self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
+                                                     bool))
+
     def testFdtCellsToCpu(self):
         val = self.node.props['intarray'].value
         self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))