A Beginner Dive into CMake

How to easily setup a Cross Platform C/C++ project

If you are a C/C++ developer you already know that the ecosystem has a few problems compared to higher-level languages. The biggest one is the lack of a concrete build system. Languages like Rust and Java have native build systems that take care of creating your libraries and executables but all C++ has is a compiler and a lot of separate build system projects maintained by other people. I choose to work with CMake. Although I know everyone has mixed feelings about CMake I enjoy the workflow. Today I will run through a quick CMake project that contains a Static Library Project and an Executable Project.

CMake Structure.PNG

In the image above I am showing what a basic CMake projects structure looks like. Let's break it down. In a folder, we have the top-level CMakeLists script. Which by the way is how we use CMake. By making a lot... and I mean a lot, of CMake scripts. Next, we have a "Lib" folder which will hold our Static Library project and an "Exec" folder which will hold our Executable Project. Let's take a look at what our top-level script looks like:

cmake_minimum_required(VERSION 3.2)

project(Blog)

add_subdirectory(Lib)

add_subdirectory(Exec)

This is by far the most simple CMake script you will ever see! The only thing we do in this script tells CMake which version is required, name the top-level project, and then tell the script to look for more scripts in the sub-folders Lib and Exec. Now let's see what our Static Library's script looks like:

cmake_minimum_required(VERSION 3.2)

project(Lib LANGUAGES CXX)

add_library(Lib lib.h lib.cpp)

target_include_directories(Lib 
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/>
PRIVATE
)

Not much has changed with this script but let's go over what has! To start we are now telling CMake what languages this library will consist of. We do this in the project() function by saying 'LANGUAGES" followed by the languages we want to use. Next, we use the add_library() function to create our library. The first parameter is the name of the compiled library and the next parameter are all the files to be compiled. Now for the more complex-looking part!

  • The target_include_directories() function is a very important one.

  • Not only can this be used to include other libraries' headers into our project it can also be used to "Install" our headers into a project linking with us using CMake!

This is the beauty of CMake, being able to effortlessly build multiple projects; link them all together and find the headers. To create the include interface we add

$<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/>

to the include directories function. The First line simply states which type of interface we are installing into our library and the second line says where our "include" files are. For this example, I set it to the current directory this script is in (God Bless the CMake macros!). That's all for the Static Library now we can move to our executable. Spoiler alert, IT IS SO SIMPLE.

cmake_minimum_required(VERSION 3.2)

project(Exec LANGUAGES CXX)

add_executable(Exec main.cpp)

target_include_directories(Exec 
PUBLIC
Lib
PRIVATE
)

target_link_libraries(Exec 
PUBLIC
Lib
PRIVATE
)

As you can see it is very similar to the library! Here are the differences:

  • We Call add_executable instead of add_library

  • Instead of building an Include Interface we just Include the "Lib" library

  • And we add a call to target_link_libraries() to link to the "Lib" library! And with that, we now have the scaffolding for our very own Cross Platform C++ Project!

build system.PNG