cmake的基础用法

文章索引

cmake是我刚开始在linux系统下管理c++程序时最头疼的一个主子,伺候好它得先学会他自己的一些基本命令、变量和环境变量。我将在一到三章介绍如何用cmake构建一个不引用任何库文件的简单cmake工程的具体流程,其输出结果是在窗口中打印出helloworld,具体实现操作会在第四章进行说明。第五章主要介绍了如何自己创建一个动态库并安装在系统中,其中5.1中讲的创建动态库方法不常用,但原理是相同的,在5.3介绍了一种常用的创建动态库的方法,并在5.5进行了小结,附上了本章所需要的全部代码和指令。

一、CMAKE版本声明

通常使用如下语法对CMAKE最低版本进行限制

cmake_minimum_required(VERSION 2.8)

二、PROJECT

2.1用法:

本指令的一般形式不展开说明,常用于声明工程名称,常见写法:

PROJECT(ProjectName)

其中ProjectName为工程名,可自定,一般和工程目录名相同,

2.2 生成的变量ProjectName_BINARY_DIR与ProjectName_SOURCE_DIR

使用PROJECT指令后,会生成两个变量,一个是ProjectName_BINARY_DIR(二进制文件路径)另一个是ProjectName_SOURCE_DIR(源文件路径)
注意:ProjectName是当前工程名,根据自己PROJECT()中的名称来确定

2.3 不同编译模式下生成变量的路径

在 in-source编译模式下(即不另建build文件夹,直接在源文件文件夹内使用cmake),ProjectName_BINARY_DIR和ProjectName_SOURCE_DIR的路径内容一样,都是当前工程所在的文件夹路径
可利用MESSAGE指令来验证

MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})

注意:其中的${}符号表示取{}中的值。
在 out-of-source 外部编译模式下(即建立build文件夹,在build文件夹中进行编译)
ProjectName_BINARY_DIR中内容是build文件夹路径,ProjectName_SOURCE_DIR中内容仍然是当前工程所在的文件夹路径,同样可以用MESSAGE指令来验证。

三、外部编译的两种形式

刚刚稍微提了一下外部编译,通常有两种形式,一种是将.cpp文件存入工程中新建的文件夹src中,另一种是直接放在工程目录下。

3.1当.cpp文件放到工程路径下的src文件夹中

类似下图形式,在工程t2文件夹下创建src文件夹,并将.cpp文件放入src文件夹中
在这里插入图片描述
需要分别在工程文件夹和src文件夹中创建CMakeLists.txt文件。
在工程文件夹中的CMakeLists.txt中使用ADD_SUBDIRECTORY指令,然后在src的CMakeLists.txt中写ADD_EXECUTABLE等其他操作。
工程路径下的CMakeLists.txt例如:
在这里插入图片描述
src路径下的CMakeLists.txt例如:
在这里插入图片描述
上图中有一些没有介绍的指令,如ADD_EXECUTABLE、SET、INSTALL、ADD_SUBDIRECTORY等,会在下文一一讲解。

3.1.1 ADD_SUBDIRECTORY命令

此命令一般的格式为:

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

其作用是将source_dir内的cpp文件添加入项目工程中。
其中source_dir表示源文件目录,刚刚使用过的此命令中的src对应的就是source_dir,binary_dir为用户指定在编译此cpp文件过程中生的二进制中间文件所处的目录(此项可写可不写),如果没有binary_dir项,则编译过程中二进制中间文件会生成在build目录下的src文件夹下(此文件夹是编译过程自动生成的),如果有binary_dir项,则中间文件会生成在build/binary_dir/文件夹中(相当于将src重命名)。

3.1.2 EXECUTABLE_OUTPUT_PATH变量

此变量表示由.cpp文件经过make后生成的可执行文件输出路径,常和SET命令搭配使用,在ADD_EXECUTABLE命令后面进行SET,通常在哪SET呢?在哪加了ADD_EXECUTABEL命令就在那SET。

3.1.3 LIBRARY_OUTPUT_PATH变量

此变量表示.cpp文件中使用到的库,静态库或者动态库的可执行二进制文件输出路径,若工程过于简单,没有include编写的库文件,则可不SET。

3.1.4 ADD_EXECUTABLE命令

作用:由.cpp文件生成可执行文件,生成的路径由EXECUTABLE_OUTPUT_PATH决定,如果没有对此变量进行SET,如果使用的是外部编译,默认生成在build文件夹下。
一般语法

