GNU Radio 3.9 OOT Module Porting Guide: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
Line 136: Line 136:


blockname_pydoc.h is generated during compilation based on the template in the docstring directory.  When the block is first created in blocktool, this template does not exist.  Run <code>gr_modtool bind</code> to generate the appropriate template used as a placeholder for the scraped docstrings
blockname_pydoc.h is generated during compilation based on the template in the docstring directory.  When the block is first created in blocktool, this template does not exist.  Run <code>gr_modtool bind</code> to generate the appropriate template used as a placeholder for the scraped docstrings
Also, the scraping of docstrings only takes place at CMake time, so it may be necessary to do a <code>make clean</code> to re-trigger the scraping
==== TypeError: 'modulename_python.blockname' object is not subscriptable ====
This is caused by an incomplete inheritance chain specified in the binding declaration of the block.
Instead of
<code>
    py::class_<blockname,
              std::shared_ptr<blockname>>(m, "blockname", D(blockname))
</code>
Try something like (taking into account your block type)
<code>
    py::class_<blockname,
              gr::sync_block,
              gr::block,
              gr::basic_block,
              std::shared_ptr<blockname>>(m, "blockname", D(blockname))
</code>

Revision as of 12:51, 23 June 2020

The major changes in the (in-progress) GNU Radio 3.9 release that will impact OOTs are:

  • C++ modernization (C++11/14?)
  • Replacement of SWIG with Pybind11



C++ Modernization

(Currently merged onto master branch)

The most obvious change that will impact OOTs is that Boost shared pointers have been replaced with std:: shared pointers and memory management. At the top level of each block, the instantiation will need to change, e.g.

In include/blockname_xx.h:

typedef std::shared_ptr<blockname_xx> sptr;

Inbound message ports receive an update too. We move from boost::function to std::function. This affects how message handlers are registered. The prefered style is to use lambdas which is already compatible with GNU Radio 3.8:

set_msg_handler(pmt::mp("message"), [this](pmt::pmt_t msg) { this->handle_msg(msg); });

Pybind11 Python Bindings

As of the GNU Radio 3.9 release, python bindings are handled using pybind11, which is inherently different than they were in previous releases

Dependencies

curl -Lo pybind11.tar.gz https://github.com/pybind/pybind11/archive/v2.4.3.tar.gz 
mkdir pybind11 && tar xzf pybind11.tar.gz -C pybind11 --strip-components=1 && cd pybind11
mkdir build && cd build 
cmake .. -DCMAKE_BUILD_TYPE=Release -DPYBIND11_TEST=OFF 
make && make install 

Components

Python bindings are contained in the python/.../bindings directory

./python
└── module_name
    ├── bindings
    │   ├── blockname1_python.cc
    │   ├── blockname2_python.cc
    │   ├── CMakeLists.txt
    │   ├── docstrings
    │   │   ├── blockname1_pydoc_template.h
    │   │   ├── blockname1_pydoc_template.h

The bindings for each block exist in blockname_python.cc under the python/bindings directory. Additionally, a template header file for each block that is used as a placeholder for the scraped docstrings lives in the docstrings/ dir

blockname_python.cc

Comment Block

Each block binding file contains an automatically generated and maintained comment block that informs CMake when the bindings are out of sync with the header file they refer to, and what to do about it

/***********************************************************************************/
/* This file is automatically generated using bindtool and can be manually edited  */
/* The following lines can be configured to regenerate this file during cmake      */
/* If manual edits are made, the following tags should be modified accordingly.    */
/* BINDTOOL_GEN_AUTOMATIC(0)                                                       */
/* BINDTOOL_USE_PYGCCXML(0)                                                        */
/* BINDTOOL_HEADER_FILE(basic_block.h)                                             */
/* BINDTOOL_HEADER_FILE_HASH(549c06530e2afdf6f2c989017cb5f36e)                     */
/***********************************************************************************/

BINDTOOL_GEN_AUTOMATIC: Many times for complex in-tree blocks, the automated tools are not entirely sufficient to generate all of the bindings in an automated fashion. In this case, the flag should be set to 0, and the bindings need to be updated manually. If the flag is set to 1, CMake will override the binding file in the source tree when it detects out of sync bindings. This should only be done in simple cases.

BINDTOOL_USE_PYGCCXML: Currently there are limitations on the amount of code generation that can be accomplished without the pygccxml dependency. If a block needs pygccxml for the bindings to be properly generated automatically, this should be set to 1

BINDTOOL_HEADER_FILE: The header file that bindings are based on, filename only

BINDTOOL_HEADER_FILE_HASH: The MD5 hash of the header file that the bindings were built on

Workflow

Out-of-Tree modules

The steps for creating an out of tree module with pybind11 bindings are as follows:

  1. Use gr_modtool to create an out of tree module and add blocks
gr_modtool newmod foo
gr_modtool add bar
  1. Update the parameters or functions in the public include file and rebind with gr_modtool bind bar

NOTE: without pygccxml, only the make function is currently accounted for, similar to gr_modtool makeyaml

If the public API changes, just call gr_modtool bind [blockname] to regenerate the bindings

When the public header file for a block is changed, CMake will fail as it checks the hash of the header file compared to the hash stored in the bindings file until the bindings are updated

  1. Build and install

Docstrings

If Doxygen is enabled in GNU Radio and/or the OOT, Docstrings are scraped from the header files, and placed in auto-generated [blockname]_pydoc.h files in the build directory on compile. Generated templates (via the binding steps described above) are placed in the python/bindings/docstrings directory and are used as placeholders for the scraped strings

Upon compilation, docstrings are scraped from the module and stored in a dictionary (using update_pydoc.py scrape) and then the values are substituted in the template file (using update_pydoc.py sub)


OOT Migration

TBD

Caveats

Pybind11 bound methods do not implicitly convert int to enum, so blocks that take enum as input, must have either "raw" or "enum" in the grc yml definition of the block. "Raw" will allow the value to be changed by another variable in the flowgraph.

Block inheritance must be specified completely in the python bindings in order to use the inherited methods. For instance, if a block inherits from sync_block, both block and basic_block must be included in the inheritance specification of the class:

    py::class_<atsc_interleaver,
               gr::sync_block,
               gr::block,
               gr::basic_block,
               std::shared_ptr<atsc_interleaver>>(
        m, "atsc_interleaver", D(atsc_interleaver)) 

Troubleshooting

Unable to find pydoc.h

fatal error: blockname_pydoc.h: No such file or directory
   28 | #include <blockname_pydoc.h>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

blockname_pydoc.h is generated during compilation based on the template in the docstring directory. When the block is first created in blocktool, this template does not exist. Run gr_modtool bind to generate the appropriate template used as a placeholder for the scraped docstrings

Also, the scraping of docstrings only takes place at CMake time, so it may be necessary to do a make clean to re-trigger the scraping

TypeError: 'modulename_python.blockname' object is not subscriptable

This is caused by an incomplete inheritance chain specified in the binding declaration of the block.

Instead of

   py::class_<blockname,
              std::shared_ptr<blockname>>(m, "blockname", D(blockname))

Try something like (taking into account your block type)

   py::class_<blockname,
              gr::sync_block,
              gr::block,
              gr::basic_block,
              std::shared_ptr<blockname>>(m, "blockname", D(blockname))