Testing an all open-source, modern C++ workflow

German Diago Gomez
Level Up Coding
Published in
13 min readJun 26, 2020

--

Coding a base85 decoder from scratch

I have spent most of my career on the backend, with things such as real-time and high availability software. I have used C++ more than any other technology in my career. I started to use C++ back in 2002 and have not stopped since.

Time keeps going on, and, the other day, by chance, I found a nice challenge in a website. That website is called “Tom’s Data Onion”. You can find it here: https://www.tomdalling.com/toms-data-onion/

The thing is that there is a payload, encoded in Adobe-flavoured Base85 encoding in that challenge, that you have to decode to be able to go ahead with the remaining problems that are nested in the payload.

I told myself: oh, this is perfect for a little from-scratch experiment. I am going to set up a project and see how it goes regarding productivity compared to other languages (not a side-by-side comparision article) and especially, regarding to C++ since I started.

The self-set goal for this project

My goal was only to produce a base85 decoder that could decode the payload in Tom’s Data Onion, not to iterate on the nested layers of the onion to solve every problem.

  1. I wanted to follow a TDD workflow so I would need a dependency for testing.
  2. I would use the latest C++ available. So I chose C++20.
  3. The goal was to finish something that can decode the base85 payload. The correctness is checked through tests (TDD), and finally I wanted to add a file decoding as a baseline benchmark in case I want to improve upon what I already have later with more competitive implementations.

Who knows, if I feel motivated, I could make this library a decoder some day and make it work fast… but that is not the initial goal.

Choosing an environment to code

As I said before, I have been coding for a while (a loooong while I would say). I find coding very enjoyable, but I also like to finish stuff. So I had to choose a sane workflow from the 200,000 custom workflow permutations that one could choose in C++ that makes sense. You could choose plain Makefiles, but that will work only in Unix-like systems. Or Autotools, same story. You could also choose a Visual Studio solution, but that just will do for Windows. So I always go with a portable build system. From SCons, to Waf, Tup, CMake,Meson or Build2. What should I choose?

For an IDE… From Visual Studio to CLion full-feature IDEs, to text editors such as Vim, Emacs, Sublime or Visual Studio code...

I chose something I could set up pretty quickly, that would have code completion, would be reasonably portable and where I could at least handle the needed dependencies fast and easy.

All in all, I went with this environment:

  • Doom Emacs
  • Meson Build System (+ wrap for external dependencies)
  • Lsp code completion

How efficient was the environment?

I will start with a section of the development as it happened so that you can get a taste of how the workflow has been.

After that, I want to show some conclusions in some different areas that I value to get things done, productivity-related, when I code.

Development of the encoder as it happened

Once the tools have been installed and setup, you need to:

  1. setup a git repo
  2. install and start ccls for lsp code completion
  3. add meson-mode via melpa in Emacs
  4. provide minimal meson build files infraestructure to start with your project

Setting up a git repo was done from the terminal with git init in the directory for the project.

ccls can be installed from the package manager via apt and can be run from the terminal. I just run it with nohup ccls & to make it easy, though you can do your own further systemd integrations or the like.

You can install a ton of packages from Emacs via MELPA. I installed meson-mode myself and I do not recall if something else or not, since Doom Emacs comes with a lot of things by default, though you need to activate them (uncomment + .emacs.d/bin/doom sync ). Doom Emacs supports updating in steps so that if something breaks you can come back to the previous setup, though I did not try myself.

Now I needed a minimal meson.build file at the top + some other recursive ones because of my directory layout, since I use one per folder. The initial main one looked like the one shown below initially.

I added an executable and a test. In Meson this is quite easy. As you can see, the test needs catch2 dependency. This is not part of the standard library:

Basic meson.build file to start coding

You can setup a project in Emacs with the help of projectile by using the key F-10 and going to menu Projectile -> Configure project or using Control-c p C . All commands have shortcuts. Projectile uses .git folder as a root directory and is intelligent enough to detect that this is a Meson project so it prompts me the first time with the configuration command that I can change, but the default is good enough. You have as well commands for running, testing and compiling, besides configuring.

Via F-9 you can also hide/show a tree navigation widget on the left. Via F-10 you can show the global menu. With a right-click you can show the contextual menu for the file.

Doom Emacs showing the tree view after pressing F-9. With F-9 it is hidden again.

Once the project is setup, I added (not sure if this is needed anymore but it used to be necessary) a link to my compile_commands.json for code completion in the root folder:

Linking compile_commands.json so that lsp can index your code

After that I restarted emacs and lsp indexes things as long as you have ccls installed and running, since ccls is a daemon.