ADD_EXECUTABLE(ExeName source.cpp)

其中ExeName为生成的二进制可执行文件的名称,可以用户自定义,后面的source.cpp是.cpp文件,由于写ADD_EXECUTABLE的CMakeLists.txt通常是写在存.cpp文件夹中,所以路径直接写source.cpp即可。

四、小结

学会上面一些基础操作即可上手写一个像样的(不包含任何第三方头文件)的C++工程了,例如要求如下:
1.建立一个helloworld工程,为工程添加一个子目录src,用来放置工程源代码;
2.添加一个子目录doc,用来放置这个工程的文档hello.txt
3.在工程目录添加文本文件COPYRIGHT, README
4.在工程目录添加一个runhello.sh脚本,用来调用hello二进制;
5.将构建后的目标文件放入构建目录的bin子目录;

按照要求建立后,文件目录框架如下:
在这里插入图片描述
工程文件的CMakeLists.txt如下:

PROJECT(HELLO)

ADD_SUBDIRECTORY(src bin)

src中的CMakeLists.txt如下:

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)

main.c内容如下:

#include<stdio.h>

int main()
{
	printf("helloSLAM\n");
	return 0;
}

脚本.sh文件内容如下:

mkdir build
cd build
cmake ..
make
cd bin
./hello

注意:要为脚本文件添加可执行权限才可运行

五、创建属于自己的共享库(动态库)

由于一个大型的cmake程序需要依赖各种各样的库,所以会添加别人写好的动态库尤为重要,在学习使用别人写好的动态库之前,我们需要先学会如何自己写一个动态库,并将其安装在系统中,这样才可以让别的程序去使用。主要分为两步,首先创建共享库并生成在build/bin中,然后安装在系统中

5.1 创建共享库,在build/bin中生成.so文件(具体实现)

注意:具体过程中的解释放在后面说明
整体思路为:创建一个lib文件夹,其中包括.cpp文件和.h文件,一个写实现一个写声明,分别在工程文件夹下和lib文件夹下创建CMakeLists.txt文件,大体文件树如下:
在这里插入图片描述
其中hello.c内容为:

#include"hello.h"

void HelloFunc()
{
	printf("HELLOSLAM\n");
}

其中hello.h内容为:

#ifndef HELLO_H
#define HELLO_H
#include<stdio.h>

void HelloFunc();

#endif

工程文件夹下的CMakeLists.txt文件内容为:

PROJECT(HELLOLIB)

ADD_SUBDIRECTORY(lib)

Lib文件夹下的CMakeLists.txt文件内容为:

ADD_LIBRARY(hello SHARED 
	    hello.c
	    hello.h)

通过在build文件夹cmake …并且make后,会在build/bin文件夹下建立libhello.so共享库
在这里插入图片描述

5.2 ADD_LIBRARY命令

除了ADD_LIBARARY命令外,其他都是基本的C语言基础,还有我们上文提到过的内容,这里就不再过多介绍了。
该命令的一般形式:

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

其中libname是指定生成库的名字,在5.1中,我们指定叫hello,则系统自动会帮我们生成libhello.X的库在build/bin中,其中的X会根据库的类型不同而不同,这就是我们要说的第二个参数。
SHARED,动态库(扩展名为.so)
STATIC,静态库(扩展名为.a)
MODULE,在使用dyld的系统有效,如果不支持dyld,则被当作SHARED对待。
EXCLUDE_FROM_ALL,参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建。
在5.1中我们使用的是SHARED,构建的是libhello.so的共享库
最后就是我们指定需要添加的库函数源文件,也就是实现库功能的.cpp文件。
注意:第三个参数中,.h文件可写可不写,写上是为了让读者更清楚.cpp文件对应的.h文件,在5.1的例子中写上了,读者可自己验证不写会怎样

5.3 创建动态库的另一种方法(常用)

我们刚刚已经完整的演示了如何创建属于自己的共享库.so文件,这种把.h和.cpp同时放在工程下的Lib文件夹方式不常用,下面介绍一种常用的方式(此方式原理和5.1相同,只是以一种更直观的目录数的形式展示):
工程的目录树如下:
在这里插入图片描述
把头文件放到了include文件夹中,实现文件放到了src文件夹中,并只用在主工程下写一个CMakeLists.txt即可,比5.1介绍的方法更直观,并且可以少写一个CMakeLists.txt。
注意:各文件内容和5.1相同,只有CMakeLists.txt内容不同,故下面只展示CMakeLists.txt内容
CMakeLists.txt内容如下:

