Carriers Everywhere

GNU Radio / CMake

Let’s talk about GNU Radio’s build system, because it seems to get more and more cluttered over time. If things don’t work, people just add stuff until it finally compiles – on Ubuntu. And maybe some other distros.

Let’s, for example, look at the includes:

include_directories(
    ${CMAKE_CURRENT_BINARY_DIR}
    ${GR_UHD_INCLUDE_DIRS}
    ${GNURADIO_RUNTIME_INCLUDE_DIRS}
    ${UHD_INCLUDE_DIRS}
    ${LOG4CXX_INCLUDE_DIRS}
    ${Boost_INCLUDE_DIRS}
)

LOG4CXX_INCLUDE_DIRS is never defined. So some lines below someone made another attempt:

include_directories(${LOG4CPP_INCLUDE_DIRS})

It’s not added to the above list (where it would be obvious that something goes wrong), but added as a new statement. OK. At least, we managed to included log4cpp in UHD. Makes a lot of sense, because UHD uses log4cpp, right?

Nope. UHD uses GNU Radio runtime, which in turn uses log4cpp.

Woot?!?! But it already included ${GNURADIO_RUNTIME_INLCUDE_DIRS}.

Yes, but that’s basically just another name for gnuradio-runtime/include, i.e., the definition is not really helpful and doesn’t provide dependency tracking whatsoever.

GR_XXX_INLCUDE_DIRS

So what’s ${GR_XXX_INCLUDE_DIRS}?

Depends. Every module uses it differently, but none in an actual useful manner. Let me explain.

Often, ${GR_XXX_INCLUDE_DIRS} is just another name for gr-XXX/include. That’s not wrong, but also not really helpful, as demonstrated by the above example.

Instead of adding log4cpp to ${GNURADIO_RUNTIME_INCLUDE_DIRS}, we have to know that runtime depends on log4cpp and manually add it to every module that uses GNU Radio runtime. Not really intuitive, pretty ugly, and a pain if dependencies change. Because then, every module that uses runtime has to be updated (and every module that uses a module that uses GNU Radio runtime, of course).

Apart from that, it’s buggy and not added in all modules. The only reason why GNU Radio compiles on some platforms is that log4cpp lives in a directory that is already included by other libraries (like boost, uhd, gsl, fftw, etc.).

On my homebrew installation that was not the case and, consequently, GNU Radio failed to compile. Following the nonsense semantic, this required a really ugly patch.

Another possible definition of ${GR_XXX_INCLUDE_DIRS} is:

GR_SET_GLOBAL(GR_ZEROMQ_INCLUDE_DIRS
    ${CMAKE_CURRENT_SOURCE_DIR}/lib
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

This defines a global variable (for use in other modules) that references an internal directory (lib). The headers in lib are, however, only needed to compile the module, but not required by an external module that just uses it. So this doesn’t make any sense at all.

In my opinion, the only meaningful definition is that ${GR_XXX_INCLUDE_DIRS} contains the include directories needed when using XXX. This semantic is, however, not adopted in any module.

Not all includes are the same

Note that this does not mean that all includes of XXX should be added to ${GR_XXX_INCLUDE_DIRS}, but only the ones that are needed when using XXX.

An example. The base class of all GNU Radio blocks is defined in gnuradio/block.h, which includes gnuradio/logger.h, which includes log4cpp. This means any block (in any module) has to include this header and, therefore, indirectly also log4cpp. For that reason, log4cpp should clearly be in ${GNURADIO_RUNTIME_INCLUDE_DIRS}. In other words, a module should export its own includes and all includes referenced by the public headers.

If, in contrast, a module uses a library internally (let’s say a .cc-file includes math to compute something), this should, of course, not be part of the exported includes, but added directly to gnuradio-runtime/lib/CMakeLists.txt.

And Linked Libraries?

Same here. Also no dependency tracking. DTV, for example, adds GSL to its library search path; not because it uses it, but because it uses FEC, which, in turn, uses GSL.

In my opinion, DTV should not have to care what FEC is linked against. The confusion is well reflected by log4cpp, which is indirectly referenced by all modules.

  • Some modules just link against runtime and that’s it (see gr-analog).
  • Some modules put ${LOG4CPP_LIBRARY_DIRS} in the search path and link against runtime (see gr-trellis).
  • Some modules put ${LOG4CPP_LIBRARY_DIRS} in the search path and link against runtime and log4cpp even though they don’t use it directly (see gr-audio).
  • Some modules link against runtime and log4cpp even though they don’t use it directly (see gr-blocks).

Btw, linking against log4cpp and adding it to the library search path doesn’t make much sense, since the library is referenced by an absolute path. Furthermore, since cmake adds library locations to the RPATH during build, the first (and simplest) option is perfectly fine and should, therefore, be preferred. The others just add unnecessary bloat.

I made a dependency graph of the GNU Radio modules and libraries. The red arrows show indirect dependencies that, currently, have to be added manually to CMake. (I didn’t draw all the red arrows to log4cpp.)

I’m currently working on a series of patches to implement the suggested semantics. I hope that this simplifies the build system and makes it more intuitive. If you have any comments or opinions, let me know.