There is an external dependency, Catch2. What happens with the dependency? Easy: if it is detected in your system that is used. If it is not found… you are in trouble… or not.

Using meson wrap dependencies

Meson wrap is a package dependencies system (like Conan). It has a collection of dependencies and it works very well with Meson because it is integrated at the source level. You can also use Conan as well with Meson, by the way, via pkg-config.

Meson wrap has the advantage that if the package is available it will integrate very well with the compilation flags issued in your project. It also integrates well with the subproject feature from Meson, where it prefers system-provided libraries and fallbacks to subprojects if the dependency is not installed.

Anyway, this is what I did. You can install subprojects in Meson in the subprojects/ directory. You just need to create that folder and do:

Installing a subproject via a Meson wrap

If you want to get more information about an available wrap:

Getting available versions of catch2 package with Meson wrap

I was using C++20 but with gcc 9. Unfortunately there was not std::span yet in this gcc version. I used meson wrap again to install microsoft-gsl, which has a std::span-compatible version.

From there and a couple of minutes, after referencing the dependency in Meson, I could start the tests coding.

All I had to do is to add the code in src/TestDecoding.cpp and run the tests. I use a shortcut Control-c c P

The iteration time was a little bit shitty with Catch2, since compile times are slow. You can use doctest as an alternative. But in my case, I split the compilation.

You can see the final solution in the repository I will link at the end of this article. Basically, I separated the main function for Catch into a TestMain.cpp and iteration time improved a lot. Instead of waiting 6–7 seconds for compilation I just waited 1 second (approximately, not measured).

In Emacs you can issue a compile or a test command easily in Emacs via Control-p c c to compile and Control-p c P to test. You can also use more Vim-oriented commands in Doom Emacs (the default). But I use Emacs-oriented shortcuts since it is what I grew up with :).

The first time you execute the shortcuts it will ask you for the command to input. In my case I added once meson test -v and that did it for tests, which recompile code automatically. Now the workflow became something like change tests, press shortcut and run.

My TDD amounted to transform blocks of text and later decode a stream with the help of a pre-saved base85 payload in a file.

The workflow at this point was pretty fast and efficient and more than enough for my purposes. The code completion in Emacs worked well, the lenses for references were accurate and I could do rename refactoring, find references, search text (if I needed) etc. without pain. Things have improved a lot in this area compared to before, taking into account this is all free software and not a proprietary, paid solution.

Once all TDD iterations were done, and the tests were passing, I split things in a tests/ directory for the tests and a stand-alone library in src/ with its own library.

I could save my work via Control-x g in Git which shows the great Magit interface. This interface allows you from saving files to push to remotes or diffing files in an easy (but very emacs-y) way and it is what I have been using for years every time I use Emacs.

Once I had in place tests + a library, I added a benchmark that is the baseline. Meson supports adding benchmarks. The benchmark just takes a payload and it is a piece of cake to be added, basically same as tests. Meson will take care that tests are not run in parallel, as it does with the tests.

Conclusion

Project setup

Setting up emacs under Linux is no big deal. You just install it from your package manager via apt and you get Doom Emacs and follow the instructions.

Setting up lsp mode is not the easiest compared to using an environment such as Visual Studio or CLion. You have to setup some config. Fortunately, Doom Emacs is a bit more friendly that some alternatives and presents you with this screen at startup:

Doom Emacs startup screen

You have to open the private configuration to init.el and uncomment “(cc +lsp)”. If you want the lsp lens mode, you have to add it by yourself in config.el. I added these two lines since I also start emacs maximized:

(toggle-frame-maximized)
(add-hook! ‘lsp-mode-hook ‘lsp-lens-mode)

You need meson, so you need to install it via pip by doing python3 -m pip install meson. You also need sudo apt install ninja-build

You should not forget to have ccls running for lsp features. With that you should be ok to go. Projectile lets you navigate files, find text, with lsp you can jump to declarations, find references, highlighting of the variable under the cursor in all places in your file, etc. easily through some key bindings or the right-click contextual menu.

Developer workflow

Once the environment is set up, the workflow is fast and good. Emacs has improved a lot: you can have accurate code completion, semantic navigation, renaming and even right-click and discover all these options contextually:

Contextual menu to navigate, highlighting of same variable, result of tests run via Projectile and references lenses in Emacs
Code completion, even for external dependencies via compile_commands.json and lsp

The only thing I miss is some kind of execution tests panel like the ones you can find in Visual Studio Code or CLion, that let you execute tests easily and hierarchically by right clicking in a very visual way.

Iteration time

This was a first pain point regarding to Catch2 compilation times. Once solved, it was pretty ok. Compile times in C++ are something that is expected to be improved with modules but sometimes you need some mitigations. In my case using a separate main file for Catch was the solution and it worked quite well.

