CMake入门笔记

2019-09-04 16:50:35

参考地址  CMake入门笔记

什么是CMake

CMake


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


Make


make工具是一个简化编译工作程序,有了它我们可以进行所谓的“自动化编译”,极大地提高了软件开发的效率。make工具相当于一个Shell,通过解释Makefile的中的命令进行工作。大多数IDE都有这个工具,比如:Visual C++的nmake,Linux下GNU的make。 

make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。


直白点说就是,CMake用来生成makefile或者是工程文件,make是用来解释makefile的。 

以Linux平台下使用gcc编译为例,我们想要编译源文件可以直接在命令行中执行gcc test.c -o test之类的命令,可是文件多了,工程复杂,而且文件不时的还会变,每次这样输多麻烦。 

这个时候make就有了用武之地,写个makefile,以后编译就直接make解释下makefile,根据makefile来执行编译命令。这样在linux上是很好,在windows上或者其他平台上怎么办呢? 

这时候就到cmake出马了,构建工程的时候,不在写makefile,而写CMakeLists.txt。需要编译的时候,先用Cmake解释CMakeLists.txt。如果在Windows平台上,设置编译器是VS,CMake就生成一个VS的工程,然后用VS来运行编译项目。如果是在Linux平台上,编译器是gcc,CMake就生成makefile,然后用make来解释makefile编译项目。 

如图,分别为原始目录、在Linux下选择gcc为编译工具生成的工程文件和在WIndows下选用Visual Studio为编译器生成的工程文件: 



cmake执行后会在cmake执行时所在的目录生成许多文件,根据cmake执行目录是否与源文件同目录,将构建方式分为两种:


out-of-source build,构建输出文件与源文件放到不同目录中;

in-source build :构建输出文件与源文件放到相同目录中。

一般选择out-of-source方式,可以在项目目录下建立一个build文件,然后进入build下,执行cmake ../,使生成的文件都在build文件夹下,以便后期清理。


示例

为了编译与演示方便,示例全部在Linux下进行。


Hello, CMake!

CMake最简单的实例,目录结构如下: 

 

main.c文件:


 #include <stdio.h>


int main(int argc, char *argv[])

{

    printf("Hello, CMake!\r\n");


    return 0;

}

1

2

3

4

5

6

7

8

CMakeLists.txt文件


 # 版本限定

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)                      

# 项目名称

PROJECT(Test1)

# 添加源文件列表变量

SET(SRC_LIST main.cpp)

# 打印编译目录和项目目录路径

MESSAGE(STATUS "This is SOURCE dir " ${PROJECT_SOURCE_DIR})

# 生成可执行文件

ADD_EXECUTABLE(HelloCMake ${SRC_LIST})

1

2

3

4

5

6

7

8

9

10

然后进入项目目录,依次执行(需要确保linux下安装了gcc、make等):


mkdir build

cd build

cmake ../

1

2

3

此时,build文件夹内容如下: 

 

。然后键入make命令,即解析了Makefile文件,build目录下的内容变为: 

 

可以看到,生成了一个HelloCMake的可执行文件,键入./HelloCMake可以运行HelloCMake文件。 

bash执行输出过程如下: 



同一目录多个源文件

在实际使用中,很少会出现这么简单的情况,往往在一个文件夹中会存在多个源文件要求编译,目录如下,这时候只需要在SET后面加上需要编译的源文件就可以了。 

 

CMakeLists.txt文件:


# 版本限定

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)                      

# 项目名称

PROJECT(Test2)

# 添加源文件列表变量

SET(SRC_LIST Stupid.cpp main.cpp)

# 打印编译目录和项目目录路径

MESSAGE(STATUS "This is BINARY dir " ${PROJECT_BINARY_DIR})

MESSAGE(STATUS "This is SOURCE dir " ${PROJECT_SOURCE_DIR})

# 生成可执行文件

ADD_EXECUTABLE(Test2 ${SRC_LIST})

1

2

3

4

5

6

7

8

9

10

11

上述方式虽然可行,但是如果源文件过多,CMakeLists.txt文件编写起来也是一件很吃力的事情,这时候,可以用aux_source_directory来解决这个问题。aux_source_directory可以遍历指定文件夹的下源文件,将它们统一复制给一个变量,然后进行编译。


CMakeLists.txt文件2:


# 版本限定

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)                      

# 项目名称

project(Test3)


# 遍历当前目录所有的源文件,赋值给test3src

aux_source_directory(. test3src)

message(STATUS "src file : " ${test3src})

# 生成可执行文件

add_executable(Test3 ${test3src})

1

2

3

4

5

6

7

8

9

10

不同目录下的多个源文件

为了使源码更为清晰,源码往往会根据不同的功能,将代码放在不同的文件夹下,结构如图: 

 

这时候CmakeLists.txt文件又该如何编写呢?答案如下:


# 版本限定

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)                      

# 项目名称

project(Test3)


# 遍历当前目录所有的源文件,赋值给test3src

aux_source_directory(. main)

aux_source_directory(./stupid stupid)

set(test4src ${main} ${stupid})

message(STATUS "src file : " ${test4src})


# 生成可执行文件

add_executable(Test4 ${test4src})

1

2

3

4

5

6

7

8

9

10

11

12

13

编译动态库与静态库

在前面的示例中,我们都是直接生成了可执行文件,而更多的时候,我们需要生成的不是可执行文件,而是一个可以让复用的库文件,这时候只需要将add_executable(Name ${srcTemp})修改为add_library(Name ${srcTemp})就可以了。add_library可以在中间增加一个参数表示动态库或静态库,默认无参数为静态库。


