Lessons learnt adapting CppMicroServices to biicode

This post summarizes the lessons learnt from adapting the CppMicroServices framework to a biicode compatible format. It does not go into detail about the actual modifications, but focuses on the lessons learnt adapting the framework.

Background

CppMicroServices is an implementation of the OSGi framework, which enables runtime dependency injection. Modules of code (bundles) can be loaded and unloaded at runtime. In the case of CppMicroServices, bundles are either shipped in the form of shared libraries, or statically linked into the executable.

cppmicroservices

Based on packaging principles, bundles should be packaged and released separately – so that they can be depended on individually. biicode’s dependency management through blocks is suitable as a dependency management tool that supports this flow.

Lessons Learnt While Adapting the CppMicroServices Framework

Never learn two things at once. This is one of the most important rules to obey when attempting a migration / modification. Another rule is to understand the specifics of each entity involved in the task – not just the concepts, but the concrete behaviour.

In my case, I understood the concepts of build and dependency management tools, but I was not familiar with biicode, CMake, and how CppMicroServices used CMake.

Tools

There are multiple layers of tools involved in a biicode build. biicode, CMake, Make, and the compiler (g++, clang, cl) and linker. When something fails, understand why it fails. It’s not always the lowest layer’s fault. Perhaps some CMake configuration wasn’t written to tell ld that a particular library needs to be linked.

biicode

Of all of the components in play, biicode probably has the shallowest learning curve – for the most part. biicode’s documentation makes it easy to see how it works for freshly created blocks. The hard part is determining if it can do some complex thing that isn’t documented. The short answer is no, what is documented is what you get – but I found that this is enough. The trickiest part is populating the variables that biicode will read.

The forum is also a very helpful place to get your questions answered.

CMake

CMake reads like a bash script – short sections are relatively easily understood, but once it gets big, navigating and understanding the code is a hassle. A few gotchas that mask obscure errors when CMake builds fail are:

  • function_call(${param1} ${param2} ${param3}) is effectively function_call(${param1} ${param3}) if ${param2} is empty
  • if a subdirectory is added as part of a CMake build using add_subdirectory(dirname), it is in a scope where it is able to access variables set in the parent directory scope, but any variables set in this scope will not be accessible to the parent directory unless PARENT_SCOPE is passed to the function

Mixing biicode and CMake

If the project to be converted produces multiple artifacts, biicode needs the source files for its targets to be declared in the relevant BII_artifactname_SRC variables. This must be done before the ADD_BIICODE_TARGETS() function is called.

If a target’s source includes generated files, then the path to those generated files must be known before the target is added (i.e. prior to the ADD_BIICODE_TARGETS() call). If there is special processing required for a biicode target in a subdirectory, but that subdirectory is added before ADD_BIICODE_TARGETS() is called, then this cannot be done. Either:

  • create a different static library target in the subdirectory, and link this to the biicode target instead, OR
  • perform that special processing in the parent directory’s CMake configuration after the biicode targets are added (may result in duplicated code)

Building and Testing

Build each combination

After making changes to the project, it is important to make sure it builds on the following combinations:

  • The non-biicode build
  • From the repository checkout (e.g. git clone)
  • From bii open user/block – in case required files are not published
  • From a block that depends on it – to discover implicit source dependencies that cannot be discovered by biicode

Build on each operating system

If your block should support different operating systems, you should build it on each to ensure it works. This also lets you discover how the block must be built. For example, CMake may default to the MINGW compiler on Windows,but your block may only support the MSVC compiler. This means you need to add an argument to the build command, e.g. bii cpp:configure -G "Visual Studio 12 2013 Win64"".

Tip: CMake does not detect your MSVC compiler if “Express 2013 for Windows” is installed, but it does if “Express 2013 for Windows Desktop” is installed.

Clean your working directory

Before you build with biicode, make sure you clean out any output from a non-biicode build (e.g. CMakeCache.txt, generated files).

Tip: you can use cmake -Bbuild -H. to have an “out of source” build. After, run cd build && make all && cd .. to compile your code. Just delete the build directory to clean up your working directory.

Post Publishing Actions

After publishing a block as stable, the next time you publish, your ancestor version for your block is bumped. Remember to commit this change shortly after.

Author Bio

Azriel is a software developer at Orion Health in New Zealand. His interest in C/C++ development is primarily driven by the control available in the language, and the opportunity to demonstrate that C/C++ code can be clean. To Azriel, biicode is a major improvement to dependency management in C/C++ projects, and compares the improvement in dependency management support from CMake to biicode akin to Apache Ivy to Apache Maven.


Related Posts