cMake practical tutorial, easy to learn cMake

        During the process of learning cMake, I found an article written in a simple and easy-to-understand manner, and took it to take notes.

        CMake is an open source, cross-platform build tool that allows us to generate a local Makefile by writing a simple configuration file. This configuration file is independent of the operating platform and compiler, so we don’t have to write the Makefile ourselves, and the configuration file It can be directly used on other platforms without modification, which is very convenient.

This article mainly describes how to use CMake to compile our programs under Linux.

1. Install CMake
This article uses ubuntu18.04. To install cmake, use the following command.
After
the installation is complete, enter cmake -version in the terminal to view the cmake version.

Write picture description here

In this way, cmake is installed.

2. Simple example
First, let us start with the simplest code and experience how cmake operates. Write main.c, as follows,

#include <stdio.h>

int main(void)
{
	printf("Hello World\n");

	return 0;
}

Then write CMakeLists.txt in the same directory as main.c, the content is as follows,

cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c)

The first line means that the minimum version requirement of cmake is 2.8, and we installed 3.10.2; the second line indicates the project information, that is, the project name is demo; the third line is more critical, indicating the final elf to be generated The name of the file is main, and the source file used is main.c.
Cut to the directory where main.c is located in the terminal, and then enter the following command to run cmake. cmake
will
output the following information

Write picture description here

 Look at the files in the directory again

Write picture description here

 You can see that the Makefile was successfully generated, and there are some files automatically generated when cmake runs.
Then enter make in the terminal and press Enter

Write picture description here

 You can see that the Makefile generated by executing cmake can display the progress with colors. Look at the files in the directory

Write picture description here

 You can see that the elf file main we need is also successfully generated, and then run main

Write picture description here

 Run successfully!

PS: If you want to regenerate main, enter make clean to delete the main elf file.

3. Multiple source files in the same directory
Next, enter a slightly more complicated example: there are multiple source files in the same directory.
Add 2 files in the previous directory, testFunc.c and testFunc.h. After adding, the overall file structure is as follows

Write picture description here

 The content of testFunc.c is as follows

/*
** testFunc.c
*/

#include <stdio.h>
#include "testFunc.h"

void func(int data)
{
	printf("data is %d\n", data);
}

The content of testFunc.h is as follows,

/*
** testFunc.h
*/

#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_

void func(int data);

#endif


Modify main.c, call the function func() declared in testFunc.h,

#include <stdio.h>

#include "testFunc.h"

int main(void)
{
	func(100);

	return 0;
}


Modify CMakeLists.txt, add testFunc.c in the parameter of add_executable

cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c testFunc.c)


Then re-execute cmake to generate Makefile and run make

Write picture description here

 Then run the regenerated elf file main

Write picture description here

 Run successfully!

By analogy, if there are multiple source files in the same directory, then just add all source files in add_executable. But if there are a hundred source files, it would be a bit tricky to do this again, and it cannot reflect the superiority of cmake. cmake provides a command to store all source files in a specified directory in a variable. This command is aux_source_directory(dir var).
The first parameter dir is the specified directory, and the second parameter var is the variable used to store the list of source files.

We add two more files, testFunc1.c and testFunc1.h, in the directory where main.c is located. After adding, the overall file structure is as follows

Write picture description here

 testFunc1.c is as follows,

/*
** testFunc1.c
*/

#include <stdio.h>
#include "testFunc1.h"

void func1(int data)
{
	printf("data is %d\n", data);
}


testFunc1.h is as follows,

/*
** testFunc1.h
*/

#ifndef _TEST_FUNC1_H_
#define _TEST_FUNC1_H_

void func1(int data);

#endif


Then modify main.c, call the function func1() declared in testFunc1.h,

#include <stdio.h>

#include "testFunc.h"
#include "testFunc1.h"

int main(void)
{
	func(100);
	func1(200);

	return 0;
}


Modify CMakeLists.txt,

cmake_minimum_required (VERSION 2.8)

project (demo)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})


Use aux_source_directory to store the source file storage list in the current directory into the variable SRC_LIST, and then call SRC_LIST in add_executable (note the way of calling the variable).
Execute cmake and make again, and run main

Write picture description here

 It can be seen that the operation is successful.

