0%

cmake笔记3

好久没有写东西了,上次写还是在去年10月,都快半年了。这期间,除了工作比较忙外,也开始参与一些开源项目,比如 pika 刚好今年pika做了一些改动,比如编译方式从MakeFile 改成了 cmake。我有幸参与了一些工作,正好趁此机会,又深入学习了一下cmake 的知识,这周末有时间,来记录一下

以前我也写过两篇和cmake相关的文章,这次又有了新的收获,就想着再写一篇记录一下

我那就从pika中用到的cmake的一些知识,记录一下吧。我没有找到好的中文cmake文档,遇到问题最好还是去官网查,文中的相关知识,很多都是从官网学到的。cmake官网 传送门

find_program

主要功能是检查当前系统上是否有对应的程序

比如在pika中,用来检查系统中是否有autoconf这个程序

  • 这个函数的第一参数是一个变量,用来接收检查结果

  • 第二个参数是要检查的程序的名字

后面是一些可选参数,在cmake中,可选参数要加上参数的名比如在这里,要给出检查的路径,参数名是PATHS

1
2
3
4
5
6
7
find_program(AUTOCONF
autoconf
PATHS /usr/bin /usr/local/bin)

if (${AUTOCONF} MATCHES AUTOCONF-NOTFOUND)
message(FATAL_ERROR "not find autoconf on localhost")
endif()

后面通过比较 AUTOCONF 是不是 AUTOCONF-NOTFOUND来确定系统中是否存在 autoconf这个程序

这知识最基本的用法,find_program 还有很多参数,可以实现更多的功能,可以去官网直接学习传送门

execute_process

主要功能是执行一个命令,一般常用于执行一个本机的程序或者脚本

这个函数常用的有下面这些参数

  • COMMAND 要执行的命令,后面可以加参数,可以支持执行多个命令

  • WORKING_DIRECTORY 这个命令执行时的工作目录

  • TIMEOUT 执行这个命令的超时时间

  • RESULT_VARIABLE 这是一个变量,保存命令执行的结果

  • OUTPUT_VARIABLE 调用结果的返回值

  • ERROR_VARIABLE 如果出错了,error的返回值

在pika中的应用

1
2
execute_process(COMMAND sh ${CMAKE_UTILS_DIR}/Get_OS_Version.sh
OUTPUT_VARIABLE OS_VERSION)

这段命令的作用是执行一个shell脚本,得到这个脚本的返回值

需要注意的是,execute_process不是使用shell去执行命令,所以要指定执行的程序。

比如这里,要执行shell脚本,必须指定用sh程序去执行这个脚本。

cmake_host_system_information

获取本机的一些信息

这个函数使用比较简单,只有两个参数,

  • RESULT 获取的结果

  • QUERY 要获取的信息

QUERY 常用的值,这些值在不同版本的cmake上支持程度也是不一样的,使用前还是去官网看一下,传送门

  • NUMBER_OF_LOGICAL_CORES:逻辑核心数量。
  • NUMBER_OF_PHYSICAL_CORES:物理核心数量。
  • HOSTNAME:主机名称。
  • TOTAL_VIRTUAL_MEMORY:总虚拟内存,单位是M
  • AVAILABLE_VIRTUAL_MEMORY:可用虚拟内存,单位是M
  • TOTAL_PHYSICAL_MEMORY:总物理内存,单位是M
  • AVAILABLE_PHYSICAL_MEMORY:可用物理内存,单位是M
  • IS_64BIT:如果处理器是64位,查询结果为1
  • HAS_FPU:如果处理器拥有浮点处理单元,查询结果为1
  • HAS_MMX:如果处理器支持MMX指令集,查询结果为1
  • HAS_MMX_PLUS:如果处理器支持Ext. MMX指令集,查询结果为1
  • HAS_SSE:如果处理器支持SSE指令集,查询结果为1
  • HAS_SSE2:如果处理器支持SSE2指令集,查询结果为1
  • HAS_SSE_FP:如果处理器支持SSE FP指令集,查询结果为1
  • HAS_SSE_MMX:如果处理器支持SSE MMX指令集,查询结果为1
  • HAS_AMD_3DNOW:如果处理器支持3DNow指令集,查询结果为1
  • HAS_AMD_3DNOW_PLUS:如果处理器支持3DNow+指令集,查询结果为1
  • HAS_IA64:如果IA64处理器可以模拟X86,查询结果为1
  • HAS_SERIAL_NUMBER:如果处理器有序列号,查询结果为1
  • PROCESSOR_SERIAL_NUMBER:处理器序列号。
  • PROCESSOR_NAME:可读的处理器全称。
  • OS_NAME:操作系统名称,也就是uname -s的输出,三大操作系统对应的名称是LinuxWindowsDarwinmasOS),也可以通过CMAKE_HOST_SYSTEM_NAME变量获取。
  • OS_RELEASE:操作系统子类型,例如Windows Professional
  • OS_VERSION:操作系统构建ID
  • OS_PLATFORM:处理器架构,Windows下可以通过PROCESSOR_ARCHITECTURE变量获取,Unix/Linux/macOS等平台可以通过uname -muname -p获取。也可以通过CMAKE_HOST_SYSTEM_PROCESSOR变量获取。

