7 March 2025

Developing C++ code simultaneously for desktop and web with cmake

In this technical entry I give a summary on my current software projects that aimed to support multiple platforms (Windows, macOS, Linux) and the web. The mentioned code has been developed mostly in C++.

I present two projects: the XaoS project (which is a fractal visualization software, created many years ago by Jan Hubička and Thomas Marsh), and bibref (which is a Bible study tool). In both cases, the program is written in C++. For XaoS, the user interface uses the Qt framework. For bibref, there is a Qt interface for the desktop version, but for the web version, a terminal based user interface is provided.

In this blog entry I focus on writing cmake configuration files that are (mostly) transparent to both scenarios: the desktop application or the web version. For XaoS, we study the CMakeLists.txt file of version 4.3.3. In the case of bibref, we discuss the content of version 2025Mar06.

The cmake utility has growing popularity in the software development process. During the last years, it has been significantly improved. For example, the Qt developers worked together with kitware, the company behind cmake. In fact, configuring cmake is not always trivial, the documentation is sometimes too verbose, but at the end of the day, it is possible to solve most of the challenges without introducing non-standard solutions or workarounds.

Short and maintainable config files

One of the most important questions is how a configuration file looks like. Is it possible to cover all situations with a short config file, or do you need to tinker the settings for the special cases manually? In the case of XaoS, special options have been defined with the keyword option on rows 97-98: the user can set if the deep zoom feature or the OpenGL build will be enabled. These settings are identified and handled later on rows 100-108. In the case of bibref, the settings are strings, they need to be handled a bit differently and with more work.

Detecting platforms is very crucial. Luckily, each platform defines some environment variables to make the detection possible. For example, identifying a Mac is possible like this or this. Unfortunately, sometimes there are multiple ways to achieve the same task, and this leads to a wide variety of implementation techniques and different cmake configurations. Why unfortunately? Because future maintenance of this diversity can be laborous.

Writing cmake configs seem to allow the maintainer some freedom. For example, if-commands may be closed with endif with or without an argument which corresponds to the argument of the if-command (and it makes the code easier to understand). On the other hand, in some cases, cmake is case sensitive: for example, if the library sword had been written in uppercase here, it would not be found during the build process. According to the cmake documentation, variable names are case sensitive, but commands are case insensitive, and this can make a kind of confusion for newcomers.

Finding packages

For XaoS, the primary prerequisite is Qt. Qt comes with a large amount of internal components like Widgets and PrintSupport, or OpenGL and OpenGLWidgets (the latter ones are important for the OpenGL build). For bibref, the Sword library, the Boost C++ extension, and for the command line version, the readline library are the dependencies, and, also, the flex/bison machinery is required to parse the BRST files.

Finding these dependencies are sometimes straightforward via the find_package command, but sometimes not. In some cases, a pkg_check_modules command is also required to set some extra variables automatically that could be important during the build. Interestingly, for the web build (via emscripten), it is not possible to detect the Boost library automatically, but there is a CXX flag, -sUSE_BOOST_HEADERS, it has to be set separately. These minor (but annoying) details will hopefully be unified in the future.

Linking libraries (statically or dynamically to the executable) is another important detail. Luckily, cmake comes with a sophisticated configuration system that provides the command target_link_libraries, and to fine-tune the linking process, the command target_link_options. Depending on the target platform, these settings could be completely different, for example, in the bibref project, a simple line is enough to link the final executable; on the other hand, for the emscripten build one may need something more complex.

Web builds

For both projects, it was crucial to use a virtual file system when running the program in a web browser. Since web applications have no access to the local filesystem, the external files that may be important for the program (for instance, the fractal examples for XaoS, or the Bible databases for bibref) should be stored in a different way. The emscripten tool comes with a sophisticated solution that can be well configured via the --preload-file linker option.

The cmake utility was originally designed for supporting C/C++ builds, but here we use it for an HTML/Javascript/WebAssembly compilation and installation. At the moment, the final steps like copying the executables to the installation folder, need to be done manually. Hopefully, there will be a simplified way to do this transparently in the future.

Simplicity

What I really like in cmake is its simplicity for those user who just want to compile the code and that is it. For example, a native build can be performed with a mkdir build && cd build && cmake .. && make command line. The same for an HTML/Javascript/WebAssembly build is the same but changing cmake to emcmake cmake (sic!). (Formerly, make had to be changed to emmake make, but this is no longer required.) This magic works on all major platforms: cmake is there and waiting for orders.


Continue reading…

See also a filtered list of the entries on topics GeoGebra, technical developments or internal references in the Bible.


Zoltán Kovács
Linz School of Education
Johannes Kepler University
Altenberger Strasse 69
A-4040 Linz