【Cmake】【Cmake实践】【cmake的使用学习记录】

【Cmake】【Cmake实践】【cmake的使用学习记录】

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 前言

1 初试cmake-cmake的helloworld

  • 默认所有的根目录是所创建的cmake_study下
  • properties 属性的意思

1.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)
  • ${}是获取变量的值
  1. 开始构建
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最重要的
  1. 进行工程的实际创建
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
  1. 运行可执行文件
bupo@bupo-vpc:~/my_study/slam14/slam14_my/cap2/cmake_study/t1$ ./hello 
Hello World!

1.2 简单的解释

1.2.0 约定说明

  • [] 表示可以省略
  • 指令大小写都可以

1.2.1 project()指令语法

  1. PROJECT的指令语法是:
PROJECT(projectname [CXX] [C] [Java])
  • projectname:工程名
  • 可以指定工程支持的语言:可省略,默认支持所有语言
  1. 该指令隐式定义两个cmake变量:
    < projectname >_BINARY_DIR:cmake执行的路径,编译路径
    < projectname >_SOURCE_DIR:CMakeLists.txt所在的路径,工程路径
    在1.1的例子中我们也对该变量进行了输出
  2. cmake也预定义了两个系统变量:
    PROJECT_BINARY_DIR:cmake执行的路径
    PROJECT_SOURCE_DIR:CMakeLists.txt所在的路径
    建议使用这个,这样修改工程名不会影响其他代码

1.2.2 set()指令语法

  1. 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()指令语法

  1. MESSAGE的指令语法:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
  • 作用:用于向终端输出用户定义的信息
  • 变量SEND_ERROR:产生错误,生成过程被跳过
  • 变量STATUS:输出前缀为-信息
  • 变量FATAL_ERROR:立即终止所有cmake过程

1.2.4 add_executable语法指令

  1. 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 其他注意事项

  1. SET(SRC_LIST main.c)也可以写成SET(SRC_LIST "main.c")没有区别
  • 但是如果一个源文件的文件名包含空格,如:fu nc.c;那么就必须使用双引号
  1. 可以省略source列表中的后缀
    比如ADD_EXECUTABLE(t1 main.c)可以写成ADD_EXECUTABLE(t1 main),cmake会自动在本目录内查找main.c或者main.cpp等;但是最好不要偷懒,以免这个目录确实存在一个main.c和一个main
  2. 参数也可以用分号来分割
    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

  • 本小节的任务是:
  1. 为工程添加一个子目录src,用来放置工程源代码;
  2. 添加一个子目录doc,用来放置这个工程的文档hello.txt;
  3. 在工程目录添加文本文件COPYRIGHT,README;
  4. 在工程目录添加一个runhello.sh脚本,用来调用hello二进制
  5. 将构建后的目标文件放入构建目录的bin子目录
  6. 最终安装这些文件:
    将hello二进制与runhello.sh安装至/usr/bin,
    将doc目录的内容以及COPYRIGHT/README安装到/usr/share/doc/cmake/t2

2.1 完整的工程

  1. 创建t2主文件夹,在该文件夹下创建src文件夹
mkdir t2
cd t2
mkdir src
  1. 并创建一些文件:在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)
  1. 编译
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,需要工程构建完成后,在进行编译
  1. 上面的例子add_subdirectory(src bin)定义了将src子目录加入到工程中,并指定编译输出(包含编译中间结果)路径为bin目录。
  • 如果不进行bin目录的指定,编译结果(包括中间结果)存放在build/src目录(这个目录跟原有的src目录对应),指定bin目录后,相当于在编译时将src重命名为bin,所有的中间结果和目标二进制都将存放在bin目录。

2.2.2 换个地方保存目标二进制

  1. 可以通过SET指令重新定义EXECUTABLE_OUTPUT_PATHLIBRARY_OUTPUT_PATH变量来指定最终的目标二进制的位置(指最终生成的hello或者最终的共享库,不包含编译生成的中间文件)
  • SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin):这个指令定义了可执行二进制的输出路径为build/bin
  • SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib):库的输出路径为build/lib
  • 注意:把握一个原则::ADD_EXECUTABLE和ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义
    在这个例子中,当然是指src下的CMakeLists.txt。

2.3 如何安装

安装的需要有两种:

  1. 一种是从代码编译后直接make install安装
  2. 一种是打包时的指定目录安装

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 目标文件的安装
  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:权限
  1. 例子
