cmake入门

一、cmake介绍

1、简介CMake是一个跨平台的项目构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。CMake 的组态文档取名为 CMakeLists.txtCmake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix/linux 的 Makefile),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是 CMake 和 SCons 等其他类似系统的区别之处。

2、为什么要用项目构建工具

       实际应用中任何一个软件项目其代码量都是十分巨大的。因此除了写代码之外,还有一个更为重要的任务,就是如何组织和管理这些代码。决定代码的组织方式及其编译方式,也是程序设计的一部分。因此,我们需要cmake和autotools这样的工具来帮助我们构建并维护项目代码。
       看到这里,也许你会想到makefile,makefile不就是管理代码自动化编译的工具吗?为什么还要用别的构建工具?

       其实cmake正是makefile的上层工具,其目的正是为了生成可移植的makefile,简化手写makefile时的巨大工作量。

换句话说cmake所做的事情就是生成makefile文件进而告诉编译器如何去编译链接源代码。上面我们突出了“可移植”和“简化工作量”两个要点:

1)可移植:这就牵扯到跨平台的问题了。Windows平台是通过project文件去组织代码编译的、Linux则是用makefile文件去组织代码编译的,如果不用cmake我们就不得不为Windows和Linux系统分别编写对应的文件,当然平台可远不止windows和Linux两个,这无疑是一件繁琐的事。使用cmake后我们只要编写一次cmake,就可以用在各个平台中,而且其语法也简单。

2)简化工作:如果你自己动手写过makefile,会发现makefile通常依赖于你当前的编译平台,而且编写makefile的工作量比较大,解决依赖关系时也容易出错。因此对于大多数项目,应当考虑使用更自动化一些的 cmake或者autotools来生成makefile,而不是上来就动手编写。

3、cmake卸载

(1)一般情况下用于卸载老版本的cmake;

yum remove cmake

4、cmake安装——源码安装

(1)yum安装:

yum install cmake   #安装cmake
cmake -version  #查看cmake版本

alias g++="g++ -std=c++11"

上述安装的版本可能太低不符合我们的要求。

(2)安装高版本:

1)下载安装包 这里 。此处我们下载的是 cmake-3.15.2.tar.gz。

2)解压 tar zxvf cmake-3.15.2.tar.gz

3)执行  ./configure

4)执行 make

5) 执行   sudo make install

6)执行 sudo update-alternatives --install /usr/bin/cmake /usr/local/bin/cmake 1 --force

7)运行  cmake --version   查看版本号

5、cmake安装——二进制安装(很简单,推荐!!)

1)下载二进制包。首先就是官网下载二进制安装包(我们是64位系统,就下载对应的包),这里。

例如:在/home/DOWNLOAD目录下执行,即下载二进制包了。

wget https://github.com/Kitware/CMake/releases/download/v3.21.0-rc3/cmake-3.21.0-rc3-linux-x86_64.tar.gz

2)解压。tar zxvf cmake-3.19.8-Linux-x86_64.tar.gz

3)配置PATH使得cmake-3.19.8-Linux-x86_64/bin目录全局可见。

例如在 /etc/profile 中添加如下语句,然后使生效即可:

export PATH=$PATH:/home/DOWNLOAD/cmake-3.19.8-Linux-x86_64/bin
source /etc/profile

注:一般开放源代码的软件都会有有两个版本发布:Source Distribution和Binary Distribution。前者个源代码版,需要我们自己编译成可执行软件;后者是已经编译好的可执行版,直接拿过来就可以用。

二、使用举例

举例一:

1、文件组织

(1)在linux中创建一个文件夹,如cmaketest。

(2)在此文件夹中创建一个cpp文件,命名为main.cpp,内容如下:

#include 
#include 

using namespace std;

int main(){
        vector vec{1,2,3,4,5};
        for(auto &elem:vec){
                cout << elem << endl;
        }
        cout << "ok" << endl;
        return 0;
}

(3)再创建cmake文件,注意名称一定要是CMakeLists.txt,cmake只认识这个文件,内容如下:

PROJECT(myproj)  #将本工程命名为myproj
#message指令的作用就是输出提示性信息,如下文演示所用
message(${PROJECT_BINARY_DIR}) 
message(${PROJECT_SOURCE_DIR}) 
MESSAGE("target dir is: ${PROJECT_BINARY_DIR}") 
MESSAGE("source file dir is: ${PROJECT_SOURCE_DIR}")

