CMake Hands-On Tutorial
What is CMake
Basic usage
Let’s say we have the following sources
Here is a simple example of a CMakeLists.txt file that builds a hello world program:
This CMakeLists.txt file specifies that a single executable, hello, should be built from the main.c source file. Save this file in the same directory
To build the project with CMake, you can use the following steps:
Create a build directory and navigate to it:
Run CMake to generate the build system:
This will generate the necessary makefiles or workspaces for your project.
This will create the makefile which you can use to build the project
Now Build the project:
This will build the hello executable.
CMake also provides several advanced features, such as support for static and dynamic libraries, integration with testing frameworks, and the ability to generate project files for IDEs such as Visual Studio and Xcode.
Advanced Usage
Now let’s move on to a more complex project setup. We must learn this because CMake is mainly used for working with a very large codebase. This example although not very big but still larger than the basic example to demonstrate CMake's working method and it's benefits on Larger Codebase cases.
Let’s Say we have the following sources
The sources look like the following
Create an empty CMakeLists.txt file and a build directory
The CMakeLists.txt file looks like the following
Then run the cmake command like below and makefile will get generated
Then Run the make command to build the executable from the generated CMakeLists.txt
Then run the executable
Explanation (Step by Step)
Here is a brief explanation of each line in the CMakeLists.txt file that I provided in the example
This line specifies the minimum version of CMake required to build the project. It ensures that the version of CMake being used is compatible with the commands and syntax used in the CMakeLists.txt file.
This line specifies the name of the project.
These lines define two variables: EXECUTABLE_NAME and SOURCE_FILES. The EXECUTABLE_NAME variable specifies the name of the executable to be built, and the SOURCE_FILES variable specifies a list of source files that should be compiled to create the executable.
This line enables support for the C and C++ languages.
These lines use the file(GLOB_RECURSE) command to recursively search for header files in the include directory and store them in the HEADER_FILES variable. The include_directories command is then used to include these header files in the build.
This line adds an executable target to be built from the source files specified in the SOURCE_FILES variable. The executable will be named according to the value of the EXECUTABLE_NAME variable.
These lines use the target_compile_definitions and target_include_directories commands to set compile definitions and include directories for the target, respectively. The PRIVATE keyword specifies that the definitions and include directories are only for the target and are not propagated to other targets that depend on it
These lines use the target_compile_options and target_link_options commands to set compile and link options for the target, respectively. The PRIVATE keyword specifies that the options are only for the target and are not propagated to other targets that depend on it.
include_directories vs target_include_directories
include_directories is used to specify directories to be included in the build process for the entire project. It adds the specified directories to the included search path for all targets in the project.
On the other hand, target_include_directories are used to specify directories to be included in the build process for a specific target. It adds the specified directories to the included search path for a particular target, rather than for the entire project.
Here's an example of how you might use these two commands:
In this example, the include_directories command specifies that the common directory should be included in the include search path for all targets in the project. The target_include_directories command specifies that the appA directory should be included in the include search path for the appA target only.
So, to summarize: include_directories is used to specify directories to be included in the include search path for the entire project, while target_include_directories is used to specify directories to be included in the include search path for a specific target.