INSTALL(TARGETS myrun mylib mtsyaticlib
	RUNTIME DESTINATION bin
	LIBRARY DESTINATION lib
	ARCHIVE DESTINATION libstatic
	)
  • 上述例子说明,会将
  1. 可执行二进制myrun安装到${CMAKE_INSTALL_PRIFIX}/bin目录
  2. 可执行二进制mylib 安装到${CMAKE_INSTALL_PRIFIX}/lib目录
  3. 可执行二进制mtsyaticlib安装到${CMAKE_INSTALL_PRIFIX}/libstatic目录
  4. 不需要关心TARGETS具体生成的路径,只需要写上TARGETS名称就可以
2.3.2.2 普通文件的安装
  1. 使用代码:
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 非目标文件的可执行程序安装(如脚本之类)
  1. 命令代码:
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 目录的安装
  1. 代码:
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过滤后的文件权限
  1. 例子:
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_EXECUTEOWNER_WRITE ,OWNER_READ,GROUP_EXECUTE ,GROUP_READ
2.3.2.5 CMAKE脚本的安装
  1. 代码
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
  • 参数SCRIPT:用于安装时调用cmake脚本文件(也就是< abd >.cmake文件 )
  • 参数CODE:用于执行CMAKE命令,必须以双引号括起来
  1. 例子
INSTALL(CODE "MESSAGE(\"Sample install message.\")")

2.3.3 改写我们的文件进行安装

  • 下面改写我们的工程文件,支持各种文件的安装,并且,我们要使用CMAKE_INSTALL_PREFIX指令,在2小节开始已经说过了要实现的内容:
  • 下面进行修改:
2.3.3.1 首先补上确实的文件
  1. 添加doc目录及文件hello.txt
mkdir doc
cd doc
touch hello.txt

hello.txt内容随便写一点

  1. 在工程目录中田间runhello.sh脚本,内容为:
hello
  1. 添加工程目录中的COPYRIGHT和README:
touch COPYRIGHT
touch README
2.3.3.2 改写各目录的CMakeLists.txt
  1. 安装COPYRIGHT/README,直接修改主工程文件CMakeLIsts.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
  1. 安装runhello.sh,直接修改主工程文件CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
  1. 安装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 运行指令进行下载
  1. 确定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
  1. 然后就可以进入/tmp/t2目录看一下安装结果
  2. 如果想要直接安装到系统,就可以使用如下指令:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..

2.3.4 INSTALL默认地址

  • 如果没有定义CMAKE_INSTALL_PREFIX,默认地址是/usr/local

3 静态库和动态库构建

  • 本节的任务:
  1. 建立一个静态库和动态库,提供HelloFunc函数供其他程序编程使用 ,HelloFunc向终端输出Hello World字符串
  2. 安装头文件与共享库。

3.1 准备工作

  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 编译共享库

  1. 仍然采用out-of-source编译的方式
mkdir build
cd build
cmake ..
make
  • 之后可以在build/lib文件夹里得到一个libhello.so,这就是我们期望的共享库
  1. 指定libhello.so生成的位置
  • 可以通过在主工程文件CMakeLists.txt中修改ADD_SUBDIRECTORY(lib)指令来指定一个编译输出位置
  • 或者在lib/CMakeLists.txt中添加SET(LIBRARY_OUTPUT_PATH<路径>)来指定一个新的位置

3.3 约定说明

3.3.1 ADD_LIBRARY指令

  1. 代码:
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
					[ECLUDE_FROM_ALL]
					source1 source2 ... sourceN)
  1. 说明:
  • 不需要写全lihello.so,只需要填写hello即可,cmake系统会自动为你生成libhello.X
  • 参数ECLUDE_FROM_ALL:这个库不会默认构建,除非有其他的组件依赖或者手工构建

3.3.2 库的类型介绍

  1. SHARED,动态库,共享库,后缀.so
  2. STATIC,静态库,后缀,.a
  3. MODULE,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待

3.3.3 SET_TARGET_PROPERIES指令

  1. 代码:
SET_TARGET_PROPERTIES(target1 target2 ...
				PROPERTILES prop1 value1
				prop2 value2 ...)
  • 作用:可以用来设置输出的名称,对于动态库,可以用来指定动态库版本和API版本
  • 参数prop1 :是要操作的属性

3.3.4 GET_TARGET_PROPERTIES指令

  1. 代码
GET_TARGET_PROPERTY(VAR target property)
  • 作用:是将target的值,赋给VAR,在property的要求下