SET(CMAKE_C_COMPILER g++) #设置变量
if(CMAKE_COMPILER_IS_GNUCXX) #
        add_compile_options(-std=c++11)
        message(STATUS "optional:-std=c++11") #这个是输出的提示性的信息
endif(CMAKE_COMPILER_IS_GNUCXX)

SET(SOURCE main.cpp) #设置变量
ADD_EXECUTABLE(testfile ${SOURCE}) #生成可执行文件testfile,其依赖于SOURCE变量包括的文件

至此在cmaketest文件夹下面有main.cpp源文件和CMakeList.txt文件。

2、编译与执行

根据生成的Makefile文件和源文件是否处于同一目录,我们分为“内部构建”和“外部构建”。前者就是混在一起,后者就是新建一个文件夹,Makefile文件和中间文件都在这个新建的文件夹中。推荐不要混在一起,即外部构建。

1)在cmaketest中新建一个build目录:mkdir build

2)进入这个目录:cd build

3)在build目录下分别执行如下命令:

cmake ..  #此处“..”表示当前目录的上一级目录,就是CMakeLists.txt文件所在的目录
make 

4)之后就可以看到生成的名为testfile的可执行文件。

注:当然要选择不做区分的话,就在cmaketest路径下,执行如下指令也是一样的:

cmake .  #此处“.”表示当前目录,同样也是CMakeLists.txt文件所在的目录
make 

举例二:3个文件示例

hello.cpp

#include 
#include 
using namespace std;
void hellofunc(string str){
        cout <<"The input str is:" << str << endl;
}

hello.h

#ifdef ZSZS_HELLO_
#define ZSZS_HELLO_
#include 
using namespace std;

void zshello(string str);

#endif

main.cpp

#include 
#include 
#include "hello.h"

using namespace std;

void hellofunc(string );

int main(){
        string str;
        cout << "Please input an string:";
        cin >> str;
        hellofunc(str);
        return 0;

}

CMakeLists.txt

PROJECT(multifietest)

SET(SOURCE main.cpp;hello.h;hello.cpp) #貌似顺序无所谓

ADD_EXECUTABLE(testfile ${SOURCE})

举例三:将hello.cpp生成一个库

复用上面的代码,将CMakeLists.txt文件改为如下即可

#这个东西也是很能说得通的。
#(1)调用add_library生成了一个库文件,依赖于hello.cpp;其实就是将这个文件生成静态库;
#(2)调用add_executable生成一个可执行文件,依赖于main.cpp;其实就是把main.cpp生成可执行文件;
#(3)调用target_link_libraries将静态库libhello同可执行文件hello链接起来;
project(multifietest)
set(LIB_SRC hello.cpp)
set(APP_SRC main.cpp)
add_library(libhello ${LIB_SRC})
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

举例四:路径分开

前面成功地使用了库,可是源代码放在同一个路径下,不太正规。我们期待是这样一种结构:

+
|
+--- CMakeList.txt
+--+ src/
|  |
|  +--- main.c
|  /--- CMakeList.txt
|
+--+ libhello/
|  |
|  +--- hello.h
|  +--- hello.c
|  /--- CMakeList.txt
|
/--+ build/

现在我们需要三个CMakeList.txt文件了,每个源文件目录都需要一个。如下:

1)最顶层CMakeList.txt文件:

project(multifietest)
add_subdirectory(src)
add_subdirectory(libhello)

       add_subdirectory的作用是添加一个文件夹进行编译,该文件夹下的CMakeLists.txt 负责编译该文件夹下的源码。其参数如src是相对于调用add_subdirectory的CMakeListst.txt的相对路径。

2)src下的CMakeList.txt文件:

include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(APP_SRC main.cpp)
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

3)libhello下的CMakeList.txt文件:

set(LIB_SRC hello.cpp)
add_library(libhello ${LIB_SRC})
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")#设置目标的一些属性来改变它们构建的方式。

和前面一样,建立一个build目录,在其内运行cmake .. ,然后可以得到

