CMake学习

2023-05-16

1. 引言

使用cmake管理SLAM工程很方便,编译便捷。

2. 具体学习

推荐《cmake实践》

1. MESSAGE在make时的输出

CMakeLists.txt内容

PROJECT (HELLO)SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello SRC_LIST)

在这里插入图片描述
外部编译时,SOURCE仍是源文件的路径不变,BINARY随着make的路径而改变。

2.清理工程

make clean

删除编译生成的可执行文件:
在这里插入图片描述

3.添加子目录

t2目录下的CMakeLists.txt文件更改为

PROJECT(HELLO) 
ADD_SUBDIRECTORY(src bin)  #将src目录添加到主工程中,并且指定src的中间二进制和目标二进制存放的位置为bin目录

其中ADD_SUBDIRECTORY 指令

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置,这里源文件夹是src,目标文件夹是bin,所以在build中cmake之后会在build中生成一个bin文件夹,make之后,hello可执行文件会出现在bin中。
在这里插入图片描述

4.换个地方保存二进制文件

在src文件夹下的CMakeLists.txt里面添加以下代码可以直接指定最终的可执行二进制文件的输出目录:

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

第一句是指定输出二进制文件的路径,第二句指定输出库文件的路径。
为了区分,我指定了bin2目录,掌握一个原则:在哪里ADD_EXECUTABLE 或ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义。

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin2)

省略cmake和make的过程,最终的二进制文件在bin2中,其余的编译中间生成的文件仍然在bin中:
在这里插入图片描述

5.如何安装

这里使用以下指令进行安装:

make install

在cmake时需要指定-DCMAKE_INSTALL_PREFIX参数,且需要在CMakeLists.ttxt中编写安装配置,主要有一下几种安装

1. 目标文件的安装

eg:

INSTALL(TARGETS myrun mylib mystaticlib
		RUNTIME DESTINATION bin
		LIBRARY DESTINATION lib
		ARCHIVE DESTINATION libstatic
)

将:
可执行二进制 myrun 安装到${CMAKE_INSTALL_PREFIX}/bin 目录
动态库 libmylib 安装到${CMAKE_INSTALL_PREFIX}/lib 目录
静态库 libmystaticlib 安装到${CMAKE_INSTALL_PREFIX}/libstatic 目录

2. 普通文件的安装

安装权限为644

INSTALL(FILES files... DESTINATION <dir>
		[PERMISSIONS permissions...]
		[CONFIGURATIONS [Debug|Release|...]]
		[COMPONENT <component>]
		[RENAME <name>] [OPTIONAL])

3. 非目标文件的可执行程序安装(比如脚本之类)

安装权限为755

INSTALL(PROGRAMS files... DESTINATION <dir>
		[PERMISSIONS permissions...]
		[CONFIGURATIONS [Debug|Release|...]]
		[COMPONENT <component>]
		[RENAME <name>] [OPTIONAL])

4. 目录的安装

目录的安装:
INSTALL(DIRECTORY dirs... DESTINATION <dir>
		[FILE_PERMISSIONS permissions...]
		[DIRECTORY_PERMISSIONS permissions...]
		[USE_SOURCE_PERMISSIONS]
		[CONFIGURATIONS [Debug|Release|...]]
		[COMPONENT <component>]
		[[PATTERN <pattern> | REGEX <regex>]
		[EXCLUDE] [PERMISSIONS permissions...]] [...])

5.实践

目前的目录:

在这里插入图片描述
主要是工程中的CMakeLists.txt和src下的CMakeLists.txt

在这里插入图片描述

然后创建build进行外部编译
在这里插入图片描述

在这里插入图片描述

6.编写库

在CMakeList.txt中添加以下语句构建库

ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
			[EXCLUDE_FROM_ALL]
			source1 source2 ... sourceN)

例如:

SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})  	#编译动态库,以.so结尾
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})  		#编译静态库,以.a结尾

实际上,上面第三句话不会生效,因为hello 作为一个target 是不能重名的,所以,静态库构建指令无效。
另起一个名字,并使用SET_TARGET_PROPERTIES来解决,改为:

SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")

这样之后就直接在build/lib下生成libhello.a和libhello.so了,没有出现冲突的现象。

为了实现动态库的版本识别,继续添加SET_TARGET_PROPERTIES

SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)

VERSION 指代动态库版本,SOVERSION 指代API版本

库的安装:
在lib文件夹下的CMkaeList.txt中添加:

INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)  
INSTALL(FILES hello.h DESTINATION include/hello)  

第一句:将动态库hello安装到PREFIX/lib目录下,将静态库hello_static也安装到PREFIX/lib下,只是注意静态库要使用ARCHIVE关键字,具体参考5.1目标文件的安装;
第二句:将头文件hello.h安装到PREFIX/include/hello目录下。

最终的CMakeLists.txt

SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS "this is the hello_static OUTPUT_NAME:"${OUTPUT_VALUE})
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)

7.使用库

按照之前的步骤构建了工程,在src中添加了main.cpp和CMakeLists.txt,实际使用库只需要.h文件和编译后的动态或者静态库文件(.so or .a)

#main.cpp
int main(int argc, char** argv)
{
	HelloFunc();
	return 0;
}

#CMakeLists.txt
ADD_EXECUTABLE(main main.cpp)
INCLUDE_DIRECTORIES(../lib)  #添加一个头文件搜索路径
TARGET_LINK_LIBRARIES(main hello)  #使用动态库

一个小坑:

INCLUDE_DIRECTORIES(/usr/include/hello)

原教程中说添加下面这句话索引头文件,但是我还是找不到头文件,想了想,还没有make install,肯定不会在这里,所以肯定是拿到了头文件放在某处,然后把头文件的路径穿进去索引,所以放在哪就写哪,我放在lib下面和hello.cpp放在一起,所以

INCLUDE_DIRECTORIES(../lib)  #添加一个头文件搜索路径

然后就正常cmake … make,然后执行build中的src下的main

在这里插入图片描述

8. 用环境变量来FIND

当然,可以使用环境变量来FIND,有两个特殊的特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH

export CMAKE_INCLUDE_PATH=~/CLionProjects/CMakeLearning/cmake/t6/lib

src下的CMakeLists.txt改为

ADD_EXECUTABLE(main main.cpp)
TARGET_LINK_LIBRARIES(main hello)

FIND_PATH(myHeader hello.h)
IF(myHeader)
#INCLUDE_DIRECTORIES(../lib)  #添加一个头文件搜索路径
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)

利用环境变量也能FIND到,CMAKE_LIBRARY_PATH 可以用在 FIND_LIBRARY 中,因为这些变量直接为 FIND_指令所使用,所以所有使用 FIND_指令的 cmake 模块都会受益。

9.CMake常用变量和环境变量

1.常用变量

1.编译目录

CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR

这三个一样,指工程编译发生的目录,内部编译和外部编译不同。

2.工程目录

CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR

这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。

3.输出目录

EXECUTABLE_OUTPUT_PATH
LIBRARY_OUTPUT_PATH

分别用来重新定义最终结果的存放目录,2.4节提到过

4. CMAKE_MODULE_PATH

用来定义自己的 cmake 模块所在的路径,以便调用,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,eg:

SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

然后就可以通过 INCLUDE 指令来调用自己的模块了:

INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])

可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜索这个模块并载入。
载入的内容将在处理到 INCLUDE 语句时直接执行。

2.环境变量

1.设置系统环境变量

$ENV{NAME}

比如MESSAGE(STATUS “HOME dir: $ENV{HOME}”)

2.使用系统环境变量

SET(ENV{变量名})

2.CMAKE_INCLUDE_CURRENT_DIR

自动添加 CMAKE_CURRENT_BINARY_DIR 和 CMAKE_CURRENT_SOURCE_DIR 到当前处理
的 CMakeLists.txt。相当于在每个 CMakeLists.txt 加入:

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) #添加搜索路径

3.CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH

分别对FIND_PATH和FIND_LIBRARY有用,在第8节提到。

3.主要开关选项

1. BUILD_SHARED_LIBS

比如BUILD_SHARED_LIBS,默认的ADD_LIBRARY都是生成静态库,当然,可以在ADD_LIBRARY时指定SHARD和STATIC控制生成的库的类型,不过还可以通过开关选项来设置默认类型:

 SET(BUILD_SHARED_LIBS ON)

设置之后,默认生成动态库。
在cmake时也可以设置,如下设置生成动态库:

cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON -DGFLAGS_NAMESPACE=gflags ../

4. 小结

介绍了一些常用的cmake变量,要了解更多cmake变量,最好的是去读别人的cmake工程文件,如KDE4的代码。

10. cmake常用指令

分为基本指令、查找指令、安装指令、控制语句。
基本指令如:CMAKE_MINIMUM_REQUIRED
查找指令如:FIND_PACKAGE
安装指令:INSTALL
控制语句:IF ELSEIF ENDIF/ WHILE ENDWHILE/ FOREACH ENDFOREACH

11. cmake模式设定

1. build模式

mkdir build && cd build
cmake ..
make

2. Debug模式,可以进行gdb调试

mkdir Debug && cd Debug 
cmake -DCMAKE_BUILD_TYPE=Debug .. 
make

3.Release模式

mkdir Release && cd Release 
cmake -DCMAKE_BUILD_TYPE=Release .. 
make

也可以在CMakeLists中设置模式,强调一下,只有在Debug模式下才能设置断点调试:

set(CMAKE_BUILD_TYPE Release)  #Release模式
#set(CMAKE_BUILD_TYPE Debug)  #设置为debug模式

12. FindPACKAGE相关的

FindPACKAGE相关的内容在这一篇中写了,填了FIND_LIBRARY的坑之后,发现自己debug的能力还是有些欠缺,

  1. 多看看stackoverflow,对于找到的debug建议多尝试,不要老憋着,效率很低,要长记性!
  2. 自己有时候很多bug都是自己对于函数用法不清楚(比如这次的FIND_LIBRARY里面是PATHS而不是PATH,这个小细节花费了我一晚上时间去debug),多看看函数怎么用的能省很多事。

13. 关于CMAKE_CXX_FLAGS相关的

在CMakeLists.txt中总是出现

add_definitions("-DENABLE_SSE")
set(CMAKE_CXX_FLAGS "-std=c++11 -O2 ${SSE_FLAGS} -msse4")  #则在编译选项中加入c++11支持

不知道有什么作用,来补一下,主要参考这两篇博客
1.CMakeLists中的add_definitions()函数
2.cmake:设置编译选项的讲究(add_compile_options和CMAKE_CXX_FLAGS的区别) 转自CSDN

1. 关于add_definitions()函数

官方解释

Adds -D define flags to the compilation of source files.
add_definitions(-DFOO -DBAR …)
Adds definitions to the compiler command line for sources in the current directory and below. This command can be used to add any flags, but it is intended to add preprocessor definitions. Flags beginning in -D or /D that look like preprocessor definitions are automatically added to the COMPILE_DEFINITIONS directory property for the current directory. Definitions with non-trivial values may be left in the set of flags instead of being converted for reasons of backwards compatibility. See documentation of the directory, target, source file COMPILE_DEFINITIONS properties for details on adding preprocessor definitions to specific scopes and configurations.

可以被用来 添加任何flags,但是主要是添加预处理定义。以-D或/D开头的命令。关键字相关的就被添加到COMPILE_DEFINITIONS里面了,非关键字相关的就被转化为flags。

ADD_DEFINITIONS(-DENABLE_DEBUG -DABC),参数之间用空格分割。
如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。

实际上就相当于是定义一个宏,只不过这个宏是在程序编译的时候定义的,就相当于源程序中的

#define abc
#ifdef abc
balabala
#endif

参考博客中的图片(懒得去水印了)
在这里插入图片描述
这样就能在cmake时候加上

cmake -DENABLE_SSE=1 
#或者
cmake -DENABLE_SSE=0

2. 第一句话

实际上就是使用SSE指令集,

add_definitions("-DENABLE_SSE")

SSE指令集:

SSE(为Streaming SIMD Extensions的缩写)是由 Intel公司,在1999年推出Pentium III处理器时,同时推出的新指令集。如同其名称所表示的,SSE是一种SIMD指令集。所谓的SIMD是指single instruction, multiple data,也就是一个指令同时对多个资料进行相同的动作。较早的MMX和 AMD的3DNow!也都是SIMD指令集。因此,SSE本质上是非常类似一个向量处理器的。SSE指令包括了四个主要的部份:单精确度浮点数运算指令、整数运算指令(此为MMX之延伸,并和MMX使用同样的暂存器)、Cache控制指令、和状态控制指令。

