【CMake】CMake构建项目入门
一、CMake介绍
CMake 是一个跨平台的自动化构建工具,用于管理软件项目的编译过程。它通过简单的配置文件(CMakeLists.txt)生成特定平台的构建文件(如 Makefile、Visual Studio 项目),让开发者可以专注于代码本身而非平台差异。
CMake官方网站:www.cmake.org
1.1 CMake 安装
这里介绍Ubuntu下安装Cmake,直接使用下述命令安装即可:
sudo apt install cmake
安装完成之后,直接在bash
中查看CMake版本,我的版本是3.22.1
cmake -version
二、使用Cmake写一个Hello World
创建一个main.cpp
,写上最简单的的程序——Hello World
#include<iostream>int main(){std::cout << "Hello World !" << std::endl;return 0;
}
在同级目录下创建一个CMakeLists.txt
,用于配置CMake
touch CMakeLists.txt
在CMakeLists
中写入对应内容
cmake_minimum_required(VERSION 3.10) # 添加最低版本要求PROJECT(hello) # 创建项目,变量为hello_SOURCE_DIRSET(SRC_LIST main.cpp) # 设置源文件列表# 打印构建目录信息
MESSAGE(STATUS "This is BINARY dir: ${hello_BINARY_DIR}")# 打印源文件目录信息
MESSAGE(STATUS "This is SOURCE dir: ${hello_SOURCE_DIR}")# 添加可执行文件
ADD_EXECUTABLE(hello ${SRC_LIST})
这个 CMakeLists.txt 的主要功能是:
- 设置项目名称为 “hello”
- 指定源文件为当前目录下的 main.cpp
- 打印构建目录和源文件目录的路径信息
- 创建名为 “hello” 的可执行文件
对于上述内容,在后续会讲解对应的语法,这里先看看CMake的整体流程
我们当前的项目结构是这样的,非常简单
我们在当前路径下,使用下述命令让cmake
构建起来
cmake .
运行后,打印出来了我们的构建目录和源文件目录,并且在当前目录生成了Makefile
tree
使用make
命令让Makefile
跑起来
make
此时在当前路径下就生成了可执行文件hello
了
运行可执行文件,得到了Hello World!
的打印
三、CMake语法介绍
在上述的工程中,我们用到了一些最基本的CMake
语法,下面依次介绍
PROJECT 关键字
可以⽤来指定⼯程的名字和⽀持的语⾔,默认⽀持所有语⾔
- PROJECT (HELLO) 指定了⼯程的名字,并且⽀持所有语⾔—建议
- PROJECT (HELLO CXX) 指定了⼯程的名字,并且⽀持语⾔是C++
- PROJECT (HELLO C CXX) 指定了⼯程的名字,并且⽀持语⾔是C和C++
该指定隐式定义了两个CMAKE的变量
_BINARY_DIR
,本例中是hello_BINARY_DIR
_SOURCE_DIR
,本例中是hello_SOURCE_DIR
MESSAGE关键字就可以直接使⽤者两个变量,当前都指向当前的⼯作⽬录,后⾯会讲外部编译
如果改了⼯程名,这两个变量名也会改变,比如下面的例子,变量就是hello2了
cmake_minimum_required(VERSION 3.10) # 添加最低版本要求PROJECT(hello2) # 创建项目,变量为hello_SOURCE_DIRSET(SRC_LIST main.cpp) # 设置源文件列表# 打印构建目录信息
MESSAGE(STATUS "This is BINARY dir: ${hello2_BINARY_DIR}")# 打印源文件目录信息
MESSAGE(STATUS "This is SOURCE dir: ${hello2_SOURCE_DIR}")# 添加可执行文件
ADD_EXECUTABLE(hello2 ${SRC_LIST})
编译生成的可执行文件的名称也改为了hello2
SET 关键字
-
SET关键字是用于设置变量名的,比如上述的
SET(SRC_LIST main.cpp)
,其中SRC_LIST
就代表了main.cpp
-
SET关键字后面也可以加入多个文件,可以使用空格隔开,比如
SET(SRC_LIST main.cpp a.cpp b.cpp)
MESSAGE 关键字
- MESSAGE 是 CMake 的一个输出信息命令,用于在 CMake 配置(生成构建文件)阶段向控制台打印信息
主要包含三种信息
SEND_ERROR
,产生错误,生成过程中被跳过STATUS
,普通状态信息,用于提示配置过程FATAL_ERROR
,致命错误,立即终止所有的cmake
过程
ADD_EXECUTABLE 关键字
-
生成可执行文件,比如上述的
ADD_EXECUTABLE(hello ${SRC_LIST})
,就是将main.cpp
源文件编译为hello
这个可执行文件 -
上述程序使用的是自定义的变量名,也可以直接写成
ADD_EXECUTABLE(hello main.cpp)
-
工程名称的
PROJECT(hello)
和我们这里的ADD_EXECUTABLE(hello ${SRC_LIST})
没有关系,名字完全可以不同,没有影响
语法的基本原则
-
变量使用
${变量名}
的方法取值,但是在IF
控制语句中是直接使用变量名,这里需要注意 -
指令(参数1 参数2 参数3 …),参数之间使用括弧,参数之间使用空格或者分号进行分开,例如我们的
ADD_EXECUTABLE
命令可以这样写:
ADD_EXECUTABLE(hello main.cpp a.cpp b.cpp)
或者
ADD_EXECUTABLE(hello main.cpp;a.cpp;b.cpp)
- 指令是大小写不区分的,但是参数和变量是大小区分的,不过通常推荐使用全大写来书写指令
语法注意事项
-
SET(SRC_LIST main.cpp)
可以改写为SET(SRC_LIST "main.cpp")
,如果文件名中含有空格,则必须使用双引号,比如SET(SRC_LIST "m ain.cpp")
-
ADD_EXECUTABLE(hello main)
后缀可以不写,此时cmake
会自动查找.c
和.cpp
后缀的文件,比如main.c
和main.cpp
,但是通常情况下我们推荐写上,防止存在main.c
和main.cpp
两个文件
四、内部构建和外部构建
我们上述使用cmake进行构建就是内部构建,内部构建的意思就是在当前文件夹下进行构建:
cmake .
内部构建产生的临时文件特别多,不方便清理,我们下面介绍外部构建,外部构建就是将生成的临时文件全部放到build
文件夹内部,这样项目结构更加清晰,并且利于清理
同样的main.cpp
,我们进行外部构建,先在当前目录下创建一个build
文件夹,进入到build
目录下
mkdir build
cd build
在build
的上级目录存在CMakeLists.txt
,因此使用..
表示上级目录
cmake ..
对于外部编译,这里的内置变量hello2_BINARY_DIR
和hello2_SOURCE_DIR
就不同了,可以发现配置路径变成了当前路径下的build
文件夹内
这样,配置文件和源文件就分开了
如果cmake
指定路径不对,则会报错,比如cmake .
,提示找不到CMakeLists.txt
后续步骤和内部构建是一样的,在build
目录下使用Makefile
编译,然后运行可执行文件
五、添加更多工程配置
为了让我们的Hello World
更像一个工程,我们还需要添加下面的几个文件/文件夹
src
文件夹,用于存放工程的源代码doc
文件夹,用于存放工程的文档READEME
和COPYRIGHT
,主要是一些项目说明和版权说明runhello
的脚本,用于调用可执行可执行文件hello
- 将构建好的目标文件放入构建目录下的
bin
子目录 - 将
doc
目录的内容以及README
和COPYRIGHT
安装到usr/share/doc/cmake
路径下
5.1 将目标文件放入构建目录的bin
子目录
每个目录下都要有一个CMakeLists.txt
说明,整体的项目结构如下
- 外层的
CMakeLists.text
如下,主要是定义项目的名称以及项目的子目录src
和bin
ADD_SUBDIRECTORY
命令用于将子目录中的 CMake 项目纳入当前构建系统,子目录必须含有CMakeLists.txt
文件
PROJECT(HELLO)ADD_SUBDIRECTORY(src bin)
内层的CMakeLists.txt
如下,添加了可执行文件hello
ADD_EXECUTABLE(hello main.cpp)
ADD_SUBDIRECTORY 关键字
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
-
这个指令用于向当前工程添加存放源文件的子目录,并且可以指定中间二进制和目标二进制存放的位置
-
EXCLUDE_FROM_ALL
函数是将写的目录从编译中排出,如程序的`example -
上述的
ADD_SUBDIRECTORY(src bin)
的意思是将src
子目录加入工程并且指定编译输出(包含编译中间结果)路径为bin
目录,如果不指定二进制文件存放位置,那么文件默认存放在build/src
目录中
进入到build
路径,执行cmake ..
,查看到我们的二进制文件都存放在了自动生成的bin
文件夹中
更改二进制的保存路径
-
SET 指令重新定义
EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH
变量 来指定最终的⽬标⼆进制的位置 -
比如,我们想让目标二进制的位置在
build/bin
路径下,我们可以这样写:
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR/bin})
- 比如,我们想让生成的库文件放在
build/lib
里面,我们可以这样写:
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR/lib})
更改上述外层的CMakeLists.txt
,可以不指定ADD_SUBDIRECTOR
中二进制的生成路径了,如下
PROJECT(HELLO)SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)ADD_SUBDIRECTORY(src)
构建的项目结构如下,效果就是目标二进制文件在build/bin
路径下,中间二进制文件在build/src
下,因为这里没有选择生成库文件,因此就没有build/lib
文件夹
5.2 安装
- 一种是从代码编译后直接
make install
安装 - 另一种是打包时指定目录安装
- 比如
make install DESTDIR=/tmp/test
- 比如
./configure -prefix=/usr/local/test
- 比如
如何安装上述hello
项目
需要使用到一个新的关键字INSTALL
INSTALL
关键字可以用于安装二进制文件、动态库、静态库、目录、文件、脚本等- 可以配合
CMake
的变量CMAKE_INSTALL_PREFIX
来指定安装的路径,CMAKE_INSTALL_PREFIX
默认是在/usr/local/
安装文件COPYRIGHT
和README
比如我们将上述的README
和COPYRIGHT
安装到/usr/local/share/doc/cmake/
下,新增README
和COPYRIGHT
touch README COPYRIGHT
修改外层CMakeLists.txt
,安装文件使用的是FILES
PROJECT(HELLO)SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
ADD_SUBDIRECTORY(src)
进入到build
路径下,启动cmake ..
,编译make
,然后执行,这里需要有权限
sudo make install
查看安装的路径下的文件,与预期配置相符
也可以在配置的时候重新指定CMAKE_INSTALL_PREFIX
的路径,比如改为/usr
,这样相对路径就是/usr
开头的
cmake -D CMAKE_INSTALL_PREFIX=/usr
配置是输出信息,显示安装的文件在/usr/share/doc/cmake
路径下,成功改变了相对路径
安装脚本runhello.sh
安装脚本使用的是PROGRAMS
,安装到usr/bin
路径下,这里我们使用绝对路径试试
INSTALL(PROGRAMS runhello.sh DESTINATION /usr/bin)
进入build
路径,执行下述命令
cmake .. && make && sudo make install
可以查看到,安装到了/usr/bin
路径下
ls /usr/bin | grep hello
安装doc
目录下的所有文件
- 使用
DIRECTORY
可以递归安装整个目录
- 修改
CMakeLists.txt
,我们把目录安装到usr/local/share/doc/cmake
中,与README
和COPYRIGHT
放在一起
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/)
执行构建 & 编译 & 安装命令
cmake .. && make && sudo make install
查看文件都安装到了正确的位置
⽬录名以/结尾:将这个⽬录中的内容安装到⽬标路径
- ⽬录名不以/结尾:这个⽬录将被安装为⽬标路径下的
- ⽬录名以/结尾:将这个⽬录中的内容安装到⽬标路径
如果上述使用doc
安装,我们测试一下:
INSTALL(DIRECTORY doc DESTINATION share/doc/cmake/)
实际上是doc
这个文件夹安装到里面了
并且这个文件夹内部的所有文件都被一起安装过去了
更多资料:https://github.com/0voice