C++ modernization

C++ has been improving a whole lot over the years. When I started in 2002 C++98 did not have lambdas, range-based for-loops, move semantics, default member functions, smart pointers (though Boost had them way before C++11), copy elision, variable templates or concepts.

But I have to do a special mention to std::span (actually I used gsl::span in my own code). This class is so, so useful that I have no idea why it has not been there before. A correct combination of C++ features and algorithms such as std::copy and std::remove_if let me code the decoder in a way that I am confident it is reasonably efficient compared to what I can do by myself and quite useable (via lambdas for example for predicates). However, I still miss some kind of abbreviated lambdas in the language.

Algorithms have from optimizations such as triviality detection and other optimizations to the capability (though not exploited in my implementation) of parallelization with execution policies or the use of ranges. C++ has improved a lot in usability and efficiency over the years.

You can go way beyond what I did with this code, but I just scratched the surface. For example, multithreading the implementation and using coroutines could be applied to this problem.

Now modules, coroutines, concepts and ranges and others are coming and I cannot wait. It is getting better to the point that combining many of these features with smart pointers, spans and others, coding efficiently and almost to the application level seems more feasible than any time before.

Of course, this is C++ and you will have to think about copyability, slicing and a few other things. But it has gotten way better.

New-generation build tools

I chose Meson since this is what I am used to and after trying CMake. Meson has a package manager integrated, it is way easier to understand than things such as Autotools and it is portable to Windows so it is easier for collaborating as well.

All in all, things such as CMake (though I hate it in many ways) and Meson have improved this area a lot over the years. There is still a lot left to do. Modules should help here but, take into account that C++ is a native-compiled language in its current implementations. This poses difficulties to things such as package managers.

Nowadays with the small amount of Meson build system code I authored you get benchmarks, tests, dependency management and compilation in many different ways (debug, release, sanitizers, valgrind, benchmarks, static vs dynamic msvc runtime, generate either static or shared libs and way more…) with just a handful of very small and readable files, all portable.

You can generate projects for Visual Studio, ninja and others and there is compile_commands.json file generated for integrating with code completion frameworks. This is definitely light-years ahead from what I had when I started coding.

How easy is further on-boarding for other people to collaborate?

Nowadays we have Github for setting up projects and publishing. I skipped that setup in the article since it was the last step.

Collaboration workflow of Github is very well-known (fork, pull-request workflow). This, packed with new generation tools such as Meson makes collaboration much easier than it used to be. You can use your project from Visual Studio in Windows the same way I used it from Linux with the Emacs setup I showed with exactly the same source tree.

You can also use CLion if you feel inclined using a compile_commands.json file as project file (and after adding a couple of scripts for build/debug) and get the full refactoring framework that CLion gives you, run and debug code and stay portable between Linux, Windows and Mac with the same environment (I tried this myself in another project). It is also ahead with the tests panel to run exactly the tests you want, something I miss in Emacs.

Definitely, things have improved a lot, though I miss direct Meson integration for IDEs. As of now, compile_commands.jsonplus a bit of glue or configuration gives very good results. I think CMake is ahead in the IDE integration area right now, but I just cannot come back since I find Meson so much nicer when I have to add things to build scripts.

Code navigation and refactoring

The code navigation gave a big step with lsp and backends such as cquery, ccls and clangd. I have been using ccls for my setup. Before, renaming was not viable except for text replacement, which is fragile.

Right now you can at least do semantic renaming, navigate to references, find overrides of functions, base-derived classes and so on from inside Emacs.

CLion offers best-in-class refactorings though I did not try the feature fully.

All in all, the experience seemed quite productive but it is not still as polished, as, let us say, Java or C# commercial IDEs. But compared to when I started coding, things have been improving steadily, especially the last 5 years or so.

Final words

The experience has been overall positive. Having used Visual Studio + Resharper for C#, the IDEs are not still on par with the best C# or Java IDEs but are much closer than years back. For example, in C# it is easy to remove unused using (equivalent of #includes more or less) and the code generation is top quality.

But all in all, I think you can get a professionally productive environment with the setup I had and some other similar setups that are close enough to the best environments so that you can consider coding in C++ even for application programming nowadays, if what you are looking for is high run-time efficiently.

If you want to be cross-platform regarding to development environment setup, I would recommend considering a CLion license. It is not that expensive and it works very well. I will definitely purchase it at some point. But for a Linux-only environment or a Windows-only environment I think going with Visual Studio or even Emacs is more than enough even for professional needs.

You can find the Github project here.

Thanks for reading!

--

--