Use CMake

Over the holiday I had some time off so I decided to use a day researching the web to finally build a good foundation of knowledge for myself on CMake.

After diving in for a third time I will say it is a very powerfull meta build system that has a slight learning curve but surpasses hand crafted make files by miles. I was able to find some promising intro videos on youtube and many answers on stack overflow but after some time a usable workflow evolved. While I am still making sense of the CMake land my skills are shaping up.

CMake cmake.org

CMake is a wonderful meta build system for your programming projects. Something I highly recommend you start using immediately. It has proven help full to me when defining a structure and organization to how I layout my source code directories yet flexible and readable enough to stray where needed.

Here are a few concepts to keep straight when using and writing CMakeLists.txt file's that will help in the long run.

Think of every directory as a sub project

In each directory create a new CMakeLists.txt file where you can define a different project name for that sub directory using project( project_name ). Try to do this for each of your sub directories. It will force you to define the project layout in a more modular fashion making it cleaner and easier to follow. Remeber to include them in the root project CMakeLists.txt using add_subdirectory( sub_directory_name )

Your build system can have a GUI

Drop your elitist views, it's time to move on. Whether it is in the windowing GUI cmake-gui or ncurses command line ccmake it make sense to embrace the ability to visualize and set your build options. The gui capabilities allow you to set advanced options that you normally will not define in your CMakeLists.txt file and are definable by default. As an example including the X11 CMake module will give you up to 60 more compile options to choose from. Just click the checkbox and look inside. Here are examples of some of the flags the gui apps will let you set.

  • What C compiler to use

  • CMAKE_C_COMPILER

  • What C++ compiler to use

  • CMAKE_CXX_COMPILER

  • Specify the release flags over defaults

  • CMAKE_C_FLAGS_RELEASE

Configuration State Is Saved

After you have defined the configuration options and CMake generates the build environment all the settings used are saved off into the CMakeCache.txt file. You can edit the options contained within then reuse the file for future builds. No more hunting through your history for the build options you used just rerun CMake with

cmake -C CMakeCache.txt

Build Is Separate from Source

CMake follows a philosophy that the build of a project should be separated from the source code of the project. This helps keep the source tree clean and tidy. You could say “I am using xyz scm, why should I care? It will clean it up for me.", I would say your thinking is flawed and is merging the work flow of source code editing with the compilation process. Should the two overlap, “how may files did I have to commit before cleaning up this repo?". Trust me it is better to separate the two work flows.

In our my-program dir

$my-program> mkdir build
$my-program> cd build
$my-program> cmake ../
$my-program> # Magic happens and build is generated
$my-program> make -j8  # or whatever number of cores you have
$my-program> ./my-program

Extension Through Custom Modules

Everything is a file that can be included from anywhere else. You may create custom modules that are easily added to your project and used to set up variables to use throughout the CMake state. Try to layout your projects with a directory “CMakeModules” to keep any custom project CMake files.

set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules")

In Conclusion

I enjoyed CMake's professional look and feel. When using and designing the layout of my projects source tree I felt there was a more solid design emerging while using CMake. I am forced to think in a modular fashion when designing how individual items of the project are put together. And now I want to break up my projects that way because CMake empowers me to do so.

Looking around it seems everyone is using it… (do it) My first intro was through QT Creator but it turns out there are plenty other tool chains you can use with CMake. CMake supports many generator types for a variety of native build systems. Some examples are Visual Studio, Ninja, Unix makefiles, and Eclipse.

My experience with CMake reminds me of the way I should approach cutting a rose from its bush. With all the power and beauty that surrounds CMake the thorny center that you first grasp it by will prick you big time if your not careful. Turns out the word recipes is a perfect name when referring to CMake liststxt files, everyone like a chef has their own way of doing things. There is no clear cut way to actually put together the CMake liststxt files but once you get a flavor that works for you it will be much easier to read and maintain. I learned a lot by trial and error looking at examples and referencing the documentation. The two projects that I originally started to manage using CMake I went back and rewrote all their CMakeLists.txt files. Granted there wasn't alot to go over but if you don't spend the time up front to learn it you may suffer in the long run.

Follow up's

There are many gotcha moments you will run into. Most of this is because of the complexity encountered when using cmake.

One item that was of concern is using IMPORTED Targets in your cmake files for setting includes and libraries. Heed the warning in the 2nd answer posted here: https://stackoverflow.com/questions/70314595/cmake-cant-link-glut-library-after-system-update

# Don't expand package variables into directory commands, like this
target_link_libraries(your_target ${GLUT_LIBRARY} )

# Always use imported targets, like this
target_link_libraries(your_target GLUT::GLUT)