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)
其中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})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
实际上,上面第三句话不会生效,因为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)
int main(int argc, char** argv)
{
HelloFunc();
return 0;
}
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(${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)
12. FindPACKAGE相关的
FindPACKAGE相关的内容在这一篇中写了,填了FIND_LIBRARY
的坑之后,发现自己debug的能力还是有些欠缺,
- 多看看stackoverflow,对于找到的debug建议多尝试,不要老憋着,效率很低,要长记性!
- 自己有时候很多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")
不知道有什么作用,来补一下,主要参考这两篇博客
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)
而后面的-O2(是字母opq的o,大写的欧)是用来调节编译时的优化程度的,最高为-O3,最低为-O0(即不做优化);
并使用SSE4指令集
在SLAM14讲代码中,slambook2/ch7/orb_self.cpp中使用了SSE指令,有这两条
#include <nmmintrin.h>
.....
.....
distance += _mm_popcnt_u32(desc1[i1][k] ^ desc2[i2][k]);
4. set()指令
CMake中的set用于给一般变量,缓存变量,环境变量赋值。(说白了就是在cmake中把后面的值赋给前面的变量)
参考博客
set(FOO “x”)。
set(FOO "x" PARENT_SCOPE)
set(FOO, "x" CACHE <type>)
set(FOO, "x" CACHE <type><docstring> FORCE)
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值和分支:
macro(get_git_hash _git_hash)
find_package(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()
macro(get_git_branch _git_branch)
find_package(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的输入文件:
主程序为:
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;
}
编译并运行该项目,会生成输出文件:
运行结果为:
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)
- main.c 不使用 libhello.so 的任何功能,因此 libhello-world.so 不需要将其依赖—— libhello.so 传递给 main.c,hello-world/CMakeLists.txt 中使用 PRIVATE 关键字;
- main.c 使用 libhello.so 的功能,但是libhello-world.so 不使用,hello-world/CMakeLists.txt 中使用 INTERFACE 关键字;
- 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(使用前将#替换为@)