build/src/hello
build/libhello/libhello.a
回头看看,这次多了点什么,顶层的 CMakeList.txt 文件中使用 add_subdirectory 告诉cmake去子目录寻找新的CMakeList.txt 子文件。在 src 的 CMakeList.txt 文件中,新增加了include_directories,用来指明头文件所在的路径。

举例五:进一步规范

如果想让可执行文件在 bin 目录,库文件在 lib 目录怎么办?

方法一:修改最顶层CMakeList.txt文件:

project(multifietest)
add_subdirectory(src bin)
add_subdirectory(libhello)

不是说build中的目录默认和源代码中结构一样么?其实我们可以指定其对应的目录在build中的名字的。

这样一来:build/src 就成了 build/bin 了,可是除了 hello,中间产物也进来了。还不是我们最想要的。

方法二:不修改顶级的文件,修改其他两个文件

src/CMakeList.txt 文件

include_directories(${PROJECT_SOURCE_DIR}/libhello)
#link_directories(${PROJECT_BINARY_DIR}/lib)
set(APP_SRC main.cpp)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

相当于说我们又知道了一个变量:EXECUTABLE_OUTPUT_PATH表示可执行文件的输出路径。

libhello/CMakeList.txt 文件

set(LIB_SRC hello.cpp)
add_library(libhello ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

相当于说我们又知道了一个变量:LIBRARY_OUTPUT_PATH表示库文件的输出路径。

举例六:使用动态库

如果不考虑windows下,这个例子应该是很简单的,只需要在上个例子的 libhello/CMakeList.txt 文件中的add_library命令中加入一个SHARED参数:

add_library(libhello SHARED ${LIB_SRC})

可是,我们既然用cmake了,还是兼顾不同的平台吧,于是,事情有点复杂:

#ifdef ZSZS_HELLO_
#define ZSZS_HELLO_
//#include 
#if defined _WIN32
    #if LIBHELLO_BUILD
        #define LIBHELLO_API __declspec(dllexport)
    #else
        #define LIBHELLO_API __declspec(dllimport)
    #endif
#else
    #define LIBHELLO_API
#endif
LIBHELLO_API void hello(string str);
#endif //DBZHANG_HELLO_
  • 修改 libhello/CMakeList.txt 文件
set(LIB_SRC hello.cpp)
add_definitions("-DLIBHELLO_BUILD")
add_library(libhello SHARED ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

举例六:cmake向源文件传递参数

1、本实例的目的很简单就是演示如何将cmake中定义的变量(or参数)传递给源文件。

2、各个文件如下

(1)主程序代码文件为main.cpp,内容如下:

#include
//注意这个demoConfig.h文件是在执行cmake指令之后生成的
#include 
using namespace std;

int main(){
	cout << "param1 is " << PARAM1 << ",param2 is " << PARAM2 << endl;
}

注意,其中的参数PARAM1和PARAM2是cmake文件(CMakeLists.txt)文件传进来的。

(2)CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 3.5)

project(proj_demo)

#cmake文件中定义的参数(变量)
set(PARAM1_IN_CMAKEFILE 111)
set(PARAM2_IN_CMAKEFILE 222)

# configure a header file to pass some of the CMake settings to
# source code(demo11Config.h.in), the file demoConfig.h does't exists 
configure_file(
        "${PROJECT_SOURCE_DIR}/demoConfig.h.in"
        "${PROJECT_BINARY_DIR}/demoConfig.h"
)

# add the binary tree to the search path for include files
# so that we will find demoConfig.h
include_directories("${PROJECT_BINARY_DIR}")

# add the exectuable
add_executable(main main.cpp)

注意,我们在里面定义了两个参数PARAM1_IN_CMAKEFILE和PARAM2_IN_CMAKEFILE,这两个参数将会被传到程序中。

(3)定义demoConfig.h.in文件用于接收CMakeLists.txt传来的参数,内容如下:

#define PARAM1 @PARAM1_IN_CMAKEFILE@
#define PARAM2 @PARAM2_IN_CMAKEFILE@

三、常用语句介绍:

注:如下指令语句大小写均可。

(1)指定 cmake 的最小版本

cmake_minimum_required(VERSION 3.4.1)

注:查看cmake版本的指令如下

cmake -version

如果cmake的版本小于该语句指定版本,在执行"cmake ./.."的时候会报如下错误:

cmake入门_第1张图片

(2)PROJECT(myproj) 

 该指令定义工程名称,语法为:

project(projectName [CXX] [C] [JAVA]) #指定支持的语言

对于一个CMakeLists.txt文件而言,project 不是强制性的,但最好始终都加上。该指令隐式定义了两个cmake变量,这两个变量即为目标文件目录和源文件目录,是projectName_BINARY_DIR 和 projectName_SOURCE_DIR。
同时,cmake还自动定义了两个等价的变量PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。由于每次修改工程名称都需要修改前者的两个变量,因此建议使用后者的两个变量。我们可以通过message来输出该变量,如下:

message(${PROJECT_BINARY_DIR}) #演示所用
message(${PROJECT_SOURCE_DIR}) #演示所用
MESSAGE("target file dir is: ${PROJECT_BINARY_DIR}") #演示所用
MESSAGE("source file dir is: ${PROJECT_SOURCE_DIR}") #演示所用

(3)SET(valueName valueList)

set指令用于将valueList赋给valueName,valueList之间可以用空格或者分号进行分隔。

(4)MESSAGE([mode],"message to display" ...)

message指令用来输出提示性信息,像C语言的printf之类的。对于mode, 可以用下述可选的关键字指定消息的类型:

1)(none) = 重要消息;
2)STATUS = 非重要消息;
3)WARNING = CMake 警告, 会继续执行;
4)AUTHOR_WARNING = CMake 警告 (dev), 会继续执行;
5)SEND_ERROR = CMake 错误, 继续执行,但是会跳过生成的步骤;
6)FATAL_ERROR = CMake 错误, 终止所有处理过程;