aux_source_directory() also has disadvantages. It will add all the source files in the specified directory, and may add some files we don’t need. At this time, we can use the set command to create variables to store the required source files, as follows,

cmake_minimum_required (VERSION 2.8)

project (demo)

set( SRC_LIST
	 ./main.c
	 ./testFunc1.c
	 ./testFunc.c)

add_executable(main ${SRC_LIST})


4. Multiple source files in different directories
Generally speaking, when there are many program files, we will manage them by category, and put the codes in different directories according to their functions, so that it is easy to find. So how to write CMakeLists.txt in this case?
Let's sort out the previous source files (create 2 new directories test_func and test_func1). After finishing, the overall file structure is as follows

Write picture description here

 Put the previous testFunc.c and testFunc.h in the test_func directory, and testFunc1.c and testFunc1.h in the test_func1 directory.

Among them, CMakeLists.txt and main.c are in the same directory, and the content is modified as follows

cmake_minimum_required (VERSION 2.8)

project (demo)

include_directories (test_func test_func1)

aux_source_directory (test_func SRC_LIST)
aux_source_directory (test_func1 SRC_LIST1)

add_executable (main main.c ${SRC_LIST} ${SRC_LIST1})


Here comes a new command: include_directories. This command is used to add search paths for multiple specified header files to the project, and the paths are separated by spaces.
Because testFunc.h and testFunc1.h are included in main.c, without this command to specify the location of the header file, it will not be able to compile. Of course, you can also use include to specify the path in main.c, as follows

#include "test_func/testFunc.h"
#include "test_func1/testFunc1.h"

It's just that the way it's written doesn't look good.
In addition, we used aux_source_directory 2 times, because the source files are distributed in 2 directories, so add 2 times.

5. Formal organizational structure
In terms of formality, the source files are generally placed in the src directory, the header files are placed in the include file, the generated object files are placed in the build directory, and the final output elf file will be placed in the Go to the bin directory, so that the whole structure is clearer. Let's reorganize the previous file again

Write picture description here

 We create a new CMakeLists.txt in the outermost directory, the content is as follows,

cmake_minimum_required (VERSION 2.8)

project (demo)

add_subdirectory (src)

A new command add_subdirectory() appears here. This command can add a subdirectory for storing source files to the current project, and can specify the storage location of the intermediate binary and the target binary. The specific usage can be Baidu.
Here, the source files are stored in the src directory. When cmake is executed, it will enter the src directory to find the CMakeLists.txt in the src directory, so a CMakeLists.txt is also created in the src directory, the content is as follows

aux_source_directory (. SRC_LIST)

include_directories (../include)

add_executable (main ${SRC_LIST})

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

There is a new command set here, which is used to define variables. EXECUTABLE_OUT_PATH and PROJECT_SOURCE_DIR are predefined variables that come with CMake. Their meanings are as follows.

EXECUTABLE_OUTPUT_PATH : the storage location of the target binary executable file
PROJECT_SOURCE_DIR: the root directory of the project
So, set here means to set the location of the elf file to the bin directory under the root directory of the project. (cmake has many predefined variables, you can search online for details)

After adding the above two CMakeLists.txt, the overall file structure is as follows

Write picture description here

 Let's run cmake, but this time let's switch to the build directory first, and then enter the following command,
cmake ..
Makefile will be generated in the build directory, and then run make in the build directory

Write picture description here

 Run ok, we then switch to the bin directory, find that main has been generated, and run the test

Write picture description here

 Test OK!

Here is an explanation of why cmake is run in the build directory? As can be seen from the previous cases, if this is not done, the accompanying files generated when cmake is running will be mixed with the source code files, which will pollute the directory structure of the program, and run cmake in the build directory, the generated The attached files will only stay in the build directory. If we don't want these files, we can directly clear the build directory, which is very convenient.

Another way of writing :
the previous project uses 2 CMakeLists.txt, the outermost CMakeLists.txt is used to control the overall situation, use add_subdirectory to control the operation of CMakeLists.txt in other directories.

The above example can also use only one CMakeLists.txt, and change the content of the outermost CMakeLists.txt to the following

cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

aux_source_directory (src SRC_LIST)

include_directories (include)

add_executable (main ${SRC_LIST})


At the same time, delete CMakeLists.txt in the src directory.

