Good evening everyone, and I wish you all a happy Dragon Boat Festival. Today, I would like to share with you the project management tool CMake. There is no prior introduction to the Makefile project management tool (to be honest, these two are quite similar; CMake ultimately generates a Makefile, but the syntax of CMake is somewhat simpler and less complex than that of Makefile!). Based on my personal experience, nowadays, most companies rarely write Makefiles or CMake files (of course, you can still see this skill requirement on job websites, so being able to write it yourself is ideal!). Generally, they directly use the Makefile or CMake provided by the vendor; however, as a learning exercise, it is still essential to study the principles behind it. For instance, if an error occurs, you need to be able to locate and resolve it, as the error may lie within the configured Makefile or CMake. Therefore, you should understand the meaning of the code within (this means that you know how to use the tool, but you should also understand its underlying principles to know why it works!). Only then can you solve the problem.
Moreover, in my opinion, traditional Linux project management often uses Makefiles (U-Boot also extensively uses Makefiles for project management); in newer fields, such as Internet of Things (IoT) development (especially in some open-source projects), CMake is more commonly used (though there may be exceptions!). Now, without further ado, let’s start learning:
1. Using CMake:
1. Installing CMake management tool:
In practical embedded Linux development, Ubuntu is almost always used for development because it is very convenient to install application programs; you only need one command: “apt install + application name” to get most of it done without additional configuration (with special cases requiring some other related configurations!). Installing CMake is done with a single command:
root@txp-virtual-machine:/home/txp# apt install cmake
Reading package lists... Done
Building dependency tree
2. Let’s start with a simple example to derive the general writing steps:
Here, I will first write a simple code project main.c, and then we will use CMake to manage the code project:
#include <stdio.h>
int main(void)
{
printf("TXP Embedded\n");
return 0;
}
Then, let’s write the CMake project management file. I will create a CMakeLists.txt file in the current directory and start writing the project management code inside it.
root@txp-virtual-machine:/home/txp/test# pwd
/home/txp/test
root@txp-virtual-machine:/home/txp/test# touch CMakeLists.txt
root@txp-virtual-machine:/home/txp/test# ls
CMakeLists.txt main.c
The contents of the CMakeLists.txt file are as follows:
cmake_minimum_required (VERSION 2.8)
project (main)
add_executable(main main.c)
Let me explain what these three statements mean:
1. Indicates that the minimum version of CMake required to manage our project is 2.8.
2. Indicates that the project name is ‘main’.
3. Indicates that the final executable ELF file will be named ‘main’ and the source file used is ‘main.c’.
Now, let’s implement the functionality of CMake. In the current directory, use the command “cmake .” (where . indicates the current directory, and .. indicates the parent directory) to generate the Makefile and other related files. Then execute the make command to compile the project, which will generate the executable file ‘main’, and a Makefile will also be generated. Finally, we can execute the ‘main’ executable file to get the desired result:
root@txp-virtual-machine:/home/txp/test# cmake .
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test
root@txp-virtual-machine:/home/txp/test# ls
CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt main main.c Makefile
root@txp-virtual-machine:/home/txp/test# make
Scanning dependencies of target main
[100%] Building C object CMakeFiles/main.dir/main.c.o
Linking C executable main
[100%] Built target main
root@txp-virtual-machine:/home/txp/test# ./main
TXP Embedded
If you are curious, you can take a look at the contents of the Makefile; it is easy to see that it is used to manage the project ‘main’ using Makefile. I won’t go into detail and paste the source code here. The cmake_install.cmake contains some related configuration options:
# Install script for directory: /home/txp/test
# Set the install prefix
IF(NOT DEFINED CMAKE_INSTALL_PREFIX)
SET(CMAKE_INSTALL_PREFIX "/usr/local")
ENDIF(NOT DEFINED CMAKE_INSTALL_PREFIX)
STRING(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
# Set the install configuration name.
IF(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
IF(BUILD_TYPE)
STRING(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
ELSE(BUILD_TYPE)
SET(CMAKE_INSTALL_CONFIG_NAME "")
ENDIF(BUILD_TYPE)
MESSAGE(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
ENDIF(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
# Set the component getting installed.
IF(NOT CMAKE_INSTALL_COMPONENT)
IF(COMPONENT)
MESSAGE(STATUS "Install component: \"${COMPONENT}\"")
SET(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
ELSE(COMPONENT)
SET(CMAKE_INSTALL_COMPONENT)
ENDIF(COMPONENT)
ENDIF(NOT CMAKE_INSTALL_COMPONENT)
# Install shared libraries without execute permission?
IF(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
SET(CMAKE_INSTALL_SO_NO_EXE "1")
ENDIF(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
IF(CMAKE_INSTALL_COMPONENT)
SET(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
ELSE(CMAKE_INSTALL_COMPONENT)
SET(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
ENDIF(CMAKE_INSTALL_COMPONENT)
FILE(WRITE "/home/txp/test/${CMAKE_INSTALL_MANIFEST}" "")
FOREACH(file ${CMAKE_INSTALL_MANIFEST_FILES})
FILE(APPEND "/home/txp/test/${CMAKE_INSTALL_MANIFEST}" "${file}\n")
ENDFOREACH(file)
~
And the CMakeFiles is a folder, with the following contents:
root@txp-virtual-machine:/home/txp/test/CMakeFiles# ls
2.8.12.2 cmake.check_cache CMakeDirectoryInformation.cmake
CMakeOutput.log CMakeTmp main.dir Makefile2
Makefile.cmake progress.marks TargetDirectories.txt
Summary:
From the above simple example, we can see that all CMake statements are written in a text file named “CMakeLists.txt”. Therefore, the complete steps to manage a project using CMake are as follows:
1. First, create a CMakeLists.txt file (just like we use a Makefile to manage a project, there is a special file for writing project management code; CMake is no exception, using the CMakeLists.txt text file for writing code to manage the project).
2. Then write configurations in CMakeLists.txt; for simple configurations, refer to the above format. We will demonstrate and explain more complex configurations below.
3. Next, use the command “cmake .” (this command must be executed in the same directory as CMakeLists.txt) to generate Makefile and other files.
4. Finally, use the make command to compile the project and generate the executable file.
3. Having multiple source files in the same directory:
Here, we will add two more files in the current directory: test1.c and test1.h. The content of test1.c is as follows:
#include <stdio.h>
#include "test1.h"
void func(int a)
{
printf("a=%d\n",a);
}
The content of test1.h is as follows:
#ifndef _TEST1_H
#define _TEST1_H
void func(int a);
#endif /* _TEST1_H */
Then, we will call the function func() in main.c:
#include <stdio.h>
#include "test1.h"
int main(void)
{
func(6);
printf("TXP Embedded\n");
return 0;
}
At this point, we certainly need to modify the writing format in CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (main)
add_executable(main main.c test1.c)
The final result is as follows:
root@txp-virtual-machine:/home/txp/test# cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test
root@txp-virtual-machine:/home/txp/test# make
Scanning dependencies of target main
[ 50%] Building C object CMakeFiles/main.dir/main.c.o
[100%] Building C object CMakeFiles/main.dir/test1.c.o
Linking C executable main
[100%] Built target main
root@txp-virtual-machine:/home/txp/test# ls
1 CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt main main.c Makefile test1.c test1.h
root@txp-virtual-machine:/home/txp/test# ./main
a=6
TXP Embedded
Summary:
You will notice that in the above CMakeLists.txt writing, the statement add_executable(main main.c test1.c) includes an additional source file (you can think of the source code as raw materials for processing, which is also how target file generation is understood in Makefile!). Therefore, if you want to add more source files to the current directory, simply add them in the third statement of the CMakeLists.txt text file. However, this method has a drawback; for instance, if there are hundreds of source files in the current directory, you cannot manually type them one by one, or else you would be back in the primitive era, which would not showcase the advantages of CMake! More usages will be explained in the next session!
If you want to join a group for discussion, you can add the author on WeChat and reply with ‘technology’. No advertisements are allowed in the group!