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

2022-05-10 15:20:15 浏览数 (1)

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

  • 《linux下,Makefile是啥??》
  • 《实战Makefile前,该知道那些知识?》
  • 《Makefile的实战例子》
  • 《autotools及Yocto下通过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$ 

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

0 人点赞