3.4 添加静态库

  1. 一般都会让静态库和动态库名字一致,使用下面的指令来添加静态库:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

hello作为一个target重名而指令无效

3.4.1 SET_TARGET_PRPERTIES同时构建同名的动态库和静态库

  1. 可以使用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两个库,

  1. 输出信息加上:
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
  1. 之所以在加上后买你这句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控制动态库版本

  1. 为了实现动态库版本号,我们仍然需要使用SET_TARGET_PROPERTIES指令:使用方法在lib/CMakeLists.txt加入代码如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
  • 属性VERSION:指动态库版本
  • 属性SOVERSION:指代API版本
  1. 运行结果:
  • build/lib生成
    libhello.so.1.2
    libhello.so.1->libhello.s0.1.2
    libhello.so->libhello.so.1

3.6 INSTALL安装共享、静态库和头文件

  1. 安装要求,参考2.3.2.1小节
    将hello的共享库和静态库安装到<prefix>/lib目录
    将hello.h安装到<prefix>/include/hello目录
  2. 安装指令为向lib/CMakeLists.txt添加:
INSTALL(TARGETS hello hello_static
		LIBRARY DESTINATION lib
		ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
  1. 编译命令:
  • 因为是测试需要,这里就不安装到系统/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 总结

做到了:

  1. 如何通过ADD_LIBRARY指令构建动态库和静态库
  2. 如何通过SET_TARGET_PRPERTIES同时构建同名的动态库和静态库
  3. 如何通过SET_TARGET_PRPERTIES控制动态库版本
  4. 最终使用上一节谈到的INSTALL指令安装头文件和动态、静态库

3.7.1 代码汇总

  1. 工程文件夹t3
  2. 然后t3/lib文件夹、t3/CMakeLists.txt文件内容如下
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
  1. 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)
  1. t3/lib/hello.c文件内容如下:
#include "hello.h"
void HelloFunc()
{
	printf("Hello World\n");
}
  1. t3/lib/hello.h文件内容如下:
#ifdef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif

4 如何使用外部共享库和头文件

  • 本节任务是编写一个程序使用我们上一节构建的共享库

4.1 约定说明

  • 先来介绍几个使用外部共享库和头文件的语句

4.1.1 INCLUDE_DIRECTORIES指令

  1. 代码
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
  • 作用:可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号括起来
  • 默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:
    a) CMAKE_INCLUDE_DIRECTORIES_BEFORE,可以通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有的路径前面
    b) 通过AFTER或者BEFORE参数,也可以控制是追加还是置前

4.1.2 TARGET_LINK_LIBRARIES语法

  1. 代码
TARGET_LINK_LIBRARIES(target library1
					  <debug | optimized> library2
					  ...)
  • 作用:可以用来为target添加需要链接的共享库

4.1.3 LINK_DIRECTORIES指令

  1. 代码
LINK_DIRECTORIES(directory1 directory2 ...)
  • 作用:添加非标准的共享库搜索路径;比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。
  • 本例子没有用这个,也就没有例子

4.1.4 检查可执行文件的链接情况

  1. 命令
ldd 可执行文件
如:
ldd src/main
  1. 例子可看4.2.8

4.1.5 FIND_PATHFIND_LIBRARY的指令

  1. 代码:

FIND_PATH用来指定路径中搜索文件名,比如**

FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
  1. 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_PATHCMAKE_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_PATHCMAKE_INCLUDE_PATH变量的设置是没有作用的
5. CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY

4.4 代码整合

在这里把前面用到的代码整合以下,主要三个文件

  1. 主目录下CMakeLists.txt内容如下:
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
  1. 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})
  1. src/main.c内容
#include <hello.h>

int main()
{
	HelloFunc();
	HelloFunc();
	return 0;
}

4.5 总结

  • 探讨了:
  1. 如何通过INCLUDE_DIRECTORIESFIND_PATH指令加入非标准的头文件搜索路径
  2. 如何通过LINK_DIRECTORIESFIND_LIBRARY指令加入非标准的库我呢见搜索路径
  3. 如果通过TARGET_LINK_LIBRARIES为库或可执行二进制加入库链接
  4. 并解释了如何链接到静态库

5 cmake常用变量和常用环境变量

5.1 cmake变量的引用方式

  • 使用${}进行变量的引用。
  • 在IF等语句中,是直接使用变量名而不是通过${}取值

5.2 cmake自定义变量的方式