PROJECT(HELLOLIB)

INCLUDE_DIRECTORIES(include)

ADD_LIBRARY(hello SHARED 
	    src/hello.c
	    include/hello.h)

里面有了一个新的命令INCLUDE_DIRECTORIES,将会在5.3.1中进行介绍,之所以会有此命令,是因为hello.c文件include了hello.h头文件,如果不加此命令,在make的过程中会报找不到hello.h文件的错误,这是因为.c文件和.h没有放在一个文件夹下(不像5.1节中把.c和.h文件放在lib文件夹中)
具体实现结果请读者自己实现。
至此,我们已经成功在build/bin中创建了我们自己写的动态库(共享库),接下来我们需要将动态库和库的头文件安装到系统中,这样别人才能去调用。

5.3.1 INCLUDE_DIRECTORIES命令

一般格式:

include_directories ([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

功能:
将指定目录添加到编译器的头文件搜索路径之下,dir是相对路径,前缀是当前工作空间文件夹,在5.3中前缀为~/cmake/t3/。
参数解释:
第一个参数:默认情况下,include_directories命令会将目录添加到列表最后,也可以在每次调用include_directories命令时使用AFTER或BEFORE选项来指定是添加到列表的前面或者后面。
通常第一个和第二个参数不设置,只写需要包含头文件的文件夹相对路径即可。

5.4 安装创建的共享库和头文件

我们只需要修改一下5.3的工程文件夹下的CMakeLists.txt内容即可:
添加了两条INSTALL命令,一个是安装动态库,一个是安装头文件。

PROJECT(HELLOLIB)

INCLUDE_DIRECTORIES(include)

ADD_LIBRARY(hello SHARED 
	    src/hello.c
	    include/hello.h)

INSTALL(TARGETS hello
	LIBRARY DESTINATION lib)

INSTALL(FILES include/hello.h DESTINATION include/hello)

在Build文件夹中输入的命令格式相应做出一点改变

cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

在cmake的同时指定了CMKAE_INSTALL_PREFIX这个环境变量的值,结果是我们将hello的共享库安装到"prefix"/lib目录,将hello.h安装到"prefix"/include/hello目录。其中CMKAE_INSTALL_PREFIX环境变量指定了"prefix"的路径,当在cmake时没有对此环境变量赋值,那么默认路径为/usr/local

5.4.1 INSTALL命令

在上文中,我们利用INSTALL将动态库和库所需要的头文件安装在了系统中,下面将介绍这两条命令
1.用INSTALL安装动态库

INSTALL(TARGETS targets
[[ARCHIVE|LIBRARY|RUNTIME]  [DESTINATION <dir>]
			)

参数中的TARGETS后面跟的就是我们通过ADD_EXECUTABLE或者ADD_LIBRARY定义的目标文件,可能是可执行二进制、动态库、静态库。目标类型也就相对应的有三种,RUNTIME特指可执行目标二进制,ARCHIVE特指静态库,LIBRARY特指动态库。

DESTINATION定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX其实就无效了。如果你希望使用CMAKE_INSTALL_PREFIX来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
2.用INSTALL安装头文件

INSTALL(FILES 
files DESTINATION <dir>
[PERMISSIONS permissions...])

可用于安装一般文件,并可以指定访问权限。
第一个参数files表示文件的路径,这里是相对路径,路径前缀是当前工作空间,这就是为什么在5.4处我们写成include/hello.h。
第三个参数是“dir”,也就是需要指定安装后的路径,也是此指令所在路径下的相对路径即${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>。如果默认不定义权限PERMISSIONS,安装后的权限为:OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即644权限。

5.5 小结

至此我们已经可以独立创建属于自己的共享库,并且可以将其安装在系统中供他人使用,本章所用到的完整代码如下:

文件目录树:
在这里插入图片描述
hello.h头文件:

#ifndef HELLO_H
#define HELLO_H
#include<stdio.h>

void HelloFunc();

#endif

hello.c实现文件:

#include"hello.h"

void HelloFunc()
{
	printf("HELLOSLAM\n");
}

工程文件夹下的CMakeLists.txt:

PROJECT(HELLOLIB)

INCLUDE_DIRECTORIES(include)

ADD_LIBRARY(hello SHARED 
	    src/hello.c
	    include/hello.h)

INSTALL(TARGETS hello
	LIBRARY DESTINATION lib)

INSTALL(FILES include/hello.h DESTINATION include/hello)

在build文件夹下输入的指令:

cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
sudo make install

最终结果:
将生成的libhello.so文件安装在了/usr/lib中,将hello.h文件安装在了/usr/include/hello文件夹中

六、使用建好的libhello.so共享库

6.1 案例介绍:

使用建立好的共享库步骤有两个,首先用INCLUDE_DIRECTORIES指令添加头文件,然后用TARGET_LINK_LIBRARIES指令将可执行文件和共享库链接在一起。
注意:两个步骤缺一不可
首先先展示我们案例的文件夹目录:
在这里插入图片描述
其中main.c内容为:

#include<hello.h>

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

工程文件夹下的CMakeLists.txt:

PROJECT(NEWHELLO)

INCLUDE_DIRECTORIES(/usr/include/hello)

ADD_EXECUTABLE(main main.c)

TARGET_LINK_LIBRARIES(main hello)

本案例完成的功能是main函数去调用动态库libhello.so中的HelloFuc()函数完成相应的功能。
其中INCLUDE_DIRECTORIES命令熟悉吧,这是我们的老朋友了,是用来为程序添加头文件搜索路径用的,我们在这里就不多介绍了,下面将介绍TARGET_LINK_LIBRARIES命令的使用。

6.2 TARGET_LINK_LIBRARIES命令

功能: 这个指令可以用来为target添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。。
一般形式:

TARGET_LINK_LIBRARIES(target 
library1 
library2
...)

本例中target为ADD_EXECUTABLE命名的二进制target名称,library为共享库名,注意:不用写libhello.so全程,只需要写hello即可(名称和ADD_LIBRARIES相同),有些时候需要写他的绝对路径!

6.3 总结

至此我们已经将所以CMAKE基础用法讲解完毕,复杂的工程用到的基础也是这点东西,学完这些可以看懂一个CMAKE工程都输出了那些二进制可执行文件,这些文件都依赖了那些库。他们的头文件在哪可执行文件在哪。

七、FIND_PACKAGE用法

在.cpp文件中使用第三方库的时候,需要工程include_directories库的头文件和链接库文件,这时就需要知道他们的具体位置,然后将绝对地址或相对地址填入对应位置,这个过程相对麻烦,这时可以通过FIND_PACKAGE快速添加。

7.1 FIND_PACKAGE产生的变量

例如使用FIND_PACKAGE(Eigen3)后会生成三个变量,Eigen3_FOUNDEigen3_INCLUDE_DIREigen3_LIBRARY,第一个变量若没有找到Eigen3将返回flase,找到返回true,第二个变量用于INCLUDE_DIRECTORIES中,第三个变量用于TARGET_LINK_LIBRARIES中,例如:

FIND_PACKAGE(Eigen3)
IF(Eigen3_FOUND)
          include_directories(${Eigen3_INCLUDE_DIR})
ELSE(Eigen3_FOUND)
          MESSAGE(FATAL_ERROR "Eigen3 library not found")
ENDIF(Eigen3_FOUND)

7.2 FIND_PACKAGE搜索路径

FIND_PACKAGE有两种搜索模式,Module模式和Config模式。Module模式用于搜索官方定义好的Find<LibaryName>.cmake文件。Config模式用于搜索<LibraryName>Config.cmake,这个文件是对外部下载下来的库cmake工程进行make后得到的。

7.2.1 Module模式

为了方便我们在项目中引入外部依赖包,cmake官方为我们预定义了许多寻找依赖包的Module,他们存储在path_to_your_cmake/share/cmake-/Modules目录下(/usr/share/cmake-3.10/Modules)。每个以Find<LibaryName>.cmake命名的文件都可以帮我们找到一个包。我们也可以在官方文档find_package中查看到哪些库官方已经为我们定义好了,我们可以直接使用find_package函数进行引用
Module模式下的搜索路径有两个,一个是上边提到的/usr/share/cmake-3.10/Modules,另一个使我们指定的CMAKE_MODULE_PATH的所在目录(用于我们自己编写的FIND<LibaryName>.cmake,通过CMakeLists.txt中用set对CMAKE_MODULE_PATH赋值,将我们自定义的文件目录包括进这个变量中)。

7.2.2 Config模式

如果Module模式没有搜索到对应的.cmake文件,则进入Config模式搜索,本模式主要搜索<LibraryName>Config.cmake文件,这个文件是使用cmake安装库后自动添加在/usr/lib/cmake或/usr/local/lib/cmake中,例如eigen3:
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值