Summer Adventure 2018: Learning CMake #1

In software development, we follow a simple pattern. Find zero or more external libraries to make life easier; write our own custom code; compile/interpret/link everything together to make a complete application. Everything else we do is a variant of this pattern (construction-wise), and it prima facie it appears extremely easy for someone to just come in and pick up. But as with most things worth doing, the devil is in the details. First a language must be chosen, compiler/interpreter flags used appropriately, directory structure intuitive, etc. Build systems simplify this, but there is always a learning curve associated with how complex that system should be. CMake has been no different. CMake is a common build system that allows for configuration of a build system to a specific platform, and it generates the native build files which are then built with usually Makefiles on *nix, and a Visual Studio solution on Windows. As a complete novice to this ecosystem of software, I dove into the CMake authors' own tutorial here.
   The first thing I had to grasp is CMake tracks two different tree structures: a source-tree that tracks the source code of your application, and a build tree where compiled code goes. If the trees occupy different directory structures then it is called an out-of-place build, else it is an in-place-build. Since I hate clutter in my projects I always do out-of-place builds. My first line of inquiry was into how these trees are built. The answer lies in the CMakeLists.txt files you place, first at the root, and the in subdirectories as the project. In a CMakeLists.txt file any time you use add_subdirectory(), the subdirectory must contain one of the .txt files and then that forms the link in the tree. By default it would seem the CMake variables *_SOURCE_DIR and *_BINARY_DIR default to the directory the CMakeLists.txt file is. For instance, the PROJECT_SOURCE_DIR is set to the directory the most recent project() has been used, CMAKE_BINARY_DIR is set to the directory that CMake was ran in and if not used out-of-place then it is the same as CMAKE_SOURCE_DIR. The most poignant fact is that CMAKE_CURRENT_SOURCE_DIR is always the directory of wherever the current CMakeLists.txt is being processed. So the flow is cmake begins with the root .txt file, and when it encounter add_subdirectory is goes onto that node in the tree and processes it, setting the appropriate variables as it travels. This post explains this more in depth, since I know I'm just spewing my thoughts rather incoherently.
   This was a major understanding for me as all the examples I had looked at used these variables seemingly interchangeably and it was very confusing. When it comes time to organise directory hierarchies making sure that I use the correct variable is invaluable.
   The last thing I followed on this day was that you can define your own and global variables using the set() command. Leaving aside the global variables, by defining your own variables you can use them in conditional logic in the various CMakeLists.txt files, but you can also auto-generate header files that have been templated and cmake interpolates these variables into placeholders in the header templates. Very useful.

I want to confirm my theory about the the various DIR variables, so I am going to use this header interpolation to print out the variable names in a C++ program. One of the many nuances of computing is that we can play and muck around when exploring and no-one gets hurt!

Till then.

Comments

Popular Posts