【Cmake】【Cmake实践】【cmake的使用学习记录】
- 00 速查汇总
- 0 前言
- 1 初试cmake-cmake的helloworld
- 2 更好一点的Hello World
- 3 静态库和动态库构建
- 4 如何使用外部共享库和头文件
- 5 cmake常用变量和常用环境变量
- 5.1 cmake变量的引用方式
- 5.2 cmake自定义变量的方式
- 5.3 cmake常用变量
- 5.3.1 `CMAKE_BINARY_DIR`、`PROJECT_BINARY_DIR`、`<projectname>_BINARY_DIR`
- 5.3.2 `CMAKE_SOURCE_DIR`、`PROJECT_SOURCE_DIR`、`<projectname>_SOURCE_DIR`
- 5.3.3 `CMAKE_CURRENT_SOURCE_DIR`
- 5.3.4 `CMAKE_CURRENT_BINARY_DIR`
- 5.3.5 `CMAKE_CURRENT_LIST_FILE`
- 5.3.6 `CMAKE_CURRENT_LIST_LINE`
- 5.3.7 `CMAKE_MODULE_PATH`
- 5.3.8 `EXECUTABLE_OUTPUT_PATH`和`LIBRARY_OUTPUT_PATH`
- 5.3.9 `PROJECT_NAME`
- 5.4 cmake调用环境变量的方式
- 5.5 输出系统信息的变量
- 5.6 主要的开关选项
- 6 cmake常用指令
- 7 收集CMakeLists好的使用习惯
- 8 参考文章
00 速查汇总
00.0 汇总一览
cmake_minimum_required(VERSION 2.8)
project(项目名)//附加自动生成变量 主目录${PROJECT_SOURCE_DIR}和编译目录${PROJECT_BINARY_DIR}
set(CMAKE_CXX_FLAGS "-std=c++11")
set(CMAKE_CXX_STANDARD 11)//根据需求选择11或者14
set(CMAKE_BUILD_TYPE "Release")//或者Debug,已Release方式编译
find_package(ceres REQUIRED)
include_directories(${CERES_INCLUDE_DIRS})//${名_INCLUDE_DIRS}和${名_DIRS}相同
add_executable(main main.cpp)
target_link_libraries(main ${CERES_LIBRARIES} ...)//${名_LIBRARIES}和${名_LIBS}不完全一样,最好用全称
//--------------------------以上为常用,以下为附加功能----------------------------------//
MESSAGE(STATUS "this is a message." ${CERES_INCLUDE_DIR})
//添加模块包(假设所有文件夹cmake_modules 文件夹内有.cmake文件,如FindG2O.cmake)
//则,添加cmake模块以使用g2o方法为
list(APPEND CMAKE_MODULE ${PROJECT_SOURCE_DIR}/cmake-modules)
0 前言
- 本文章主要参考的是Cmake实践这本书,
1 初试cmake-cmake的helloworld
- 默认所有的根目录是所创建的cmake_study下
- properties 属性的意思
1.1 简单的整个流程介绍
- 进入cmake_study文件夹,创建t1文件夹作为练习目录,并再该目录下创建main.cpp和CMakeLists.txt文件:
mkdir t1
cd t1
touch main.cpp
touch CMakeLists.txt
main.cpp内容为:
#include <iostream>
using namespace std;
int main(int argc,char ** argv)
{
cout << "Hello World!" << endl;
return 0;
}
CMakeLists.txt的内容为
project(HELLO)
set(SRC_LIST main.cpp)
message(STATUS "This is BINARY dir" ${HELLO_BINARY_DIR})
message(STATUS "This is SOURCE dir" ${HELLO_SOURCE_DIR})
add_executable(hello ${SRC_LIST})
#add_executable(hello main.cpp)
- ${}是获取变量的值
- 开始构建
cmake .
注意:命令后面的点号,代表本目录
- 可以看到输出是:
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t1$ cmake .
-- This is BINARY dir/home/bupo/my_study/slam14/slam14_my/cap2/cmake_study/t1
-- This is SOURCE dir/home/bupo/my_study/slam14/slam14_my/cap2/cmake_study/t1
-- Configuring done
-- Generating done
-- Build files have been written to: /home/bupo/my_study/slam14/slam14_my/cap2/cmake_study/t1
- 并且生成了:
CMakeFiles文件夹
CMakeCache.txt
cmake_install.cmake
Makefile最重要的
- 进行工程的实际创建
make
输出:
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t1$ make
[ 50%] Building CXX object CMakeFiles/hello.dir/main.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
- 可以看到在当前目录生成可执行文件
hello
- 运行可执行文件
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t1$ ./hello
Hello World!
1.2 简单的解释
1.2.0 约定说明
- [] 表示可以省略
- 指令大小写都可以
1.2.1 project()
指令语法
- PROJECT的指令语法是:
PROJECT(projectname [CXX] [C] [Java])
- projectname:工程名
- 可以指定工程支持的语言:可省略,默认支持所有语言
- 该指令隐式定义两个cmake变量:
< projectname >_BINARY_DIR:cmake执行的路径,编译路径
< projectname >_SOURCE_DIR:CMakeLists.txt所在的路径,工程路径
在1.1的例子中我们也对该变量进行了输出 - cmake也预定义了两个系统变量:
PROJECT_BINARY_DIR:cmake执行的路径
PROJECT_SOURCE_DIR:CMakeLists.txt所在的路径
建议使用这个,这样修改工程名不会影响其他代码
1.2.2 set()指令语法
- SET的指令语法是
set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
- 作用:用来显示的定义变量
- 变量VAR:所定义的变量名称
- 变量VALUE:变量值,可以是任何东西,这里就是文件main.cpp等,甚至多个文件`set(SRC_LIST main.cpp t1.cpp t2.cpp)
- 定义的是变量,若想获取其值,使用
${VAR}
1.2.3 message()
指令语法
- MESSAGE的指令语法:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
- 作用:用于向终端输出用户定义的信息
- 变量SEND_ERROR:产生错误,生成过程被跳过
- 变量STATUS:输出前缀为-信息
- 变量FATAL_ERROR:立即终止所有cmake过程
1.2.4 add_executable
语法指令
- ADD_EXECUTABLE的指令语法
add_executable(hello ${SRC_LIST})
- 会生成一个文件名为hello的可执行文件
- 相关的源文件是SRC_LIST中定义的源文件列表
1.3 基本语法规则
1.3.1 ${}变量取值
- 变量使用
${}
方式取值 - 但是在IF控制语句中是直接使用变量名引用,而不需要使用 。如果是用了 {}。如果是用了 。如果是用了{}去应用变量,其实IF会去判断名为${}所代表的值的变量,这当然是不存在的了。
1.3.2 指令(参数1 参数2 …)
- 参数使用括弧括起,参数之间使用空格或分号分开
1.3.3 大小写的使用
- 指令的大小写无关的,但推荐你全部使用大写指令
- 参数和变量是大小写相关的。
1.3.4 其他注意事项
SET(SRC_LIST main.c)
也可以写成SET(SRC_LIST "main.c")
没有区别
- 但是如果一个源文件的文件名包含空格,如:
fu nc.c
;那么就必须使用双引号
- 可以省略source列表中的后缀
比如ADD_EXECUTABLE(t1 main.c)
可以写成ADD_EXECUTABLE(t1 main)
,cmake会自动在本目录内查找main.c或者main.cpp等;但是最好不要偷懒,以免这个目录确实存在一个main.c和一个main - 参数也可以用分号来分割
如add_executable(t1 main.c t1.c)
可以写成add_executable(t1 main.c;t1.c)
注意统一风格即可
1.4 清理工程
make clean
清理构建结果,也就是生成的可执行文件- 无法对中间文件进行清理,因为没办法跟踪这些临时文件到底是哪些。
1.5 外部编译
- 创建build文件夹,进入内部编译
- 注意修改CMakeLists.txt里面的个文件路径
- 即out-of-source外部编译
2 更好一点的Hello World
- 本小节的任务是:
- 为工程添加一个子目录src,用来放置工程源代码;
- 添加一个子目录doc,用来放置这个工程的文档hello.txt;
- 在工程目录添加文本文件COPYRIGHT,README;
- 在工程目录添加一个runhello.sh脚本,用来调用hello二进制
- 将构建后的目标文件放入构建目录的bin子目录
- 最终安装这些文件:
将hello二进制与runhello.sh安装至/usr/bin,
将doc目录的内容以及COPYRIGHT/README安装到/usr/share/doc/cmake/t2
2.1 完整的工程
- 创建t2主文件夹,在该文件夹下创建src文件夹
mkdir t2
cd t2
mkdir src
- 并创建一些文件:在t2下创建CMakeLists.txt,在src下创建main.cpp和CMakeLists.txt问价,其中:
t2下CMakeLists.txt内容为:
project(HELLO)
add_subdirectory(src bin)
src下main.cpp内容为:
#include <iostream>
using namespace std;
int main(int argc,char ** argv)
{
cout << "Hello World!" << endl;
return 0;
}
src下CMakeLists.txt内容为:
add_executable(hello main.cpp)
- 编译
mkdir build
cd build
cmake ..
make
你会发现生成的目标文件hello位于build/bin目录中
2.2 语法解释
2.2.1 add_subdirectory
语法指令
有个有意思的发现,就是使用多个CMakeLists.txt时候,主CMakeLists.txt遇到add_subdirectory会先执行这个,执行结束然后才会返回主CMakeLists.txt
1.
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- 作用:用于向当前工程添加存放源文件的子目录;并可以指定中间二进制和目标二进制存放的位置。
- 参数EXCLUDE_FROM_ALL:将这个目录从编译过程中排除,比如工程的example,需要工程构建完成后,在进行编译
- 上面的例子
add_subdirectory(src bin)
定义了将src子目录加入到工程中,并指定编译输出(包含编译中间结果)路径为bin目录。
- 如果不进行bin目录的指定,编译结果(包括中间结果)存放在build/src目录(这个目录跟原有的src目录对应),指定bin目录后,相当于在编译时将src重命名为bin,所有的中间结果和目标二进制都将存放在bin目录。
2.2.2 换个地方保存目标二进制
- 可以通过
SET
指令重新定义EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH
变量来指定最终的目标二进制的位置(指最终生成的hello或者最终的共享库,不包含编译生成的中间文件)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
:这个指令定义了可执行二进制的输出路径为build/binSET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
:库的输出路径为build/lib- 注意:把握一个原则::ADD_EXECUTABLE和ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义
在这个例子中,当然是指src下的CMakeLists.txt。
2.3 如何安装
安装的需要有两种:
- 一种是从代码编译后直接make install安装
- 一种是打包时的指定目录安装
2.3.1 make install
- 将hello直接安装到
/usr/bin
目录,也可以通过make install DESTDIR=/tmp/test
将他安装在/tmp/test/usr/bin
目录,打包时就这个方式经常被使用
2.3.2 新指令INSTALL和变量CMAKE_INSTALL_PREFIX
CMAKE_INSTALL_PREFIX
类似于configure脚本的-prefix,常用方法是cmake -CMAKE_INSTALL_PREFIX=/usr .
- INSTALL指令用于定义安装规则,安装的内容可以包括目标二进制,动态库,静态库以及文件、目录、脚本等。
2.3.2.1 目标文件的安装
- 代码使用说明:
INSTALL (TARGETS targets...
[[ARCHIVER|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]]
[...])
- 参数
TARHGETS
:后面跟的就是通过ADD_EXECUTABLE或者ADD_LIBRARY定义的目标文件,可能是可执行二进制、动态库、静态库等。 - 参数
ARCHIVER|LIBRARY|RUNTIME
:
ARCHIVER指静态库
LIBRARY指动态库
RUNTIME特指可执行目标二进制 - 参数
DESTINATION
:定义了安装的路径,路径如果以/开头,那么就是指绝对路径,这时候CMAKE_INSTALL_PREFIX就无效了,如果希望使用CMAKE_INSTALL_PREFIX就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
- 参数
PERMISSIONS
:权限
- 例子
INSTALL(TARGETS myrun mylib mtsyaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
- 上述例子说明,会将
- 可执行二进制myrun安装到
${CMAKE_INSTALL_PRIFIX}/bin
目录 - 可执行二进制mylib 安装到
${CMAKE_INSTALL_PRIFIX}/lib
目录 - 可执行二进制mtsyaticlib安装到
${CMAKE_INSTALL_PRIFIX}/libstatic
目录 - 不需要关心TARGETS具体生成的路径,只需要写上TARGETS名称就可以
2.3.2.2 普通文件的安装
- 使用代码:
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release]...]
[COMPONENT <component>]
[RENAME <name> [OPTIONAL]])
- 作用:可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。
- 参数
DESTINATION
:定义了安装的路径,路径如果以/开头,那么就是指绝对路径,这时候CMAKE_INSTALL_PREFIX就无效了,如果希望使用CMAKE_INSTALL_PREFIX就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
- 参数
PERMISSIONS
:如果默认不定义权限PERMISSIONS,安装后的权限为WONER_WRITE
,OWNER_READ
,GROUP_READ
,WORLD_READ
,即644权限
2.3.2.3 非目标文件的可执行程序安装(如脚本之类)
- 命令代码:
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release]...]
[COMPONENT <component>]
[RENAME <name> [OPTIONAL]])
- 使用同2.3.2.2
- 唯一不同的就是安装后的权限为:
WONER_EXECUTE
,GROUP_EXECUTE
,WORLD_EXECUTE
,即755权限
2.3.2.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...]] [...])
- 参数
DIRECTORY
:后面接的是所在source目录的相对路径,但务必注意:abc和abc/有很大的区别:如果目录不以/结尾,那么这个目录将被安装为目标路径下的abc,如果目录名以/结尾,代表将这个目录中的内容安装到目录路径,但不包括这个目录本身 - 参数
PAYTTERN
:用于使用正则表达式进行过滤 - 参数
PERMISSIONS
:用于指定PATTERN过滤后的文件权限
- 例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECULE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ)
- 这条指令执行的是:
将icons目录安装到<prefix>/share/myproj
将scripts/中的内容安装到<prefix>/share/myproj
不包含目录名为CVS
的目录,对于scripts/*
文件指定权限为OWNER_EXECUTE
,OWNER_WRITE
,OWNER_READ
,GROUP_EXECUTE
,GROUP_READ
2.3.2.5 CMAKE脚本的安装
- 代码
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
- 参数
SCRIPT
:用于安装时调用cmake脚本文件(也就是< abd >.cmake文件 ) - 参数
CODE
:用于执行CMAKE命令,必须以双引号括起来
- 例子
INSTALL(CODE "MESSAGE(\"Sample install message.\")")
2.3.3 改写我们的文件进行安装
- 下面改写我们的工程文件,支持各种文件的安装,并且,我们要使用CMAKE_INSTALL_PREFIX指令,在2小节开始已经说过了要实现的内容:
- 下面进行修改:
2.3.3.1 首先补上确实的文件
- 添加doc目录及文件hello.txt
mkdir doc
cd doc
touch hello.txt
hello.txt内容随便写一点
- 在工程目录中田间runhello.sh脚本,内容为:
hello
- 添加工程目录中的COPYRIGHT和README:
touch COPYRIGHT
touch README
2.3.3.2 改写各目录的CMakeLists.txt
- 安装COPYRIGHT/README,直接修改主工程文件CMakeLIsts.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
- 安装runhello.sh,直接修改主工程文件CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
- 安装doc中的hello.txt,这里两种方式:
- 一种是通过doc目录建立CMakeLists.txt并将doc目录通过ADD_SUBDIRECTORY加入工程来完成。这种的简单
- 另一种方法是直接在工程目录通过INSATLL来完成,我们来尝试后者
- 由于是要把hello.txt安装到
/<profix>/share/doc/cmake/t2
,所以不能直接安装整个doc目录,这里采用的方式是安装doc目录中的内容,也就是使用“doc/
” - 综上,在工程目录的CMakeLists.txt文件中添加:
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
2.3.3.3 运行指令进行下载
- 确定
CMAKE_INSTALL_PREFIX = /tmp/t2
参数,需要进入build目录进行外部编译,则指令为:
mdkir build
cd build
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
make
make install
输出为:
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t2/build$ make install
Consolidate compiler generated dependencies of target hello
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /tmp/t2/usr/share/doc/cmake/t2/COPYRIGHT
-- Installing: /tmp/t2/usr/share/doc/cmake/t2/README
-- Installing: /tmp/t2/usr/bin/runhello.sh
-- Up-to-date: /tmp/t2/usr/share/doc/cmake/t2
-- Installing: /tmp/t2/usr/share/doc/cmake/t2/hello.txt
- 然后就可以进入
/tmp/t2
目录看一下安装结果 - 如果想要直接安装到系统,就可以使用如下指令:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
2.3.4 INSTALL默认地址
- 如果没有定义CMAKE_INSTALL_PREFIX,默认地址是
/usr/local
3 静态库和动态库构建
- 本节的任务:
- 建立一个静态库和动态库,提供HelloFunc函数供其他程序编程使用 ,HelloFunc向终端输出Hello World字符串
- 安装头文件与共享库。
3.1 准备工作
- 建立工程文件夹
mkdir t3
cd t3
mkdir lib
touch CMakeLists.txt
CMakeLists.txt内容如下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib下建立两个源文件hello.c和hello.h
cd lib
touch hello.c hello.h CMakeLists.txt
hello.c的内容如下:
#include "hello.h"
void HelloFunc()
{
printf("Hello World\n");
}
hello.h内容如下:
#ifdef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
CMakeLists.txt内容如下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
3.2 编译共享库
- 仍然采用out-of-source编译的方式
mkdir build
cd build
cmake ..
make
- 之后可以在build/lib文件夹里得到一个libhello.so,这就是我们期望的共享库
- 指定libhello.so生成的位置
- 可以通过在主工程文件CMakeLists.txt中修改
ADD_SUBDIRECTORY(lib)
指令来指定一个编译输出位置 - 或者在lib/CMakeLists.txt中添加
SET(LIBRARY_OUTPUT_PATH<路径>)
来指定一个新的位置
3.3 约定说明
3.3.1 ADD_LIBRARY
指令
- 代码:
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[ECLUDE_FROM_ALL]
source1 source2 ... sourceN)
- 说明:
- 不需要写全
lihello.so
,只需要填写hello即可,cmake系统会自动为你生成libhello.X - 参数
ECLUDE_FROM_ALL
:这个库不会默认构建,除非有其他的组件依赖或者手工构建
3.3.2 库的类型介绍
SHARED
,动态库,共享库,后缀.so
STATIC
,静态库,后缀,.a
MODULE
,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED
对待
3.3.3 SET_TARGET_PROPERIES
指令
- 代码:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTILES prop1 value1
prop2 value2 ...)
- 作用:可以用来设置输出的名称,对于动态库,可以用来指定动态库版本和API版本
- 参数
prop1
:是要操作的属性
3.3.4 GET_TARGET_PROPERTIES
指令
- 代码
GET_TARGET_PROPERTY(VAR target property)
- 作用:是将target的值,赋给VAR,在property的要求下
3.4 添加静态库
- 一般都会让静态库和动态库名字一致,使用下面的指令来添加静态库:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
hello作为一个target重名而指令无效
3.4.1 SET_TARGET_PRPERTIES
同时构建同名的动态库和静态库
- 可以使用
lib/CMakeLIsts.txt
:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
但要求名字相同的静态库和动态库,仅仅这样也是不太好的,会得到hello_static.a
所以在lib/CMakeLIsts.txt
加上:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
这就同时且仅得到libhello.so/libhello.a
两个库,
- 输出信息加上:
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS "This is the hello_static OUTPUT_NAME:"${OUTPUT_VALUE})
加上这个在cmake ..
时候会输出:
-- This is the hello_static OUTPUT_NAME:hello
- 之所以在加上后买你这句
libhello.so
会消失,这是因为cmake在构建一个新的target时,会尝试清理掉其他使用这个名字的库,因为,在构建libhello.a
时,就会清理掉libhello.so
,但是我操作的没有出现这种情况,这里也附上解决方法,以防万一
- 解决方法:再次使用
SET_TARGET_PROPERTIE
定义CLEAN_DIRECT_OUTPUT
属性,向lib/CMakeLists.txt
添加:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
3.5 通过SET_TARGET_PRPERTIES
控制动态库版本
- 为了实现动态库版本号,我们仍然需要使用
SET_TARGET_PROPERTIES
指令:使用方法在lib/CMakeLists.txt
加入代码如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
- 属性
VERSION
:指动态库版本 - 属性
SOVERSION
:指代API版本
- 运行结果:
- 在
build/lib
生成
libhello.so.1.2
libhello.so.1->libhello.s0.1.2
libhello.so->libhello.so.1
3.6 INSTALL安装共享、静态库和头文件
- 安装要求,参考2.3.2.1小节
将hello的共享库和静态库安装到<prefix>/lib
目录
将hello.h安装到<prefix>/include/hello
目录 - 安装指令为向
lib/CMakeLists.txt
添加:
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
- 编译命令:
- 因为是测试需要,这里就不安装到系统
/usr
里面了,安装到/tmp/t3/usr
里面
cd build
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t3/usr ..
make
make install
输出为:
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t3/build$ make install
[ 50%] Built target hello
[100%] Built target hello_static
Install the project...
-- Install configuration: ""
-- Installing: /tmp/t3/usr/lib/libhello.so.1.2
-- Installing: /tmp/t3/usr/lib/libhello.so.1
-- Installing: /tmp/t3/usr/lib/libhello.so
-- Installing: /tmp/t3/usr/lib/libhello.a
-- Installing: /tmp/t3/usr/include/hello/hello.h
3.7 总结
做到了:
- 如何通过
ADD_LIBRARY
指令构建动态库和静态库 - 如何通过
SET_TARGET_PRPERTIES
同时构建同名的动态库和静态库 - 如何通过
SET_TARGET_PRPERTIES
控制动态库版本 - 最终使用上一节谈到的INSTALL指令安装头文件和动态、静态库
3.7.1 代码汇总
- 工程文件夹
t3
- 然后
t3/lib
文件夹、t3/CMakeLists.txt
文件内容如下
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
t3/lib/CMakeLists.txt
文件内容如下:
SET(LIBHELLO_SRC hello.c)
#创建静态库
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})
# 避免在构建`libhello.a`时,就会清理掉`libhello.so`
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
#动态库版本号,动态库版本为1.2 API版本为1
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
# 安装到/tmp/t3/usr,即cmake -DCMAKE_INSTALL_PREFIX=/tmp/t3/usr ..
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
t3/lib/hello.c
文件内容如下:
#include "hello.h"
void HelloFunc()
{
printf("Hello World\n");
}
t3/lib/hello.h
文件内容如下:
#ifdef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
4 如何使用外部共享库和头文件
- 本节任务是编写一个程序使用我们上一节构建的共享库
4.1 约定说明
- 先来介绍几个使用外部共享库和头文件的语句
4.1.1 INCLUDE_DIRECTORIES
指令
- 代码
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
- 作用:可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号括起来
- 默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:
a)CMAKE_INCLUDE_DIRECTORIES_BEFORE
,可以通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有的路径前面
b) 通过AFTER或者BEFORE参数,也可以控制是追加还是置前
4.1.2 TARGET_LINK_LIBRARIES
语法
- 代码
TARGET_LINK_LIBRARIES(target library1
<debug | optimized> library2
...)
- 作用:可以用来为target添加需要链接的共享库
4.1.3 LINK_DIRECTORIES
指令
- 代码
LINK_DIRECTORIES(directory1 directory2 ...)
- 作用:添加非标准的共享库搜索路径;比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。
- 本例子没有用这个,也就没有例子
4.1.4 检查可执行文件的链接情况
- 命令
ldd 可执行文件
如:
ldd src/main
- 例子可看4.2.8
4.1.5 FIND_PATH
和FIND_LIBRARY
的指令
- 代码:
FIND_PATH
用来指定路径中搜索文件名,比如**
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
FIND_LIBRARY
的例子在4.2.6中可以看到
4.2 准备工作
1. 构造主文件夹
mkdir t4
cd t4
touch CMakeLists.txt
工程主文件CMakeLists.txt
内容如下:
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
2. 构建main函数文件
mkdir src build
cd src
touch main.c CMakeLists.txt
其中main.c
内容如下:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
其中src/CMakeLists.txt
内容如下:
ADD_EXECUTABLE(main main.c)
3. 错误提示fatal error: hello.h: 没有那个文件或目录 #include <hello.h>
- 如果此时编译一定会报错
fatal error: hello.h: 没有那个文件或目录 #include <hello.h>
有人说,这是因为hello.h位于第3节编译的目录/tmp/t3/usr/include/hello中,没有位于系统标准的头文件路径/usr中,直接include</tmp/t3/usr/include/hello/hello.h>,这可以解决这个问题,但是仍然要面对后面的链接库的问题,这样写,主要就是为了举例子
4. 在src/CMakeLists.txt
中加入解决3的报错:
INCLUDE_DIRECTORIES(/tmp/t3/usr/include/hello)
- 编译会发现新的报错
main.c:(.text+0xa):对‘HelloFunc’未定义的引用
,这是因为没有link到共享库libhello上
5. 为target添加共享库,解决报错main.c:(.text+0xa):对‘HelloFunc’未定义的引用
普通情况(也就是把库安装在/usr/lib
文件夹里)在src/CMakeLists.txt
中加入:
TARGET_LINK_LIBRARIES(main hello)
或者
TARGET_LINK_LIBRARIES(main libhello.so)
这里的hello指的是我们上一节构建的共享库libhello
6. 指定查找自己的库
====在这里由于我们的库安装在了自己指定的路径/tmp/t3/usr/lib
,所以会有所不同,使用FIND_LIBRARY来寻找库的地址:
#这句话的意思是:在/tmp/t3/usr/lib路径下,搜索hello库,并保存到my_hello变量中
FIND_LIBRARY(my_hello hello /tmp/t3/usr/lib)
TARGET_LINK_LIBRARIES(main ${my_hello})
7. 编译运行就可以在build/src
找到main可执行文件
./build/src/main
8. 检查可执行文件
ldd src/main
输出为:
linux-vdso.so.1 (0x00007ffc21fce000)
libhello.so.1 => /tmp/t3/usr/lib/libhello.so.1 (0x00007f6e3b1e6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6e3adf5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6e3b5ea000)
- 看到main确实链接到了libhello,而且连接的是动态库
libhello.so.1
9. 链接到静态库
只需要把对应方法的hello
修改为libhello.a
就可以了,在自己的方法就是:
FIND_LIBRARY(my_hello libhello.a /tmp/t3/usr/lib)
重新编译就好:
一定注意:重新编译要首先执行make clean不然的话仍然是链接的之前的共享库
cd build
make clean //重新编译一定要执行,不然还是看到链接的共享库
cmake ..
make
ldd src/main
输出为:
linux-vdso.so.1 (0x00007ffc92ba3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f587b37e000)
/lib64/ld-linux-x86-64.so.2 (0x00007f587b971000)
4.3 特殊的环境变量CMAKE_INCLUDE_PATH
和CMAKE_LIBRARY_PATH
环境变量
1. 说明
CMAKE_INCLUDE_PATH
环境变量和CMAKE_LIBRARY_PATH
环境变量是环境变量而不是cmake变量- 使用方法是要在bash中用export或者csh中使用set命令设置或者
CMAKE_INCLUDE_PATH=/home/include
cmake ..
等方式 - 如果头文件没有存放在常规路径
(/usr/include)
和/usr/local/include
等,则可以使用这些变量弥补,就像上述例子中,直接查找肯定是找不到的,所以咱们是用了FIND_
2. 一种方法:(我没做到,以后好好看下这个,这个环境变量export加在哪里不知道,但可以用set(CMAKE_INCLUDE_PATH /tmp/t3/usr/include/hello ))
- 在bash(也就是~/.bashrc文件)加上
export CMAKE_INCLUDE_PATH = /tmp/t3/usr/include/hello
或者在CMakeLIsts.txt文件中加上:
set(CMAKE_INCLUDE_PATH /tmp/t3/usr/include/hello )
- 然后在CMakeLIsts.txt文件中将
INCLUDE_DIRECTORIES(/tmp/t3/usr/include/hello)
替换为:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
3. FIND_PATH
用来指定路径中搜索文件名,比如
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
这里我们没有指定路径,cmake仍然可以找到hello.h存放的路径,就是因为设置了环境变量CMAKE_INCLUDE_PATH
4. 不使用FIND_PATH
,CMAKE_INCLUDE_PATH
变量的设置是没有作用的
5. CMAKE_LIBRARY_PATH
可以用在FIND_LIBRARY
中
4.4 代码整合
在这里把前面用到的代码整合以下,主要三个文件
- 主目录下
CMakeLists.txt
内容如下:
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
src/CMakeLists.txt
内容如下:
ADD_EXECUTABLE(main main.c)
#使用环境变量
set(CMAKE_INCLUDE_PATH /tmp/t3/usr/include/hello )
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
# 添加头文件寻找的路径
#INCLUDE_DIRECTORIES(/tmp/t3/usr/include/hello)
#两种方法用一种
# 对指定路径寻找hello库,并保存到变量my_hello,此时是动态库
#这句话的意思是:在/tmp/t3/usr/lib路径下,搜索hello库,并保存到my_hello变量中
# FIND_LIBRARY(my_hello hello /tmp/t3/usr/lib)
# 对指定路径寻找libhello.a库,并保存到变量my_hello,此时是静态库
FIND_LIBRARY(my_hello libhello.a /tmp/t3/usr/lib)
#静态库和共享库用一种即可
TARGET_LINK_LIBRARIES(main ${my_hello})
MESSAGE(${my_hello})
src/main.c内容
#include <hello.h>
int main()
{
HelloFunc();
HelloFunc();
return 0;
}
4.5 总结
- 探讨了:
- 如何通过
INCLUDE_DIRECTORIES
和FIND_PATH
指令加入非标准的头文件搜索路径 - 如何通过
LINK_DIRECTORIES
和FIND_LIBRARY
指令加入非标准的库我呢见搜索路径 - 如果通过
TARGET_LINK_LIBRARIES
为库或可执行二进制加入库链接 - 并解释了如何链接到静态库
5 cmake常用变量和常用环境变量
5.1 cmake变量的引用方式
- 使用
${}
进行变量的引用。 - 在IF等语句中,是直接使用变量名而不是通过
${}
取值
5.2 cmake自定义变量的方式
5.2.1 隐式定义
- 例子,如PROJECT指令,他会隐式的定义
<projectname>_BINARY_DIR
和<projectname>_SOURCE_DIR
两个变量
5.2.2 显示定义
- 使用SET指令,就可以构建一个自定义变量了
- 比如:
SET(HELLO_SRC main.c)
那么PROJECT_BINARY_DIR
就可以通过${HELLO_SRC}
来引用这个自定义变量了
5.3 cmake常用变量
5.3.1 CMAKE_BINARY_DIR
、PROJECT_BINARY_DIR
、<projectname>_BINARY_DIR
- 三个变量指代的内容是一致的
- 内部(in source)编译,指的就是工程顶层目录
- 外部(out-of-source)编译,指的是工程编译发生的目录
PROJECT_BINARY_DIR
跟其他指令稍有区别,目前可以理解为一致的
5.3.2 CMAKE_SOURCE_DIR
、PROJECT_SOURCE_DIR
、<projectname>_SOURCE_DIR
- 三个变量指代的内容是一致的
- 不论采用何种编译,都是工程顶层目录
- 内部编译的时候与5.1等变量一致
PROJECT_SOURCE_DIR
跟其他指令稍有区别,目前可以理解为一致的
5.3.3 CMAKE_CURRENT_SOURCE_DIR
- 指的是当前处理的
CMakeLists.txt
所在的路径,比如上面所提到的src子目录,即t4/src/
5.3.4 CMAKE_CURRENT_BINARY_DIR
- 如果in-source编译,它和
CMAKE_CURRENT_SOURCE_DIR
一致 - 如果out-of-source编译,指的是target编译目录,也就是build下面的路径
- 使用
ADD_SUBDIRECTORY(src bin)
可以更改这个变量的值 - 使用
SET(EXECUTABLE_OUTPUT_PATH <新路径>)
并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径
5.3.5 CMAKE_CURRENT_LIST_FILE
- 输出调用这个变量的CMakeLIsts.txt的完整路径
5.3.6 CMAKE_CURRENT_LIST_LINE
- 输出这个变量所在的行
5.3.7 CMAKE_MODULE_PATH
- 这个变量用来定义自己的cmake模块所在的路径。
- 为了让cmake在处理
CMakeLists.txt
时找到这些模块,你需要通过SET指令,将自己的cmake模块路径设置一下,如:
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
就可以通过INCLUDE指令来调用自己的模块了。
5.3.8 EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH
- 分别用来重新定义最终结果的存放路径,前面已经提到了这两个变量
- 在2.2.2小节
5.3.9 PROJECT_NAME
- 返回通过PROJECT指令定义的项目名称
5.4 cmake调用环境变量的方式
- 使用
$ENV{NAME}
指令就可以调用系统的环境变量了 - 比如:
MESSAGE(STATUS "HOME dir:" $ENV{HOME})
输出:
-- HOME dir:/home/bupo
- 设置环境变量的方式是:
SET(ENV{变量名} 值)
5.4.1 CMAKE_INCLUDE_CURRENT_DIR
- 自动添加
CMAKE_INCLUDE_CURRENT_DIR
和CMAKE_CURRENT_SOURCE_DIR
到当前处理的CMakeLists.txt。相当于在每个CMakeLists.txt加入:
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
5.4.2 CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE
- 将工程提供给的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时可以提供一些帮助
5.4.3 CMAKE_INCLUDE_PATH
和CMAKE_LIBRARY_PATH
- 4.3节已经介绍过
5.5 输出系统信息的变量
5.5.1 CMAKE版本号
CMAKE_MAJOR_VERSION
:CMAKE主版本号,比如3.23.0中的3(下面这些信息是实验室系统的信息)CMAKE_MINOR_VERSION
:CMAKE主版本号,比如3.23.0中的23CMAKE_PATCH_VERSION
:CMAKE主版本号,比如3.23.0中的0
5.5.1 系统和处理器名称及版本
CMAKE_SYSTEM
:系统名称,比如Linux-5.4.0-120-genericCMAKE_SYSTEM_NAME
:不包含版本的系统名:如LinuxCMAKE_SYSTEM_VERSION
:系统版本,如5.4.0-120-genericCMAKE_SYSTEM_PROCESSOR
:处理器名称,如x86_64UNIX
:在所有的类UNIX平台为TRUE,包括OS X和cygwin,如1WIN32
:在所有的WIN32平台为TRUE,包括cygwin,在这里会报错
5.6 主要的开关选项
5.6.1 IF ELSE书写方式的控制CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS
- 用来控制IF ELSE语句的书写方式,在下一节语法部分会讲到
5.6.2 编译库的默认生成共享库和静态库控制BUILD_SHARED_LIBS
- 这个开关用来控制默认的库编译方式,如果不进行设置,使用
ADD_LIBRARY
并没有指定库类型的情况下,默认编译生成的库都是静态库 - 默认生成共享库:
SET(BUILD_SHARED_LIBS ON)
5.6.3 设置C编译选项CMAKE_C_FLAGS
- 可以通过指令
ADD_DEFINITIONS()
添加
5.6.4 设置C++编译选项CMAKE_CXX_FLAGS
- 可以通过指令
ADD_DEFINITIONS()
添加
6 cmake常用指令
- 本节会引入更多的cmake指令,为了编写方便,按照
cmake man page
的顺序来介绍各种指令,不再推荐使用的指令不再介绍,INSTALL系列指令在安装部分已经做了很详细的说明,本节也不再提及。
6.1 基本指令
6.1.1 ADD_DEFINITIONS
- 向
C/C++
编译器添加-D
定义,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG -DABD)
参数之间用空格分割
- 如果代码中定义了:
#ifdef
ENABLE_DEBUG
#endif
这个代码块就会生效
- 如果要添加其他的编译器开关,可以通过
CMAKE_C_FLAGS
变量和CMAKE_CXX_FLAGS
变量设置。
6.1.2 ADD_DEPENDENCIES
- 定义
target
依赖的其他target
,确保在编译本target
之前,其他的target
已经被构建。
ADD_DEPENDENCIES(target-name depend-target1
depend-target2 ...)
6.1.3 ADD_EXECUTABLE
、ADD_LIBRARY
、ADD_SUBDIRECTORY
- 前面已经介绍过了,这里不再罗索
ADD_EXECUTABLE
:1.2.4ADD_LIBRARY
:3.3.1ADD_SUBDIRECTORY
:2.2.1
6.1.4 ADD_TEST
与ENABLE_TESTING
指令
ENABLE_TESTING
指令用来控制Makefile
是否构建test
目标,涉及工程所有目录。语法很简单,没有任何参数:
enable_testing()
一般情况这个指令放在工程的主CMakeLists.txt
中:
参考:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】
ADD_TEST
指令的语法是:
ADD_TEST(testname Exename arg1 arg2 ...)
- 参数
testname
:是自定义的test名称 - 参数
Exename
:可以是构建的目标文件也可以是外部脚本等等。 - 后面连接传递给可执行文件的参数
- 如果没有在同一个
CMakeLists.txt
中打开ENABLE_TESTING()
指令,任何ADD_TEST
都是无效的
参考:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】,这里好像就没有放在同一个,所以没有test文件
- 比如前面的
Helloworld
例子,可以在主CMakeLists.txt
中添加
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING()
生成makefile后,就可以运行make test
来执行测试了,t2执行结果如下:
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t2/build$ make test
Running tests...
Test project /home/bupo/my_study/slam14/slam14_my/cap2/cmake_study/t2/build
Start 1: mytest
1/1 Test #1: mytest ........................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.01 sec
6.1.5 AUX_SOURCE_DIRECTORY
- 基本语法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
- 作用是发现一个目录下(是指当前
CMakeLists.txt
所在路径下)所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前cmake还不能自动发现新添加的源文件。
比如:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
- 你也可以通过后面提到的
FOREACH
来处理这个LIST
6.1.6 CMAKE_MINIMUM_REQUIRED
- 其语法为:
CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
- 比如
CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果cmake版本小于2.5,则出现严重错误,整个过程中止
6.1.7 EXEC_PROGRAM
- 在
CMakeLists.txt
处理过程中执行命令,并不会在生成的Makefile
中执行。具体语法为:
EXEC_PROGRAM(Executable [directory in which to run]
[ARGS <arguments to ececutable>]
[OUTPUT_VARIABLE <var>]
[RETURN_VALUE <var>] )
- 用于在指定的目录==(不指定就是当前编译build的路径下)==运行某个程序,通过
ARGS
添加参数,如果要获取输出和返回值,可通过OUTPUT_VARIABLE
和RETURN_VALUE
分别定义两个变量。 - 这个指令可以帮助你在
CMakeLists.txt
处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。 - 举个简单的例子,在t2中,我们要在src目录下执行ls命令,并把结果和返回值存下来。
可以直接在src/CMakeLists.txt
中添加:
EXEC_PROGRAM(ls ${PROJECT_SOURCE_DIR}/src/ ARGS *.cpp OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_RVALUE})
IF(NOT LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(NOT LS_RVALUE)
在cmake生成Makefile
的过程中,就会执行ls
命令,如果返回0,则说明成功执行,那么就输出${PROJECT_SOURCE_DIR}/src/
下ls *.cpp
的结果为:
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t2/build$ cmake ..
-- ls result: 0
-- ls result: main.cpp
-- Configuring done
-- Generating done
-- Build files have been written to: /home/bupo/my_study/slam14/slam14_my/cap2/cmake_study/t2/build
6.1.8 FILE
指令
- 文件操作指令,基本语法为:
FLIE(WRITE filename "message to write" ...)
FILE(APPEND filename "message to write" ...)
FILE(READ filename variable)
FILE(GLOB variable [RELATIVE path] [globbing expressions ] ...)
FILE(GLOB_RECURSE variable [RELATIVE path] [globbing expressions] ...)
FILE(REMOVE [directory] ...)
FILE(REMOVE_RECURSE [directory] ...)
FILE(MAKE_DIRECTORY [directory] ...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)
这里的语法都比较简单,不在展开介绍了
6.1.9 INCLUDE
指令
- 用来载入
CMakeLists.txt
文件,也用来载入预定义的cmake模块:
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
- 参数
OPTIONAL
的作用是文件不存在也不会产生错误。
- 可以指定载入一个文件,如果定义的是一个模块,那么将在
CMAKE_MODULE_PATH
中搜索这个模块并载入 - 载入的内容将在处理到
INCLUDE
语句时直接执行
6.2 INSTALL
指令-前面有
6.3 FIND_
指令
FIND_
系列指令主要包含以下指令:
6.3.1 FIND_
指令介绍
FIND_FILE(<VAR> name1 path1 path2 ...)
VAR
变量代表找到的文件全路经,包含文件名FIND_LIBRARY(<VAR> name1 path1 path2 ...)
VAR
变量表示找到的库全路经,包含库文件名
FIND_LIBRARY
示例:
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR "libX not found")
ENDIF(NOT libX)
FIND_PATH(<VAR> name1 path1 path2 ...)
VAR
变量代表包含这个文件的路径FIND_PROGRAM(<VAR> name1 path1 path2 ...)
VAR
变量代表包含这个程序的全路经FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets ...]])
用来调用预定义在CMAKE_MODULE_PATH
下的Find<name>.cmake
模块;
也可以定义Find<name>
模块,通过SET(CMAKE_MODULE_PATH dir)
将其放入工程的某个目录中供工程使用,我们在后面的章节会详细介绍FIND_PACKAGE
的使用方法和Find
模块的编写
6.4 控制指令:
6.4.1 IF
指令
6.4.2 WHILE
指令
6.4.3 FOREACH
指令
注意:直到遇到ENDFOREACH指令,整个语句才会得到真正的执行
FOREACH
指令的使用方法有三种形式:
6.4.3.1 使用形式一:列表
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
- 像我们前面使用的
AUX_SOURCE_DIRECTORY
的例子,在6.1.5节,例子是在t2中,代码加在src/CMakeLists.txt
:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
编译输出:
./main.cpp
6.4.3.2 使用形式二:范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
从0到total以1为步进
举例如下,t2下,可以加在任何一个CMakeLists.txt
:
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)
输出:
0
1
2
3
4
5
6
7
8
9
10
6.4.3.3 使用形式三:范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
从start开始到stop结束,以step为步进,
举例如下,t2中,同样可以加在任何一个CMakeLists.txt
:
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
输出:
5
8
11
14
7 收集CMakeLists好的使用习惯
7.1 分模块分文件-创建子文件
7.1.1 将各个包放在单独的cmake文件中
- 调用一个包,就是常规三步:
find_package
,include_directions
,target_link_libraries
如: - 这样也是同样的问题,包多的时候代码太杂。所以我们把每个包对应的这些操作放在
cmake
文件夹下对应的XX.cmake
文件中,然后在主CMakeLists中include(cmake/XX.cmake)
一行代码就可以搞定。
7.1.2 合并变量
7.1.2.1 库对应的变量合并
- 为了避免
target_link_libraries
后面跟很长一串库的名字,而且库增减的时候它也得跟着增减,我们在CMakeLists文件一开始定义一个变量:
set(ALL_TARGET_LIBRARIES "")
- 然后在每个库对应的
XX.cmake
文件中,把库的名字合并到这个变量中去
list(APPEND ALL_TARGET_LIBRARIES ${XX_LIBRARIES})
- 这样在
target_link_libraries
使就只使用ALL_TARGET_LIBRARIES
这一个变量就好了。
7.1.2.2 .cpp
文件对应的变量合并
- 在
add_executable
的时候要把所需要的cpp文件路径都要写进去,文件多的时候也是太麻烦,所以可以使用下面的指令把所有cpp文件合并到一个变量ALL_SRCS
中:
file(GLOB_RECURSE ALL_SRCS "*.cpp")
- 但是,当工程中有多个node文件的时候,就要把他们从这个变量中踢出去,因为多个node文件编到一个可执行文件中会出错。用下面的代码踢:
file(GLOB_RECURSE NODE_SRCS "src/*_node.cpp")
list(REMOVE_ITEM ALL_SRCS ${NODE_SRCS})
这两句的意思是:把NODE_SRCS
变量包含的文件从ALL_SRCS
中全部踢走
8 参考文章
- 《cmake实践》