(5)STRING(REPLACE ...)

对输入变量input 匹配字符串match-string,让后将匹配到的字串用replace-string字串进行替换,然后把得到的结果赋值给out-var变量。当然后续会用这个out-var变量。举个例子,如下:

cmake_minimum_required(VERSION 3.1)
project(test)
set( in "shuozhuotest567" )
message( STATUS "in = ${in}" )
string(REPLACE "test" "1234" out "${in}")
message( STATUS "out = ${out}" )

将上述内容放到CMakeLists.txt文件中 → 放在装了cmake的机器 → cmake .  。执行结果如下:

cmake入门_第2张图片

(5)add_library(libhello ${LIB_SRC})

     add_library指令的作用就是将指定的源文件生成链接文件,然后添加到工程中去。该指令常用的语法如下:

add_library( [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2] [...])

其中表示库的名字,该库文件会根据命令里列出的源文件来创建。对应于逻辑目标名称它在一个工程的全局域内必须是唯一的。这里说明下待构建的库文件的实际文件名根据对应平台的命名约定来构造(比如lib.a或者.lib)。而STATIC、SHARED和MODULE的作用是指定生成的库文件的类型。STATIC库是目标文件的归档文件,在链接其它目标的时候使用。SHARED库会被动态链接(动态链接库),在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。默认状态下,库文件将会在于源文件目录树的构建目录树的位置被创建,该命令也会在这里被调用。

  使用下述格式,add_library命令也可以用来创建导入的库目标:

add_library(  IMPORTED)

  导入的库目标是引用了在工程外的一个库文件的目标(例如说引用了tlist、hippo这些)。没有生成构建这个库的规则。这个目标名字的作用域在它被创建的路径及以下有效。他可以向任何在该工程内构建的目标一样被引用。导入库为类似于target_link_libraries命令中引用它提供了便利。关于导入库细节可以通过指定那些以IMPORTED_的属性设置来指定。其中最重要的属性是IMPORTED_LOCATION(以及它的具体配置版本,IMPORTED_LOCATION_),它指定了主库文件在磁盘上的位置。查阅IMPORTED_*属性的文档获取更多的信息。


(6)target_link_libraries(hello libhello)

该指令的作用是将目标文件与库文件进行链接。通常情况下对于编译时遇到的依赖问题我们只需要一句target_link_libraries就可以解决。该指令语法如下:

target_link_libraries( [item1] [item2] [...]
                      [[debug|optimized|general] ] ...)