5.2.1 隐式定义

  1. 例子,如PROJECT指令,他会隐式的定义<projectname>_BINARY_DIR<projectname>_SOURCE_DIR两个变量

5.2.2 显示定义

  1. 使用SET指令,就可以构建一个自定义变量了
  2. 比如:
SET(HELLO_SRC main.c)

那么PROJECT_BINARY_DIR就可以通过${HELLO_SRC}来引用这个自定义变量了

5.3 cmake常用变量

5.3.1 CMAKE_BINARY_DIRPROJECT_BINARY_DIR<projectname>_BINARY_DIR

  1. 三个变量指代的内容是一致的
  2. 内部(in source)编译,指的就是工程顶层目录
  3. 外部(out-of-source)编译,指的是工程编译发生的目录
  4. PROJECT_BINARY_DIR跟其他指令稍有区别,目前可以理解为一致的

5.3.2 CMAKE_SOURCE_DIRPROJECT_SOURCE_DIR<projectname>_SOURCE_DIR

  1. 三个变量指代的内容是一致的
  2. 不论采用何种编译,都是工程顶层目录
  3. 内部编译的时候与5.1等变量一致
  4. PROJECT_SOURCE_DIR跟其他指令稍有区别,目前可以理解为一致的

5.3.3 CMAKE_CURRENT_SOURCE_DIR

  1. 指的是当前处理的CMakeLists.txt所在的路径,比如上面所提到的src子目录,即t4/src/

5.3.4 CMAKE_CURRENT_BINARY_DIR

  1. 如果in-source编译,它和CMAKE_CURRENT_SOURCE_DIR一致
  2. 如果out-of-source编译,指的是target编译目录,也就是build下面的路径
  3. 使用ADD_SUBDIRECTORY(src bin)可以更改这个变量的值
  4. 使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径

5.3.5 CMAKE_CURRENT_LIST_FILE

  1. 输出调用这个变量的CMakeLIsts.txt的完整路径

5.3.6 CMAKE_CURRENT_LIST_LINE

  1. 输出这个变量所在的行

5.3.7 CMAKE_MODULE_PATH

  1. 这个变量用来定义自己的cmake模块所在的路径。
  2. 为了让cmake在处理CMakeLists.txt时找到这些模块,你需要通过SET指令,将自己的cmake模块路径设置一下,如:
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

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

5.3.8 EXECUTABLE_OUTPUT_PATHLIBRARY_OUTPUT_PATH

  1. 分别用来重新定义最终结果的存放路径,前面已经提到了这两个变量
  2. 在2.2.2小节

5.3.9 PROJECT_NAME

  1. 返回通过PROJECT指令定义的项目名称

5.4 cmake调用环境变量的方式

  1. 使用$ENV{NAME}指令就可以调用系统的环境变量了
  2. 比如:
MESSAGE(STATUS "HOME dir:" $ENV{HOME})

输出:

-- HOME dir:/home/bupo
  1. 设置环境变量的方式是:
SET(ENV{变量名})

5.4.1 CMAKE_INCLUDE_CURRENT_DIR

  1. 自动添加CMAKE_INCLUDE_CURRENT_DIRCMAKE_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

  1. 将工程提供给的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时可以提供一些帮助

5.4.3 CMAKE_INCLUDE_PATHCMAKE_LIBRARY_PATH

  • 4.3节已经介绍过

5.5 输出系统信息的变量

5.5.1 CMAKE版本号

  1. CMAKE_MAJOR_VERSION:CMAKE主版本号,比如3.23.0中的3(下面这些信息是实验室系统的信息)
  2. CMAKE_MINOR_VERSION:CMAKE主版本号,比如3.23.0中的23
  3. CMAKE_PATCH_VERSION:CMAKE主版本号,比如3.23.0中的0

5.5.1 系统和处理器名称及版本

  1. CMAKE_SYSTEM:系统名称,比如Linux-5.4.0-120-generic
  2. CMAKE_SYSTEM_NAME:不包含版本的系统名:如Linux
  3. CMAKE_SYSTEM_VERSION:系统版本,如5.4.0-120-generic
  4. CMAKE_SYSTEM_PROCESSOR:处理器名称,如x86_64
  5. UNIX:在所有的类UNIX平台为TRUE,包括OS X和cygwin,如1
  6. WIN32:在所有的WIN32平台为TRUE,包括cygwin,在这里会报错

5.6 主要的开关选项