我简单把它理解为一个和CPU硬件交互的库函数,操作CPU进行一些运算、控制等功能。

3. 第二句话

set(CMAKE_CXX_FLAGS "-std=c++11 -O2 ${SSE_FLAGS} -msse4")  

其中,参数CMAKE_CXX_FLAGS含义是: set compiler for c++ language,针对C++的编译选项;
如果要指定C++标准版本
可以使用

set(CMAKE_CXX_STANDARD 11)       # 指定C++标准:98、11、14、17、20

而后面的-O2(是字母opq的o,大写的欧)是用来调节编译时的优化程度的,最高为-O3,最低为-O0(即不做优化);
并使用SSE4指令集
在SLAM14讲代码中,slambook2/ch7/orb_self.cpp中使用了SSE指令,有这两条

#include <nmmintrin.h>  //使用mmx多媒体扩展指令集,属于SSE指令集
.....
.....
   distance += _mm_popcnt_u32(desc1[i1][k] ^ desc2[i2][k]);  //多媒体扩展指令集,mmx

4. set()指令

CMake中的set用于给一般变量,缓存变量,环境变量赋值。(说白了就是在cmake中把后面的值赋给前面的变量)
参考博客

set(FOO  “x”)。         //FOO作用域为当前作用域。
set(FOO "x" PARENT_SCOPE)   //FOO作用域跳上一级(懂点编程的都懂哈)。

set(FOO, "x" CACHE <type>)  
//原缓存中没有FOO则将FOO赋值为x且存入cache中。
//原缓存中有FOO则不做任何改变,即便原cache中FOO存的不是x。

set(FOO, "x" CACHE <type><docstring> FORCE)    
//即便原cache中存在FOO也会创建另一个FOO,官方文档原话(If FORCE is specified, the value of the cache variable 
//is set, even if the variable is already in the cache.This should normally be avoided, as it will 
//remove any changes to the cache variable’s value by the user.),小弟笨拙没有搞懂。

5. list()指令

list(LENGTH <list><output variable>)
list(GET <list> <elementindex> [<element index> ...]<output variable>)
list(APPEND <list><element> [<element> ...])
list(FIND <list> <value><output variable>)
list(INSERT <list><element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value>[<value> ...])
list(REMOVE_AT <list><index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)

含义:

LENGTH            返回list的长度

GET              返回list中index的element到value中

APPEND            添加新element到list中

FIND             返回list中element的index,没有找到返回-1

INSERT           将新element插入到list中index的位置

REMOVE_ITEM      从list中删除某个element

REMOVE_AT       从list中删除指定index的element

REMOVE_DUPLICATES       从list中删除重复的element

REVERSE         将list的内容反转

SORT           将list按字母顺序排序

14. configure_file指令

参考博客
指令:

configure_file(<input> <output>
               [COPYONLY] [ESCAPE_QUOTES] [@ONLY]
               [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

浅显的理解:configure_file,复制一份输入文件到输出文件,替换输入文件中被@VAR@或者${VAR}引用的变量值。也就是说,让普通文件,也能使用CMake中的变量。例如:
比如在CMakeLists.txt中定义了如下的变量:

set(BUILD_Version 1)

输入文件中为:

#define BUILD_Version @BUILD_Version@

那么,在输出文件中就会被转化为:

#define BUILD_Version 1

通常情况下,输入文件以.h.in为后缀,输出文件以.h为后缀。
具体的例子:
编写utils.cmake,定义macro宏,用于获取Git的hash值和分支:

# get git hash
macro(get_git_hash _git_hash)   # 宏的开始
    find_package(Git QUIET)     # 查找Git,QUIET静默方式不报错
    if(GIT_FOUND)
      execute_process(          # 执行一个子进程
        COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%h # 命令
        OUTPUT_VARIABLE ${_git_hash}        # 输出字符串存入变量
        OUTPUT_STRIP_TRAILING_WHITESPACE    # 删除字符串尾的换行符
        ERROR_QUIET                         # 对执行错误静默
        WORKING_DIRECTORY                   # 执行路径
          ${CMAKE_CURRENT_SOURCE_DIR}
        )
    endif()
endmacro()                      # 宏的结束

# get git branch
macro(get_git_branch _git_branch)   # 宏的开始
    find_package(Git QUIET)     # 查找Git,QUIET静默方式不报错
    if(GIT_FOUND)
      execute_process(          # 执行一个子进程
        COMMAND ${GIT_EXECUTABLE} symbolic-ref --short -q HEAD
        OUTPUT_VARIABLE ${_git_branch}        # 输出字符串存入变量
        OUTPUT_STRIP_TRAILING_WHITESPACE    # 删除字符串尾的换行符
        ERROR_QUIET                         # 对执行错误静默
        WORKING_DIRECTORY                   # 执行路径
          ${CMAKE_CURRENT_SOURCE_DIR}
        )
    endif()
endmacro()                      # 宏的结束

编写CMakeLists.txt,定义编译时间戳变量、版本变量、Git信息变量,同时实现编译可执行程序、复制文件和变换变量:

cmake_minimum_required(VERSION 3.0)
include(cmake/utils.cmake)
project(main)

string(TIMESTAMP BUILD_TIMESTAMP "%Y-%m-%d %H:%M:%S")
message("Build timestamp is ${BUILD_TIMESTAMP}")

set(VERSION_MAJOR 0)
set(VERSION_MINOR 0)
set(VERSION_PATCH 1)
message("Version is ${VERSION_MAJOR} ${VERSION_MINOR} ${VERSION_PATCH}")

set(GIT_HASH "")
get_git_hash(GIT_HASH)
message("Git hash is ${GIT_HASH}")

set(GIT_BRANCH "")
get_git_branch(GIT_BRANCH)
message("Git branch is ${GIT_BRANCH}")

configure_file (
    "${PROJECT_SOURCE_DIR}/include/utils.h.in"
    "${PROJECT_SOURCE_DIR}/include/utils.h"
)

add_executable(${PROJECT_NAME} src/main.cc)

include_directories(
    ${PROJECT_SOURCE_DIR}/include
)

install(TARGETS ${PROJECT_NAME}
    RUNTIME DESTINATION ${PROJECT_SOURCE_DIR})

configure_file的输入文件:

#ifndef UTILS_H_IN
#define UTILS_H_IN

#define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@
#define VERSION_PATCH @VERSION_PATCH@

#define BUILD_TIMESTAMP "@BUILD_TIMESTAMP@"

#define GIT_BRANCH "@GIT_BRANCH@"
#define GIT_HASH "@GIT_HASH@"

#endif // UTILS_H_IN

主程序为:

#include "utils.h"
#include <iostream>

int main(int argc, char const *argv[])
{
  std::cout << "version is " << VERSION_MAJOR << ", " << VERSION_MINOR << ", "<< VERSION_PATCH << std::endl;
  std::cout << "timestamp is " << BUILD_TIMESTAMP << std::endl;
  std::cout << "git is " << GIT_BRANCH << ", " << GIT_HASH << std::endl;
  
  return 0;
}

编译并运行该项目,会生成输出文件:

#ifndef UTILS_H_IN
#define UTILS_H_IN

#define VERSION_MAJOR 0
#define VERSION_MINOR 0
#define VERSION_PATCH 1

#define BUILD_TIMESTAMP "2019-12-27 20:28:57"

#define GIT_BRANCH "master"
#define GIT_HASH "14e77d2"

#endif // UTILS_H_IN

运行结果为:

yngzmiao@yngzmiao-virtual-machine:~/test/test$ ./main 
version is 0, 0, 1
timestamp is 2019-12-27 21:28:57
git is master, 14e77d2

即在main函数中使用了由configure指令生成的输出文件中的变量,该指令输入文件为utils.h.in,输出文件为utils.h

简单的实例:
在这里插入图片描述
在这里插入图片描述

15. target_include_directories指令

1. 指令说明

参考链接,以下搬自于此博客
在这里插入图片描述

2. 指令讲解

测试工程目录结构:

cmake-test/ 工程主目录,main.c 调用 libhello-world.so
├── 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

调用关系:

                                 ├────libhello.so
 可执行文件────libhello-world.so
                                 ├────libworld.so

即指定目标包含的头文件的路径,和继承的方式有些类似,target_include_directories指令有3个关键字指定include范围(scope)或者是一种传递(propagate):

PRIVATE
INTERFACE
PUBLIC

PRIVATE:私有的。 生成 libhello-world.so时,只在 hello_world.c 中包含了 hello.h,libhello-world.so 对外的头文件——hello_world.h 中不包含 hello.h。而且 main.c 不会调用 hello.c 中的函数,或者说 main.c 不知道 hello.c 的存在,那么在 hello-world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world PRIVATE hello)
target_include_directories(hello-world PRIVATE hello)

INTERFACE:接口。 生成 libhello-world.so 时,只在libhello-world.so 对外的头文件——hello_world.h 中包含 了 hello.h, hello_world.c 中不包含 hello.h,即 libhello-world.so 不使用 libhello.so 提供的功能,只使用 hello.h 中的某些信息,比如结构体。但是 main.c 需要使用 libhello.so 中的功能。那么在 hello-world/CMakeLists.txt 中应该写入:

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

PUBLIC:公开的。 PUBLIC = PRIVATE + INTERFACE。生成 libhello-world.so 时,在 hello_world.c 和 hello_world.h 中都包含了 hello.h。并且 main.c 中也需要使用 libhello.so 提供的功能。那么在 hello-world/CMakeLists.txt 中应该写入:

target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)
  1. main.c 不使用 libhello.so 的任何功能,因此 libhello-world.so 不需要将其依赖—— libhello.so 传递给 main.c,hello-world/CMakeLists.txt 中使用 PRIVATE 关键字;
  2. main.c 使用 libhello.so 的功能,但是libhello-world.so 不使用,hello-world/CMakeLists.txt 中使用 INTERFACE 关键字;
  3. main.c 和 libhello-world.so 都使用 libhello.so 的功能,hello-world/CMakeLists.txt 中使用 PUBLIC 关键字