示例:

1
2
cmake_host_system_information(RESULT CPU_CORE QUERY NUMBER_OF_LOGICAL_CORES)
message(STATUS "Cpu core ${CPU_CORE}")

获取当前电脑的逻辑核数

ExternalProject_Add

这个就比较复杂了,主要功能是引入一个外部项目。比如在一些项目中,需要别的库,可以使用这个函数来实现。只需要一些简单的配置就能完成依赖的编译和安装,极大的简化了C++的依赖管理。

使用这个函数前,需要先导入

1
include(ExternalProject)

这个函数的常用参数有

Directory Options:

这部分是目录相关的配置

  • PREFIX:外部项目的根目录

  • TMP_DIR:外部项目的临时目录

  • LOG_DIR :外部项目在执行时,产生的log目录

  • STAMP_DIR:外部项目在执行时,每一步的时间戳都会记录在这里,如果没有设置LOG_DIR log文件默认也会在这里

  • DOWNLOAD_DIR: 外部项目下载文件的存放目录

  • SOURCE_DIR: 外部项目的源码存放目录

  • BINARY_DIR: 外部项目编译产生的二进制文件的存放目录

  • INSTALL_DIR: 外部项目编译产生的动态库和静态库的存放目录

如果设置了PREFIX 属性,那么默认的目录结构是这样的

1
2
3
4
5
6
7
TMP_DIR      = <prefix>/tmp
STAMP_DIR = <prefix>/src/<name>-stamp
DOWNLOAD_DIR = <prefix>/src
SOURCE_DIR = <prefix>/src/<name>
BINARY_DIR = <prefix>/src/<name>-build
INSTALL_DIR = <prefix>
LOG_DIR = <STAMP_DIR>

如果设置了EP_BASE,目录结构是这样的

1
2
3
4
5
6
7
TMP_DIR      = <base>/tmp/<name>
STAMP_DIR = <base>/Stamp/<name>
DOWNLOAD_DIR = <base>/Download/<name>
SOURCE_DIR = <base>/Source/<name>
BINARY_DIR = <base>/Build/<name>
INSTALL_DIR = <base>/Install/<name>
LOG_DIR = <STAMP_DIR>

在pika中,是这样设置的

1
2
set(EP_BASE_SUFFIX "buildtrees")
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_CURRENT_SOURCE_DIR}/${EP_BASE_SUFFIX})

最后的目录结构是这样的

1
2
3
4
5
6
7
buildtrees/
├── Build
├── Download
├── Install
├── Source
├── Stamp
└── tmp

Download Step Options:

这部分是依赖下载相关的,下载又可以分url下载,git、svn、等等版本管理工具的下载

  • URL:没什么好说的,就是下载依赖包我网址

  • URL_HASH:对下载的文件做校验,必须要指定校验的算法,比如 SHA1=0cf3c3d176a2134dec9702c64abb13da593aea0c

  • URL_MD5:使用md5对文件校验

  • DOWNLOAD_NAME:下载的文件名

  • TIMEOUT:下载的超时时间

  • GIT_REPOSITORY:拉取代码的git仓库的url地址

  • GIT_TAG:拉取代码的tag

还有一些就不一一列举了

Configure Step Options:

编译前的准备步骤,比如生成MakeFile

  • CONFIGURE_COMMAND: 通过autoconf生成MakeFile文件

  • CMAKE_COMMAND:使用CMakeList.txt生成MakeFile文件

  • CMAKE_ARGS:cmake的参数

Build Step Options:

编译相关的参数

  • BUILD_COMMAND:编译命令

  • BUILD_IN_SOURCE:是否在源代码目录进行编译,一般来说这个不用指定即可

  • BUILD_ALWAYS:启用此选项将强制始终运行构建步骤。这可能是最简单的方法,可以可靠地确保评估外部项目自己的构建依赖关系,而不是依赖于默认的基于成功时间戳的方法。一般也不用设置

后面的一些不太常用,就不列举了。有需要还是看官方文档

在pika中的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ExternalProject_Add(gflags
URL
https://github.com/gflags/gflags/archive/refs/tags/v2.2.2.tar.gz
URL_HASH
MD5=1a865b93bacfa963201af3f75b7bd64c
DOWNLOAD_NO_PROGRESS
1
UPDATE_COMMAND
""
LOG_CONFIGURE
1
LOG_BUILD
1
LOG_INSTALL
1
BUILD_ALWAYS
1
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
-DCMAKE_BUILD_TYPE=${LIB_BUILD_TYPE}
-DGFLAGS_NAMESPACE=gflags
-DBUILD_STATIC_LIBS=ON
-DBUILD_SHARED_LIBS=OFF
BUILD_COMMAND
make -j${CPU_CORE}
)

就是一些很常规的使用

总结

cmake功能还是非常强大,但是同样的,使用也是想对复杂的,而且也没有相对好的中文文档,要用好还是有一定学习门槛的。但是用好cmake 可以很大程度上减轻一些工作量的。

pika在改用cmake的过程中我也提交过几个简单的PR,也是一次很好的学习过程,先记录这些吧,后面想起来再补充。