6. Compilation control of dynamic libraries and static libraries
Sometimes it is only necessary to compile dynamic libraries and static libraries, and then wait for other programs to use them. Let's see how to use cmake in this case. First reorganize the files as follows, leaving only testFunc.h and TestFunc.c

insert image description here

 We will run cmake in the build directory and store the generated library files in the lib directory.
The content of CMakeLists.txt is as follows,

cmake_minimum_required (VERSION 3.5)

project (demo)

set (SRC_LIST ${PROJECT_SOURCE_DIR}/testFunc/testFunc.c)

add_library (testFunc_shared SHARED ${SRC_LIST})
add_library (testFunc_static STATIC ${SRC_LIST})

set_target_properties (testFunc_shared PROPERTIES OUTPUT_NAME "testFunc")
set_target_properties (testFunc_static PROPERTIES OUTPUT_NAME "testFunc")

set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

There are new commands and predefined variables here again,

add_library: Generate a dynamic library or a static library (the first parameter specifies the name of the library; the second parameter determines whether it is dynamic or static, if not, it defaults to static; the third parameter specifies the source file of the generated library) set_target_properties: Set the final
generation The name of the library, and other functions, such as setting the version number of the library, etc.
LIBRARY_OUTPUT_PATH: The default output path of the library file, here is set to the lib directory under the project directory
, let us enter the build directory and run cmake .., Run make after success

Write picture description here

 cd to the lib directory to check, found that the dynamic library and static library have been successfully generated

Write picture description here

 PS: Set_target_properties was used to redefine the output name of the library. If set_target_properties is not used, then the name of the library is the name defined in add_library, but when add_library is used to specify the library name (the first parameter) twice in a row, the name They cannot be the same, but set_target_properties can set the names to be the same, but the suffixes of the final generated library files are different (one is .so, the other is .a), which will look relatively good.

7. Link the library
Now that we have generated the library, let's test the link. Re-build a project directory, then copy the library generated in the previous section, then create a new src directory and bin directory under the project directory, add a main.c under the src directory, the overall structure is as follows,

insert image description here

The content of main.c is as follows

#include <stdio.h>

#include "testFunc.h"

int main(void)
{
    func(100);
    
    return 0;
}

The content of CMakeLists.txt in the project directory is as follows

cmake_minimum_required (VERSION 3.5)

project (demo)


set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.c)

# find testFunc.h
include_directories (${PROJECT_SOURCE_DIR}/testFunc/inc)

find_library(TESTFUNC_LIB testFunc HINTS ${PROJECT_SOURCE_DIR}/testFunc/lib)

add_executable (main ${SRC_LIST})

target_link_libraries (main ${TESTFUNC_LIB})

2 new commands appear here

find_library : Find the specified library in the specified directory, and store the absolute path of the library in a variable. The first parameter is the variable name, the second parameter is the library name, the third parameter is HINTS, and the fourth parameter is For other usages, please refer to the cmake document
target_link_libraries : Link target files and library files
The advantage of using find_library is that it will check whether the library exists when executing cmake .., so that errors can be found in advance without waiting for linking.

cd to the build directory, then run cmake .. && make, and finally enter the bin directory to check, found that main has been generated, run it

Write picture description here

 Run successfully!

