CMake笔记
文章目录
- CMake笔记
- 1 工程项目一般形式
- 2 常见命令
- 2.1 project
- 2.2 set
- 2.3 message
- 2.4 add_executable()
- 2.5 语法原则
- 2.6 add_subdirectory
- 2.7 add_library
- 2.8 list
- 3 安装
- 3.1 安装.h文件/文本文件
- 3.2 安装工程脚本
- 3.3 安装目录/目录下内容
- 3.4 安装库文件
- 3.5安装过程
- 4 静态库和动态库
- 4.1 同时构建静态库和动态库
- 4.2 安装头文件.h和库.a/.so
- 4.2.1 安装过程
- 4.2.2 使用外部共享库和头文件
- 4.2.3 使用find_package()来找到自定义库
- 5 find_package()
- 5.1 find_package中的xxx是什么?
- 5.2 使用find_package命令就是为了找到头文件和库文件位置
- 5.3 find_path
- 5.4 find_library
- 6 target_link_libraries(库文件LIBRARIES)
- 7 include_directories()
1 工程项目一般形式
- 建立build文件夹
mkdir build
- 进入build文件
cd build
- cmake生成makefile文件
cmake .. # cmake是对CMakeLists.txt操作,操作命令要在同一文件夹
- make编译
make # make是对Makefile操作
- 调用
./useHello # 这里是调用build文件夹下的helloSLAM
cmake会自己找.h文件 如果.h不在当前文件夹,需要加上include_directories(文件名)
若.cpp也不在当前文件,在库文件哪里也加上路径
# 声明要求的 cmake 最低版本
cmake_minimum_required(VERSION 2.8)
include_directories("include")
# 声明一个 cmake 工程
project(HelloSLAM)
# 设置编译模式
set(CMAKE_BUILD_TYPE "Debug")
# 添加hello库 静态库
add_library(hello_shared ./src/libHelloSLAM.cpp)
# 共享库 动态库
add_library(hello_shared SHARED ./src/libHelloSLAM.cpp)
# 添加可执行程序调用hello库中函数
# 语法:add_executable( 程序名 源代码文件 )
add_executable(useHello ./src/useHello.cpp)
# 将库文件链接到可执行程序上
target_link_libraries(useHello hello_shared)
2 常见命令
2.1 project
PROJECT(projectname [CXX] [C] [Java]) #项目名 以及支持的语言 默认支持所有的语言
- 创建项目指令
project
默认创建了一下两个cmake
变量
<projectname>_BINARY_DIR # 内部编译!!!
<projectname>_SOURCE_DIR
# 可以用message输出查看,在cmake在编译CMakeLists.txt时候就会输出对应的信息
message(STATUS "this is a binary dir" <projectname>_BINARY_DIR)
- 系统预定义的两个变量,修改工程名不会影响这两个变量的值!
PROJECT_BINARY_DIR # (make)编译路径
PROJECT_SOURCE_DIR # 工程(源文件)路径
- 实验
project(Hello CXX)
message(STATUS "this is binary dir" ${Hello_BINARY_DIR})
# 等价 message(STATUS "this is binary dir" ${PORECT_BINARY_DIR})
message(STATUS "this is source dir" ${Hello_SOURCE_DIR})
# 等价 message(STATUS "this is source dir" ${PROJECT_SOURCE_DIR})
add_executable(hello hello.cpp)
2.2 set
- 用来设置指定变量的一个替换变量
SET(SRC_LIST main.cpp) # SRC_LIST变量就代表了main.cpp
SET(SRC_LIST main.cpp t1.cpp t2.cpp) # SRC_LIST变量就代表了main.cpp t1.cpp t2.cpp
- 其它的一些变量设置
# 设置CMake预定义的内建变量
set(CMAKE_CXX_FLAGS "-O3") # 优化选项,告诉编译器优化我们的代码
set(CMAKE_CXX_FLAGS "-std = c++11") # 设置为c++11标准
# 设置编译模式
set(CMAKE_BUILD_TYPE "Debug") # 占用空间比较大,可以debug
set(CMAKE_BUILD_TYPE "Release") # 占用空间比较小
- 实验1:利用变量cc来替换Hello.cpp
project(Hello CXX)
set(CC hello.cpp)
add_executable(hello CC)
2.3 message
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
SEND_ERROR
,产生错误,生成过程被跳过。STATUS
,输出前缀是一个 — 的信息。FATAL_ERROR
,立即终止所有cmake 过程.
上面三种选择一种,引号用来解释要输出的信息,三个点表示对应的变量
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
2.4 add_executable()
ADD_EXECUTABLE(hello ${SRC_LIST}) # 生成的可执行文件名是hello
# 源文件读取变量SRC_LIST中的内容
ADD_EXECUTABLE(hello main.cpp) # 这两句是等价的。
2.5 语法原则
-
变量使用
${}
方式取值,但是在 IF 控制语句中是直接使用变量名 -
指令(参数 1 参数 2…) 参数使用括弧括起,参数之间使用空格或封号分开。
-
指令是大小写无关的,参数和变量是大小写相关的。
2.6 add_subdirectory
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# source_dir 用于向当前工程添加存放 源文件 的子目录
# [binary_dir] 指定(子目录里面生成的可执行文件)中间二进制和目标二进制存放的位置
# EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除
- 实验验证
首先改写SLAM第三讲中的CMakeLists.txt文件,指定可执行文件的位置(build/…)。
cmake_minimum_required(VERSION 2.8)
project(chapter3)
set(CMAKE_CXX_FLAGS "-std=c++11")
add_subdirectory(useEigen useEigen)
add_subdirectory(useGeometry useGeometry)
add_subdirectory(visualizeGeometry visualizeGeometry)
add_subdirectory(examples examples)
编译之后,其可执行文件都被保存在了build/…与定义的路径下面
比如
相反,如果我们不设置其可执行文件的输出位置,默认在最外层build/建立和项目名一致的文件夹
cmake_minimum_required(VERSION 2.8)
project(chapter3)
set(CMAKE_CXX_FLAGS "-std=c++11")
add_subdirectory(useEigen ./cc)
add_subdirectory(useGeometry)
add_subdirectory(visualizeGeometry)
add_subdirectory(examples)
2.7 add_library
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
hello
:就是正常的库名,生成的名字前面会加上lib,最终产生的文件是libhello.soSHARED
,动态库 STATIC,静态库${LIBHELLO_SRC}
:源文件,比如自己写的hello.cpp
2.8 list
set( CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" )
list( APPEND CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules" )
# 表示将后面的 "" 里面的内容添加到CMAKE_MODULE_PATH列表里
3 安装
install
安装可以包括:二进制、动态库、静态库以及文件、目录、脚本等。测试样例在主目录下CMake下面test1.
.
├── build
├── CMakeLists.txt
├── COPYRIGHT # 版权
├── doc # 目录
│ └── hello.txt
├── README # 帮助文档
├── runhello.sh
└── src
├── CMakeLists.txt
└── hello.cpp
3 directories : build,doc,src 7 files : ...
3.1 安装.h文件/文本文件
project(Hello)
add_subdirectory(src bin)
# 安装文件FILES(上面创建的COPYRIGHT和README) share/doc/cmake/是安装路径
install(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
# 也可以安装头文件.h 见4.2
install(FILES hello.h DESTINATION include/hello)
参数1:DESTINATION
DESTINATION # destination 目的地 即安装的路径
1、写绝对路径
2、可以写相对路径,相对路径实际路径是:${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
CMAKE_INSTALL_PREFIX 默认是在 /usr/local/
cmake -DCMAKE_INSTALL_PREFIX=/usr 在cmake的时候指定CMAKE_INSTALL_PREFIX变量的路径
3.2 安装工程脚本
project(Hello)
add_subdirectory(src bin)
# 2.安装工程脚本PROGRAMS 实际路径: /usr/local/bin
install(PROGRAMS test.sh DESTINATION bin)
参数2:PROGRAMS,非目标文件的可执行程序安装(比如脚本之类)
3.3 安装目录/目录下内容
project(Hello)
add_subdirectory(src bin)
# DIRECTORY 3.安装目录下内容hello.txt 因为doc后面加了/
# DIRECTORY 后面连接的是所在 Source 目录的相对路径
install(DIRECTORY doc/ DESTINATION share/doc/cmake)
参数3:doc/
目录名不以/结尾:这个 目录 将被安装为目标路径下的
目录名以/结尾:将这个 目录中的内容 安装到目标路径
3.4 安装库文件
project(Hello)
add_subdirectory(src bin)
# 4 安装库文件 见4.2
# 二进制,静态库,动态库安装都用TARGETS
# ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
3.5安装过程
pj@p:~/slambook/CMake/build$ camke ..
pj@p:~/slambook/CMake/build$ make -j4
[ 50%] Building CXX object bin/CMakeFiles/hello.dir/main.o
[100%] Linking CXX executable hello
[100%] Built target hello
pj@p:~/slambook/CMake/build$ sudo make install # 这里才开始安装install
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Up-to-date: /usr/local/share/doc/cmake/COPYRIGHT
-- Up-to-date: /usr/local/share/doc/cmake/README
-- Installing: /usr/local/bin/test.sh
-- Up-to-date: /usr/local/share/doc/cmake
-- Installing: /usr/local/share/doc/cmake/test_cmake.txt
这里很重要,因为有些安装包你下载的地方不是默认的地方,把它cmake编译之后,还要将其加入的默认路径 /usr/local/...
4 静态库和动态库
静态库和动态库的区别
- 静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”
- 静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行
- 动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。
测试样例于cmake/test2
4.1 同时构建静态库和动态库
先构建如下文件
pj@p:~/CMake/test2$ tree
.
├── build
├── CMakeLists.txt
└── lib
├── CMakeLists.txt
├── hello.cpp
└── hello.h
2 directories, 4 files
hello.h中的内容
#ifndef HELLO_H
#define Hello_H
void HelloFunc();
#endif
hello.cpp中的内容
#include "hello.h"
#include <iostream>
void HelloFunc(){
std::cout << "Hello World" << std::endl;
}
项目中的cmake内容
PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin)
下面这种方法不能同时构建,只会生成一中类型的库
# 如果用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是.a
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
# 修改静态库的名字,这样是可以的,但是我们往往希望他们的名字是相同的,只是后缀不同而已
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
- SET_TARGET_PROPERTIES
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本,同时构建静态和动态库。lib下cmakelists.txt内容如下。
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
# 对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1) # 清除
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
4.2 安装头文件.h和库.a/.so
我们将 hello 的共享库安装到/lib目录,将 hello.h 安装到/include/hello 目录
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
# 对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1) # 清除
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
# 文件放到该目录下
INSTALL(FILES hello.h DESTINATION include/hello)
# 二进制,静态库,动态库安装都用TARGETS
# ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
4.2.1 安装过程
pj@p:~/CMake/test2/build$ cmake -D CMAKE_INSTALL_PREFIX=/usr ..
pj@p:~/CMake/test2/build$ make
[ 25%] Building CXX object bin/CMakeFiles/hello_static.dir/hello.o
[ 50%] Linking CXX static library libhello.a
[ 50%] Built target hello_static
[ 75%] Building CXX object bin/CMakeFiles/hello.dir/hello.o
[100%] Linking CXX shared library libhello.so
[100%] Built target hello
pj@p:~/CMake/test2/build$ sudo make install # 更改默认路径
Consolidate compiler generated dependencies of target hello_static
[ 50%] Built target hello_static
Consolidate compiler generated dependencies of target hello
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /usr/include/hello/hello.h
-- Installing: /usr/lib/libhello.so
-- Installing: /usr/lib/libhello.a
结果
4.2.2 使用外部共享库和头文件
准备工作,新建一个目录来使用外部共享库和头文件(使用上面安装的库)
[root@MiWiFi-R4CM-srv cmake3]# tree
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
CMakeLists.txt
project(hello)
add_subdirectory(src bin)
main.cpp
#include <hello.h>
int main(){
HelloFunc();
}
src
下的CMakeLists.txt
include_directories("/usr/include/hello") # 链接头文件 .h
add_executable(test main.cpp)
target_link_libraries(test /usr/lib/libhello.so) # 链接库文件 .cpp libhello.so也可
4.2.3 使用find_package()来找到自定义库
- FindHELLO.cmake
find_path(HELLO_INCLUDE_DIR hello.h /usr/include/hello)
find_library(HELLO_LIBRARY NAMES libhello.so PATHS /usr/lib )
if (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
set(HELLO_FOUND TRUE)
endif (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
写在最外层CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(hello)
## set(CMAKE_MODULE_PATH "../cmake") 这里可以写绝对路径,然后可以分子项目,具体见test4
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
add_executable(test main.cpp)
find_package(HELLO)
message(STATUS "HELLO_INCLUDE_DIRS " ${HELLO_INCLUDE_DIR} )
message(STATUS " HELLO_LIBRARY " ${HELLO_LIBRARY} )
if(HELLO_FOUND)
target_include_directories(test PRIVATE ${HELLO_INCLUDE_DIR})
target_link_libraries(test ${HELLO_LIBRARY}) # 链接库文件 .cpp libhello.so也可
else(HELLO_FOUND)
message(FATAL_ERROR "HELLO library not found")
endif(HELLO_FOUND)
如果分为子项目,即最外层只写
cmake_minimum_required(VERSION 3.22)
project(hello)
add_subdirectory(src bin)
和上面的cmakelists相同,只需要改一句
set(CMAKE_MODULE_PATH "../cmake") # 这里可以写绝对路径,然后可以分子项目,具体见test4
5 find_package()
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER])
${XXX_INCLUDE_DIRS} ## 一般要查找这两个变量,即头文件与源文件位置
${XXX_LIBRARIES}
find_package(Pangolin REQUIRED) # 查找Pangolin库,REQUIRED指找不到就在此停止程序。
include_directories(${Pangolin_INCLUDE_DIRS}) # Pangolin库的头文件
add_executable(plotTrajectory plotTrajectory.cpp)
target_link_libraries(plotTrajectory ${Pangolin_LIBRARIES}) # 链接Pangolin的源文件,库
5.1 find_package中的xxx是什么?
# locate 库名 | grep cmake xxx就是Config前面那个名字
locate sophus | grep cmake
locate Pangolin | grep cmake
5.2 使用find_package命令就是为了找到头文件和库文件位置
include_directories(${XXX_INCLUDE_DIRS}) # 目的就是链接
target_link_libraries(x ${XXX_LIBRARIES})
# 这种方法是可行的,比如Pangolin库是没问题的
find_package(Pangolin REQUIRED) # 查找Pangolin库,REQUIRED指找不到就在此停止程序。
include_directories(${Pangolin_INCLUDE_DIRS}) # Pangolin库的头文件
add_executable(plotTrajectory plotTrajectory.cpp)
target_link_libraries(plotTrajectory ${Pangolin_LIBRARIES}) # 链接Pangolin的源文件,库
但是,sophus这个库直接加这两个路径是不行的。我们可以载程序中使用message查看信息,它输出的是空白!
message(STATUS "Sophus_INCLUDE_DIRS" ${Sophus_INCLUDE_DIRS})
message(STATUS "Sophus_INCLUDE_DIRS" ${Sophus_LIBRARIES})
实际上我们已经安装了sophus的,并且它确实不能找到(以这种方式)
- 解决上述问题的办法
# 1. 直接将绝对路径赋给它
include_directories(/usr/local/include/sophus)
# 2. Sophus::Sophus
target_link_libraries(useSophus Sophus::Sophus)
解释一下第二种方法的原因,
我们打开那个SophusTargets.cmake
,里面有部分内容如下。就是可以把Sophus理解为一个类,对象,通过::来导入库。
# Create imported target Sophus::Sophus
add_library(Sophus::Sophus INTERFACE IMPORTED)
之所以不能用${Sophus_INCLUDE_DIRS})
,是因为在Config.cmake文件种没有这个东西。
但是eigen里面就有,所以eigen可以使用那种方式来链接库,eigen由头文件组成,没有库文件
5.3 find_path
find_path(myCeres NAMES ceress.h PATHS /ceres/include/ceres NO_DEFAULT_PATH)
include_directories(${myCeres})
# eg hello.h为要找的头文件 /usr/include/hello为绝对路径
find_path(HELLO_INCLUDE_DIR hello.h /usr/include/hello)
5.4 find_library
find_library (<VAR> name [path1 path2 ...]) #<VAR> 找到的库的变量名(全路径)
# eg NAMES是指要找的动态库的名字 PATHS指我们为出现提供的路径,可以有多个
find_library(HELLO_LIBRARY NAMES libhello.so PATHS /usr/lib )
message(STATUS "HELLO_INCLUDE_DIRS " ${HELLO_INCLUDE_DIR} )
message(STATUS " HELLO_LIBRARY " ${HELLO_LIBRARY} )
6 target_link_libraries(库文件LIBRARIES)
# 这里要说的是,item可以库文件
target_link_libraries(plotTrajectory ${Pangolin_LIBRARIES})
# item可以是路径
target_link_libraries(useHello ./src/libHelloSLAM.cpp)
# item可以是target,就是上面我们locate xx | grep cmake 找的东西
# 而且,一旦有这个东西,那么用例如这样形式 Sophus::Sophus 我们就不需要include加头文件、库文件等
target_link_libraries(useSophus Sophus::Sophus)
7 include_directories()
这里想补充以下关于include_directories()的用法,用于指定头文件的搜索路径,方便编译器查找相应的头文件。
#include <pangolin/pangolin.h> // 比如说我们要调用eigen库和pangolin库
#include <Eigen/Core>
查看库的路径