上述指令中的是指通过add_executable()和add_library()指令生成已经创建的目标文件。而[item]表示库文件没有后缀的名字。默认情况下,库依赖项是传递的。当这个目标链接到另一个目标时,链接到这个目标的库也会出现在另一个目标的连接线上。这个传递的接口存储在interface_link_libraries的目标属性中,可以通过设置该属性直接重写传递接口。

6.1 target_link_libraries中属性PRIVATE、PUBLIC、INTERFACE含义:

1)如果源文件(.cpp文件)中包含第三方的头文件,但是头文件(.h文件)中不包含该第三方文件头,则采用PRIVATE;

2)如果源文件和头文件中都包含该第三方文件头,采用PUBLIC;

3)如果源文件中不包含第三方的头文件,但是头文件中包含,采用INTERFACE;

常见的CMakeLists.txt语句如下:

target_link_libraries(online_status_svr PRIVATE smart_monitor spp_ptlogin_helper)
target_link_libraries(online_status_svr PUBLIC dcapi)
target_link_libraries(online_status_svr PRIVATE hippocppclient)

上述语句的含义是给online_status_svr这个库(or可执行文件)链接dcapi、hippocppclient等第三方库。

dcapi、hippocppclient是库名,而 libdcapi.a、libhippocppclient.a才是对应的库(文件)的文件名,切记“前者是库名,后者才是我们链接的库对应的文件的名字(好像是这样的,晚点确认)

(7)add_dependencies

      为顶层目标引入一个依赖关系。

add_dependencies(target-name depend-target1
depend-target2 ...)

      前面说了“通常情况下对于编译时遇到的依赖问题我们只需要一句target_link_libraries就可以解决”。用到此指令的情况是两个target有依赖关系并且依赖库也是通过编译源码产生的。这时候一句add_dependencies可以在编译上层target时自动检查下层依赖库是否已经生成。没有的话就先编译下层依赖库,然后再编译上层target,最后连接depend target。

      结合下面这个说法应该就能理解其含义。让一个顶层目标依赖于其他的顶层目标。顶层目标是指由命令ADD_EXECUTABLE,ADD_LIBRARY,或者ADD_CUSTOM_TARGET产生的目标。为这些命令的输出引入依赖性可以保证某个目标在其他的目标之前被构建。查看ADD_CUSTOM_TARGET和ADD_CUSTOM_COMMAND命令的DEPENDS选项,可以了解如何根据自定义规则引入文件级的依赖性。查看SET_SOURCE_FILES_PROPERTIES命令的OBJECT_DEPENDS选项,可以了解如何为目标文件引入文件级的依赖性。

     用我的话说就是:让一个顶层目标依赖于其他顶层目标。举个例子:顶层目标1依赖于顶层目标2;通过添加一下依赖关系,在编译1的时候如果2没ready就先去编译2;好了后再编译1,最后进行链接。

(8)add_subdirectory(src)

该指令的作用是添加一个文件夹进行编译,该文件夹下的CMakeLists.txt 负责编译该文件夹下的源码。src是相对于调用add_subdirectory的CMakeListst.txt的相对路径。

注:还有另外一个作用那就是修改目录的名字。我们知道默认情况下build目录下的结构是和源文件中的结构一致的,其实我们也可以通过add_subdirectory指令执行修改,例如add_subdirectory(src bin)就是讲src改名为bin了。

(9)include_directories(${PROJECT_SOURCE_DIR}/libhello)

该指令的作用是添加头文件路径。在参数中把所有需要添加的路径加进去就可以了。该指令语法如下:

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

其实具体的做法就是将给定的路径添加到编译器搜索包含文件(.h文件)的路径列表中。缺省情况下,该路径会被附加在当前路径列表的后面。这种缺省行为可以通过设置CMAKE_include_directories_BEFORE变量为ON被改变。通过将该变量改变为BEFORE或AFTER,你可以在追加和附加在前端这两种方式中选择,而不用理会缺省设置。如果指定了SYSTEM选项,编译器将会认为该路径是某种平台上的系统包含路径。

(10)LINK_DIRECTORIES

该函数的作用是添加库路径同样。在参数中把所有需要添加的路径加进去就可以了。

(11)set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

该指令的作用是设置目标的一些属性来改变它们构建的方式,语法如下:

set_target_properties(target1 target2 ...
                        PROPERTIES prop1 value1
                        prop2 value2 ...)

为一个目标设置属性。该命令的语法是列出所有你想要变更的文件,然后提供你想要设置的值。你能够使用任何你想要的属性/值对,并且在随后的代码中调用GET_TARGET_PROPERTY命令取出属性的值。

举例:例如上述指令的作用就是使得build/libhello目录下的生成的静态库的名字为libhello.a。

重要属性:

1)IMPORTED_LOCATION(以及它的具体配置版本,IMPORTED_LOCATION_)它指定了主库文件在磁盘上的位置。

2)CLEAN_DIRECT_OUTPUT:cmake在构建一个target的时候,会删除之前生成的target。这个也是由set_target_properties来设置的。具体语句就是:

SET_TARGET_PROPERTIES(qdmysqlclient PROPERTIES CLEAN_DIRECT_OUTPUT 1) #构建target时候,删除之前的target

(12)add_executable(testfile ${SOURCE}) 

       告诉工程生成生成可执行文件,并指定依赖关系。例如对于 add_executable(demo main.cpp)

其作用就是将main.cpp编译成一个可执行文件demo。

(13)aux_source_directory(. DIR_SRCS)  参见

       承接上一条指令。使用add_executable如果有两个源文件还可以直接写,如:add_executable(Demo main.cpp MathFunctions.cpp)但是如果源文件非常多例如几十个,这时候把每个都加进去非常烦人了。更省事的方法是使用aux_source_directory。这个命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。于是就可以这样写:

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})

       这样,CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 Demo 的可执行文件。

(14)ExternalProject_Add

       ExternalProject命令的作用就是将第三方库封装到项目中。例如看工程代码tlist、hippo、cmlb等工程均是如此。如今的mongo也不例外。

       这里可以看一下官方网站对于ExternalProject的方法定义:https://cmake.org/cmake/help/v3.0/module/ExternalProject.html  这里面是最全的,每个属性的含义这地方都有。

       一般情况下cmake文件都是这么写的:

#首先都要包含这一句,用来加载ExternalProject
include(ExternalProject)

