- 0 kr
Configuring CMake for success
One common question I get on our Advanced C++ course is how the project structure should look, e.g. should I split the source code into multiple directories? How can I introduce unit tests? Should I compile into multiple libraries? Each project is unique in what it needs, so in this blog post I’ll go over my general preferred setup and then look at how we adopted it for C++ on Android where we also interface with Kotlin/Java.
I love to use modern tools and practices when developing so that I can focus on moving forward. This implies knowing my programming language well so that I follow the same direction as the language wants me to go, and using practices like unit testing and CI so I can refactor mercilessly when needed without being afraid of introducing bugs. I use these practices both when writing code professionally and on my spare time projects.
I’ve recently been working with a colleague of mine (Stephen Lau) on an Augmented Reality app for solving a Rubik’s Cube, the app runs on an Android phone. We have developed the main detection and solving algorithms using C++ because it gives us access to helper libraries like OpenCV and the performance we need.
One of the first problems we ran into was how long it took to deploy our application to a device while tweaking our algorithms. Deploying (after building) took about 20-30 seconds, and then factor in slower debugging and the non-reproducibility of testing with a real camera. Sounds like a clear case for some unit/module testing where we run our algorithm against a set of pre-captured images so we also can track performance and regressions of our tweaks. We also realised that we could execute our unittests on desktop without involving Android at all, which gives a huge performance boost in build times.
One common question I get on our Advanced C++ course is how the project structure should look, e.g. should I split the source code into multiple directories? How can I introduce unit tests? Should I compile into multiple libraries?
Each project is unique in what it needs, so in this blog post I’ll go over my general preferred setup and then look at how we adopted it for C++ on Android where we also interface with Kotlin/Java.
General C++ Project Setup
First I prefer to use CMake, I like that I can have one file and get a predictable configuration for working with my source code in multiple IDE’s, be it CLion, Xcode or Microsoft Visual Studio.
I then split the code with following general directory structure:
I compile all source files into a static library that will be consumed by both the main executable, and keep the main function separate from this library. The static library is also linked into unit tests.
There are lots of variations you can make to this directory structure, if I’m working on a library with an exported API I will add
include/libProject (in this case
src where I place public header files. Private header files are kept alongside the source. External users of the API would add
<projectRoot>/src/include to their include path, and then import headers using
Sometimes I have multiple tests, like unit tests, performance tests and end-to-end tests. Then I would add an extra directory layer under tests to separate the code for these different tests.
If I have external dependencies I like to download them using CMake by using this extension: https://github.com/Crascit/Dow... . This ensures I have the version number of external dependencies encoded into my repository so I can reproduce build artifacts (which you will be super grateful for when bug reports start coming in). Also this means that new members joining the team will have as simple an onboarding process as possible.
One rule I try to stick to is to only have one type of sources per directory, for instance keep the public header files separate from the source. In my experience this makes it easier to write installation scripts or to have different linter rules for the public header files compared with rest of the project.
C++ on Android with native tests
Now let’s turn to a C++ application on Android. The UI handling is written in Kotlin, and it has bindings into a C++ library for performance or other reasons via the Java Native Interface (jni). The jni layer we have is just a thin layer on top of native library. A diagram to clarify:
Here we compile
libPalindrome.a multiple times with different compiler toolchains (desktop and Android). When we are building the Android executable, Gradle will execute CMake for us. What we need to ensure in addition to this, is that we can use CMake directly. For example we will not compile native unit tests when building the Android application so we have this in our CMake files:
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Android") add_executable(unittests …) endif()
Most configuration will be shared between both desktop and Android builds. The directory structure for this project ends up like this:
We changed our directory layout somewhat from the general setup to better fit the gradle build system where we have multiple source code languages.
Finally we use two different IDE:s, in my case I open the root folder
03-android-jni with Android Studio for building the Android executable, and I open the app folder with CLion when I want to develop with the unittests on desktop.
I’ve described a general C++ project setup and how we adjusted this for an Android project.
When running on desktop we test the image detection algorithm against a set of captured images from different environments/lighting conditions/orientations etc.
Running the unit tests takes about seven seconds so I get instant feedback and the predictable set of test images makes it easy to detect regressions and debug incorrect detections.
Starting the application on Android can take up to thirty seconds and then the real camera would be used which makes it harder to check for regressions in a specific lighting environment. Instead we have a button in the UI for saving the current detection image so we can save incorrect detections and debug later on desktop. These images are also used for expanding our set of test images so we continuously vet our algorithms against the problematic images we have encountered.
You can see our how our structure works in our example projects at https://github.com/edumentab/c... which uses modern CMake style.
By: Erik Man