My First CMake

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

My First CMake

Post by cyboryxmen » June 5th, 2020, 1:35 pm

Open this folder in Visual Studio to start using CMake!

What's CMake? CMake is a build system designed to build C++ projects.

What's a build system? Well, you know how a C++ compiler creates an application from source(.cpp) files? Those source files also `#include` header files too. Question is, how does the compiler find those files in the first place? In fact, how does the compiler know to build the application using settings like "build in debug mode" or "Use this library"? Visual Studio users don't think about it too much as Visual Studio takes care of that for you. Underneath the hood, Visual Studio uses a tool called MSBuild that configures the compiler to build your application.

Before build systems were used, programmers would interface with the compiler directly by running commands like:

Code: Select all

compiler build-app "app.exe" -debug_mode -header_files "header.hpp" -source_files "main.cpp"
This obviously doesn't scale well once you start using multiple files and libraries with tons of settings so programmers use tools like build systems to automate the build process for them.

Unlike Visual Studio, CMake is more universal as it could work with multiple compilers and editors from Windows to even Linux. Plus, a lot of libraries use it meaning that if your project uses CMake, it can more easily intergrate those libraries.

CMake works with CMake projects defined by a CMakeLists.txt. These projects contain "targets" that can then be "linked" together to build a library, an application or an "interface library"(a target that builds no library, no application and is just a raw collection of settings to use). There are various commands you can put in a CMakeLists.txt but the ones in this project contain all the commands you need for most projects.

Let's start with the main CMakeLists.txt in the main directory.

Code: Select all

cmake_minimum_required(VERSION 3.11)
This is the first line of every CMakeLists.txt. It specifies the version of CMake the project uses.

Code: Select all

project(first-cmake VERSION 1.0.0 DESCRIPTION "A test to see if I can make a CMake Project" LANGUAGES CXX)
This should be the second line of your CMakeLists.txt. It takes the name of your project + some optional details. Those optional details are VERSION(Major.Minor.Patch.Tweak), DESCRIPTION, LANGUAGES like C and CXX(C++) and HOMEPAGE_URL(not shown here). The order you specify these optional details don't matter.

Code: Select all

# Build the math project.
This is a comment. It starts with a # and follows it up with some text. CMake just ignores comments so you can use them to write any sort of information you want in the file. Be sure that the text are all in one line.

Code: Select all

# Build the math project.
add_subdirectory("${PROJECT_SOURCE_DIR}/math")
This command tells CMake to include another CMake project to the build. `${PROJECT_SOURCE_DIR}` simply specifies the directory the current CMake project is in.

This is a common pattern where you have one main CMake project include all the other CMake projects. Other than that, the main CMake project doesn't have much else.

Let's take a look at the parser project. This project has a library called parser.

Code: Select all

# Declare the library.
add_library(parser)
This declares that the project has a library called parser. In CMake terms, parser is a target. More on that later.

Code: Select all

# Declare that the library uses C++17
target_compile_features(parser PUBLIC cxx_std_17)
This declares that the parser target uses C++17. Notice the `PUBLIC` keyword there. This means that any other CMake target that links with parser will inherit this setting. In other words, targets that use parser will also use C++17.

Code: Select all

# Declare our includes.
target_include_directories(parser
	PUBLIC
		"${PROJECT_SOURCE_DIR}/include"

	PRIVATE
		"${PROJECT_SOURCE_DIR}/internal"
)
This declares the directories where parser's header files are located in. Notice the keyword `PRIVATE`. Unlike `PUBLIC`, `PRIVATE` settings will not be inherited by other targets that link to parser. This is good for settings that are only used internally by the target and you don't want other targets to get them.

Code: Select all

# Declare the library's files.
target_sources(parser
	PUBLIC
		"${PROJECT_SOURCE_DIR}/include/parser/parser.hpp"

	PRIVATE
		"${PROJECT_SOURCE_DIR}/source/parser.cpp"
		"${PROJECT_SOURCE_DIR}/internal/internal/internal.hpp"
		"${PROJECT_SOURCE_DIR}/source/internal.cpp"
)
This lists all the files that the target has including source files and headers.


Now let's look at the math project. This project has a header only library. Since the library is header only(no source files), it needs to be declared slightly differently from regular libraries.

Code: Select all

# The library is a header only library. It won't be compiled.
add_library(math INTERFACE)
As you can see, the math library target is defined the same as the parser target but it has the `INTERFACE` keyword indicating that it is an interface library. Interface libraries don't build anything. They're just a collection of settings that other targets can inherit.

Header only libraries have to be interface libraries because header files don't actually get compiled by the compiler. They're just `#include`d into source files which are then compiled into an application. If you try to compile an application or library with no source files, the compiler will complain. As an interface library, other targets can link to it to inherit its headers.

The rest of the file is similar to the parser project but with the `INTERFACE` keyword.

Code: Select all

# Declare that the library uses C++17.
target_compile_features(math INTERFACE cxx_std_17)
# Declare the library's includes.
target_include_directories(math INTERFACE "${PROJECT_SOURCE_DIR}/include")
# Declare the library's files.
target_sources(math INTERFACE "${PROJECT_SOURCE_DIR}/include/math/math.hpp")


Finally, let's look at the calculator project which will build an actual application using these libraries.

Code: Select all

# Declare the executable.
add_executable(calculator)
This declares an executable target called calculator.

The rest of the commands are ones you've seen before except for this line here:

Code: Select all

# Declare the libraries this executable uses.
target_link_libraries(calculator
	PRIVATE
		math
		parser
)
This links the targets math and parser to calculator. This command is one of the most useful commands you'd use in CMake. Targets are basically just a collection of settings. Where your headers are located, what files your project has, which version of C++ it uses etc. You can store these settings into small individual targets and then link them together into a large target. With the keywords `PUBLIC`, `PRIVATE` and `INTERFACE`, you can control which settings will be inherited by other targets. This allows you to reuse settings by linking to specific targets saving you from having to set them up again. It's a bit like how C++ uses inheritance for code reuse.

That's pretty much it. This is all you need to create CMake projects and use other CMake projects people make. CMake is incredibly simple to use when used right and with additional commands, you can do incredible things like building different applications for different platforms(Windows, Linux, Android) with one project or automate thousands of tests that debug your application the moment you build it. CMake gives you much more flexibility than what the Visual Studio settings allow.

To learn more about CMake, check out the documentation here:
https://cmake.org/cmake/help/latest/gui ... index.html
Zekilk

albinopapa
Posts: 4169
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: My First CMake

Post by albinopapa » June 5th, 2020, 3:58 pm

If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

Re: My First CMake

Post by cyboryxmen » June 6th, 2020, 3:21 pm

I really love how Visual Studio automatically changes the CMakeLists.txt when you add/delete/rename files. Makes the whole process so much more convenient.
Zekilk

trojanfoe
Posts: 1
Joined: June 25th, 2020, 6:16 am

Re: My First CMake

Post by trojanfoe » June 25th, 2020, 6:19 am

If you are using cmake then you can manage all your external library deps using Microsoft's vcpkg (https://github.com/microsoft/vcpkg).

Once you install the libraries you want to use and run "vcpkg integrate install" then those library headers and lib/dll files become magically available to your project. It even copies the dll files into the $(OutDir) for debugging.

This can save a lot of time.

I use it for DirectXTK (which everyone doing DirectX11 should be using) as well as entt, zlib, pugixml and fmt.

Post Reply