CMake Best Practices

3 minute read

Motivation

CMake is a great tool for C++ developers; especially those working on cross platform apps/tools. It’s also great because it abstracts the project setup to instead live in a CMakeLists.txt rather than having to manually setup a Visual Studio solution (.sln) or a Makefile.

So how can you make the most of CMake and better organize your C++ project? Well, let me share what I’ve learned over the years.

Introduction

Say you have a huge C++ project you’re working on which is backed by CMake; awesome! Your problem now is that the CMakeLists.txt file of said project is getting really, really long and you don’t know what to do about it.

Don’t worry, I’ve got you covered. In this brief post I’ll share what I’ve found to be the best way to structure your large CMake based project so that it’s easier to:

  • Add new features/modules
  • Maintain
  • Refactor
  • Update

Sounds great right? I should note that what I present here as “best practices” are not backed by other than my experience and ultimately, my personal preference/opinion. Take everything with a grain of salt…or pepper. Or both!

Proper Project Structuring

In a large CMake based project, you typically have many find_package() calls, many variables created by set() and possibly a large number of configuration options for different build variants or flags using option().

The first thing you can do to help clean up your project is use a cmake folder in your top level directory and split that long CMakeLists.txt into smaller, more manageable .cmake files.

Below is what I usually separate into separate .cmake files:

  • Find modules (Find*.cmake files)
  • Install rules (Install<Target>.cmake)
  • Options (<ProjectName>Options.cmake)
  • Installer generation settings (CPackConfig.cmake)
  • External project additions (External<ProjectName>.cmake)

Then to make all your cmake files available to your project, in your top level CMakeLists.txt just add the following line:

list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

And now you can use your *.cmake files by including them (i.e. include(<ProjectName>Options). You can find a small example here.

While this does result in a lot of files, it does make everything very neat and nicely contained. Seperation of concerns is generally good practice and the same is true here.

I even have a separate file that holds the current version of the software I’m working on (typically using semantic versioning). This way, if I need to update the version number of the project; again I know exactly where to go.

Libraries and Sub-Projects

Bottom line: for big projects, use libraries and sub-projects within your main project. I cannot stress this enough. You will be so happy that you’ve modularized your project before it blows up. There’s usually a natural divide between front end and back end. Use that to your advantage and then take those two halves and split them more (to the point that makes sense). At a certain point, separating things out too much can actually become a hinderance.

This blog post will give you a great intro into a great way to setup library projects and good directory structures in large projects.

Let me present to you what works for me. On most projects that I work on, there is a main “application” (i.e. the front end GUI the user sees) and then a number of back end libraries and sub-projects the main application depends on. I separate these into different directories and have each directory be it’s own self contained cmake project so that I can copy and paste that directory (if need be) into another project and easily start using it.

So your project directory could look like this:

top level directory/
    cmake/
    application/
        include/
            app/
		src
		CMakeLists.txt
    utilities/
        include/
          utilities/
        src/
    	CMakeLists.txt
CMakeLists.txt

Note that the structure above is in line with the suggestions in the blog post I linked to above. What I like about it is:

  • All includes are namespaced by nature of the directory structure which makes it clear where an header file is coming from (so if you include a file from the utilities sub project, your include looks like #include <utilities/myfile.h>. )
  • All subprojects are their own self contained CMake project. You can copy and paste the whole directory into another project and then simply use add_subdirectory() in your other project to start using the library.

Conclusion

CMake is a powerful development tool and can help you organize your larger projects with ease. To get the benefits though, you still have to do the work of properly structuring your project. Trust me; it’s definitely worth it.

If you enjoyed this article or found it useful please share it! Be sure to subscribe to my feed (link in the footer of every blog post) to make sure you don’t miss another post. Have questions? Leave a comment!

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...