#接下来就是设置几个变量,这个不通工程不一样
set(EXTLIB_DOWNLOAD_URL "${QD_COMM_PROJ_ROOT}/comm_tarball")  
set(EXTLIB_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/install")
set(ENV_COMMAND ${CMAKE_COMMAND} -E env 

set(GFLAG_ROOT          ${CMAKE_BINARY_DIR}/thirdparty/gflag-2.2.2)
set(GFLAG_LIB_DIR       ${GFLAG_ROOT}/lib)
set(GFLAG_INCLUDE_DIR   ${GFLAG_ROOT}/include)

set(GFLAG_URL           https://github.com/gflags/gflags/archive/v2.2.2.zip)
set(GFLAG_CONFIGURE     cd ${GFLAG_ROOT}/src/gflag-2.2.2 && cmake -D CMAKE_INSTALL_PREFIX=${GFLAG_ROOT} .)
set(GFLAG_MAKE          cd ${GFLAG_ROOT}/src/gflag-2.2.2 && make)
set(GFLAG_INSTALL       cd ${GFLAG_ROOT}/src/gflag-2.2.2 && make install)

ExternalProject_Add(
    tlist3_proj   #自定义目标库的名称(必须要有)
    URL ${EXTLIB_DOWNLOAD_URL}/tlist3.0api.zip #源文件的完整URL路径
    URL_MD5 b1d6650ca9b9eea880a9d91cfaae69d9 #源文件的md5用于文件校验
    DOWNLOAD_NAME  tlist3.0api.zip #如果不对文件重命名的话可以没有
    PREFIX ${GFLAG_ROOT} #整个工程的root目录(也是可有可无)
    …………
    …………
    CMAKE_ARGS ${CMAKE_ARGS} #看起来就是给cmake设置一些属性.如 -DCMAKE_INSTALL_PREFIX=xx 等(这个参数就是用来设置安装目录的)。
    CONFIGURE_COMMAND  ${GFLAG_CONFIGURE} #执行cmake指令(也相当于执行./configure)
    BUILD_COMMAND      ${GFLAG_MAKE}  #执行make指令进行编译
    INSTALL_COMMAND    ${GFLAG_INSTALL} #执行install指令进行安装
    BUILD_IN_SOURCE 1    #使用源码构建的意思

    ………… 当然还有n多字段,参见官网链接 …………
    )
#ExternalProject_Get_Property用于获取外部项目目标的一些属性
ExternalProject_Get_Property(tlist3_proj SOURCE_DIR)
#就是设置变量tlist3_INCLUDE_DIRS
set(tlist3_INCLUDE_DIRS "${SOURCE_DIR}")
#就是设置变量tlist3_LIBRARIES
set(tlist3_LIBRARIES "${SOURCE_DIR}")
#static就是静态库的意思(归档文件);IMPORTED这种是用来创建导入的库目标。
add_library(tlist3 STATIC IMPORTED)
#该指令的作用是设置目标的一些属性来改变它们构建的方式
set_target_properties(tlist3 PROPERTIES
    IMPORTED_LOCATION ${tlist3_LIBRARIES}/libtlist_api.a)
#设置依赖关系,就是说库tlist3依赖于库tlist3_proj。
add_dependencies(tlist3 tlist3_proj)

至于这个cmake文件是怎么集成到工程可以参见: CMake详解之ExternalProject_weiguow的博客-CSDN博客_externalproject

对于我用到的工程,有如下几个点:

(1)ExternalProject_Add指令应该是自带了验证文件、解压文件到目的路径的一系列方法:

cmake入门_第3张图片

(2)我们的源文件其实就解压在build目录下。

对于mongo-c-driver就解压在build/qd_mong_c_proj-prefix/src/qd_mongo_c_proj/路径下;

对于mysql-c-proj就解压在build/qd_mysql_c_proj-prefix/src/qd_mysql_c_proj/路径下。

(3)解压到上述路径后,首先执行的是cmake指令,如下:

(4)然后开始执行ExternalProject_Add中配置的BUILD_COMMAND(make)、INSTALL_COMMAND(make install)指令。

(15)其他

Cmake脚本,其实就是CMakeLists.txt的基本语法规则:
1, 变量使用${}方式取值,但是在 IF 控制语句中是直接使用变量名; 
2, 指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令; 
3, 参数可以使用分号进行间隔。比如:set(valueList valueName1 valueName2 valueName3)或者 set(valueList valueName1;valueName2;valueName3)
4, 指令(参数 1 参数 2...) 参数使用括弧括起,参数之间使用空格或分号分开。  
5,常用参数:
(1)EXECUTABLE_OUTPUT_PATH 表示可执行文件的输出路径;
(2)LIBRARY_OUTPUT_PATH 表示库文件的输出路径;
(3)PROJECT_BINARY_DIR 表示目标文件目录,放置目标文件的目录如../cmaketest/build;
(4)PROJECT_SOURCE_DIR 表示源文件目录,就是放置源文件的目录如../cmaketest;
(5)CMAKE_BINARY_DIR,PROJECT_BINARY_DIR,_BINARY_DIR:
这三个变量内容一致.如果是内部编译,就指的是工程的顶级目录;如果是外部编译,指的就是工程编译发生的目录。
(6)CMAKE_SOURCE_DIR,PROJECT_SOURCE_DIR,_SOURCE_DIR:
这三个变量内容一致,都指的是工程的顶级目录。
(7)CMAKE_CURRENT_BINARY_DIR:外部编译时,指的是target目录,内部编译时,指的是顶级目录;
(8)CMAKE_CURRENT_SOURCE_DIR:当前CMakeList.txt所在的目录;
(9)CMAKE_CURRENT_LIST_DIR:CMakeList.txt的完整路径;
(10)CMAKE_CURRENT_LIST_LINE:当前所在的行;
(11)CMAKE_MODULE_PATH:如果工程复杂,可能需要编写一些cmake模块,这里通过SET指定这个变量;
(12)LIBRARY_OUTPUT_DIR,BINARY_OUTPUT_DIR:库和可执行的最终存放目录;

实例主要参照:cmake 学习笔记(一)_dbzhang800的博客-CSDN博客_camke _g

更多指令介绍请参照:CMake命令 - _木头人 - 博客园

你可能感兴趣的:(Linux,cmake)