CMake is a universal build system that allows you to create C++ projects that can be used by various programs. A large variety of programs use it to develop and build C++ projects including CodeBlocks, CLion, Eclipse, MinGW, Sublime Text and of course, Visual Studio. As a result, your C++ project can be used by any program other than Visual Studio by simply using CMake instead of a solution file. It'll also work across different Visual Studio versions so you no longer have to worry about opening solution files that are made in a different version of Visual Studio!
A CMake project is based around a CMakeLists.txt. With Visual Studio 2017, you can now open these CMakeLists.txt directly using their CMake plugin!
Here is a simple example of a CMake project that you can try to open with this new plugin. It should be a good example of a standard CMake project while also being simple enough to show you all you need to know to use CMake. Let's take a look at the main CMakeLists.txt file in the root folder.
Code: Select all
cmake_minimum_required(VERSION 3.11)
project(first-cmake VERSION 1.0.0 DESCRIPTION "A test to see if I can make a CMake Project" LANGUAGES CXX)
# Build the math project.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/math")
# Build the calculator project.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/calculator")
# Build the parser project.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/parser")
The first line specifies what version of CMake the file uses. The latest version is 3.12 but 3.11 is the latest one Visual Studio 2017's CMake plugin supports.
Each CMakelists.txt represents a CMake project. The next line defines what the project is. You can specify the project name, description, version and the languages the project uses. All of the CMake projects in this example is set to use The C++ language(CXX).
The next couple of lines will add additional CMake projects to the build. We do this by specifying the folder the additional CMakeLists.txt(s) are located in. This is what allows us to build multiple CMake projects using only one CMakeLists.txt. ${CMAKE_CURRENT_SOURCE_DIR} will give out the location of the current CMakeLists.txt. By using this instead of hard-coding a fixed location, we can shift the entire CMake project around anywhere we want and it'll still build!
Now let's look at the other CMake projects.
Code: Select all
cmake_minimum_required(VERSION 3.11)
project(math VERSION 1.0.0 DESCRIPTION "A basic header only math library" LANGUAGES CXX)
# The library is a header only library. It won't be compiled.
add_library(math INTERFACE)
# 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 "${CMAKE_CURRENT_SOURCE_DIR}/include")
# Declare the library's files.
target_sources(math INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include/math/math.hpp")
We just need to set its "properties" like the version of C++ it uses and the files used in the library. These properties define how the library target is going to be used by other targets. The math library is set to use C++17 and all of its include headers is located in the folder "${CMAKE_CURRENT_SOURCE_DIR}/include". The last line defines what files are part of the library. Right now, <math.hpp> is the only file in the math library. In Visual Studio, this line is important as it defines what files are given Intellisense for the math project. For non-INTERFACE targets, it also defines what to compile too. You might also notice the keyword INTERFACE is used when setting these properties. This is required for INTERFACE targets like the math library. I'll explain this keyword later in more detail when it becomes relevant.
Now onto the next CMake project.
Code: Select all
cmake_minimum_required(VERSION 3.11)
project(parser VERSION 1.0.0 DESCRIPTION "A simple parser library" LANGUAGES CXX)
# Declare the library.
add_library(parser)
# Declare that the library uses C++17
target_compile_features(parser PUBLIC cxx_std_17)
# Declare our includes.
target_include_directories(parser
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/internal")
# Declare the library's files.
target_sources(parser
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include/parser/parser.hpp"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/source/parser.cpp"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/internal/internal/internal.hpp"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/source/internal.cpp")
Keywords like PUBLIC, PRIVATE and INTERFACE are scope specifiers. They set whether these properties "transfer" to other targets that link to it. PUBLIC means that the properties apply to the target and other targets that link to it. PRIVATE means that the property only applies to the target and targets that link to it are not affected by them. Finally, INTERFACE properties will not affect the target but will affect targets that link to it. You can take a look at how these properties are set to get a feel of which scope to use for which property depending on the situation.
Now let's take a look at our last CMake project.
Code: Select all
cmake_minimum_required(VERSION 3.11)
project(calculator VERSION 1.0.0 DESCRIPTION "A simple calculator application" LANGUAGES CXX)
# Declare the executable.
add_executable(calculator)
# This executable is compiled with C++17.
target_compile_features(calculator PRIVATE cxx_std_17)
# Declare the libraries this executable uses.
target_link_libraries(calculator PRIVATE math PRIVATE parser)
# Declare the executable's includes.
target_include_directories(calculator PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
# Declare the executable's files.
target_sources(calculator
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/command-line-calculator.hpp"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/source/command-line-calculator.cpp"
PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/source/main.cpp")
Additionally, you can see how linking works in CMake here using the target_link_libraries() command. This command is what allows you to link targets like "math" and "parser" to the current target, "calculator". As you can see, we can link "math" and "parser" to "calculator" even though they are not specified anywhere in the file. This is where the beauty of CMake with targets shine. Remember when the first CMake project adds all of these projects into one build? By doing that, it'll add the targets they define to the build too allowing them to be linked later. The calculator project does not need to know about the other projects to use their targets. It doesn't need to know where they are located, what targets they define nor what properties those targets have. All it needs is this one line:
Code: Select all
target_link_libraries(calculator PRIVATE math PRIVATE parser)
That's all you need to know to do everything you're already doing but better using CMake! I'm going to use CMake to build by C++ projects from now on. The portability of it is nice but I also find it easier to configure my projects with them than editing the project settings in Visual Studio. It's certainly makes it easier to manage multiple libraries in a project.
To read more about CMake, click here!