[CMake entry and advanced (5)] CMakeLists.txt grammar rule basis and some commonly used instructions-continued (with code)

project

        The project command is used to set the project name:

# 设置工程名称为 HELLO
project(HELLO)

        After executing this, two variables will be introduced: HELLO_SOURCE_DIR and HELLO_BINARY_DIR. Note that the prefix of these two variable names is the project name. The HELLO_SOURCE_DIR variable refers to the source code directory of the HELLO project, and the HELLO_BINARY_DIR variable refers to the output file directory of the HELLO project source code; we can Use the message command to print variables, for example, the content of CMakeLists.txt is as follows:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

message(${HELLO_SOURCE_DIR})
message(${HELLO_BINARY_DIR})

        Enter the build directory and execute cmake:

         But if the project (HELLO) command is not added, these two variables do not exist; the project source code directory refers to the directory where the top-level source code is located, and cmake defines two equivalent variables PROJECT_SOURCE_DIR and PROJECT_BINARY_DIR, which are usually found in the CMakeLists.txt source code Use these two equivalent variables.

        Usually you only need to call project in the top-level CMakeLists.txt source code!

set

        The set command is used to set variables, and the command definition is as follows:

set(<variable><value> ... [PARENT_SCOPE])

        Set the value of the variable, the optional parameter PARENT_SCOPE affects the scope of the variable.

        For example, the source code content of CMakeLists.txt is as follows:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

# set 命令
set(VAR1 Hello) #设置变量 VAR1=Hello
set(VAR2 World) #设置变量 VAR2=World

# 打印变量
message(${VAR1} " " ${VAR2})

        Corresponding print information:

        list of strings

        A list of strings is implemented with the set command, as follows:

# 字符串列表
set(SRC_LIST 1.c 2.c 3.c 4.c 5.c)

        At this time, SRC_LIST is a list, which contains 5 elements (1.c, 2.c, 3.c, 4.c, 5.c), and each element of the list is separated by a semicolon ";", as follows:

SRC_LIST = 1.c;2.c;3.c;4.c;5.c #列表

        Let's test it. For example, the source code content of CMakeLists.txt is as follows:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

# set 命令
set(SRC_LIST 1.c 2.c 3.c 4.c 5.c)

# 打印变量
message(${SRC_LIST})

         Execute the cmake command to print the following information:

         At first glance at the printed information, do you think that SRC_LIST is an ordinary variable (SRC_LIST= 1.c2.c3.c4.c5.c), not a list? This is not the case, we can modify the message command to place ${SRC_LIST} in double quotes, as follows:

# 打印变量
message("${SRC_LIST}")

        Execute cmake again, and the printed information is as follows:

         It can be seen that what is printed out at this time is indeed a list. Since it is a list, it is natural to use the list command to perform related operations on the list:

# 顶层 CMakeLists.txt
cmake_minimum_required("VERSION" "3.5")
project("HELLO")

# 列表
set(SRC_LIST main.c world.c hello.c)
message("SRC_LIST: ${SRC_LIST}")

#列表操作
list(LENGTH SRC_LIST L_LEN)
message("列表长度: ${L_LEN}")
list(GET SRC_LIST 1 VAR1)
message("获取列表中 index=1 的元素: ${VAR1}")

list(APPEND SRC_LIST hello_world.c) #追加元素
message("SRC_LIST: ${SRC_LIST}")

list(SORT SRC_LIST) #排序
message("SRC_LIST: ${SRC_LIST}")

        The cmake print information is as follows:

         In addition, in cmake, loop statements can be used to read each element in the list in turn, and I will introduce it to you later.

target_include_directories 和 target_link_libraries

        The target_include_directories command sets the header file search path for the specified target, and the target_link_libraries command sets the link library file for the specified target. This sounds like the include_directories and link_libraries commands have the same effect, and it is true. Their functions are indeed the same, but in some details There are differences, and the differences between them will be explained to you later!

        The target_include_directories and target_link_libraries commands are defined as follows:

target_include_directories(<target> [SYSTEM] [BEFORE]
                         <INTERFACE|PUBLIC|PRIVATE> [items1...]
                         [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
                
target_link_libraries(<target>
                     <PRIVATE|PUBLIC|INTERFACE> <item>...
                     [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

        These two commands have the same parameter target, which refers to the target created by the add_executable and add_library commands, for example. First of all, for the target_include_directories command, the two options SYSTEM and BEFORE have the same meaning as the SYSTEM and BEFORE options in the include_directories command, so I won't say more here!

        What we focus on is the difference between the three options INTERFACE|PUBLIC|PRIVATE? Let me explain to you through an example. For example, the project directory structure is as follows:

├── build //build 目录
├── CMakeLists.txt
├── hello_world //生成 libhello_world.so,调用 libhello.so 和 libworld.so
│ ├── CMakeLists.txt
│ ├── hello //生成 libhello.so
│ │ ├── CMakeLists.txt
│ │ ├── hello.c
│ │ └── hello.h //libhello.so 对外头文件
│ ├── hello_world.c
│ ├── hello_world.h //libhello_world.so 对外头文件
│ └── world //生成 libworld.so
│   ├── CMakeLists.txt
│   ├── world.c
│   └── world.h //libworld.so 对外头文件
└── main.c

        call relationship

                                 ├────libhello.so
可执行文件────libhello_world.so
                                 ├────libworld.so

        According to the above project, we explain the three keywords INTERFACE, PUBLIC, and PRIVATE:          PRIVATE: private. The main.c program calls libhello_world.so. When generating libhello_world.so, only hello.h is included in hello_world.c, and the external header file of libhello_world.so - hello_world.h does not include hello.h. And main.c will not call the functions in hello.c, or main.c does not know the existence of hello.c, it only knows the existence of libhello_world.so; then it should be written in hello_world/CMakeLists.txt:

target_link_libraries(hello_world PRIVATE hello)
target_include_directories(hello_world PRIVATE hello)

        INTERFACE: interface. When generating libhello_world.so, only the external header file of libhello_world.so - hello_world.h contains hello.h, hello_world.c does not contain hello.h, that is, libhello_world.so does not use the functions provided by libhello.so, But main.c needs to use the functions in libhello.so. Then it should be written in hello_world/CMakeLists.txt:

target_link_libraries(hello-world INTERFACE hello)
target_include_directories(hello-world INTERFACE hello

        PUBLIC: public. PUBLIC = PRIVATE + INTERFACE. When generating libhello_world.so, hello.h is included in both hello_world.c and hello_world.h. And the functions provided by libhello.so also need to be used in main.c. Then it should be written in hello_world/CMakeLists.txt:

target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)

        I don’t know if you understand it, but it’s actually very simple to understand. For target_include_directories, these keywords are used to indicate when the include directory list needs to be passed to the target , and specify the scope of use of the include directory list :

⚫ When modified with the PRIVATE keyword, it means that the list of included directories is only for the current target;

⚫ When modified with the INTERFACE keyword, it means that the list of included directories is not used for the current target and can only be used for other targets that depend on this target, that is to say, cmake will pass the list of included directories to the dependent targets of the current target;

⚫ When decorated with the PUBLIC keyword, this is a collection of the above two, containing a list of directories both for the current target and passed to the current target's dependent targets.

        The same is true for target_link_libraries, except that the list of include directories is replaced by a list of link libraries . for example:

         target_link_libraries(hello_world INTERFACE hello): Indicates that the target hello_world does not need to link the hello library, but the dependent target for the hello_world target (the target that depends on hello_world) needs to link the hello library.

        The above is the author's general understanding of the three keywords INTERFACE, PUBLIC, and PRIVATE, so these keywords are mainly used to control the scope of use of the include directory list or link library list . This is the target_include_directories, target_link_libraries command and The difference between include_directories and link_libraries commands. The functions of target_include_directories() and target_link_libraries() can be realized by using include_directories() and link_libraries(). But the author recommends that you use target_include_directories() and target_link_libraries().

        include_directories() and link_libraries() are for all targets in the current source code, and will also be passed down (for example, when sub-source code is loaded through add_subdirectory, it will also be passed to sub-source code). In a large project, this is usually not standardized, and sometimes compilation errors and confusion occur, so we should try to use target_include_directories() and target_link_libraries() to keep the directory of the entire project clear.

        Summarize

        This is the end of the study of common cmake commands and variables. I have introduced some basic and commonly used commands and explained them in detail. In addition, there are many commands that have not been mentioned, which will be in Introduced later.

Guess you like

Origin blog.csdn.net/cj_lsk/article/details/131122127