ps: There are static libraries and dynamic libraries of testFunc in the lib directory, find_library(TESTFUNC_LIB testFunc ... the default is to find the dynamic library, if you want to directly specify whether to use the dynamic library or the static library, you can write find_library(TESTFUNC_LIB libtestFunc.so ... or find_library(TESTFUNC_LIB libtestFunc.a ...

ps: To check which libraries are used by the elf file, you can use readelf -d ./xx to check

The previous tutorial in this section used link_directories as the library search method, but many readers reported that there were problems during runtime. I checked the official documentation and found that it is not recommended to use it. It is recommended to use find_library or find_package

insert image description here

 8. Add compilation options
Sometimes you want to add some compilation options when compiling the program, such as -Wall, -std=c++11, etc., you can use add_compile_options to operate.
Here is a simple program for demonstration, main.cpp is as follows

#include <iostream>

int main(void)
{
    auto data = 100;
    std::cout << "data: " << data << "\n";
    return 0;
}

The content of CMakeLists.txt is as follows

cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_compile_options(-std=c++11 -Wall) 

add_executable(main main.cpp)

The overall directory structure is as follows

insert image description here

 Then cd to the build directory, execute cmake .. && make command, you can get the main elf file in the bin directory

Nine, add control options
Sometimes you want to compile only some specified source code when compiling the code, you can use the option command of cmake, there are mainly two kinds of situations encountered:

I originally wanted to generate multiple bin or library files, but now I only want to generate some specified bin or library files
. For the same bin file, I only want to compile part of the code (using macros to control)
The first case
assumes that our current project will generate 2 bin files, main1 and main2, now the overall structure is as follows

insert image description here

 The content of the outer CMakeLists.txt is as follows

cmake_minimum_required(VERSION 3.5)

project(demo)

option(MYDEBUG "enable debug compilation" OFF)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(src)


The option command is used here, the first parameter is the name of the option, the second parameter is a string, which is used to describe what the option is for, and the third is the value of the option, ON or OFF, you can also Do not write, do not write is the default OFF.

Then write CMakeLists.txt in the src directory, as follows

cmake_minimum_required (VERSION 3.5)

add_executable(main1 main1.c)

if (MYDEBUG)
    add_executable(main2 main2.c)
else()
    message(STATUS "Currently is not in debug mode")    
endif()


Note that if-else is used here to decide whether to compile main2.c according to the option.
The contents of main1.c and main2.c are as follows

// main1.c
#include <stdio.h>

int main(void)
{
    printf("hello, this main1\n");
    
    return 0;
}
// main2.c
#include <stdio.h>

int main(void)
{
    printf("hello, this main2\n");
    
    return 0;
}

Then cd to the build directory and enter cmake .. && make to compile only main1. If you want to compile main2, set MYDEBUG to ON, and enter cmake .. && make again to recompile.

Every time you want to change MYDEBUG, you need to modify CMakeLists.txt, which is a bit troublesome. In fact, you can operate it through the command line of cmake. For example, if we want to set MYDEBUG to OFF, first cd to the build directory, and then enter cmake .. -DMYDEBUG= ON, so that main1 and main2 can be compiled (in the bin directory)

insert image description here

Case 2
Suppose we have a main.c whose content is as follows

#include <stdio.h>

int main(void)
{
#ifdef WWW1
    printf("hello world1\n");
#endif    

#ifdef WWW2     
    printf("hello world2\n");
#endif

    return 0;
}


You can control the printed information by defining macros. The content of our CMakeLists.txt is as follows

cmake_minimum_required(VERSION 3.5)

project(demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

option(WWW1 "print one message" OFF)
option(WWW2 "print another message" OFF)

if (WWW1)
    add_definitions(-DWWW1)
endif()

if (WWW2)
    add_definitions(-DWWW2)
endif()

add_executable(main main.c)


Here, the name of the option is kept the same as the macro name in main.c, which is more intuitive, and you can also choose a different name. By cooperating with add_definitions(), you can control the printout of a single bin file.

The overall project structure is as follows

insert image description here

 cd to the build directory and execute cmake .. && make, and then execute ./main in the bin directory. You can see that the print is empty, and
then follow the instructions below to execute, and then check the printing effect.

cmake .. -DWWW1=ON -DWWW2=OFF && make
cmake .. -DWWW1=OFF -DWWW2=ON && make
cmake .. -DWWW1=ON -DWWW2=ON && make
There is a small pit here to pay attention to: suppose there is The two options are called A and B. First call cmake to set A, and then call cmake to set B next time. If the cache file generated during the last execution of cmake is not deleted, then although A is not set this time, A will be used by default. The last option value.

So if the option changes, either delete the cache file generated when cmake was executed last time, or explicitly specify its value for all options.

Ten Summary
The above is a little learning record of learning CMake by myself. Through simple examples, let everyone get started with CMake. While learning, I also read a lot of blogs from netizens. There are still many knowledge points about CMake, and specific details can be searched online. In short, CMake can save us from writing complicated Makefiles, and it is cross-platform. It is a very powerful tool worth learning.
————————————————
Copyright statement: This article is the original article of CSDN blogger "Love is Perseverance", following the CC 4.0 BY-SA copyright agreement, please attach the original source link for reprinting and this statement.
Original link: https://blog.csdn.net/whahu1989/article/details/82078563

Guess you like

Origin blog.csdn.net/weixin_49071468/article/details/130364101