前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CMake,大型项目采用的构建工具

CMake,大型项目采用的构建工具

作者头像
Rice加饭
发布2022-05-10 15:20:15
1K0
发布2022-05-10 15:20:15
举报
文章被收录于专栏:Rice嵌入式

本篇文章主要描述CMake的基本用法。在之前的文件中我对Makefile,Autotools这两个构建工具。相关文章如下:

之前对这两个工具进行了描述,其中autotools最终的目的为了实现Makefile。在上一篇文章中我们介绍了autotools工具的时候说到,他是为了解决Makefile复杂的语法结构的问题。使其更加方便。不过就是有这么一堆大神觉得autotools还是有一些问题。所以这一堆整天闲着没事做的大神,就在思考如何优化这些问题。所以另外一个工具就产生了--CMake

CMake的最终目的也是生成Makefile。所以建议学习这两个工具之前,先学习一下Makefile的内容。不过在此声明,不要因为CMake的出现,就不学Makefile和autotools。他们三者没有最好的,而是应该看应用场合,然后再去选择。比如只有几个文件的构建,使用Makefile是最好的选择。而autotools和CMake大多都是应用在大型的项目上。接下来讲讲新工具:

CMake的特点

  • 开放源代码。
  • 跨平台,并可生成native编译配置文件,在Linux平台,生成makefile。在苹果平台,生成xcode。在Windows平台,生成MSVC的工程文件。
  • 能够管理大型项目。
  • 简化编译构建过程和编译过程,工具链也非常简单:cmake+make。
  • 高效率,比autotools效率快。主要原因:CMake在工具链中没有libtool。
  • 可扩展,可以为CMake编写特定功能的模块,扩充CMake功能。

如何使用CMake编译工程

一个简单的cmake例子只需要两个文件:CMakeLists.txt和main.c。我们准备一下这两个文件。其中main.c是我们要编译的源文件,CMakeLists.txt是关键,他就是告诉cmake如何编译。可以理解为编译的规则。接下来我们来初体验:

main.c:

代码语言:javascript
复制
include "stdio.h"

int main(int argc, char *argv[])
{
        printf("Rice CMake!!!\n");

        return 0;
}

CMakeLists.txt:

代码语言:javascript
复制
PROJECT(RICE)
SET(SRC_LIST main.c)
MESSAGE(STATUS "THIS IS BINARY DIR " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "THIS IS SOURCE DIR " ${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(rice ${SRC_LIST})

注意:CMakeLists.txt文件名的规范,注意大小写,否则编译不过。

编译测试:首相执行命令cmake .命令,该命令会根据CMakeLists.txt的规则构建出Makefile,然后执行make命令,生成可执行程序,然后运行./rice查看结果:

代码语言:javascript
复制
rice@rice:~/rice_file/cmake$ cmake .
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- THIS IS BINARY DIR /home/rice/rice_file/cmake
-- THIS IS SOURCE DIR /home/rice/rice_file/cmake
-- Configuring done
-- Generating done
-- Build files have been written to: /home/rice/rice_file/cmake
rice@rice:~/rice_file/cmake$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  main.c  Makefile
rice@rice:~/rice_file/cmake$ make
Scanning dependencies of target rice
[ 50%] Building C object CMakeFiles/rice.dir/main.c.o
[100%] Linking C executable rice
[100%] Built target rice
rice@rice:~/rice_file/cmake$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  main.c  Makefile  rice
rice@rice:~/rice_file/cmake$ ./rice 
Rice CMake!!!
rice@rice:~/rice_file/cmake$ 

CMake语法说明

我们先以上面的CMakeLists.txt进行描述。如下:

指令

PROJECT

语法

PROJECT(projectname [CXX] [C] [Java])

说明

用于指定工程名称,并可指定工程支持的语言(支持的语言列表可以忽略,默认支持所有语言)。这个指令隐式的定义了两个cmake变量:<projectname>_BINARY_DIR和<projectname>_SOURCE_DIR。cmake帮我们预定义PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR变量。建议使用这两个变量,即使修改了工程名称,也不会影响这两个变量。如果使用了<projectname>_SOURCE_DIR,修改工程名称后,需要同时修改这些变量。

指令

SET

语法

SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])

说明

SET 指令可以用来显式的定义变量,比如SET(SRC_LIST main.c)。如果有多个源文件,也可以定义成SET(SRC_LIST main.c t1.c t2.c)。

指令

MESSAGE

语法

MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)

说明

这个指令用于向终端输出用户定义的信息,它包含了三种类型:SEND_ERROR:产生错误,生成过程被跳过STATUS:输出前缀为-的信息。FATAL_ERROR:立即终止所有cmake过程。

指令

ADD_EXECUTABLE

语法

ADD_EXECUTABLE([BINARY] [SOURCE_LIST])

说明

定义了这个工程会生成一个文件名为[BINARY]可执行文件,相关的源文件是 SOURCE_LIST 中定义的源文件列表

内部构建和外部构建

在上面的例程中,我们是采用的内部构建,会看到cmake生成的临时文件比我们编写的源文件还要多,而且在同一级目录下。当我们要删除这些中间文件时会显得特别麻烦。所以有什么办法变得更加干净简洁呢?答案是采用外部构建。

外部构建简单理解就是将cmake生成的中间文件与源文件分离。不让他们同一级目录。如下:

代码语言:javascript
复制
rice@rice:~/rice_file/cmake$ mkdir build
rice@rice:~/rice_file/cmake$ cd build/
rice@rice:~/rice_file/cmake/build$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- THIS IS BINARY DIR /home/rice/rice_file/cmake/build
-- THIS IS SOURCE DIR /home/rice/rice_file/cmake/
-- Configuring done
-- Generating done
-- Build files have been written to: /home/rice/rice_file/cmake/build
rice@rice:~/rice_file/cmake/build$ cd ..
rice@rice:~/rice_file/cmake$ tree -L 2
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   ├── cmake_install.cmake
│   └── Makefile
├── CMakeLists.txt
└── main.c

2 directories, 5 files
rice@rice:~/rice_file/cmake$

过程:创建目录build,然后在build目录下执行cmake,将构建的中间文件生成到build下,这样源文件就很干净。所以接下来我们采用的讲解采用外部构建。

更加完美的工程

一个稍微完整一点的工程,我们该如何做呢?步骤如下:

  • 为工程创建一个子目录src,用于放置工程源代码main.c和CMakeLists.txt文件

在src的目录中的CMakeLists.txt内容如下:

代码语言:javascript
复制
SET(SRC_LIST main.c)

ADD_EXECUTABLE(rice ${SRC_LIST})

INSTALL(TARGETS rice RUNTIME DESTINATION bin)
  • 添加一个子目录doc,用于放置工程文档rice.txt。(在rice.txt随便写点内容,目的是为了规范)
  • 在工程目录添加文本文件COPYRIGHT,README。(同样随便写点内容,目的是为了规范)
  • 将构建后的目标可执行文件(rice)放入构建目录的bin目录。
  • 最终安装这些文件:将可执行文件(rice)安装至tmp/usr/bin,将doc目录的内容,COPYRIGHT,README安装至、tmp/usr/share/doc/cmake。 在工程目录的CMakeLists.txt内容如下:
代码语言:javascript
复制
PROJECT(RICE)

ADD_SUBDIRECTORY(src bin)

INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/rice)
INSTALL(PROGRAMS runrice.sh DESTINATION bin)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/ric)

编译结果目录:

代码语言:javascript
复制
rice@rice:~/rice_file/cmake$ tree -L 3
.
├── build
│   ├── bin
│   │   ├── CMakeFiles
│   │   ├── cmake_install.cmake
│   │   ├── Makefile
│   │   └── rice
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 3.10.2
│   │   ├── cmake.check_cache
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── CMakeOutput.log
│   │   ├── CMakeTmp
│   │   ├── feature_tests.bin
│   │   ├── feature_tests.c
│   │   ├── feature_tests.cxx
│   │   ├── Makefile2
│   │   ├── Makefile.cmake
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── cmake_install.cmake
│   ├── install_manifest.txt
│   └── Makefile
├── CMakeLists.txt
├── COPYRIGHT
├── doc
│   └── rice.txt
├── README
└── src
    ├── CMakeLists.txt
    └── main.c

8 directories, 24 files
rice@rice:~/rice_file/cmake$

其中:

指令

ADD_SUBDIRECTORY

语法

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

说明

此指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL参数的含义是将这个目录从编译过程中排除,比如,工程的example,可能就需要工程构建完成后,再进入example目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)

INSTALL指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。INSTALL指令包含了各种安装类型,我们需要一个个分开解释:

类型

目标文件

指令

INSTALL

语法

INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION <dir>] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [OPTIONAL] ] [...])

说明

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

类型

普通文件

指令

INSTALL

语法

INSTALL(FILES files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [RENAME <name>] [OPTIONAL])

说明

可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限PERMISSIONS,安装后的权限为:OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即644权限。

类型

非目标文件的可执行程序(如脚本之类)

指令

INSTALL

语法

INSTALL(PROGRAMS files... DESTINATION <dir> [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT <component>] [RENAME <name>] [OPTIONAL])

说明

跟上面的FILES指令使用方法一样,唯一的不同是安装后权限为:OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755权限。

类型

目录

指令

INSTALL

语法

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、PATTERN和PERMISSIONS参数:DIRECTORY:后面连接的是所在Source目录的相对路径。PATTERN:用于使用正则表达式进行过滤。PERMISSIONS:用于指定PATTERN过滤后的文件权限。

上述工程进行编译:

代码语言:javascript
复制
rice@rice:~/rice_file/cmake$ cmake -DCMAKE_INSTALL_PREFIX=/tmp/usr ..
rice@rice:~/rice_file/cmake$ make
rice@rice:~/rice_file/cmake$ make instal

执行完,所有结果在/tmp/usr目录中可以查看,如下:

代码语言:javascript
复制
rice@rice:/tmp/usr$ tree 
.
├── bin
│   └── rice
└── share
    └── doc
        └── cmake
            └── rice
                ├── COPYRIGHT
                ├── README
                └── rice.txt

5 directories, 4 files
rice@rice:/tmp/usr$ 

有写的不对的地方,欢迎找作者探讨。。。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rice 嵌入式开发技术分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
NAT 网关
NAT 网关(NAT Gateway)提供 IP 地址转换服务,为腾讯云内资源提供高性能的 Internet 访问服务。通过 NAT 网关,在腾讯云上的资源可以更安全的访问 Internet,保护私有网络信息不直接暴露公网;您也可以通过 NAT 网关实现海量的公网访问,最大支持1000万以上的并发连接数;NAT 网关还支持 IP 级流量管控,可实时查看流量数据,帮助您快速定位异常流量,排查网络故障。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档