# 版本限定

cmake_minimum_required(VERSION 2.8)


# 遍历当前目录所有的源文件,赋值给STUPID_SRC

aux_source_directory(. STUPID_SRC)

# 编译STUPID_SRC为静态库

add_library(stupid STATIC ${STUPID_SRC})

# 编译STUPID_SRC为动态库

# add_library(stupid SHARED ${STUPID_SRC})

1

2

3

4

5

6

7

8

9

库文件和头文件导出到指定位置

编译出的库文件是要使用的,而库文件的使用一般需要库文件和头文件。虽然可以编译出库后自己整理库文件和头文件,但是终归是一件麻烦事,尤其是工程比较庞大的时候。这时候,我们可以使用INSTALL来将库文件和头文件导出到指定位置。


# 版本限定

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)

SET(OUTPUT ${CMAKE_CURRENT_BINARY_DIR})

AUX_SOURCE_DIRECTORY(. STUPID_SRC)

# 设置库文件输出目录

# SET(LIBRARY_OUTPUT_PATH ${LIB_DIRS})

# 创建动态库

ADD_LIBRARY(stupid SHARED ${STUPID_SRC})

# 导出动态库

INSTALL(TARGETS stupid DESTINATION ${OUTPUT}/libs)

# 导出头文件

INSTALL(DIRECTORY . DESTINATION ${OUTPUT}/include/stupid 

    FILES_MATCHING PATTERN "*.h")

1

2

3

4

5

6

7

8

9

10

11

12

13

然后进入项目目录/build/ 目录下,依次执行:


cmake ../

make 

make install

1

2

3

即可在build下生成以下结构: 

 

include为头文件目录,其中包含了stupid/stupid.h文件。libs为库文件目录,其中包含了libstupid.so文件。


使用动态库/静态库

在上一个示例中,我们生成并导出了动态库,现在来使用它。复制出上个示例导出的libs及include文件夹,形成以下结构: 

 

CMakeLists.txt文件内容如下:


# 版本限定

CMAKE_MINIMUM_REQUIRED(VERSION 2.8)                      

# 项目名称

PROJECT(Test8)

# 遍历当前目录所有的源文件,赋值给test3src

AUX_SOURCE_DIRECTORY(. main)

MESSAGE(STATUS "src file : " ${main})

# 指定库文件搜索目录,必须为绝对路径

LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/libs)

# 生成可执行文件

ADD_EXECUTABLE(Test8 ${main})

# 指定头文件目录

INCLUDE_DIRECTORIES(./include)

# 指定目标需要连接的库

TARGET_LINK_LIBRARIES(Test8 stupid)


  • 2019-06-04 16:40:30

    为了美观当网页图片不存在时不显示叉叉图片

    当在页面显示的时候,万一图片被移动了位置或者丢失的话,将会在页面显示一个带X的图片,很是影响用户的体验。即使使用alt属性给出了”图片XX”的提示信息,也起不了多大作用。

  • 2019-06-04 17:38:44

    PHP时间转换今天昨天前天几天前

    实际情况应该是,昨天任何时间都算一天前,前天任意时间都算2天前,所以自己琢磨了一番,去动态更新时间与今天23:59:59相差的时间秒数与86400(24 x 3600)相除后,向下取整,这样就得到了相差的天数,比如昨天00:00~昨天23:59:59的任何时间与今天的23:59:59,都相差 86400~(86400 x 2) 天,也就是2天。

  • 2019-06-04 17:44:14

    【VUE】图片预览放大缩小插件

    在看项目时,突然看到预览图片的弹窗,感觉好僵硬,不能放大,不能切换,于是便在网上找下关于图片预览的插件,有找到三个插件,具体的优劣势的看自己的使用吧,我目前只是在电脑查看效果,分别是viewerjs插件、基于photoswipe的vue-photo-preview插件以及vue-picture-preview插件 作者:北极星丶超帅的 链接:https://www.jianshu.com/p/e3350aa1b0d0 来源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 2019-06-04 21:32:05

    Android 全局Dialog的简单实现

    这里所说的全局Dialog是指无论当前应用是处于哪一个页面上,都能够及时弹出Dialog来提示用户一些信息,用户体验不会很好,一般应用于优先级非常高的通知。

  • 2019-06-06 16:14:50

    intent-filter的action,category,data匹配规则

    我们知道有两种方式来启动Activity,显示调用和隐式调用。当使用隐式调用时,又会涉及到IntentFilter的匹配规则。我确信大多数开发者很少关注隐式调用,因为平时开发中用到大多数是显示调用。例如:用Intent直接打开一个Activity,或者用Intent通过包名等其他信息打开另外一个应用等。而隐式调用则使用的比较少,当然也不是完全不使用。例如:当我们需要打开浏览器访问某个链接时,手机上可能存在多个浏览器,我们也无法拿到某一个浏览器的包名,那么一般情况下我们会写如下代码:

  • 2019-06-06 17:57:57

    input【type="checkbox"】标签与字体对齐

    今天分享一个比较实用的技巧,在实际项目中我们会经常遇到表单的input标签多选和单选的问题,但是往往由于标签自身的样式和我们项目的风格很不搭调,就不能实现了,今天就来告诉大家怎么去实现吧。

  • 2019-06-10 11:54:52

    html.div禁用点击事件

    今天做项目中偶然误把元素加上了pointer-events属性,结果导致后来在js中给该元素加点击事件不能用,检查了半天才发现是这个属性的问题。之前没有好好研究,于是决定仔细研究一下。