5.6.1 IF ELSE书写方式的控制CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS

  1. 用来控制IF ELSE语句的书写方式,在下一节语法部分会讲到

5.6.2 编译库的默认生成共享库和静态库控制BUILD_SHARED_LIBS

  1. 这个开关用来控制默认的库编译方式,如果不进行设置,使用ADD_LIBRARY并没有指定库类型的情况下,默认编译生成的库都是静态库
  2. 默认生成共享库:
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

  1. C/C++编译器添加-D定义,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG -DABD)

参数之间用空格分割

  1. 如果代码中定义了:
#ifdef
ENABLE_DEBUG
#endif

这个代码块就会生效

  1. 如果要添加其他的编译器开关,可以通过CMAKE_C_FLAGS变量和CMAKE_CXX_FLAGS变量设置。

6.1.2 ADD_DEPENDENCIES

  1. 定义target依赖的其他target,确保在编译本target之前,其他的target已经被构建。
ADD_DEPENDENCIES(target-name depend-target1
											depend-target2 ...)

6.1.3 ADD_EXECUTABLEADD_LIBRARYADD_SUBDIRECTORY

  • 前面已经介绍过了,这里不再罗索
  • ADD_EXECUTABLE:1.2.4
  • ADD_LIBRARY:3.3.1
  • ADD_SUBDIRECTORY:2.2.1

6.1.4 ADD_TESTENABLE_TESTING指令

  1. ENABLE_TESTING指令用来控制Makefile是否构建test目标,涉及工程所有目录。语法很简单,没有任何参数:
enable_testing()

一般情况这个指令放在工程的主CMakeLists.txt中:
参考:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】

  1. ADD_TEST指令的语法是:
ADD_TEST(testname Exename arg1 arg2 ...)
  • 参数testname:是自定义的test名称
  • 参数Exename:可以是构建的目标文件也可以是外部脚本等等。
  • 后面连接传递给可执行文件的参数
  • 如果没有在同一个CMakeLists.txt中打开ENABLE_TESTING()指令,任何ADD_TEST都是无效的

参考:【slam十四讲第二版】【课本例题代码向】【第十三讲~实践:设计SLAM系统】,这里好像就没有放在同一个,所以没有test文件

  1. 比如前面的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

  1. 基本语法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
  1. 作用是发现一个目录下(是指当前CMakeLists.txt所在路径下)所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前cmake还不能自动发现新添加的源文件。
    比如:
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
  1. 你也可以通过后面提到的FOREACH来处理这个LIST

6.1.6 CMAKE_MINIMUM_REQUIRED

  1. 其语法为:
CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
  1. 比如
CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)

如果cmake版本小于2.5,则出现严重错误,整个过程中止

6.1.7 EXEC_PROGRAM

  1. CMakeLists.txt处理过程中执行命令,并不会在生成的Makefile中执行。具体语法为:
EXEC_PROGRAM(Executable [directory in which to run]
									[ARGS <arguments to ececutable>]
									[OUTPUT_VARIABLE <var>]
									[RETURN_VALUE <var>] )
  1. 用于在指定的目录==(不指定就是当前编译build的路径下)==运行某个程序,通过ARGS添加参数,如果要获取输出和返回值,可通过OUTPUT_VARIABLERETURN_VALUE分别定义两个变量。
  2. 这个指令可以帮助你在CMakeLists.txt处理过程中支持任何命令,比如根据系统情况去修改代码文件等等。
  3. 举个简单的例子,在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指令

  1. 文件操作指令,基本语法为:
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指令

  1. 用来载入CMakeLists.txt文件,也用来载入预定义的cmake模块:
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
  • 参数OPTIONAL的作用是文件不存在也不会产生错误。
  1. 可以指定载入一个文件,如果定义的是一个模块,那么将在CMAKE_MODULE_PATH中搜索这个模块并载入
  2. 载入的内容将在处理到INCLUDE语句时直接执行

6.2 INSTALL指令-前面有

6.3 FIND_指令

FIND_系列指令主要包含以下指令:

6.3.1 FIND_指令介绍

  1. FIND_FILE(<VAR> name1 path1 path2 ...)
    VAR变量代表找到的文件全路经,包含文件名
  2. 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)
  1. FIND_PATH(<VAR> name1 path1 path2 ...)
    VAR变量代表包含这个文件的路径
  2. FIND_PROGRAM(<VAR> name1 path1 path2 ...)
    VAR变量代表包含这个程序的全路经
  3. 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_packageinclude_directionstarget_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实践》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值