3. include_directories(dir)

target_include_directories() 的功能完全可以使用 include_directories() 实现。但是我还是建议使用 target_include_directories()。为什么?保持清晰!

include_directories(header-dir) 是一个全局包含,向下传递。什么意思呢?就是说如果某个目录的 CMakeLists.txt 中使用了该指令,其下所有的子目录默认也包含了header-dir 目录。

上述例子中,如果在顶层的 cmake-test/CMakeLists.txt 中加入:

include_directories(hello-world)
include_directories(hello-world/hello)
include_directories(hello-world/world)

那么整个工程的源文件在编译时都会增加:

-I hello-world -I hello-world/hello -I hello-world/world …
各级子目录中无需使用 target_include_directories() 或者 include_directories()了。如果此时查看详细的编译过程(make VERBOSE=1)就会发现编译过程是一大坨,很不舒服。

当然了,在最终子目录的 CMakeLists.txt 文件中,使用 include_directories() 和 target_include_directories() 的效果是相同的。

4. 目录划分

每一个目录都是一个模块,目录内部应将对外和对内的头文件进行区分,由模块的调用者决定模块是否被传递(PRIVATE,INTERFACE,PUBLIC)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CMake学习 的相关文章

随机推荐

  • 【SpringSecurity教程】认证 1.Basic认证

    前言 Basic 认证是在请求接口之前要输入账号密码 xff0c 是简单的Http验证模式 本章主要描述 xff1a SpringBoot如何整合Basic认证 后端Okhttp和前端Vue Axios如何请求Basic认证的接口 Spri
  • 【SpringSecurity教程】认证 2.Digest摘要认证

    前言 Digest xff08 摘要 xff09 认证是在请求接口之前要输入账号密码 xff0c 是在Basic认证传输账号密码的基础上加密 SpringBoot整合Digest pom xml span class token tag s
  • VIM3刷系统

    一 升级到最新系统 如果你的板子上已经运行的是官方发布的Ubuntu固件 xff0c 那么你可以通过如下命令升级系统到最新版本 span class token function sudo span span class token fun
  • 设置Chrome页面为黑色

    文章目录 1 设置主题2 设置页面 1 设置主题 设置 gt 外观 gt 主题背景 gt 选择一个黑色的主题 2 设置页面 输入chrome flags enable force dark将其设置为Enable即可
  • 网线每根的含义以及类别和距离传输问题

    文章目录 1 八芯线每根的含义2 传输距离限制3 双绞线的主要分类 1 八芯线每根的含义 网线采用8根线芯 xff0c 主要是为了减少电磁信号的相互干扰 xff0c 只用四根 xff0c 另外四根可做备份使用 每两根按一定的密度缠绞在一起
  • Windows快捷键

    文章目录 1 创建虚拟桌面2 虚拟桌面间切换3 虚拟桌面关闭4 锁住PC5 显示桌面6 从任务栏打开新应用7 两个应用分屏8 应用切换9 打开文件管理视窗10 打开放大镜11 截取屏幕12 打开操作中心13 进入设置14 与Cortana或
  • ubuntu(18):对‘pthread_create’未定义的引用

    报错 xff1a 对 pthread create 未定义的引用 usr local lib libbenchmark a benchmark runner cc o xff1a 在函数 benchmark internal Benchma
  • Git修改与删除commit记录

    Git修改与删除commit记录 修改commit信息删除未push的commit删除已push的commit 修改commit信息 1 修改最近一次commit的信息 git commit span class token operato
  • 电机转矩、功率、转速之间的关系及计算公式

    P 61 W T 61 FS T 61 FV T 61 F R V 61 2 RN N 转速 所以 P 61 FV 61 T R 2 RN 61 2 TN T 61 P 2 n 用千瓦 xff08 KW xff09 和转 分 xff08 r
  • 使用示波器测量串口波特率

    使用示波器测量串口波特率 波特率和bit时间计算关系基础概念基础知识 xff1a 测量结果 波特率和bit时间计算关系 基础概念 简而言之 xff0c 串口传输的波特率即为每秒钟传输二进制的位数 脱离枯燥乏味的文字描述 xff0c 我们用波
  • 雷达基本原理和组成

    雷达由天线辐射电磁波 xff0c 并通过天线接收目标反射回的电磁波 目标回波 xff0c 然后通过接收机和信号处理机从目标回波中提取信息 提取的目标信息主要包括距离 方位 俯仰和速度等 雷达系统的主要组成如图所示 xff0c 主要由发射机
  • 雷达坐标转换

    function r azimuth elevation flag AE r a etrue flag AE Truenum coordinate conversion XYZ t Angle Az Angle El 1 a 雷达直角坐标系
  • STM32F4驱动GPS(寄存器版)

    本次使用STM32F4的USART1对GPS模块进行驱动 xff0c 并且将GPS的时间 经纬度通过串口打印出来 gps模块与接线图 注意 xff1a GPS 模块需放到窗户边 阳台 xff0c 否则可能收不到 GPS 信号 1 GPS驱动
  • 宏的使用 extern

    https www cnblogs com chulin p 9389254 html
  • 【linux tcp抓包之三次握手】

    linux tcp抓包之三次握手 写在前面三次握手tcpdump参数说明返回值说明IP 127 0 0 1 42004 gt 127 0 0 1 5051Flags S win 43690 options mss 65495 sackOK
  • 姿态解算原理(一)——旋转矩阵

    像我们常见的MPU6050 MPU9250等等都是一种捷联式的惯性元件 xff0c 还是一种低成本的 xff0c 还有一种是平台式的惯性导航 xff0c 不过我们能够用得起的就是低成本的MEMS惯性元件 xff0c 本文的内容是姿态解算的原
  • 【c++编译】makefile与CMake【转】

    makefile与CMake 简单介绍CMake简介检查是否安装CMake常用指令简单应用 简单介绍 我们一般使用makefile文件组织大型C C 43 43 或者含有多个C C 43 43 文件的项目 xff0c 有人认为makefil
  • octomap(1):octomap_server报错MessageFilter [target=odom_combined ]: Dropped 100.00% of messages so far

    报错 xff1a WARN 1652516663 964101392 MessageFilter target 61 odom combined Dropped 100 00 of messages so far Please turn t
  • QT定位地图制作

    一 qt绘画图形 1 创建绘图事件函数 nbsp nbsp nbsp nbsp lt 1 gt paintEvent QPaintEvent QPainter painter this 2 设置画布尺寸 确定原点 X Y轴方向 nbsp n
  • CMake学习

    1 引言 使用cmake管理SLAM工程很方便 xff0c 编译便捷 2 具体学习 推荐 cmake实践 1 MESSAGE在make时的输出 CMakeLists txt内容 PROJECT span class token punctu